《UNIX网络编程 卷1》 笔记: I/O复用 select函数

来源:互联网 发布:淘宝网毛呢女装中长款 编辑:程序博客网 时间:2024/06/18 11:58

上节我们实现的TCP回射客户程序有缺陷,代码如下:

void str_cli(FILE *fp, int sockfd)  {      char sendline[MAXLINE], recvline[MAXLINE];        while (Fgets(sendline, MAXLINE, fp) != NULL) { /*从标准输入读取一行文本*/          Writen(sockfd, sendline, strlen(sendline)); /*发送到服务器*/          if (Readline(sockfd, recvline, MAXLINE) == 0) /*从服务器读取一行文本*/              err_quit("str_cli: server terminated prematurely");          Fputs(recvline, stdout); /*输出到标准输出*/      }  }  
缺陷在于它仅仅阻塞在标准输入上,只有先处理完标准输入之后才能处理套接字的输入,而不能同时处理两者的输入。有了select函数,我们就能同时监听两个描述符解决这个问题。参考select函数的用法,我们修改了str_cli函数,代码如下:

void str_cli(FILE *fp, int sockfd){int maxfdp1;fd_set rset;char sendline[MAXLINE], recvline[MAXLINE];FD_ZERO(&rset);for ( ; ; ) {FD_SET(fileno(fp), &rset);FD_SET(sockfd, &rset);maxfdp1 = max(fileno(fp), sockfd) + 1;Select(maxfdp1, &rset, NULL, NULL, NULL);if (FD_ISSET(sockfd, &rset)) { /*套接字输入*//*!!!!*/if (Readline(sockfd, recvline, MAXLINE) == 0)err_quit("str_cli: server terminated prematurely");Fputs(recvline, stdout);}if (FD_ISSET(fileno(fp), &rset)) { /*标准输入*//*!!!!*/if (Fgets(sendline, MAXLINE, fp) == NULL)return;Writen(sockfd, sendline, strlen(sendline));}}}
很不幸的是,修改之后的代码是不正确的。理由如下:

    1. readline函数使用的是自己的缓冲区,fgets函数使用的是标准I/O(stdio)缓冲区。而select函数判读描述符是否可读是从read系统调用的角度来看的,它并不知道readline函数和fgets函数使用了缓冲区。这样就会出现当数据已经在缓冲区时,而select函数并不知道的情况。所以使用标准I/O库的函数(fgets等)不能和select函数混合使用!!!

   2. 在批量方式下(将标准输入重定向到一个输入文件,将标准输出重定向到一个输出文件),我们会发现输出文件总是小于输入文件。原因在于在标准输入中遇到EOF时,str_cli函数就会返回,客户进程调用exit函数退出,随后关闭套接字描述符时会同时关闭接收端和发送端,这样在管道中等待接收的数据就被丢弃了。


为了解决第一个问题,我们取消以文本行为中心的操作,不使用标准I/O缓冲区,而使用read函数,改为单个字节的操作方式。

为了解决第二个问题,我们使用shutdown函数,执行TCP的半关闭操作,使得TCP连接的发送端被关闭的同时接收端仍能接收数据。

修改后的代码如下:

void str_cli(FILE *fp, int sockfd){int maxfdp1, stdineof;fd_set rset;char buf[MAXLINE];int n;stdineof = 0; /*标准输入遇到EOF时置1*/FD_ZERO(&rset);for ( ; ; ) {if (stdineof == 0)FD_SET(fileno(fp), &rset);FD_SET(sockfd, &rset);maxfdp1 = max(fileno(fp), sockfd) + 1;Select(maxfdp1, &rset, NULL, NULL, NULL);if (FD_ISSET(sockfd, &rset)) { /*套接字可读*//*当我们在套接字遇到EOF时,如果我们已在标准输入遇到EOF,那么服务器就是正常的终止,函数返回。否则服务器就是异常的终止。*/if ((n = Read(sockfd, buf, MAXLINE)) == 0) {if (stdineof == 1)return;elseerr_quit("str_cli: server terminated prematurely");}Write(fileno(stdout), buf, n);}if (FD_ISSET(fileno(fp), &rset)) { /*标准输入可读*/if ((n = Read(fileno(fp), buf, MAXLINE)) == 0) { /*标准输入遇到EOF*/stdineof = 1;Shutdown(sockfd, SHUT_WR); /*关闭连接的发送端*/FD_CLR(fileno(fp), &rset);continue;}Writen(sockfd, buf, n);}}}


阅读全文
0 0