TCP/IP Network Programming Chapter 16: Other Content About IO Stream Separation

Separate I/O streams


Two I/O stream separation


We have separated the IO flow in two ways before, the first one is "TCPI/O Process (Routine) Separation" in Chapter 10. This method duplicates a file descriptor by calling the fork function to distinguish the file descriptors used in input and output. While the file descriptors themselves do not differentiate in terms of input and output, we have separated the purpose of the 2 file descriptors, so this also falls under the separation of "streams".
The second separation is in chapter fifteen. Create a read mode FILE pointer (FILE structure pointer) and a write mode FILE pointer by calling the fdopen function twice. In other words, we separated the input tool and the output tool, so it can also be seen as a separation of "flow". The reasons for the separation are explained below, issues not yet mentioned are discussed and solutions are proposed.


Benefits of Separating "Streams"


The "flow" separation in Chapter 10 and the "flow" separation in Chapter 15 have a certain difference in purpose. First analyze the "flow" separation purpose in Chapter 10.
□ Reduce the implementation difficulty by separating the input process (code) and output process.
□ Input-independent output operations can improve speed

This is discussed in Chapter 10, so the reasons for these advantages will not be explained. The purpose of the separation in Chapter 15 "Streams" is given next.
□In order to distinguish the FILE pointer according to the read mode and write mode.
□The difficulty of implementation can be reduced by distinguishing between read and write modes.
□By differentiating IO buffering to improve buffering performance
The benefits of "flow" separation vary depending on the method and situation (purpose).


EOF problem caused by "stream" separation


The following explains the problems caused by "flow" separation. Chapter 7 introduced the EOF transfer method and the necessity of half-close (if you can't remember
clearly, please review the corresponding chapter). You should remember the following function call statement:

shutdown(sock, SHUT_WR);


At that time, I talked about the half-closed EOF transfer method based on calling the shutdown function. Chapter 10 also uses these techniques to add half-close related code to the examples. That said, there is no problem with the separation of "streams" in Chapter 10. But the "stream" based on the fdopen function in Chapter 15 is different. We don't know how to half-close it in this case, so we may make the following mistakes: "Half-closed? Isn't it possible to call the fclose function for the FILE pointer in output mode? In this way, EOF can be passed to the other party, and it becomes a half-closed state that can receive data but cannot send data." Do you think so
? This is a good guess, but I encourage you to read the following code first. In addition, exception handling is not added in order to simplify the code in the following examples, I hope you will not misunderstand. Give the server-side code first.

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<arpa/inet.h>
#include<sys/socket.h>
#define BUF_SIZE 1024

int main(int argc,char *argv[]){
    int serv_sock,clnt_sock;
    FILE* readfp;
    FILE* writefp;
    
    struct sockaddr_in serv_sock,clnt_sock;
    socklen_t clnt_addr_sz;
    char buf[BUF_SIZE]={0,};
  
    serv_sock=socket(PF_INET,SOCK_STREAM,0);
    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]));

    bind(serv_sock,(struct sockaddr*)&serv_addr,sizeof(serv_addr));
    listen(serv_sock,5);
    clnt_addr_sz=sizof(clnt_sock);
    clnt_sock=accept(serv_sock,(struct sockaddr*)&clnt_addr,&clnt_addr_sz);

    readfp=fdopen(clnt_sock,"r");
    writefp=fdopen(clnt_sock,"w");

    fputs("FROM SERVER: HI~ client? \n",writefp);
    fputs("I love all of the world \n",writefp);
    fputs("You are awesome! \n",writefp);
    fflush(writefp);

    fclose(writefp);
    fgets(buf,sizeof(buf),readfp);
    fputs(buf,stdout);
    fclose(readfp);
    return 0;
}

Some people may think that the last string sent by the client can be accepted by the function call on line 39. The above example does send EOF after calling the fclose function. The client given later will also send the final string after receiving EOF, just need to verify whether the function call on line 39 is acceptable. The client code is given next.

#include<"头文件声明和服务器端声明一样,故省略">
#define BUF_SIZE 1024

int main(int argc,char *argv[]){
    int sock;
    char buf[BUF_SIZE];
    struct sockaddr_in serv_addr;
 
    FILE * readfp;
    FILE * writefp;

    sock=socket(PF_INET,SOCK_STREAM,0);
    memset(&serv_addr,0,sizepf(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]));

    connect(sock,(struct sockaddr*)&serv_addr,sizeof(serv_addr));
    readfp=fdopen(sock,"r");
    writefp=fdopen(sock,"w");

    while(1){
         if(fgets(buf,sizeof(buf),readfp)==NULL)break;
         fputs(buf,stdout);
         fflush(stdout);
    }

    fputs("FROM CLIENT: Thank you! \n",writefp);
    fflush(writefp);
    fclose(writefp);
    fclose(readfp);
    return 0;
}

The following conclusions can be drawn from the running results: "The server failed to accept the last string sent by the client!".

It's easy to figure out why: the fclose function called on line 38 in the server example completely terminates the socket, not half-closes it. These are the problems that need to be solved through this chapter.

Copying and half-closing of file descriptors

Reason for not being able to half-close when terminating a "stream"

 This diagram depicts the two file pointers on the server side in the previous example. Then everything will be revealed. When the read pointer calls the fclose function, the file descriptor will be closed, and the socket will be closed at this time. So how to solve it? Isn't it just copying the file descriptor before creating the FILE pointer.

 As shown in the figure, another file descriptor is created after copying, and then the read mode FILE pointer and the write mode FILE pointer are generated by using the respective file descriptors. This prepares the environment for a half-close, since sockets and file descriptors have the following relationship:

"A socket cannot be destroyed until all file descriptors have been destroyed"

That is to say, when the fclose function is called for the FILE pointer in write mode, only the file descriptor related to the FILE pointer can be destroyed, but the socket cannot be destroyed.

Is the state at this time a half-closed state? no! Just get ready for a semi-closed environment. To get into a true half-closed state requires special handling. Look carefully, there is 1 file descriptor left. Moreover, the file descriptor can perform IO at the same time. Therefore, not only is EOF not sent, but the file descriptor can still be used for output . The method of sending EOF and entering the half-closed state will be described later. First, how to copy the file descriptor is introduced, the previous fork function is not considered.

copy file descriptor

The file descriptor copy mentioned earlier is different from the copy performed in the fork function. When the fork function is called, the entire process will be copied, and the copy at this time is, in a sense, copied to another process. But the copy mentioned here is that the copy of the file descriptor can be completed in the same process . Of course, the value of the file descriptor cannot be repeated, so use different values. To form this structure, file descriptors need to be duplicated. The so-called "copy" here has the following meaning: "to access the same file or socket, create another file descriptor."

dup&dup2

The copy method of the file descriptor is given below, and it is completed by one of the following two functions.

#include<unistd.h>
int dup(int fildes);
int dup2(int fildes,int fildes2);
//成功时返回复制的文件描述符,失败时返回-1
     fildes     //需要复制的文件描述符
     fildes2    //明确指定的文件描述符整数值

The dup2 function explicitly specifies the copied file descriptor integer value. When passed a value greater than 0 and less than the maximum file descriptor the process can generate, this value will become the value of the copied file descriptor.

Detachment of a "stream" after copying a file descriptor

Our goal below is to make the previous server-client model work properly. The so-called "normal" work refers to receiving the last string sent by the client through the half-closed state of the server. Of course, in order to accomplish this task, the server needs to send EOF at the same time. Here is sample code:

#include<"头文件和之前的示例相同,故省略">
#define BUF_SIZE 1024

int main(int argc,char*argv[]){
    int serv_sock,clnt_sock;
    FILE *readfp;
    FILE *writefp;
    
    struct sockaddr_in serv_addr,clnt_addr;
    socklen_t clnt_addr_sz;
    char buf[BUF_SIZE]={0,};

    serv_sock=socket(PF_INET,SOCK_STREAM,0);
    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]));

    bind(serv_sock,(struct sockadd*)&serv_addr,sizeof(serv_addr));
    listen(serv_sock,5);
    clnt_addr_sz=sizeof(clnt_addr);
    clnt_sock=accept(serv_sock,(struct sockaddr*)&clnt_addr,&clnt_addr_sz);

    readfp=fdopen(clnt_sock,"r");
    writefp=fdopen(dup(clnt_sock),"w");

    fputs("FROM SERVER: HI~ client? \n",writefp);
    fputs("I love all of the world \n".writefp);
    fputs("You are awesome! \n",writefp);
    fflush(writefp);

    shutdown(fileno(writefp),SHUT_WR);
    fclose(writefp);//关闭并发送EOF

    fgets(buf,sizeof(buf),readfp);
    fputs(buf,stdout);
    fclose(readfp);
    return 0;
}

The result proves that the server sent EOF to the client when it was half closed. Through this example, I hope you can grasp a little:"

No matter how many file descriptors are copied, the shutdown function should be called to send EOF and enter the half-closed state"

Guess you like

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