TCP

协议头

image.png

在研究UDP的时候我们就看过UDP的协议头,现在看下TCP的协议头,和UDP一样需要源端口号和目的端口号用于多路复用和多路分解,然后下面就是重要的序号和确认号用于可靠传输,下面还有接受窗口大小用于流量控制,然后就是6位的标志位ACK,FIN等,4位就是首部的长度,因为后面有选项字段是可变的所以需要说明首部长度。

可靠传输协议

序列号和确认号

当数据传输的时候数据被分为不同的报文段,每一个报文段都有自己的序号用于表明它在整个数据中的位置。确认号就是当服务端收到报文段之后会返回一个报文段,在确认号填上所希望收到的下一段报文段的序号,比如已经收到了255的序列号的报文之后希望收到256的序列号的报文时就可以填上256到确认号中返回。
但是如果收到了1-255的报文这时候来了257,它还是会返回256的确认号,TCP始终返回第一个缺失的报文。

可靠传输数据

我们可以简单的称已经收到的ACK的数据序号为 SandBase-1。用书上的伪代码表示就是

  1. switch(){
  2. case(收到传输应用层的数据){
  3. 生成TCP报文发送给IP
  4. if(没有启动定时器){
  5. 启动定时器
  6. }
  7. break;
  8. }
  9. case(定时器超时){
  10. 重传具有最小序号未应答的数据
  11. 重新启动定时器
  12. }
  13. case(收到ACK){
  14. if(ack>SendBase){
  15. ' sendBase = ack
  16. if(仍旧有未应答的报文段){
  17. 重启定时器
  18. }
  19. }else{
  20. 冗余ACK数量++
  21. if(冗余ACK数量 == 3){
  22. 快速重传
  23. }
  24. }
  25. }
  26. }

简单来说TCP会接受三种事件

  1. 收到上层发过来的数据,那么它就会封装称TCP报文段然后交给IP传输,同时启动一个定时器。
  2. 定时器超时,这时候就重传最小序号的报文段,就是我们的SendBase
  3. 收到ACK,如果返回的报文的确认号比当前的SendBase大说明ACK之前的数据都收到了,这时候就更新SendBase,如果还有其他未收到ACK的报文那么就重启定时器。如果收到的比预期的报文小说明中间发生了报文的丢失或者乱序,当到达三次之后就判断为丢失这时候就立即重传数据并重启定时器。至于为什么是三次https://www.jianshu.com/p/4f2e412b0570 这篇博客有解释。

    流量控制

客户端发送数据的时候服务端会放入缓存,然后上层的会从缓存中获取这些数据,但是当客户端发送的速度比服务端上层获取的速度快的时候缓存就会不够用,如果不加限制超过缓存的数据就会被丢失,从而重传占用资源。为了解决这个问题TCP让发送方维护了一个 接受窗口 ,就是接受方还有多少缓存能够时候,接收方在返回的ACK中加入了一个字段就是剩余缓存的大小。发送方要一直保证下面的条件成立
发送的总数据 - ACK确认收到的数据 < 接受窗口
当接受窗口为0之后发送方会发送字节大小为1的数据报文段,这是应为不发送数据就没有ACK,那么当接受方有缓存之后也无法重新启动发送。发送之后等到ACK返回接受窗口不为0之后继续发送数据。

连接管理

3次握手

来到了大家喜闻乐见的三次握手环节了。首先放一张大家都很常见的图
image.png

首先解释下发送的都是啥。SYN就是我们前面说过的标志位,表示建立连接,sep就是序列号,ACK表示是返回标志位,ack表示确认号。下面看下三次握手

  1. 首先客户端生成一个标志位为SYN的TCP报文,其中随机一个报文序列号发送服务器。
  2. 服务器收到之后会返回ACK,并且将确认号赋值为序列号的值+1,同时发送自己的序列号,客户端收到后进入已连接的状态。
  3. 最后一步客户端将服务器的序列号+1返回给服务器,服务器收到后进入已连接状态完成三次握手。

有一个开玩笑的解释三次握手就是

  1. 客户端吼一声说我要说话了,你能听到吗
  2. 服务器听到了回答一句我听到了,你能听到我说话吗
  3. 客户端回一句我也能听到,然后双方就开始交流了

有一点我们要注意,进入已连接状态后就能发送数据了所以其实第三次握手的时候客户端就已经能发送数据了。

四次挥手

image.png

  1. 首先客户端发送一个FIN标志位的报文表示自己要断开连接,然后自己处于FIN_WAIT_1的状态下,服务器收到FIN之后进入CLOSE_WAIT状态。
  2. 当服务器将FIN的序列号+1返回ack,客户端收到后进入FIN_WAIT_2的状态。
  3. 服务器准备好断开之后发送FIN给客户端然后进入LAST_ACK状态,客户端接受之后进入TIME_WAIT状态,同时返回一个ack,之后客户端等待两个2MSL之后断开。
  4. 服务器接受到ack之后TCP连接断开。

    拥塞控制

    很多人会把这个和流量控制搞混,但实际上是不一样的。和流量控制一样拥塞控制有拥塞窗口。

  5. 慢启动,开始时拥塞窗口为一个MSS大小开始发送数据,当每收到一个ACK之后就加一个MSS的大小当发送2个报文之后收到拥塞窗口就变为4是指数级别增长的。当什么时候停止呢,首先就是一个超时事件,那拥塞窗口大小变为1,并且记录一个值ssthresh大小为1/2当拥塞窗口大小,当拥塞窗口重新增长到达ssthresh的时候如果继续慢启动就有可能超时了所以进入拥塞避免状态。还有一种可能就是收到了3个冗余ACK,那么马上进行一次快速重传然后进入快速恢复。

  6. 拥塞避免状态,这个状态下在一个RTT(报文往返时间)内就不能翻倍增长而是拥塞窗口只能+1,也就是线性增长了,当发生一次超时情况之后就会和慢启动一样将拥塞窗口设置为1然后重新开始,当发生3个冗余ACK时,TCP觉得接收端还是能收到一些数据当也就是情况没这么严重,所以拥塞窗口会变为现在当一半然后将ssthresh设置为当前拥塞窗口的一半然后进入快速恢复。
  7. 在快速恢复的时候,每收到一个冗余的ACK,拥塞窗口就会加1直到收到快速重传而进入快速恢复状态的那个报文段时,进入拥塞避免状态。如果超时那么和慢启动一样拥塞窗口变为1,ssthresh设置为拥塞窗口的一半然后进入慢启动状态。