Wednesday, August 31, 2011

RPN main server code

Previous to this I have presented the RPN calculator code Now here is the server code to work with that. Below I am also presenting how to work with the RPN Server also. First let us present the server code, if you feel any difficulty in understanding the code post me I will give you the explanations step by step

Trying out the RPN Server

Here are all the files


To compile all the related source modules for the RPN server, you can perform the following make command:

gcc -c -D_GNU_SOURCE -Wall -Wreturn-type rpnsrv.c
gcc -c -D_GNU_SOURCE -Wall -Wreturn-type rpneng.c
gcc -c -D_GNU_SOURCE -Wall -Wreturn-type mkaddr.c
gcc rpnsrv.o rpneng.o mkaddr.o -o rpnsrv -lgmp

After the executable rpnsrv for the server has been created, you can start the server as follows:

$ ./rpnsrv &
[1] 13321

In the output shown, the server was started with a process ID of 13321, and run in the background. To keep things simple at this point, you'll just use the telnet command to try out the server. The
next chapter will fully outline this server's functions. For now, just try some simple tests.

The server presented is not a production-grade server. Some forms of incorrect input can provoke the server to abort. The RPN calculator computes based upon numbers that are pushed onto the stack. To perform the add operation, for example, requires at least two numbers to exist on the stack. To push a number onto the stack, you will enter a line as follows:


After you press Enter, the server will respond with something like this:

This tells you that the number has been stacked at the bottom of the stack (entry number zero). To stack another number, simply do the same, as follows:

The server will respond with

This indicates that the number 2636364 was stacked at position 1, although the original number 970976453 still sits at the bottom of the stack at position 0. You can list the current contents of
the stack by entering the following:


The following example shows what the session and its output might look like this:

$ telnet localhost 9090
Trying . . .
Connected to localhost.
Escape character is '^]'.
E:end of stack dump

To perform a binary operation, you simply enter the name of the operation or its symbol. For example, to add these numbers, you would just enter the + character and press return. The session
repeated without entering the dump command would appear as follows if the + operation was performed, and then followed by the = operation:

$ telnet localhost 9090
Trying . . .
Connected to localhost.
Escape character is '^]'.
telnet> c
Connection closed.

The + operation caused the two stacked numbers to be added together, and the result replaced the two original values. The = operator here pops the result off the stack and displays it for you. To exit the server, type CTRL+] and you will be prompted with the prompt:


From there, enter a c to indicate that you want the session closed, and press Enter. To terminate the server, just use the kill command.
Take a few minutes now to have some fun with the new RPN calculating server program. Restart the server, and see whether you can figure out how to compute the equation (3 + 2) * (2 + 4) using the
calculating server just presented.

The RPN Calculator Engine C Code

The RPN calculator engine code will be presented next. It is not expected that you will understand all of the GMP function calls because they have not been presented. However, if you have Red Hat Enterprise Linux installed, you can find out more about the GMP calls by performing the following command:

$ info GMP

This will bring up the info viewer with documentation about the GMP function library. Listing 10.5 lists the RPN calculator code. Note especially the standard I/O calls in the functions rpn_dump() and rpn_process().

How the server works from the client side, will be examined after the remainder of the server code is presented. Listing 10.6 shows the remainder of the server source code. This represents the main program segment of the server.

Applying FILE Streams to Sockets

Now it is time to introduce some source code that makes use of the concepts that have been discussed so far in my tutorials. The server program that will be presented next implements a Reverse Polish Notation (abbreviated RPN) calculator. It accepts arbitrarily long integer values, stacks them, and then permits operations to be performed upon the stacked numbers. The result of the operation is placed on the top of the stack.

The integer arithmetic will be performed by the GNU Multi-Precision (GMP) library. This library permits virtually unlimited sized integer numbers to be evaluated. Space does not permit the GMP library to be described here. The purpose of this code is simply to illustrate some server concepts using FILE streams. This same server will help illustrate some advanced topics that will be covered in the nextchapter.

Presenting the mkaddr() Function

The mkaddr.c subroutine function will be presented here, to make this project easier to read. The mkaddr() function being presented, accepts an input string that consists of an IP number and an optional port number, or a hostname and optional port number. The port number can also be a symbolic Internet service name such as "telnet" or "ftp." The function synopsis of the function is as follows


int mkaddr(void *addr,
           int *addr_len,
           char *str_addr,
           char *protocol);

The function arguments can be described as follows:
  1. The argument addr points to the receiving socket address structure. This is the socket address, which is being returned. This value must not be null.
  2. The argument addr_len is a pointer to an integer value, which will be filled with the length of the address created in addr when the function returns. The input value that is pointed to must contain the maximum size in bytes of the area pointed to by addr.
  3. Argument str_addr is the symbolic hostname and optional port number (or service). This will be more fully described later. A null pointer implies a string value of "*".
  4. The protocol argument specifies the protocol that this service will be using. A null pointer implies the protocol string "tcp".

The str_addr input string is designed to be as flexible as possible. It contains two components, separated by a colon character:


The host_name portion of the string can be one of the following:
  • An IP number such as, for example.
  • A hostname such as, for example.
  • An asterisk, which indicates that the IP address should be the value INADDR_ANY.
The colon character and the service portion of the string are optional within str_addr. When not omitted, this component can be one of the following:
  • A port number such as 8080, for example.
  • A service name, such as telnet, for example.
  • An asterisk, implying port zero. The bind(2) function call will assign a port number when this value is used.
The following examples show valid string values for the argument str_addr in the mkaddr() function call:
The mkaddr() function returns the following possible values:
  • Zero indicates that the conversion was successful.
  • -1 indicates that the host part of the string was invalid, or that the hostname was unknown.
  • -2 indicates that the port number was invalid, or that the service name was unknown.

The code for the mkaddr() subroutine is presented in Listing 10.4. This subroutine may be useful to use in projects that you might write. The instructions for compiling the code for mkaddr.c will be provided later, when the whole server is compiled.

mkaddr C Subroutine for socket

Defining Buffer Operation in socket api's

Defining Buffer Operation

When you make use of the stdio(3) facilities, you generally make use of some buffering behind the scenes. Buffered writes, for example, reduce the frequency that the system function write(2) is called. This increases the overall system output efficiency. Likewise, read requests are also buffered.

For example, fgetc(3) will fetch one character from a buffer. Only when the input buffer is empty will it request more data to be read in using the read(2) system call. This, again, is done to
improve the I/O efficiency.

When the underlying file descriptor of a stream is a terminal device, the I/O under Linux will be line buffered. Files, on the other hand, are usually fully buffered (buffered in large blocks).

There are three basic modes of buffering to choose from using FILE streams under Linux. These are
  • Fully buffered (or "block" buffered)
  • Line buffered
  • Unbuffered

Choosing "unbuffered" mode might be appropriate for some socket programs, although no efficiency from buffering can be gained this way. This does save you, however, from worrying about when to call fflush(3).


If your network application is experiencing hangs, the cause might be output buffering. Change the buffering on your output streams to "unbuffered mode" for testing. If the problem vanishes, then you need some calls to fflush(3) added to the appropriate places. Alternatively, you could reconsider the buffering mode being used by the application.

Line buffered mode is often useful when your socket interaction is text line based. Using line buffered mode means that you are never forced to call upon fflush(3) to force the last text line to be written to the socket.

If you choose to use the "fully" buffered mode, then you must apply fflush(3) at the point where you want a physical write to take place to the socket. Otherwise, your data might sit in an output buffer, while your application waits in vain for a response, because the output data was never sent. The function synopses of the buffer control functions are shown in Listing 10.3.

 * Listing 10.3: Stream I/O Buffer Functions

#include <stdio.h>

int setbuf(FILE *stream,char *buf);
int setbuffer(FILE *stream, char *buf, size_t size);
int setlinebuf(FILE *stream);
int setvbuf(FILE *stream, char *buf, int mode, size_t size);

The functions in Listing 10.3 permit the caller to change the buffering mode of the specified stream. The Linux documentation indicates that these functions might be called at any time to change the buffering characteristics of the stream. The non-setvbuf(3) calls are aliases to the function setvbuf(3), which performs the operation.


If your code must be portable to other UNIX platforms, then you must only call the buffer adjustment functions before I/O calls have been made on the streams affected. The arguments, by argument name, are described as follows:
  • Argument stream is the FILE pointer of the stream that is to be affected.
  • Argument buf is the pointer to the buffer being supplied. This pointer can be NULL. If a buffer is required, and NULL is supplied, then an internal buffer is allocated instead.
  • Argument size is the size in bytes of the buffer provided (by argument buf), or the size of the internal buffer to be allocated.
  • Argument mode is the buffering mode to be used.

A suggested buffer size is defined by the include file stdio.h as the macro BUFSIZ. Table 10.1 shows the list of mode values that can be supplied to setvbuf(3).

Table 10.1: The Mode Values for setvbuf(3)

C Macro
Input and/or output on the stream will be fully buffered.
Input and/or output will be line buffered.

Input and/or output will not be buffered at all.

As an example of how to use the function to change the socket stream tx to use line buffered mode, you could code the following function call after the fdopen(3) call:

setlinebuf(tx); /* Line Buffered Mode */

Alternatively, you could accomplish the same thing by using the setvbuf(3) function directly:


In this example, you allow the software to allocate its own internal buffer of BUFSIZ bytes. The buffering mode for the stream, however, is set to line buffered mode, due to the use of the macro _IOLBF in the function call.

Tuesday, August 30, 2011

Handling Interrupts

interrupted System Calls issue:

Reading the Linux man page for fread(3) or fwrite(3) doesn't reveal much about the possible errors that can be returned. Only the return value is described, which indicates that if the returned count is short, or the count is zero, an error has occurred.

The fread(3) and fwrite(3) functions are described in more detail within the AT&T System V Interface Definition documentation. What is interesting about this UNIX documentation is the fact that the error code EINTR can be returned.

The AT&T System V Interface Definition (SVID) was one attempt to specify a UNIX operating system environment that allowed applications to be created, which was independent of the computer hardware used. The SVID standard was stated as compliant with the POSIX 1003.1 Full Use Standard and the ANSI C X3J11 industry standard.

The EINTR error indicates that an interrupted system call has occurred. This error is returned when your process has been signaled, and a signal handler has been called to process it, and that handler has returned from its call. This error code is not returned by all function calls, but it is returned in instances in which the function call might block for a long period of time. Certainly, a read(2) call waiting for incoming data on a socket fits in this category.

You'll recall that the fread(3) function call is simply a functional layer over the underlying read(2) function call that is invoked as required by buffering. Consequently, it follows that the fread(3) might be susceptible to the EINTR error code, if signals have been received by your process. The same is also true of the fwrite(3) function call. If a large volume of data is written to a socket, the underlying write(2) call might also block for a long time. If, while it is blocked, a signal is received and handled, the write(2) function will return an EINTR error, which might cause the fwrite3) function to return this error.

The word "might" was used because this depends upon the design of the stdio(3) library that you are using. I have seen some UNIX implementations hide this error from the caller, whereas others return EINTR. Given that Linux has generally been moving from the libc5 library to the newer glibc2 version of the C libraries, your mileage might vary.

Some simple testing for this under Red Hat Linux 6.0 suggests that EINTR will not be returned. However, as the GNU C library code moves with standards, which themselves are undergoing revision and further clarification, this might change. If your application must run on other UNIX platforms in addition to Linux, then you should test for EINTR in your code.

If you must allow for EINTR in your code, then the following code fragment represents a template that you might use:


int ch;
do {
   ch = fgetc(rx);
} while ( ferror(rx) && errno == EINTR );

The basic procedure used here is
  1. Call clearerr(3) to clear any pending error that might have occurred on this stream.
  2. Perform your input/output operation.
  3. If the operation failed and the errno value was set to EINTR, then repeat step

After the code has exited the loop, this indicates that the operation either succeeded, or it failed with a different error code other than EINTR. The general principle at work here is that you retry the operation when the error EINTR is returned.

Simple tests that were performed by the author suggested that the Red Hat Linux 6.0 distribution included a C library that hides the EINTR error from the application code. This can be both a blessing and a curse to the programmer. It is a blessing in the sense that you do not have to code to handle the error condition. It is a curse if you need to test to see whether a signal was processed by the signal handler while execution was blocked in the function call.

Handling EINTR for Other Functions

It should be noted at this point that EINTR is potentially a problem for a host of other functions that you might use for socket programming. The functions affected by signals include

• connect(2)
• accept(2)
• read(2)
• write(2)
• readv(2)
• writev(2)
• recvfrom(2)
• sendto(2)
• select(2)
• poll(2)

The list presented is not meant to be an exhaustive list, and some of these functions have not been covered yet. It is a list of commonly used functions, however, which are affected by signal handling. The examples that are shown in this text will largely ignore this issue of EINTR, in favor of keeping the example programs small and easier to understand. However, you must allow for the occurrence of EINTR for any production-level code.

Always test for the EINTR error when calling functions affected by signal handling in production-level code.

closing streams associated sockets

Winding up Communications

The astute reader might have been wondering about the shutdown(2) call that was introduced earlier. How should this function call be exercised when it is needed? With the dual stream approach, you might be tempted to misuse the shutdown(2) function, based on a bad assumption. For example, because there are actually two underlying file descriptors being used in Listing 10.2, it might be tempting to call shutdown(2) on each of the file descriptors. On one, you might shut down the read side, whereas on the other file descriptor, you might shut down the write side. Do not do this!

Here we will discuss the issues that can occur when you are going to close the streams that are associated with the socket.

Recall from the Basic Tutorials of the socket first Post, "Introducing Sockets," that the shutdown(2) function was described. There, it was stated that one of the advantages of its use was that "it disregards the number of open references on the socket." Consequently, calling shutdown(2) on duplicated sockets will affect all references to the same socket. Consequently, it also affects all existing streams you have connected with that socket!

When winding up communications between your process and the remote process over a socket, there are three basic scenarios to be considered:
  • The process is not going to write any further data, but is expecting to receive more data (shutdown of the write side only).
  • The process is not going to receive any further data, but is expecting to write more data (shutdown of the read side only).
  • The process is not going to read or write any further data (shutdown of reading and writing).

Using the two streams shown in Listing 10.2, the scenarios will be described in the following sections.

Shutting down the Write Side Only

The shutdown(2) function is called upon to indicate to the Linux kernel that the calling process intends no further writes of data, in this particular case. Because the shutdown(2) call affects the socket and not the file descriptor, either file descriptor could actually be used. However, for program clarity, I would encourage you to use the writing stream to accomplish this task. The procedure for this task consists of the following steps:
  1. Flush any data that might exist in the stream buffers using fflush(3).
  2. Shut down the write side of the socket using shutdown(2).
  3. Close the stream, using fclose(3).

Before shutting down the write side, you must always flush the output stream. This is important because there might be some unwritten data that is sitting in a buffer. This can be accomplished as follows:


(tx); /* Flush buffer out */

To accomplish the shutdown(2) step, you need to obtain the underlying file descriptor of the stream tx. To access it, you can use the following C language macro:

#include <stdio.h>
int fileno(FILE *stream);

You simply pass the stream pointer to the macro as input, and it returns the underlying integer file descriptor that it is using. This is a portable and the only acceptable way of doing this. Applying this macro, you can perform the shutdown step as follows:



The last step of this procedure is to simply fclose(3) the tx stream that you no longer need:


Putting the procedure all together, the shutdown procedure looks like this in C code:



This sequence will leave the rx stream intact for reading, but forces all buffered data in the tx stream to be written out to the socket. The shutdown(2) call tells the kernel to expedite the
sending of the socket data because there will be no more data to send. Finally, the fclose(3) call on the tx stream closes the file descriptor and releases the memory resources associated with the stream.

Shutting down the Read Side Only

This procedure is similar to shutting down the write side only. The procedure does vary slightly:
  1. Call shutdown(2) to indicate that there is no more receive data expected.
  2. Close the stream using fclose(3).

You'll notice that there is no fflush(3) step required in this case. The procedure can be summarized in code as follows:



Note again the portable use of the fileno(3) macro to fetch the underlying file descriptor for the stream rx. Although Listing 10.2 shows the original socket number is available in variable s, for program clarity it is probably preferred to use the fileno(3) macro after the rx stream has been created.

This procedure accomplishes the indication of no further reads to the Linux kernel, as well as the closing and releasing of all stream resources for rx. However, the application will still be able to write to stream tx unhindered.

Shutting down Both Read and Write Sides

This procedure might be perceived as being more complex, but it actually turns out to be quite simple:
  1. Close the write stream by calling fclose(3).
  2. Close the read stream by calling fclose(3).

No fflush(3) is required in step 1 because the fclose(3) function for the write stream will implicitly ensure that this flush takes place. Closing the read stream in step 2 closes the last open file descriptor for the socket, so the socket is implicitly shut down for both reading and writing.

One exception to the rule, which might prove to be a sticking point, depends upon your application design. If your process has forked, then there might be other open file descriptors to your socket. You'll recall that only when the last close(2) takes place will the socket actually be shut down. If there is some doubt about this, you might want to follow a more elaborate procedure as follows:
  1. Close the write stream using fclose(3). This will force unwritten data out to the socket, and release the write stream's resources.
  2. Call shutdown(2) to terminate both reading and writing to this socket.
  3. Close the read stream using fclose(3) to close the read file descriptor, and to release the stream's buffer and FILE structure.

The only real change to the procedure is that the shutdown(2) function is called as follows:



The entire procedure boils down to this:


I will submit to you that this procedure is the best one to use, even if you do not expect to have problems with the two -step procedure. This procedure will always accomplish your task, regardless of any future program modifications that might otherwise impact the other procedure.

Monday, August 29, 2011

Closing the Dual Streams on a socket

After you have two streams established, as shown in Listing 10.2(using separate Read and Writes Stream on sockets), you can safely use functions such as fgetc(3) or fgets(3) on your rx stream. Write calls using fputs(3) or fputc(3), for example, can use the output stream tx instead. Use of the separate streams eliminates buffer interaction and removes the need to call fgetpos(3) at various points in your program flow.

However, when you are finished with these streams, you must perform the following:
  • fclose(rx) to close the input stream.
  • fclose(tx) to close the output stream.

The preceding procedure accomplishes the following:
  • Flushes any buffered writes for the writing stream.
  • Closes the underlying file descriptor.
  • Releases the buffers, if any.
  • Releases the stream managed by the FILE object.

Duplicating a Socket

Duplicating a UNIX / Linux Socket 

Earlier we have seen a how to associate a socket with a stream and Using separate read and write streams on a socket. In the previous post (using separate read and write stream on a socket) I have shown the dup function. To understand why Listing 10.2 works, the dup(2) function will be reviewed here in case some readers are unfamiliar with its use:

#include <unistd.h>
int dup(int oldfd);

UNIX systems, like Linux, allow multiple file descriptors to refer to the same open file (or, in this case, a socket). By calling dup(2) with the socket s as an input argument, you are returned a new file descriptor. This new descriptor also refers to the original socket s. After this duplication has been performed, however, the socket itself will be shut down by the kernel only when the last of these two file descriptors are closed (assuming shutdown(2) is not used). Numbers always help to clarify an example. Assume that, in Listing 10.2, socket s is created on file descriptor 3. Assume also that the socket s is duplicated as follows:

int s2;      /* dup'ed socket */
s2 = dup(s); /* duplicate */

If file descriptor 4 is not currently in use, the Linux kernel will return 4 in the example shown. This allows the file descriptor 3 (variable s) and the file descriptor 4 (variable s2) to both refer to the same socket.

Now we have seen how we can duplicate the socket. Now it is time to see how we can close the dual streams. Follow this link.

Using Separate Read and Write Streams on socket

Sockets with separate Read and Write Streams : 

Listing 10.1 (follow this link) showed how you could associate a socket with a stream that allows both input and output. Although this might be conceptually appealing, it is actually a safer practice to open separate streams for input and output. The reason for this is the fact that the buffering of the stream plays a more complex role on one stream than it does for two separate streams. The Linux fdopen(3) man page indicates that for I/O streams it is often necessary for a fgetpos(3) call to be performed between switching from write to read modes and vice versa. Rather than try to explain why and when these special circumstances apply to an I/O stream, I'll just advise you to use two separate streams for reading and writing instead. This technique has very little overhead and provides better overall buffering performance in many cases.

Listing 10.2 shows how to create a separate read and write stream from one file descriptor.

 * Listing 10.2: Creating a Read and Write Stream
 int s;     /* socket */
 FILE *rx;  /* read stream */
 FILE *tx   /* write stream */
 s = socket(PF_INET,SOCK_STREAM,0);
 * some code part. I am skipping for time being
 rx = fdopen(s,"r");
 if ( rx == NULL ) {
    fprintf(stderr, "%s: fdopen(s,'r')\n",strerror(errno));
 tx = fdopen(dup(s),"w");
 if ( tx == NULL ) {
    fprintf(stderr,"%s: fdopen(s,'w')\n",strerror(errno));

Examine Listing 10.2 carefully. Although the listing looks simple, there is one subtle function call included, which must not be overlooked. Can you find it? Look at the statement where the variable tx is assigned. Then, examine the first argument to the fdopen(3) call in that statement. Did you notice the dup(2) call when you first looked at Listing 10.2? This is very important, because different streams should use different file descriptors. One simple reason for this is so that when fclose(tx) is called, it will not close the same file descriptor being used by the rx stream.

Sunday, August 28, 2011

Associating a Socket with a Stream

The stdio(3) stream is managed through the FILE control block. For example, you've probably already written code that looks something like this many times:


FILE *in;
in = fopen(pathname,"r");
if ( in == NULL ) {
   fprintf(stderr,"%s: opening %s for
In the example presented, the file known as variable pathname is opened for reading. If the open call succeeds, the variable in receives a pointer to the FILE structure, which manages the stream I/O for you. Otherwise, variable in receives a null pointer, and your application must handle or report the error. For socket programming, however, there is no stdio(3) call available to open a socket. How then does a programmer accomplish associating a stream with a socket? Read the next section to find out.

Using fdopen(3) to Associate a Socket with a Stream

The function call fopen(3) should be quite familiar to you. However, for many, the fdopen(3) call is new or unfamiliar. Because this function is likely to be new to some of you, let's introduce its function synopsis and describe it:

#include <stdio.h>
FILE *fdopen(int fildes, const char *mode);

This function takes two arguments:
  1. An integer file descriptor (fildes) to use for performing I/O.
  2. The standard I/O mode to use. This will be an open mode, which is the same as the familiar fopen(3) mode argument. For example, "r" indicates that the stream is to be opened for reading, whereas "w" indicates the stream is to be opened for writing.

Like the fopen(3) call, if the function is successful, a pointer to the controlling FILE structure is returned. Otherwise, a null pointer indicates that a problem developed, and external variable errno will contain the nature of the error.

Note that the first argument was a file descriptor. You will recall that the socket returned from the socket(2) function is also a file descriptor. This then makes it possible to associate any existing socket to a stream. Listing 10.1 shows a short example of associating a socket to a stream that can be read or written.

 * Listing 10.1: Associating a Socket with a Stream
int s; // socket
FILE *io; // stream
s = socket(PF_INET,SOCK_STREAM,0);
. . .
io = fdopen(s,"r+");
if ( io == NULL ) {
   fprintf(stderr,"%s: fdopen(s)\n",strerror(errno));

Listing 10.1 demonstrates how the socket number, which was held in variable s, was associated with a FILE stream named io. The mode argument of the fdopen(3) call in this example established a stream for input and output. After this open call has been successfully accomplished, the other standard I/O functions such as fgetc(3), for example, can be employed.

Understanding the Need for Standard I/O in socket

Why standard I/O is required in socket

The stdio(3) facility in Linux conforms to the ANSI C3.159-1989 standard. This standardization of the interface helps programs to be portable to many platforms. This might be useful to you, when
porting source code from other UNIX systems to your own Linux platform, for example.

The stdio(3) package will itself issue read(2) and write(2) calls, "under the hood," so to speak. You, however, use the standard I/O calls instead, because they will offer you the convenience of getting a line or character at a time, according to your application needs. The read(2) call, for example, cannot return to your application one text line. Instead, it will return as much data as it can, even multiple text lines.

When writing to the socket, the standard I/O routines allow your application to write characters out one at a time, for example, without incurring large overhead. On the other hand, calling write(2) to write one character at a time is much more costly. The standard I/O functions permit your application to work with convenient units of data.

The stdio(3) package also provides the capability to buffer your data, both for input and for output. When buffering can be used, it can significantly improve the I/O performance of your application. Unfortunately, buffering creates difficulties for some forms of communication, and so it cannot always be used.

It will be assumed in this text that you are already familiar with the basics of stdio(3). This is usually taught in C programming texts, along with the C language itself. Consequently, this text will focus on things you need to watch out for, and other subtleties that might not be obvious, as it applies to socket programming.

Linux introduces the standard I/O routines in its stdio(3) man page. Perform the following command to display this introductory text:

[sgupta@rhel55x86 using-I_O-on-socket]$ man 3 stdio

This will provide a list of standard I/O functions. If  these all seem new to you, then you might want to review some of them. You should be acquainted with at least fopen(3), fread(3), fgets(3), fwrite(3), fflush(3), and fclose(3).