TCP

TCP包头格式

def132524f225cc400994f545150ddcd.png
首先,源端口号和目标端口号是不可少的.
接下来是包的序列号.
然后就是确认序号.
接下来就是状态位.例如SYN是发起一个连接,ACK是回复,RST是重新连接,FIN是结束连接
还有一个重要的是窗口大小.TCP要做流量控制,通信双方各声明一个窗口,标识自己当前能够的处理能力.

TCP 的三次握手

64b9847002e0e29c96f141533a835cfc.png
一开始,客户端和服务端都处于CLOSED状态.先是服务端主动监听某个端口,处于LISTEN状态.然后客户端主动发起连接SYN,之后处于SYN-SENT状态.服务端收到发起的连接,返回SYN,并且ACK客户端的SYN,之后处于SYN-RCVD状态.客户端收到服务端发送的SYN和ACK之后,发送ACK的ACK,之后处于ESTABLISHED状态,因为它一发一收成功了.服务端收到ACK的ACK之后,处于ESTABLISHED状态,因为它也一发一收了.

TCP的四次挥手

4b15ec76165a5f158ee7ef4ec25219fe.png
断开的时候,我们可以看到A发了一个断开的数据包给B,然后就进入了FIN_WAIT_1的状态,B收到后就发送一个回应,就进入了CLOSE_WAIT的状态

A收到了B的数据包之后就进入了FIN_WAIT_2的状态,如果这个时候B直接断开,则A将永远在这个状态.TCP协议里面并没有对这个状态的处理,但是Linux有,可以调整TCP_FIN_TIMEOUT这个参数,设置一个超时时间

如果B没有直接断开,那么就会给A也发送一个表示断开的数据包,请求到达A之后会发送一个答应ACK给B,A会从FIN_WAIT_2状态结束,按理来说到这里A可以断开了,但是如果这个ACK万一B收不到呢?则B会重新发一个表示断开的数据包给A,这个时候如果A已经断开,那么B就再也收不到ACK了,因而TCP协议要求A最后等待一段时间TIME_WAIT,这个时间要足够长,长到如果B没收到ACK的话,能够一直等到B重发,并A再次发送

等待时间设置为2MSL,MSL是Maximum Segment Lifetime,报文最大生存时间,它是任何报文在网络上存在的最长时间,超过这个时间报文将被丢弃

还有一种异常的情况就是,B超过了2MSL的时间,依然没有收到它发送的FIN的ACK,按照TCP的原理,B当然还是会重发FIN,这个时候A再收到这个包之后,A就直接发送RST,B就知道A早已断开了

TCP状态机

将连接建立和断开的这个时序状态综合起来,就是著名的TCP的状态机
a19e4eed5be91226f6b5edd1b10167ff.png

累计确认

在TCP里,接收端会给发送端报一个窗口的大小,叫Advertised window.

发送端需要保持的数据结构
fdbaa065755dc607cc22d38dbe1e2a0a.png

  • LastByteAcked:第一部分和第二部分的分界线
  • LastByteSent:第二部分和第三部分的分界线
  • LastByteAcked+AdvertiedWindow:第三部分和第四部分的分界线

接收端需要保持的数据结构:
069f0076e0c8bf6d499770077b0e3cf0.png

  • MaxRcvBuffer: 最大缓存的量
  • LstByteRead: 已经接收了,但是还没被应用层读取的
  • NextByteExpected: 第一部分和第二部分的分界线

NextByteExpected 和 LastByteRead的差其实是还没被应用层读取的部分占用掉的MaxRcvBuffer 的量,我们定义为A.

AdvertiedWindow其实就是MaxRcvBuffer减去A

也就是说:AdvertiedWindow = MaxRcvBuffer-((NextByteExpected-1)-LastByteRead).

顺序问题与丢包问题

还是刚才的图,在发送端看来,1/2/3已经发送并确认;4/5/6/7/8/9都是发送了还没确认;10/11/12是还没发出的;13/14/15是接收方没有空间,不准备发的.

在接收端看来,1/2/3/4/5是已经完成ACK,但是没有读取的;6/7是等待接收的;8/9是已经接收,但是没有ACK的.

发送端和接收端当前的状态如下:

  • 1/2/3没有问题,双方达成一致
  • 4/5接收方说ACK;但是发送方还没收到,有可能丢了,有可能在路上
  • 6/7/8/9肯定都发了,但是8/9已经到了,但是6/7没到,出现了乱序,缓存着但是没办法ACK

假设4确认到了,不幸的是,5的ACK丢了,6/7的数据包丢了.

那么这会引起一种是超时重试,另一种是超时间隔加倍.

超时重试是指,对每一个发送了,但是没有ACK的包,都有一个定时器,超过了一定的时间就重新尝试.这个时间的评估就由自适应重传算法决定.TCP需要采样RTT的时间,然后进行加权平均,算出一个值,而且这个值是要不停变化的,因为网络状况不断的变化.除了采样RTT,还要采样RTT的波动范围,计算出一个估计的超时时间.

超时间隔加倍指,如果过一段时间,5/6/7都超时了,就会重新发送.接收方发现5原来接受过,于是丢弃5;6收到了,发送ACK,要求下一个是7,7不幸又丢失了.当7再次超时的时候,又需要重传的时候,TCP的策略是超时间隔加倍.每当遇到一次超时重传的时候,都会将下一次超时时间间隔设为钱值的两倍.两次超时,就说明网络环境差,不宜频繁反复发送

流量控制问题

在对于包的确认中,同时会携带一个窗口的大小.

我们假设窗口始终为9.4确认来的时候会右移一个,这个时候第13个包也发送了.
95431ffd78714da4a37bf76b8e343116.png
这个时候,假设发送端将10//11/12/13全发送完毕,之后就停止发送
5e9b0381c49c3478e284db37c5ce4c1b.png
当对于包5的确认到达的时候,在客户端相当于窗口再滑动一格
bddb59ebbf7eecc4853cafce0bb1dcc3.jpeg
如果接收方实在处理太慢,导致缓存中没有空间了,可以通过信息修改窗口的大小,甚至可以设置为0,则发送方将暂时停止发送.

我们假设一格极端的情况,接收端的应用一直不读取缓存中的数据,当数据包6确认后,窗口大小就不能再是9了,就要缩小一格变为8
92f66b1556b76c46c669aba232d35a31.jpeg

这个心的窗口8通过6的确认消息到达发送端的时候,窗口并没平行右移,但LastByteAcked右移了,窗口的大小从9改成了8

a78f5195ebf9b4f9dc4ea5a9b91e94ba.jpeg

如果接收端还是一直不处理数据,那么窗口越来越小, 直到0

150f28d9e745952f5968eff05e3f0ad2.jpeg

当这个窗口通过包14的确认到达发送端的时候,发送端的窗口也调整为0,停止发送

3014a6a259f74b0c950bf3067581ac30.jpeg

这种通过接收方来告诉发送方调整窗口大小的行为叫流量控制

拥塞控制问题

滑动窗口(rwnd)是怕发送方把接收方缓存塞满,而拥塞窗口cwnd,是怕把网络塞满

公式LastByteSent - LastByteAcked <=min{cwnd,rwnd} ,是拥塞窗口和滑动窗口共同控制发送的速度

TCP拥塞控制就是在不堵塞不丢包的情况下,尽量发挥带宽.

通道的容量= 带宽* 往返延迟

TCP的拥塞控制主要来避免两种现象, 包丢失和超时重传.

一开始的时候TCP发送数据的时候会慢启动,cwnd设置为一个报文段,一次只能发送一个;当收到这一个确认的时候cwnd加一,于是一次能够发送两个;当这两个的确认到来的时候,每个确认cwnd加一,两个确认cwnd加二,于是一次能够发送四个;当这四个的确认到来的时候,每个确认cwnd加一,四个确认cwnd加四,于是一次能够发送八个.可以看出这是指数性的增长.

涨到超过ssthresh设置的值,即65535个字节的时候,再慢下来.

每收到一个确认后,cwnd增加1/cwnd,我们接着上面的过程来,一次发送八个,当八个确认到来的时候,每个确认增加1/8,八个确认一共cwnd增加1,于是一次能够发送九个,变成了线性增长

原文地址:https://www.cnblogs.com/luozhiyun/p/10260037.html