专栏首页Jackie技术随笔I/O复用——shutdown函数
原创

I/O复用——shutdown函数

尽管修改后的str_cli函数已经可以同时处理输入和网络套接口的事件,但是它仍旧是不正确的。在它修改前的版本,即阻塞I/O模型下,一个回射请求的总时间是RTT(往返时间)加上服务器的处理时间。根据这个总时间,我们可以估计出回射固定行数的请求,需要花费多长的时间。

使用ping简单估算RTT

使用ping是一个测量RTT的简单方法。简单的用主机ping一下回射服务器所在的腾讯云云主机,取30次的平均值得到平均RTT是21.476ms。

--- 150.*.*.* ping statistics ---
30 packets transmitted, 30 packets received, 0.0% packet loss
round-trip min/avg/max/stddev = 14.283/21.476/99.440/15.753 ms

ping报文的大小为84字节。其中ICMP报文56个字节,再加上20个字节的IP头和8个字节的ICMP头。因此IP报文的总长度为84字节。

那么我们可以估算一下,一行文本,长度假设为44字节,那么加上20个字节的IP头和20个字节的TCP头,每行对应的分组刚好是84字节,与ping分组的大小相同,那么运行回射客户端服务器,发送这行文本的RTT大约需要21.476ms。

使用原始的回射客户端服务器程序,发送10条44字节的文本测试一下,可以看到实际的时延和我们预估的一致。

jackieluo@JACKIELUO-MB1 ~/Desktop/unpv13e/tcpcliserv ./tcpcli01 150.*.*.* < tcpcli_input.txt
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
cost:222

停-等方式填充管道

将客户与服务器间的网络当做全双工管道来考虑,假设:

  1. 以停-等方式发送请求,即客户向服务器发送请求,服务器应答,然后发送下一个,以此类推。
  2. 假设RTT为8个时间单位,即时刻0发送请求,时刻4服务器收到请求并应答,时刻7客户收到应答。
  3. 假设服务器没有处理时间,收到请求立即应答。
  4. 请求和应答的数据大小相同。

绘制满足上述假设的一个请求过程:

以停-等方式填充管道

由于管道是全双工的,这样一个请求过程中,我们只用了1/8的管道容量,为了充分利用管道,我们可能会考虑批量地在客户端进行输入。

批量方式填充管道

在批量方式下,假设:

  1. 发出第一个请求后马上发出下一个。
  2. 客户可以以网络能接受的最快速度发送请求。
  3. 客户可以以最快的速度处理应答。

绘制一系列请求过程:

以批量方式填充管道

上图能够解释,为什么在当前版本的str_cli函数下,当我们对输入输出进行重定向时,输出文件总是会小于输入文件。

#include "unp.h"

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);
  }
}

假设输入文件有9行,时刻8发送完这行以后,Fgets返回NULL,跳出循环,到达函数尾,main程序中止,但是此时仍有请求和应答在路上,未被客户处理。

管道中仍有未完成请求和应答

因此我们需要一种方式来关闭TCP连接的一半,给服务器发送一个FIN,告诉它已经完成数据发送,但是仍开放套接口描述字用于读数据。这就需要shutdown函数来完成。

shutdown 函数

# include <sys/socket.h>
int	shutdown(int sockfd, int howto);//返回——0 成功,-1——出错

函数具体的行为取决于第二个参数howto

参数

备注

SHUT_RD

关闭连接的读一半,不再接收套接口中的数据,且接收缓冲区数据作废。进程不能再对套接口执行任何读操作。调用后,由TCP套接口接收到的数据仅做确认,而不实际接收。

SHUT_WD

关闭连接的写一半,又称半关闭。发送缓冲区的数据都发送出去,然后TCP连接终止。无论描述字访问计数是否为0,进程都不能再对套接口执行任何写操作。

SHUT_RDWR

关闭连接的读和写。等效于先使用SHUT_RD调用,然后使用SHUT_WD调用。

终止网络连接的正常方法是调用close,但close有两个限制可由函数shutdown来避免。

  1. close将描述字的访问计数减1,仅在计数为0时才关闭套接口。shutdown可发起TCP的正常连接终止序列,无需访问计数为0。
  2. close会关闭数据传输的读/写两个方向。shutdown可以只关闭连接的某一半。
调用shutdown关闭TCP连接的写一半

再修订版str_cli函数

在上一节加入select模型的str_cli函数的基础上再次进行修改,标准输入遇到文件结束符时,调用shutdown函数,关闭TCP连接的读一半,修改标志位为1,当从套接口读到文件终止符,而此标志位为1时,说明这是正常的终止。

再修订版str_cli函数

使用批量方式后,再次运行输入之前的10行文本的文件,比较耗时:

jackieluo@JACKIELUO-MB1 ~/Desktop/unpv13e/tcpcliserv ./tcpcli02 150.*.*.* < tcpcli_input.txt
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
cost:34

可以看到,批量输入的方式比停-等输入的方式快了很多。

原创声明,本文系作者授权云+社区发表,未经许可,不得转载。

如有侵权,请联系 yunjia_community@tencent.com 删除。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • htons&ntohs

    而TCP/IP协议栈使用大端字节序。应用程序交换格式化数据时,字节序问题就会出现。对于TCP/IP,地址用网络字节序来表示,所以应用程序有时需要在处理器的字节序...

    jackieluo
  • TCP回射客户-服务器程序

    创建一个TCP套接口,用通配地址(INADDR_ANY)和unp.h中定义的众所周知端口(SERV_PORT),端口号为9877。

    jackieluo
  • 《More Effective C++》——异常(Exceptions)

    main函数中首先抛出了异常,导致Session对象析构,logDestruction被调用,抛出异常21,而析构函数没有捕获这个异常,而是让它流出了destr...

    jackieluo
  • 12.5 Cassandra安全配置--密码认证

    之后可以根据新的用户名和密码登录Cassandra。 至于Cassandra远程连接相关配置请看下一节。

    王小雷
  • CentOS7安装python3.7环境

    把安装包移动到该新建文件夹下,解压安装包,安装python3,依次执行以下命令,花费时间较长,耐心等待

    无道
  • pay as you go:当程序员盯上了车险

    车险可能是这个世界最不合理的保险之一。如果你每天坐公交上下班,只是周末偶尔出去玩玩,一周开不了两百公里,一年开不了几十次的话,你还是需要支付和那些天天开车,一年...

    tyrchen
  • 三星宣布将其AI算法应用于医疗诊断的成像设备

    三星宣布将其AI算法应用于其医学成像设备。在芝加哥举行的北美放射学会2018年年会(RSNA 2018)上,三星与其医疗设备子公司Samsung Medison...

    AiTechYun
  • 记一道贝叶斯公式的裸题

    上课好不容易听懂了,赶紧整理一下,不然以我的记性估计明天就要忘干净了 题目 一个用户所有邮件分为两类:$A_1$代表垃圾邮件, $A_2$代表非垃圾邮件 根据经...

    attack
  • 在Linux上安装Python3

    人生不如戏
  • Java学习记录--自动拆箱与装箱

    自动拆箱与装箱是Java5引入的新特性,目的是解决基本类型与包装类型之间切换的麻烦. 装箱

    屈定

扫码关注云+社区

领取腾讯云代金券