1、TCP中一些名词解释
(1)MSS(maximum segment size)
TCP的最大报文段大小,在TCP报文段中有一个16位的部分用于放置该值,因此最大为65535,可以利用setsockopt() 和getsockopt设置和获取TCP_MAXSEG来影响MSS;
(2)MSL(maximum segment lifetime)
IP报文段能在网络中存在的最长时间,这个是系统级的参数,没有接口修改,windows上可以通过注册表修改,通常为2分钟,最低为30秒,linux上面没法修改;
(3)TTL(time to live)
是由源主机设置的生存时间,不是具体的时间值,是只ip数据报在网络中能够经历的最大跳数,即能够经过的最大路由数,这个字段的设置在IP的首部,由8位组成,因此最大的跳数是255;可以通过setsockopt设置;
(4)RTT(round trip time)
指客户端到服务端往返所花时间,tcp含有动态估算rtt的功能,同时RTT也是受到网络拥塞情况的变化而变化;
(5)MTU(maximum transmission unit)
最大传输单元,这个是由硬件规定,不同网络MTU是不一样的;
2、TCP报文头中标志位含义
(1)URG
紧急标志位,当这个标志置1时,TCP报文中的16位紧急指针就有效,这个紧急指针就是指紧急数据的偏移量,一共这么多字节;
(2)ACK
确认序号有效,表示收到指定序号的TCP报文;
(3)PSH
指示接收方应该尽快将这个报文段交给应用层,这个PSH标志位跟socket中的no_delay方法相关联,就是设置发送不延迟,发送方不管发送内容有多小,都直接发送,接收方接收到这个数据后不等待,直接将数据交由应用程序处理;这个就是关闭TCP中数据发送的Nagle算法(Nagle算法是指在TCP连接上最多只能有一个未被确认的未完成小分组,在该分组的确认到达之前不发送其他小分组,而是将这些小分组缓存起来等确认到达时以一个大分组的形式发出去,这样可以有效避免过多的小分组出现在网络中导致数据传送效率低,网络拥塞);
(4)SYN
TCP建立连接的标志;
(5)FIN
发送端完成发送任务,TCP关闭连接的标志;
(6)RST
重置连接,这个标志位通常用来表示连接的异常断开,RST标志位表示TCP连接出现硬错误;
3、TCP连接的状态
LISTEN:监听连接请求
SYN_SENT:在发送连接请求后(发送SYN),等待匹配的连接请求
SYN_RECEIVED:在收到连接请求并发送连接连接请求的确认ack之后(接收到SYN,并发送SYN+ACK)
ESTABLISHED:收到了SYN+ACK,并发送了ACK之后,或者收到建立连接的ACK之后,连接建立起来了
FIN_WAIT_1:应用进程先主动关闭,发送了FIN之后;
FIN_WAIT_2:在发送FIN之后,收到对方的FIN+ACK之后;
CLOSING:说明是同时关闭连接,发送FIN之后,又收到了对方的FIN,因此也会回复FIN+ACK,之后进入此状态
TIME_WAIT:收到对方的FIN,并发送了FIN的ACK之后,或者在同时关闭连接中,收到对方对自己发的FIN的ACK回复之后进入此状态,表示处于即将关闭双方的连接的状态,这个阶段需要等待2MSL时间,因为最后一个确认FIN的ACK可能丢失或者未到达对方,对方会重复发送一次FIN,这个FIN又会在MSL时间内到达,因此需要等待一个往返的最大时间,如果这期间没有收到新的FIN,说明最后一个ACK已经到达,如果收到,说明最后一个ACK丢失了,需要重新发送FIN的ACK,并再次等待2MSL时间;
CLOSE_WAIT:应用进程还没有发送FIN,但是收到了对方的FIN之后,发送FIN的ACK然后进入此状态;
LAST_ACK:应用进程在发送了对方FIN的ACK之后,自己又发送了FIN之后,就进入此状态,在此状态等待FIN的ACK到达;
CLOSED:在2ML时间到达之后,或者收到最后一个FIN的ACK之后进入此状态;
TCP连接的建立正常情况下需要三次握手,断开正常情况下需要四次挥手;这些状态在编程中都是不能看到的,都是TCP自己维护,API没法获取到;以上各个状态的变迁图如下:
4、TCP传输协议
(1)重传(从发送方角度考虑)
TCP连接是可靠的数据传输连接,其可靠性的一个重要保障就是能够重传在网络中丢失的报文,因此TCP重传就很重要了,而TCP重传又分为超时重传和快速重传两种,超时重传是指发送方自己在发送数据之后,在超时重传值(RTO)时间到达时还没收到ACK,则触发超时重传;快速重传是指接收方发现接收到的报文不是自己想要的报文,中间有漏掉的报文序列时,就会连续发送三个相同的ACK(请求丢失序列的报文)给发送方,发送方在收到连续三个相同的报文ACK的时候就会触发快速重传机制,发送丢失的报文;快速重传是对超时重传的一个补充,使得重传更加及时;
a)超时重传
超时重传的时间RTO(retransmission timeout),是根据若干次的RTT计算平均而来(有一个较复杂的计算算法),会根据网络环境的变化而动态调整;每次发送数据后到达RTO时间,如果ACK还没到达,就会进行超时重传,如果重传之后还没收到ack,下次重传的RTO值就会翻倍(乘以2),知道重传到达系统设置的重传次数,然后就会关闭连接;默认情况下,Windows主机默认重传5次。大多数Linux系统默认最大15次。两种操作系统都可配置。
b)快速重传
发送方一旦收到三个重复的ACK时,就会触发快速重传,这时,所有待发送的报文都会放在队列中等待,直到快速重传发送完为止;
(2)流量控制(从接收方角度考虑)
TCP发送数据时,发送方会考虑接收方能够接受处理多少数据,不能随意发送数据包大小,否则会导致网络拥塞,TCP使用滑动窗口来进行流量控制;在TCP报文段中一个16位的窗口字段,用来保存当前自己可以接收数据的缓冲区大小,接收方可以通过根据接收缓冲区大小设置窗口的大小,从而控制发送的发送速度;
发送方的发送窗口包括已发送但是没有收到ACK的报文以及等待发送的报文;而接收窗口指接收缓冲区空余的大小;当然这里每次发送的数据报大小是MSS控制的,在TCP连接建立之初的三次握手中会包含通信双方各自的MSS大小,从而发送的时候直接发送最大报文段大小(MSS)的报文,如果过程中接收方的接收窗口小于MSS,则发送方会以实际窗口大小来发送报文段;
滑动窗口协议包括以下几种:
a)停止等待协议
发送方需要在上一次发送的数据报的ACK到达之后才能发送新的数据,这会导致信道利用率降低;
b)后退N协议
发送方可以连续发送N个数据报,每次发送一个报文都会设置超时计时器,如果某个报文触发了超时重传,则发送方需要重传该报文之后的所有报文,接受方也必须丢弃该报文之后的所有报文;因此这样会导致很大的浪费;在网络环境不好的情况下会导致效率更低;
c)选择重传协议
选择重传协议是对后退N协议的改进,也就是说在发送方依然可以连续发送N个报文,只是在其中某个报文触发超时重传或者快速重传之后,发送方只发送丢失的那个报文段,接受方也不会丢弃之前接收的报文,会将出错报文之后的报文存放在缓冲区,等收到丢失报文之后在一起交给应用层;这样有效避免了浪费,不过对接收缓冲区有一定的要求,通常滑动窗口采用此协议;
d)零窗口
某些情况下,接收方可能来不及处理接收到的数据包,无法再接收新的数据,这时就可以使用零窗口协议,将接收窗口大小设置为0,发送方就不会再发新的数据;等到接受方有足够的缓冲空间了,又会发ACK消息给发送方来打开窗口,但是这个ACK可能丢失,从而导致发送方和接受方进入相互等待死循环;为了避免这种情况,发送方会设置坚持定时器,每隔一段时间就会向接受方发送一个探查报文,看接受窗口是否打开,坚持定时器的时间是指数退避的(每次坚持时间乘以2),最多每隔60秒发送一次,知道窗口打开或者应用程序连接关闭;
e)糊涂窗口综合征
指的是滑动窗口出现接收窗口较小(小于一个报文段大小即MSS)的情况下发送数据包,不是报文段满长的情况下发送数据,这种情况下带宽利用率很低;要避免这种情况,可以在发送方和接收方两边着手;
接收方不通告小窗口:当接受窗口为0后,除非能够接收一个最大报文MSS大小或者缓冲区的一半大小,否则不通告窗口打开
发送方不发送小包:使用TCP的Nagle算法,或者在没有等待被确认的报文时,直接发送数据;或者发送方累积到一个满长度的报文段(MSS),或者是报文大小大于接收窗口一半的时候在发送;
(3)拥塞控制(从网络角度考虑)
TCP重传以及滑动窗口的流量控制都只是从发送方和接受方的角度来考虑,还没有从网络环境的角度来考虑,如果网络出现拥塞了,各种重传只会导致拥塞更加严重,因此TCP有针对网络拥塞的控制策略,包括有:慢启动、拥塞避免、拥塞发生和快速恢复;
a)慢启动,指数增长
TCP支持慢启动算法,此算法通过控制发送端发送新的数据报进入网络的速率应该跟接收端返回确认的数据报的速率相同,从而起作用;慢启动为发送方增加了一个拥塞窗口(congestion window,cwnd),这是发送方用于控制流量的方法,而接收方的通告窗口大小是接收方控制流量的方法;发送方每次取拥塞窗口和通告窗口的最小值作为发送数据报总数的上限;每次发送方会以大小为MSS的数据报为单位,将允许发送数量的数据报全都发出去;
在TCP连接建立之初,cwnd初始化为一个MSS报文段大小,当每接收到一个ACK时,拥塞窗口就会增加一个MSS报文段大小,因此cwnd会呈指数增长(因为随着cwnd增大,允许发送的报文数量增加了,每个报文收到ACK都会增加一个MSS,因此是翻倍),如下图;
慢启动是指最初的启动发送上限小,但是增长其实并不慢;当然不能一直这样增长下去,发送方还有一个参数是慢启动阈值(ssthresh,通常是65535),当cwnd的大小达到ssthresh时,慢启动结束,进入拥塞避免阶段,如下;
b)拥塞避免,线性增长
在这个阶段,在每收到一个ACK,为cwnd增加(1/cwnd)* MSS大小,所以一个完整的轮回后,即全部发送的数据报的ACK都收到后,cwnd一共只增加了一个MSS大小((1/cwnd)*MSS*cwnd=MSS),因此cwnd的增长从指数增长变为线性增长,避免增长过快导致网络拥塞;
c)拥塞发生,乘法减小
当出现数据包丢失的情况,说明网络中出现拥塞了,这个时候设置ssthresh为当前窗口(实际发送上限:cwnd和接受方通告窗口的较小值)的一半,同时根据不同情况处理:
如果是超时重传:说明拥塞比较严重,进入慢启动阶段,将cwnd设置为1个MSS报文段大小,重传丢失的数据,当有一个新的数据报被确认后,cwnd就可以增长一个MSS大小;
如果是快速重传:说明可能发生了轻度拥塞,但还不需要进入慢启动阶段,进入快速恢复阶段(跟快速重传对应处理,类似于拥塞避免阶段);
d)快速恢复
重传丢失的报文段,并将cwnd设置为ssthresh+3*MSS报文段大小(因为有3个重复ACK,因此有三个老的报文离开网络被接受了),每次收到另一个重复的ACK时,cwnd增加一个MSS报文段大小并发送一个报文(进行重传),当收到新的数据包的ACK时,说明之前丢失的数据包都已经被确认了,恢复到拥塞避免阶段,设置cwnd为ssthresh的大小;
(4)保活机制
TCP提供保活心跳控制机制,默认情况下是2小时内通信双方没有任何通信,TCP保活机制就会激活,每隔75秒发送一次探查报文,一共发送10次,如果这10次都没有相应,则判定连接已经关闭,断开连接;
5、TCP服务器设计
呼入连接请求队列,当服务器正在相应其它事件时,这时有新的连接请求出现,TCP默认会先完成三次握手,接受连接,然后将该连接放入一个连接请求队列,等待应用层接受连接进行处理,这个队列的长度可以设置,python中是socket.listen(backlog),这个backlog就是连接队列的长度,叫积压值,如果这个队列满了,这新进来的连接不会再收到响应,如果队列中的连接长时间没有被应用层处理,也会因为超时而断开;