关于TCP数据收发细节

Last Updated: 2023-03-17 09:00:52 Friday

-- TOC --

Keep in mind,TCP处理的无尽的字节流......

TCP数据收发

tcp的send接口,可以一次传入一块很大的数据,我测试过一次发10万个字节,不会异常,send接口会返回成功写入buffer的数据量。tcp的recv接口,不管你设置多大的缓冲区都没关系,只要数据还没有收完,下一次recv接着收。如果发送端在接收端两次recv之间,连续send了好几次数据,接收端在recv的时候,如果缓冲区足够大,可以直接一锅端。

用TCP收发数据,发送方和接收方要做一些约定,tcp保证了数据的可靠传输,对数据的解释,需要应用层来完成。

一个可能出问题的场景:

Tcp发送方通过调用多次send函数来发送数据,由于网络传输的不确定性,接收方仅仅通过一次recv,可能收不全数据,接收方需要有一个判断机制,来判断消息数据是否接收完毕。这种判断有3种常用的方式:

用Python实现一个极简TCP私有协议

有时需要传输的数据量可能会非常大,如果直接传输会不好控制,因此当传输层的数据包大小超过MSS(TCP最大报文段⻓度,Maximum Segment Size),协议栈会将数据包分块,这样即使中途有一个分块丢失或损坏了,只需要重新这一个分块,而不用重新发送整个数据包,而且也避免了在IP层分片!在TCP协议中,我们把每个分块称为一个TCP段(TCP Segment)。

TCP分段的原因是MSS,IP分片的原因是MTU,由于一直有MSS<MTU,很明显,分段后的每一段TCP报文段再加上IP首部后的长度不可能超过MTU,因此也就不需要在网络层进行IP分片了。因此TCP报文段很少会发生IP分片的情况。

UDP可以发长度为0的datagram,接收方收到的也是长度为0的数据。TCP发送长度为0的msg,等于什么都没发,send函数会返回0,tcp链接依旧,接收方还阻塞在recv函数不会返回。

MSS_MTU

由于Nagle算法的存在,有个术语:小包,即有效负载占比非常低的TCP报文,长度肯定小于MSS。大包,长度等于MSS的TCP报文。

如何判断TCP连接是否中断

首先,send/recv接口有可能返回错误,此时tcp连接已经异常。

对于同步通信的TCP连接,即调用send/recv函数的时候是阻塞的时候,如果send返回-1,recv返回0(buffer长度不为0时),就可以判断此TCP连接已经中断。(这里有坑,见下)

严格来说,recv 0表示对方关闭了发送,send -1表示对方关闭了接收。

如果是异步通信,以上规则依然适用,只是异步通信一般会先使用select来判断哪些可读,哪些可写。当然,同步通信依然可以使用select!

全双工

TCP是全双工,即可以在不同的线程中分别处理收或发。

Nagle算法

在网络拥塞控制领域(拥塞控制是做公益),有一个非常有名的算法叫做Nagle算法(Nagle algorithm),这是使用它的发明人John Nagle的名字来命名的,John Nagle在1984年首次用这个算法来尝试解决福特汽车公司的网络拥塞问题(RFC 896),该问题的具体描述是:如果我们的应用程序一次产生1个字节的数据,而这个1个字节数据又以网络数据包的形式发送到远端服务器,那么就很容易导致网络由于太多的数据包而过载。

比如,当用户使用Telnet连接到远程服务器时,每一次击键操作就会产生1个字节数据,进而发送出去一个数据包,所以,在典型情况下,传送一个只拥有1个字节有效数据的数据包,却要发费40个字节长包头(即ip头20字节+tcp头20字节)的额外开销,这种有效载荷(payload)利用率极其低下的情况被统称之为愚蠢窗口症候群(Silly Window Syndrome)。可以看到,这种情况对于轻负载的网络来说,可能还可以接受,但是对于重负载的网络而言,就有可能承载不了而发生拥塞瘫痪。

Nagle算法主要是避免发送小的数据包,此算法要求TCP连接上最多只能有一个未被确认的小分组,在该分组的确认到达之前不能发送其他的小分组(但可以发大分组)。相反,TCP收集这些少量的小分组,并在确认到来时以一个大分组的方式发出去。

Nagle算法对于socket编程来说,是默认开启的!

import socket
s = socket.socket()
# close nagle algo on tcp socket
s.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, True)
# or
s.setsockopt(socket.SOL_TCP, socket.TCP_NODELAY, 1)

有实时性要求的通信场景,要关闭此算法。

现在主流的HTTP1.1协议,不太需要考虑禁用这个算法,因为在一条TCP连接上发送小报文,不管多小都代表了一个请求,协议要完成了这个请求之后,才能继续执行下一个请求,即使Sender端提前发送过去也没有作用,所以开启Nagle算法是能够优化网络传输的。

Nagle算法和Delay ACK一起可能会产生不必要的延迟!

在Windows下,send返回了-1,但WSAGetLastError返回0,此时如果丢弃待发送数据,可能会造成数据丢失,除非在send返回-1之后,主动断开链接。(丢包不重连,这是个bug)

WSAGetLastError返回0的原因,极有可能是另一个线程在成功recv,将这个错误码刷掉了!

send接口在各个平台下,都均有丰富的返回值,-1并不表示tcp连接中断,仅按照返回-1就丢掉待发送数据的行为,可能是错误的!

另一方面,接收端如何有效处理这种没有TCP中断情况下的数据丢失呢?

本文链接:https://cs.pynote.net/net/tcp/202303011/

-- EOF --

-- MORE --