TCP/IP Network Programming Chapter 15: Sockets and Standard I/O

Advantages of Standard I/O Functions

Two advantages of standard I/O functions


It is not difficult to use standard I/O functions for data communication. But it doesn't make much sense to just know how to use the functions, at least you should
understand the advantages of these functions. Listed below are the two major advantages of standard I/O functions:
□Standard I/O functions have good portability (Portability)
□Standard I/O functions can use buffering to improve performance.
Portability needs no explanation. Not only IO functions, all standard functions have good portability. Because, in order to support all operating systems (compilers), these functions are defined in accordance with the ANSI C standard. Of course, this is not limited to network programming, but applies to all programming fields.
A second advantage of the standard I/O functions is discussed next. You get additional buffering support when using the standard IO functions. This expression may cause some confusion, because as mentioned before, the operating system will prepare the I/O buffer when the socket is created. Before causing more confusion, let's explain the relationship between these two buffers. When a socket is created, the operating system will generate buffers for I/O. This buffer plays a very important role in the implementation of the TCP protocol. At this time, if you use the standard IO function, you will get the support of another additional buffer. The approximate meaning is shown in the figure below:

 As can be seen from the figure, when using standard I/O functions to transfer data, two buffers are passed. For example, when the string "Hello" is transmitted through the fputs function, the data is first passed to the buffer of the standard IO function. The data will then be moved to the socket output buffer and finally the string will be sent to the other host.
Now that we know the relationship between the two buffers, let's explain their respective uses. The main purpose of setting the buffer is to improve performance, but the buffer in the socket is mainly set up to implement the TCP protocol. For example, when data is lost in a TCP transmission, it will be delivered again
, and sending data again means that the data is saved somewhere. Where does it exist? Output buffering for the socket. In
contrast, the main purpose of using standard IO function buffering is to improve performance, but in fact, buffering does not bring excellent performance in all cases. However, the more data that needs to be transferred, the greater the performance difference between buffering and non-buffering. The improvement in performance can be explained from the following two perspectives.
□Amount of transferred data
□Number of times data is moved to the output buffer
Compare the case of sending 1 byte of data 10 times (10 packets) with the case of sending 10 bytes in total. The packet used when sending data contains header information (header). The header information has nothing to do with the data size, and is filled in according to a certain format. Even assuming the header occupies 40 bytes (actually larger), there is a big difference in the amount of data that needs to be passed.
□1 byte 10 times 40×10=400 bytes
□10 bytes 1 time 40x1=40 bytes
In addition, in order to send data, it takes a lot of time to move data to the socket output buffer . But it's also about the number of moves
. The time it takes to move 1 byte of data 10 times is nearly 10 times the time it takes to move 10 bytes of data once.

Several Disadvantages of Standard I/O Functions


If you end the description here, you may think that the standard I/O functions have only advantages. In fact, it also has shortcomings, sorted out as follows.
□It is not easy to carry out two-way communication.
□ Sometimes the fflush function may be called frequently. (used to refresh the buffer)
□Need to return the file descriptor in the form of FILE structure pointer.
It is assumed that you have mastered most of the file IO related knowledge in C language. When opening a file, if you want to perform read and write operations at the same time, you should open it in r+, w+, a+ mode. But because of buffering, the fflush function should be called every time the read and write working state is switched. This also affects buffer-based performance gains. Moreover, in order to use standard IO functions, a FILE structure pointer (hereinafter referred to as "FILE pointer") is required. When creating a socket, the file descriptor is returned by default, so the file descriptor needs to be converted into a FILE pointer.

Use standard I/O functions


As mentioned earlier, when creating a socket, a file descriptor is returned, but in order to use standard IO functions, it can only be converted into a FILE
structure pointer. First introduce its conversion method.


Use the fdopen function to convert to a FILE structure pointer



The file descriptor returned when creating a socket can be converted into a FILE structure pointer used in standard IO functions through the fdopen function .

#include<stdio.h>
FILE * fdopen(int fildes, const char * mode);
//成功时返回转换的FILE结构体指针,失败时返回NULL。
     fildes     //需要转换的文件描述符。
     mode       //将要创建的FILE结构体指针的模式(mode)信息。

The second parameter of the above function is the same as the open mode in the fopen function. Commonly used parameters are read mode "r" and write mode "w".

Use the fileno function to convert to a file descriptor

Next, we introduce a function that provides the opposite function to the fdopen function, which is very useful in some cases.

#include<stdio.h>
int fileno(FILE * stream);//成功时返回转化后的文件描述符,失败时返回-1

The usage of this function is also very simple. When the FILE pointer parameter is passed to this function, the corresponding file descriptor is returned.

Socket-based standard I/O function usage
 

The advantages and disadvantages of standard IO functions are introduced above, and the method of converting file descriptors to FILE pointers is also introduced. The following applies this to sockets. Although it is a socket operation, there is no need to explain the content, just simply apply these functions. Next, change the previous echo server and client to a data exchange form based on standard IO functions.
Whether it is server-side or client-side, there is no difference in how you change it. Just call the fdopen function and use the standard IO function,
I believe you can change it yourself. The changed server-side code is given first.

#include<"头文件声明和之前章节中的回声服务端相同,故省略">
#define BUF_SIZE 30
void error_handling(char *message);

int main(int argc,char *argv[]){
    int serv_sock,clnt_sock;
    char message[BUF_SIZE];
    int str_len,i;

    struct sockaddr_in serv_addr;
    struct sockaddr_in clnt_addr;
    socklen_t clnt_addr_sz;
    FILE * readfp;
    FILE * writefp;
    if(argc!=2){
         printf("Usage : %s <port>\n",argv[0]);
         exit(1);
    }

    serv_sock=socket(PF_INET,SOCK_STREAM,0);
    if(serv_sock==-1)error_handling("socket() error");

    memset(&serv_addr,0,sizeof(serv_addr));
    serv_addr.sin_family=AF_INET;
    serv_addr.sin_addr.s_addr=htonl(INADDR_ANY);
    serv_addr.sin_port=htons(atoi(argv[1]));

    if(bind(serv_sock,(struct sockaddr*)&serv_addr,sizeof(serv_addr))==-1)
        error_handling("bind() error");

    if(listen(serv_sock,5)==-1)error_handling("listen() error");

    clnt_addr_sz=sizeof(clnt_sock);
    for(int i=0;i<5;++i){
        clnt_sock=accept(serv_sock,(struct sockaddr*)&clnt_sock,&clnt_addr_sz);
        if(clnt_sock==-1)error_handling("accept() error");
        else printf("Connected client %d \n",i+1);

        readfp=fdopen(clnt_sock,"r");
        writefp=fdopen(clnt_sock,"w");
        while(!feof(readfp)){
              fgets(message,BUF_SIZE,readfp);   
              fputs(message,writefp);
              fflush(writefp);
        }
        fclose(readfp);
        fclose(writefp);
    }
    close(serv_sock);
    return 0;
}

void error_handling(char*message){
    //与之前章节的错误处理函数相同,故省略
}

The echo client code is given next

#include<"与之前章节的头文件声明相同,故省略">
#define BUF_SIZE 1024
void error_handling(char *message);

int main(int argc,char *argv[]){
    int sock;
    char message[BUF_SIZE];
    int str_len;
    struct sockaddr_int serv_addr;
    FILE * readfp;
    FILE * writefp;
    if(argc!=3){
         printf("Usage : %s <IP> <port>\n",argv[0]);
         exit(1);
    }

    sock=socket(PF_INET,SOCK_STREAM,0);
    if(sock==-1)error_handling("socket() error");

    memset(&serv_addr,0,sizeof(serv_addr));
    serv_addr.sin_family=AF_INET;
    serv_addr.sin_addr.s_addr=inet_addr(argv[1]);
    serv_addr.sin_port=htons(atoi(argv[2]));

    if(connect(sock,(struct sockaddr*)&serv_addr,sizeof(serv_addr))==-1)
        error_handling("connect() error");
    else
        puts("Connected.......");

    readfp=fdopen(sock,"r");
    writefp=fdopen(sock,"w");
    while(1){
         fputs("Input message(Q to quite):",stdout);
         fgets(message,BUF_SIZE,stdin);
         if(!strcmp(message,"q\n")||!strcmp(message,"Q\n"))break;

         fputs(message,writefp);
         fflush(writefp);
         fgets(message,BUF_SIZE,readfp);
         printf("Message from server: %s",message);
    }
    fclose(writefp);
    fclose(readfp);
    return 0;
}

void error_handling(char *message){
    //和前面章节的错误处理函数相同,故省略。
}

The above is the application method of standard IO functions in socket programming. Because additional code needs to be written, it is not as commonly used as imagined. But it is also very useful in some cases, and it is also very beneficial for everyone to review the standard IO functions again.

Guess you like

Origin blog.csdn.net/Reol99999/article/details/131782455