zoukankan      html  css  js  c++  java
  • 详解TCP:三次握手、四次挥手

    TCP协议同样是运输层的协议,掌握TCP重点要关注这几个问题:顺序问题、丢包问题、连接维护、流量控制、拥塞控制。先解析下TCP报文段结构,相比于UDP要复杂很多。

    • 首先还是两个端口号,对应着具体的应用进程。
    • 序号指的是包的序号,为了解决包乱序问题。
    • 发出去的包应该有确认,如果接收方没有收到就应该重新发送,直到送达。确认序号解决的是丢包问题。
    • 由于TCP选项字段的原因,TCP首部长度是可变的。通常选项字段为空,所以TCP首部字段长度一般是20字节。
    • 窗口大小用于流量控制,用来指示接收方愿意接收的字节数量。
    • 选项字段用于发送方与接收方协商最大报文段长度或者在高速网络环境下用作窗口调节因子。
    • 标志字段由6个状态位组成,SYN是发起一个连接、ACK是回复、RST是重新连接、FIN是结束连接。URG表示报文段里存在着被发送端的上层实体置为“紧急” 的数据,紧急数据的最后一个字节由16b位的紧急数据指针字段指出。实际使用中,PSH、URG和紧急数据指针用不到。

    所有的问题起于连接,先关注连接管理问题。TCP的连接建立分为三步,通常称为三次握手。具体的的状态是:

     

    客户端收到服务端发送的 SYN 和 ACK 之后,发送ACK 的 ACK,之后处于 ESTABLISHED 状态,因为它一发一收成功了。服务端收到 对ACK 的 ACK 之后,处于 ESTABLISHED 状态,因为它也一发一收了。

    为什么不是两次、四次或者更多次?

      比如AB要建立连接,A发起一个连接但连接请求没有响应,可能包丢了、包绕弯路了、超时了等等,A于是继续给B发包,终于B收到了请求包但A并不知道,所以A可能还会继续发。B收到包之后,知道了A存在并且要和它建立连接。如果B不想建立连接,A重试一会后放弃,连接建立失败没有问题。如果B愿意建立连接,那就会发应答包给A。同样这个应答包会和请求包出现一样的问题,那么对于B不能认为连接建立好了。

      B发送的应答包可能会发送多次,只要有一次到达A,那么A就认为连接已经建立了,因为A的消息有去有回,相当于两次握手。假如这时候连接已经建立,并且做了简单通信后,结束了连接。上面讲了A要建立连接的时候,可能会发送多个请求包。有的请求包绕了一大圈又回来了,B 会认为这也是一个正常的的请求,再次建立连接,但这个连接不会正常接发数据、也不会终结,B就一直等待。A发的消息有去有回,但B发出的消息没有回,所以两次握手肯定不行。其实三次握手中,A发给B回复的回复也会丢、绕路或者B挂了,按照这个逻辑B还要再发个确认就变成了四次握手,这样不停套娃多达几百次也可以,但这也不能保证就可靠了。TCP连接的设计原则是双发的消息都有去有回就基本可以了。

      大部分情况下,A和B建立连接后会A会马上发送数据,如果A发给B的应答丢了,A后续发送的数据到达时,B可以认为连接已经建立;又或者B挂了,那么直接报错返回B不可达信息都没问题。如果A就是不发送数据,客户端可以开启keeplive机制,发送探活包。服务端设置规定时间,超时了就主动关闭,释放资源。

      TCP把数据看成一个无结构、有序的字节流,一个报文段的序号是该报文段首字节的字节流编号。三次握手除了确保双方消息有去有回,最重要的是解决tcp包的序号问题。A、B双方互相告知对方包的起始序号。又会有个疑问,为什么一定要交换序号,不能从序号1开始发包吗?假设A给B发3个包,序号为1、2、3,但3包可能绕路了,没有正常到达B,A又重新发送。后来A掉线了,重新连上B后又从1开始,发送两个包1、2,但上次出问题的包3又回来了,发给了B,B认为这就是A要发给它的下一个包,于是出现错误。因此每个连接都要有不同的序号,序号的起始序号每4ms加一,等遇到重复的序号要4个多小时。比如3号包绕路了,就要隔4个多小时连接的起始序号才是3,这么长时间3号包已经无效了,因为IP包头里有TTL(生存时间)。

    再来看四次挥手,前两次和握手连接差不多。同样将发送接收方用AB表示。当B进入CLOSED-WAIT,A进入FIN-WAIT2状态时,如果B突然挂了,那么A会一直处于这个状态,TCP本身没有对这种个状态进行处理。在Linux中可以调整 tcp_fin_timeout 参数,设置超时时间。

      如果一切正常,进行第三次挥手A收到B的主动关闭请求并发送确认后进入TIME-WAIT状态,为什么需要这个状态呢?假设A发送完确认消息后直接关闭,如果B没收到这个确认消息,那B会重发结果就是B关闭失败。还有一种情况是A的端口空出来,但B还保持原来状态不知道A的情况。如果 A 的端口被一个新的应用占用了,这个新的应用会收到上个连接中 B 发过来的包,数据接收错误。所以A要进入TIME-WAIT状态,并且TCP设置了等待时间2MSL(报文段最大生存时间),任何报文在网络上存在超过这个时间将被丢弃。协议规定 MSL 为 2 分钟,实际应用中常用的是 30秒,1 分钟和 2 分钟等。还有一种情况是超过了2MSL,B依然没有收到对FIN的ACK。这时即便A收到了B重发的FIN,A会直接发送RST,表示重新开始挥手。

    参考资料:《趣谈网络协议》刘超

         《计算机网络:自顶向下方法》原书第六版 陈鸣译

  • 相关阅读:
    Django开发笔记一
    Netty+SpringBoot写一个基于Http协议的文件服务器
    SQL优化
    mysql 字符串数字转换
    git 常用的命令总结
    eclipse 使用Maven deploy命令部署构建到Nexus
    intellij idea远程debug调试resin4教程
    postman 请求种添加用户权限
    对List中每个对象元素按时间顺序排序
    List根据时间字符串排序
  • 原文地址:https://www.cnblogs.com/fly-bryant/p/13340927.html
Copyright © 2011-2022 走看看