7-阻塞IO下的TTCP实验

背景

本章是基于echo的应用场景来测试,来展示阻塞IO可能会由于协议设计缺陷导致永久阻塞。

 

echo协议及具体实现

echo协议:客户端往服务端每发送一次数据,服务端就将收到的数据原封不动的发送会客户端。

 

通过命令行参数获取参数len,数据的长度len。然后往服务端一次过发送len字节的数据,发送(send)完毕后,就从服务端接收(recv)返回的数据并打印。

这里的send和recv都是阻塞IO。

每当有一个客户端,就新建一个线程(c++11的线程)来处理。线程函数中,主要逻辑在while循环中,阻塞read 4KB数据,然后将收到数据发回客户端。

 

测试步骤

在本机测试,在两个终端启动客户端和服务端。当发送数据为1KB,1MB,10MB时,不会阻塞。

image-20230210201026384

发送大约20MB时,会阻塞

image-20230210201151452

用netstat来查看原因,见下图。服务端接收缓冲区有大约6.1MB数据,发送缓冲区有大约2.6MB数据。客户端接收缓冲区有大于0.96MB数据,发送缓冲区有大约4.1数据。

image-20230210201419839

 

查看内核中TCP接收和发送缓冲最大大小(可设置改变)。接收缓冲区为大约6.29MB,发送缓冲区为大约4.2MB。

image-20230210201739318

 

客户端与服务端发送数据的流程:

  1. 客户端调用send,将应用层的数据拷贝到内核的发送缓冲区,待发送;当服务端的内核接收缓冲区未满,客户端将内核发送缓冲区真正发送到服务端

  2. 服务端的内核接收缓存区接收到数据。当服务端调用recv后,将数据从内核接收缓冲区拷贝到应用层

  3. 服务端调用send,将应用层的数据拷贝到内核的发送缓冲区,待发送;当客户端的内核接收缓冲区未满,服务端将内核发送缓冲区真正发送到客户端

  4. 客户端的内核接收缓存区接收到数据。当客户端调用recv后,将数据从内核接收缓冲区拷贝到应用层

分析阻塞原因:客户端往服务端发送20MB数据,在发送完20MB数据之前是不会接收数据的。而服务端就会4KB一次的读取数据,并发送回客户端,但客户端此时并不会接收recv,因为20MB还没发完。这样服务端往客户端发送的数据一直得不到Ack(TCP协议),导致除了第一个4KB拷贝到应用层缓冲区,并且尝试发送,其余数据(一个个4KB)堆积堆积在服务端的接收缓冲区。因为服务端只recv了一次,而第一次的send是阻塞的,且客户端未recv,导致服务端第一次的send一直种颜色。当服务端的内核接收缓冲区堆积到接近极限时(6.2MB),服务端之后开始不会接收客户端发来的数据。

image-20230210202022129

 

协议设计

从程序的设计考虑,客户端发送一个20MB的数据是合法的,而服务端预先不知客户端请求数据的大小,采用了读取4K就响应一次的方式,最后导致的阻塞。

因此我们在设计应用层协议时,在发送数据之前(无论是客户端向服务端发送,还是服务端向客户端发送),先发送一个header,表示接下来要发送多大的数据。这样接收方,接收并解析header,获取到数据的大小为len。接下来,在读到len字节数据之前一直recv,直至接收了len字节数据。然后才把完整的消息解析并发送回接收方。

 

参考:TCP Send函数的阻塞和非阻塞,以及TCP发送数据的异常情况 - maji233 - 博客园 (cnblogs.com)

posted @ 2023-04-29 14:57  DavidJIAN  阅读(52)  评论(0)    收藏  举报