尽管修改后的str_cli
函数已经可以同时处理输入和网络套接口的事件,但是它仍旧是不正确的。在它修改前的版本,即阻塞I/O模型下,一个回射请求的总时间是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/8的管道容量,为了充分利用管道,我们可能会考虑批量地在客户端进行输入。
在批量方式下,假设:
绘制一系列请求过程:
上图能够解释,为什么在当前版本的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
函数来完成。
# include <sys/socket.h>
int shutdown(int sockfd, int howto);//返回——0 成功,-1——出错
函数具体的行为取决于第二个参数howto
:
参数 | 备注 |
---|---|
| 关闭连接的读一半,不再接收套接口中的数据,且接收缓冲区数据作废。进程不能再对套接口执行任何读操作。调用后,由TCP套接口接收到的数据仅做确认,而不实际接收。 |
| 关闭连接的写一半,又称半关闭。发送缓冲区的数据都发送出去,然后TCP连接终止。无论描述字访问计数是否为0,进程都不能再对套接口执行任何写操作。 |
| 关闭连接的读和写。等效于先使用 |
终止网络连接的正常方法是调用close
,但close
有两个限制可由函数shutdown
来避免。
close
将描述字的访问计数减1,仅在计数为0时才关闭套接口。shutdown
可发起TCP的正常连接终止序列,无需访问计数为0。close
会关闭数据传输的读/写两个方向。shutdown
可以只关闭连接的某一半。在上一节加入select
模型的str_cli
函数的基础上再次进行修改,标准输入遇到文件结束符时,调用shutdown
函数,关闭TCP连接的读一半,修改标志位为1,当从套接口读到文件终止符,而此标志位为1时,说明这是正常的终止。
使用批量方式后,再次运行输入之前的10行文本的文件,比较耗时:
jackieluo@JACKIELUO-MB1 ~/Desktop/unpv13e/tcpcliserv ./tcpcli02 150.*.*.* < tcpcli_input.txt
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
cost:34
可以看到,批量输入的方式比停-等输入的方式快了很多。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。