网络协议 -- TCP上

TCP包头格式

  1. 源端口号和目标端口号与UDP的一致
  2. 序号是为了解决乱序问题,确认哪个包应该先到,哪个包应该后到
  3. 确认序号,发出去的包应该有确认,如果没有收到确认就应该重新发送,直到送达,用于解决不丢包的问题
  4. TCP是可靠的协议
    • 从IP层面来讲,如果网络状况很差,是没有任何可靠性保证的
    • 作为IP的上一层TCP也是无能为力的,唯一能做的就是不断重传通过各种算法保证
  5. 状态位
    • SYN是发起一个连接,ACK是回复,RST是重新连接,FIN是结束连接
    • TCP是面向连接的,因而双方都要维护连接的状态,而带状态位的包的发送,会引起双方的状态变更
  6. 窗口大小
    • TCP会做流量控制,通信双方都要各自声明一个窗口,标识自己当前的处理能力
    • TCP还会做拥塞控制
  7. 小结
    • 序号 - 确保有序
    • 确认序号 - 防止丢包
    • 状态位 - 连接维护
    • 窗口大小 - 流量控制 + 拥塞控制

TCP三次握手

  1. TCP建立连接,被称为三次握手
    • 你好,我是A
    • 你好A,我是B
    • 你好B
  2. 请求 -> 应答 -> 应答之应答

为什么不是两次

  1. 假设网络非常不可靠,A要发起一个连接,如果发了第一个请求后没有得到响应,有三种可能:丢包超时B拒绝响应
  2. A会不断重发,终于有一个请求达到了B,但请求到达了B这个事情,A目前还不知道,A有可能还会继续重发
  3. B收到请求包后,知道A想要和它建立连接,如果B拒绝响应,过段时间后A会放弃重试,A和B都认为连接没有建立,这OK
  4. 一旦B愿意建立连接,就会发送应答包给A,如果采用两次握手,对于B来说就有问题了,因为B的应答包同样会面临上面问题
    • B根本就没办法确认应答包是不是真的能顺利到达A,自然就没办法认为连接已经建立好了
  5. 还有另一个诡异的现象,A和B原本建立了连接,简单通信后,结束了连接,而A之前建立时,可能会重发几次请求包
    • 有的请求包绕了一大圈又回来了,如果基于两次握手,B会认为这也是一个正常的请求,因此建立了连接
    • 但这个连接不会进行下去,也没有终结的时候
  6. B发送的应答可能会发送多次,但只要有一次到达A,A就可以认为连接已经建立,因为对A来说,它的消息是有去有回
  7. A会给B发送应答之应答,而B也只有等到这个消息,才能认为连接已经建立,因为对于B来说,它的消息也是有去有回

为什么不是四次

  1. 因为没有必要,因为A发给B的应答之应答也会丢失,如果推演下去,还应该有个应答之应答之应答,这会没有尽头
  2. 所以4次握手是可以的,但哪怕400次握手也无法保证真的可靠,只要双方的消息都有去有回,基本就可以了
  3. 而且大部分情况下,A和B建立连接之后,A会马上发送数据,一旦A发送数据,很多问题就能得到解决
    • 例如A发送给B的应答之应答丢失了,当A后续发送的数据到达时,B也会可以认为这个连接已经建立
    • 有例如B直接挂了,当A发送数据时,就会报错,提示B不可达
  4. 如果A建立连接后就不发送数据,在程序设计的时候,可以开启keepalive机制,即使没有真实的数据包,也会有探活包
  5. 作为服务端B的程序设计者,对于长时间不发包的客户端,应该主动关闭,从而节约系统资源

沟通序号

  1. A和B需要沟通各自发起包起始序号位,不能直接从1开始,因为往往会出现冲突
  2. A连上B之后,发送1、2、3三个包,但发送3的时候,绕路了,于是重新发送
    • 后来A掉线,重新连上B后,序号又从1开始,然后发生2,但没发送3,但上次绕路的3又回来了,B认为这是下一个包
    • 但这个绕路回来的3并不是A重连后想发送的3,因此会发生数据错误
  3. 因此每个连接都要有不同的序号
    • 序号的起始序号是随着时间变化的,可以看成32位的计数器,需要很长时间才能重复,绕路的包早已死亡(IP TTL)

状态变化时序图

  1. 一开始,客户端和服务端都处于CLOSED状态
  2. 服务端主动监听某个端口,处于LISTEN状态
  3. 客户端主动发起连接SYN,处于SYN-SENT状态
  4. 服务端收到客户端发起的连接,返回SYN,并且ACK客户端的SYN,之后处于SYN-RCVD状态
  5. 客户端收到服务端发送的SYN和ACK之后,发送ACK的ACK,之后处于ESTABLISHED状态,此时A的一发一收成功了
  6. 服务端收到ACK的ACK之后,处于ESTABLISHED状态,此时B的一发一收也成功了

TCP四次挥手

过程

  1. A:我不玩了,B:我知道了
  2. 此时只是表明A不会再发送数据,但B不能在ACK的时候,直接关闭连接
    • 因为A发送完最后的数据后,B可能还没有做完自己的事情,还可以继续发送数据,称为半关闭状态
    • 此时A可以选择不再接收数据,也可以选择最后接收一段数据,等待B也主动关闭
  3. B:我也不玩了,A:好的,此时整个连接就关闭了

异常情况

  1. A说“我不玩了”后,A直接跑路,因为B还没有发起结束,就算B发起结束,也得不到回答,B不知道如何处理
  2. A说“我不玩了”后,B直接跑路,A不知道B是有事情要处理,还是过一会会发送结束
  3. TCP协议专门设计了几个状态来处理这些异常情况

状态变化时序图

  1. A说“我不玩了”,进入FIN_WAIT_1状态
  2. B收到“A不玩”的消息,进入CLOSE_WAIT状态
  3. A收到“B知道”的消息,进入FIN_WAIT_2状态,如果此时B直接跑路,A将会永远停留在这个状态
    • TCP协议并没有对这个状态进行处理,但Linux有,可以调整tcp_fin_timeout参数
  4. 如果B没有跑路,发送了“B也不玩了”的消息,进入LAST_ACK状态
  5. “B也不玩了”的消息到达A后,并且A发送“好的”的ACK后,A的FIN_WAIT_2状态结束,按理来说A可以直接跑路
    • 但万一B收不到最后的ACK,B会重发“B也不玩了”,如果此时A已经跑路的话,B就再也收不到ACK了
    • 因此TCP协议要求A最后等待一段时间,即进入TIME_WAIT状态
      • 这个时间要足够长,长到如果B没有收到ACK的话,B会重发“B不玩了”,A会重发ACK并且有足够时间到达B
    • A直接跑路还有另外一个问题,就是A的端口会直接空出来,但B不知道,B原来发出的很多包可能都还在路上
      • 如果A的端口被一个新的应用占用了,新的应用会收到上个连接中B发过来的包,可能会产生混乱
      • 因此要等到足够长的时间,等到原来B发送的所有包都死亡了,再空出端口来

2MSL

  1. MSL:Maximum Segment Lifetime,报文最大生存时间,它是任何报文在网络中存在的最长时间
  2. TCP报文是基于IP协议的,而IP头中有一个TTL域,是IP数据报可以经过的最大路由数
    • 每经过一个处理它的路由器此值减1,当TTL为0时数据报将被丢弃,同时发送ICMP报文通知源主机
  3. TCP协议规定MSL为2分钟,实际中常用的是30秒1分钟2分钟
  4. 如果超过2MSL,B依然没有收到它发出的FIN所对应的ACK,B还会重发FIN,但此时A会直接返回RST,表明A已经跑路了

TCP状态机

加粗实线是客户端A的状态变迁,加粗虚线是服务端B的状态变迁

0%