TCP
TCP的特性
- 1.TCP提供面向连接的、可靠的字节流服务
- 2.上层应用数据被TCP分割为TCP认为合适的报文段
- 3.TCP使用超时重传机制,而接收到一个TCP数据后需要发送一个确认
- 4.TCP使用包含了首部和数据的校验和来检查数据是否在传输过程中发生了差错
- 5.TCP可以将失序的报文重新排序
- 6.TCP连接的每一端都有固定大小的缓冲区,只允许另一端发送发送接收缓冲区所能接纳的数据
- 7.TCP提供面向字节流的服务,不在字节流中插入记录标识符,也不对字节流的内容作任何解释(由上层应用解释)
TCP数据也是封装在IP数据报中,TCP首部格式如下图所示:

其中,
- 序列号:用于对报文进行计数(注SYN和FIN都会消耗一个序列号),TCP为应用层提供全双工服务,连接的每一端都要保持每个方向上的传输序列号
- SYN:用来发起一个连接,当新建一个链接时,SYN变为1
- ACK:确认序号有效,其序列号为上次接收的序号加1
- 首部长度:首部中32bit的长度(最多60字节),如果没有任选字段,长度为20字节
- URG:标志紧急指针有效
- PSH:接收方应该尽快将这个报文交给应用层
- RST:重建连接
- FIN:发端完成发送任务
- 窗口大小:用于TCP的流量控制,最大65535字节
- 检验和:覆盖首部和数据,由发端计算和存储,接收端验证
- 紧急指针:只有当URG为1时才有效,用于发送紧急数据
- 数据部分是可选的,在连接建立和终止时,双方交换的报文中只有TCP首部
TCP可以表述为一个没有选择确认或否认的滑动窗口协议(滑动窗口协议用于数据传输)。我们说TCP缺少选择确认是因为TCP首部中的确认序号表示发方已成功收到字节,但还不包含确认序号所指的字节。当前还无法对数据流中选定的部分进行确认。例如,如果1~1024字节已经成功收到,下一报文段中包含序号从2049~3072的字节,收端并不能确认这个新的报文段。它所能做的就是发回一个确认序号为1025的ACK。它也无法对一个报文段进行否认。例如,如果收到包含1025~2048字节的报文段,但它的检验和错,TCP接收端所能做的就是发回一个确认序号为1025的ACK。

上图由wireshark抓取,并显示了TCP状态图(注意:由于网络阻塞,发生了丢包现象,4是对2的重发,而5是对4的响应(同3相同))。
根据上图可以看到建立一个TCP连接的过程为(三次握手的过程):
- 1.客户端向服务器端发送一个SYN请求,同时传送一个初始序列号(ISN);
- 2.服务器发回包含客户端初始序列号的SYN报文段作为应答,同时将ACK序号设置为ISN+1;
- 3.客户端向服务器发送一个ACK确认,ACK序号为ISN+1.
终止一个TCP连接需要4次握手,这是由于TCP的半关闭(当一方调用shutdown关闭连接后,另一端还是可以发送数据,典型的例子为rsh)导致的:TCP连接是全双工的,连接的每一端在关闭连接时都向对方发送一个FIN来终止连接,同时对方会对其进行确认(回复ACK)。通常,都是一方完成主动关闭,另一方来完成被动关闭:
- 1.以上面的抓包为例,客户端向服务器发送了一个FIN(NO. 6);
- 2.服务器端对上面的FIN进行确认(NO. 7),同时向客户端发送一个FIN(这儿其实是两个动作,一个是对上面FIN的ACK,另一个是发送一个FIN,但由于TCP的捎带ACK机制,两者放在一个包里发送了);
- 3.客户端对服务器端的FIN进行确认(NO. 8)。
最大报文长度(MSS)表示TCP传往另一端的最大块数据的长度。MSS在连接建立时传送给对方,只会出现在SYN报文段中。
MSS让主机限制另一端发送数据报的长度。

- 1.当连接到一个不在监听的端口时,客户端回收到一个RST响应(UDP连接到一个不存在的端口时会产生一个ICMP端口不可达的差错)。
- 2.在连接终止时,也可以通过发送一个复位报文段而不是FIN来终止连接,可通过设置SO_LINGER来这么做。
- 3.可通过TCP的SO_KEEPALIVE选项来检测半打开连接,当检测到这种连接时会发送一个RST报文。关于该选项更多的内容参见http://www.tldp.org/HOWTO/html_single/TCP-Keepalive-HOWTO/。
SO_LINGER 选项
此选项指定函数close对面向连接的协议如何操作(如TCP)。内核缺省close操作是立即返回,如果有数据残留在套接口缓冲区中则系统将试着将这些数据发送给对方。
前面可以看到,TCP交互的双方每次发送数据的时候(即便是只有一个字节的数据),都需要产生一个(数据长度+40字节)的分组。当数据的长度远小于40字节时,网络的实际利用率其实很低,并且大量的小分组也会增加拥塞的可能。
Nagle算法正是解决了该问题。它要求一个TCP连接上最多只能有一个未被确认的未完成的小分组,在该分组的确认到达之前不能发送其他的小分组。TCP收集这些小的分组,并在确认到来时以一个分组的形式发出去。其特点是:确认到达的越快,数据也就发送的越快,并可以发送更少的分组。
TCP链接的过程中,默认开启Nagle算法,进行小包发送的优化。优化网络传输,兼顾网络延时和网络拥塞。
Nagle虽然解决了小封包问题,但也导致了较高的不可预测的延迟,同时降低了吞吐量。这个时候可以置位TCP_NODELAY关闭 Nagle算法,有数据包的话直接发送保证网络时效性。
在进行大量数据发送的时候可以置位TCP_CORK关闭Nagle算法保证网络利用性。尽可能的进行数据的组包,以最大mtu传输,如果发送的数据包大小过小则如果在0.6~0.8S范围内都没能组装成一个MTU时,直接发送。如果发送的数据包大小足 够间隔在0.45内时,每次组装一个MTU进行发送。如果间隔大于0.4~0.8S则,每过来一个数据包就直接发送。
Nagle算法和CORK算法非常类似,但是它们的着眼点不一样,Nagle算法主要避免网络因为太多的小包(协议头的比例非常之大)而拥塞,而CORK算法则是为了提高网络的利用率,使得总体上协议头占用的比例尽可能的小。如此看来这二者在避免发送小包上是一致的,在用户控制的层面上,Nagle算法完全不受用户socket的控制,你只能简单的设置TCP_NODELAY而禁用它,CORK算 法同样也是通过设置或者清除TCP_CORK使能或者禁用之,然而Nagle算法关心的是网络拥塞问题,只要所有的ACK回来则发包,而CORK算法却可以关心内容,在前后数据包发送间隔很短的前提下(很重要,否则内核会帮你将分散的包发出),即使你是分散发送多个小数据包,你也可以通过使能CORK算法将这些内容拼接在一个包内,如果此时用Nagle算法的话,则可能做不到这一点。
- 1.Keepalive定时器用于用于检测空闲连接的另一端是否崩溃或重启。
- 2.设置SO_KEEPALIVE选项后,如果2小时内在此套接口的任一方向都没有数据交换,TCP就自动给对方 发一个保持存活探测报文段,客户主机处于以下4种状态之一:(1)客户主机接收一切正常,服务器收到期望的ACK响应,并将keepalive定时器复位。(2)客户主机已崩溃,并且关闭或者正在重启。此时,服务器无法收到相应,在75s后超时。服务器=总共发出10个这样的探查,每个间隔75秒。如果一个响应都没有收到,则终止连接。(3)客户主机已重启,此时服务器将收到一个复位响应,终止连接。(4)客户主机正常运行,但服务不可达,同(2)。
- 3.keepalive定时器默认2小时的间隔备受争议,通常应用上需要的时间要比2小时短的多。并且,当系统关闭一个由KEEPALIVE机制检查出来的死连接时,是不会主动通知上层应用的,只有在调用相应的IO操作在返回值中检查出来。因此,如果上层应用需要保活机制,最好还是自己实现。
- 1)根据自身MTU及对方SYN中携带的MSS确定发送报文数据部分的最大容量(如果对方没有指定MSS,则默认为536);
- 2)在IP头部打开DF标志位;
- 3)如果收到ICMP错误信息告知需要分片, 如果ICMP信息中包含下一跳MTU的信息, 那么根据这个值调整数据的最大容量, 如果ICMP信息中不支持这种新协议(下一跳MTU值为0), 那么调整数据的最大容量至下一个可能的大小;
- 4)DF标志位会一直打开, 以保证能够测量得到正确的Path MTU;
- 5)超时后会重新探询Path MTU以保证链路改变也能用到正确的Path MTU.
TCP Path MTU探询的好处是:
- 1)避免在通过MTU小于576的中间链路时进行分片;
- 2)防止中部链路的某些网络的MTU小于通信两端所在网络的MTU时进行分片;
- 3)充分利用链路的吞吐量.