45-批量输入异常处理(shutdown 函数)

本文解决上一篇文章遇见的 bug.

1. 程序路径

代码托管在 gitos 上,请使用下面的命令获取:

git clone https://git.oschina.net/ivan_allen/unp.git

如果你已经 clone 过这个代码了,请使用 git pull 更新一下。本节程序所使用的程序路径是 unp/program/echo/multiplexing_select_client_shutdown

2. shutdown 函数

终止网络连接一般使用 close,不过 close 有两个限制:

  • close 把描述符引用计数减 1,只有该计数为 0 时才关闭套接字。而 shutdown 不管引用计数,只要调用,直接发送 FIN 报文。
  • close 终止读和写两个方向的数据传送。

2.1 shutdown 函数原型

#include <sys/socket.h> int shutdown(int sockfd, int howto);

2.2 函数参数

该函数的行为依赖于 howto 的值:

  • SHUT_RD: 关闭连接读这一半,而且接收缓冲区中现有数据全部丢弃。进程不能再对这样的套接字调用任何读函数。当然,对端对此毫不知情,任仍可以发送数据过来,关闭读半部的 TCP 收到对端发送的数据半进行确认后,再丢弃掉,而不是放入接收缓冲区。使用该选项,并不会引起任何 TCP 报文的传输。
  • SHUT_WR: 关闭连接写这一半。这就是半关闭。该行为首先将发送缓冲区中所有数据发送出去,最后引发 TCP 向对端发送 FIN 报文段。进程不能再对这样的套接字执行任何写操作。
  • SHUT_RDWR: 连接的读写全部关闭,这相当于调用 shutdown(sockfd, SHUT_RD); shutdown(sockfd, SHUT_WR).

3. 客户端修改

根据 shutdown 函数的特性,显然我们应该使用 SHUT_WR 这个选项来执行半关闭操作。

void doClient(int sockfd) {   fd_set rfds, fds;   int nr, nw, nready, maxfd, stdinclosed;   char buf[4096];    // 客户端关闭标志   stdinclosed = 0;    FD_ZERO(&rfds);   FD_SET(STDIN_FILENO, &rfds);   FD_SET(sockfd, &rfds);   maxfd = sockfd;    while(1) {     fds = rfds;     nready = select(maxfd + 1, &fds, NULL, NULL, NULL);     if (nready < 0) {       if (nready == EINTR || nready == ECONNRESET) continue;     }     else if (nready == 0) continue;      if (FD_ISSET(STDIN_FILENO, &fds)) {       if (fgets(buf, 4096, stdin) != NULL) {         nw = writen(sockfd, buf, strlen(buf));         if (nw < strlen(buf)) {           perror("short write");         }       }       else {         // 不直接 break,而是先执行半关闭操作,然后继续执行循环         shutdown(sockfd, SHUT_WR);         FD_CLR(STDIN_FILENO, &rfds);         stdinclosed = 1;       }     }      if (FD_ISSET(sockfd, &fds)) {       nr = readline(sockfd, buf, 4096);       if (nr == 0) {         if (stdinclosed) {           fputs("peer closed\n", stderr);         }         else {           // 服务器先执行关闭           fputs("server exception!\n", stderr);         }         break;       }       else if (nr < 0) ERR_EXIT("readline");       write(STDOUT_FILENO, buf, nr);     }   } }

4. 实验过程

  • flower 上启动服务器
flower $ ./echo -s -h flower
  • sun 上启动客户端
sun $ ./echo -h flower <input >output && ll

5. 结果及分析


这里写图片描述
图1 运行结果

图 1 中左上角是客户端,我们可以看到客户端输出的 output 文件已经正常了,它的大小和 input 一样大。

再看 tcpdump 的输出,第一个红色框框是 sun 客户端执行 shutdown 是发送的 FIN 段,不过发现这个 FIN 是随着数据一起捎带过去的。第二个红色框框是 flower 对该 FIN 段的确认,为什么该确认不是 ack = 28785,要知道,FIN 段本身也会消耗一个字节号,所以 ack = 29786 了。

最后一个红色框框,是服务器在 readline 的时候,读取到了 EOF,也就是返回了 0,因此它就知道对端已经没有数据要发送了,然后也执行了 close。

接下来再看看客户端与服务器通信的时序图(简化),如图 2.


这里写图片描述
图2 使用 shutdown 执行半关闭

当客户端关闭写通道时,服务器仍然在向客户端发送数据,而客户端也要做好接收对端数据的准备,不能随意就退出程序。

6. 总结

  • 掌握 shutdown 函数的用法

说明:本文转自blog.csdn.net,用于学习交流分享,仅代表原文作者观点。如有疑问,请联系我们删除~