专栏首页一杯82年的JAVA从TCP的三次握手和四次挥手说起

从TCP的三次握手和四次挥手说起

只知道它有三次握手和四次挥手是不足以应付严格的面试官的...

传输控制协议(TCP,Transmission Control Protocol)是一种面向连接的、可靠的、基于字节流的传输层通信协议。

我们熟悉的HTTP就是基于TCP来的。

TCP连接在面试中也是一个很高频的话题,一般面试官的起手式为:请讲讲TCP的三次握手/四次挥手...

本文大纲如下,看不完可以收藏慢慢看(看完怕忘也是)

  • 三次握手和四次挥手的简单讲解
  • 三次握手和四次挥手的进阶讲解
  • 为什么是三次握手
  • 为什么是四次挥手
  • TCP报文格式
  • 慢开始与拥塞避免
  • 快重传与快恢复
  • time_wait何时出现,大量出现时怎么处理
  • close_wait何时出现,大量出现时怎么处理

入门

入门级回答,简单描述下客户端和服务端之间每次在做什么。

发起连接请求的是客户端,接收连接请求的是服务端。

对于图中的几个关键词,是TCP报文中的控制位中的标志(为1表示有对应标志)

  • SYN 表示建立连接
  • FIN 表示关闭连接
  • ACK 表示响应

三次握手(建立连接,红色部分):

  1. 客户端向服务端发送一个SYN包(建交吧)
  2. 服务端收到并发送一个ACK + SYN包(好的,建交吧)
  3. 客户端收到并发送一个ACK包(好的)

完成这三个步骤后客户端和服务端建立起深厚的友谊,开始你来我往,传递数据。当双发无话可说时,友谊的小船说翻就翻。

四次挥手(断开连接,绿色部分):

  1. 客户端向服务端发送一个FIN包(我对你言尽于此)
  2. 服务端收到并发送一个ACK包(好,很好)
  3. 服务端再发送一个FIN包(我对你也没啥好说的了)
  4. 客户端向服务端发送一个ACK包(嗯,你也不错)

断开连接可以由任意一方先提出,一般是客户端提出的。

进阶

上面的描述比较简单,我们可以更加深入探索客户端和服务端之间的种种行为。

  • SYN 表示建立连接
  • FIN 表示关闭连接
  • ACK 表示响应
  • seq sequence number,表示的是我方(发送方)这边,这个packet的数据部分的第一位应该在整个data stream中所在的位置
  • ack acknowledge number:表示的是期望的对方(接收方)的下一次sequence number是多少

三次握手(建立连接,红色部分):

服务端处于监听状态(LISTEN),随时准备接受连接。

  1. 客户端向服务端发送一个SYN包,同时用个随机数作为初始序列号seq = x,进入SYN_SENT状态
  2. 服务端收到并发送一个ACK + SYN包,也随机个数来作为初始序列号seq = y,进入SYN_RCVD状态
  3. 客户端收到并发送一个ACK包(好的),进入ESTABLISHED状态,表示连接建立
  4. 服务端收到ACK后也进入ESTABLISHED状态,表示连接建立

完成这四个步骤后(只是多了一个状态变化的描述,还是三次握手哈)客户端和服务端再次建立起深厚的友谊,开始你来我往,传递数据。

当然天下无不散之筵席,挥手再见的时刻即将到来。

四次挥手(断开连接,绿色部分):

还是客户端先提出分手。

  1. 客户端向服务端发送一个FIN包,进入FIN_WAIT1状态
  2. 服务端收到并发送一个ACK包,进入CLOSE_WAIT状态
  3. 服务端再发送一个FIN包,进入LAST_ACK状态
  4. 客户端向服务端发送一个ACK包,进入TIME_WAIT状态,并在等待2MSL(报文最大生存时间)变为CLOSED状态
  5. 服务端收到ACK后也变为CLOSED状态

扩展

对于上面描述的过程,如果你之前不太了解,那么针对某些点肯定会有些许疑问。

下面总结一些可以延伸的问题。

为什么建立连接要三次握手

为何是三,不是二,也不是四?借助经典的打电话的场景来帮助理解。

  • 第一次握手:A对B说,小B,能听到吗?(SYN)
  • 第二次握手:B对A说,听得到(ACK),你能听到吗?(SYN)
  • 第三次握手:A对B说,俺也一样!(ACK)

在三次握手之后,A和B都能确定这么一件事:我说的话,你能听到;你说的话,我也能听到。如此这般,就可以开始愉快地交流了。

  • 如果两次,那么B无法确定B的信息A是否能收到,可能B发出的消息A都收不到。
  • 如果四次,可以,但没必要。

为什么断开连接需要四次挥手

为什么不能像建立连接那样三次?毕竟三次就能保证互相知晓了。

回顾上面的图,可以看到服务端得知客户端想要断开连接后,先给客户端发了一个ACK包,然后又发了一个FIN包,问题的关键在于这两步能否合并,如果可以那么就可以精简为三次挥手。

答案当然是不可以。因为服务端得知客户端想断开连接时,它这边可能还有些事没处理完,比如还有些消息没发完(我还有话说系列)。等它处理好后,再给客户端发送一个FIN包,表示它也可以结束了,这是客户端再发个ACK包到服务端,表示他知道了。

TCP报文格式

这个问题笔者面试时被问到过,当时自信且流畅地说完TCP的连接过程,甚至在内心默默给自己点了个赞。。。后面不说也罢。

TCP的报文构造还是有点复杂的,这里不讨论了,网上找了个图(来源见水印)。

可以看到上面曾有过出镜的FIN、ACK、SYN等东东,这些都存在报文对应位置。

慢开始与拥塞避免

发送方维持一个叫做拥塞窗口cwnd(congestion window)的状态变量。拥塞窗口的大小取决于网络的拥塞程度,并且动态地在变化。发送方让自己的发送窗口等于拥塞窗口,另外考虑到接受方的接收能力,发送窗口可能小于拥塞窗口。

快重传与快恢复

快重传

发送方只要一连收到三个重复确认就应当立即重传对方尚未收到的报文段,而不必继续等待设置的重传计时器时间到期

快恢复

  1. 当发送方连续收到三个重复确认时,就执行“乘法减小”算法,把ssthresh门限减半。但是接下去并不执行慢开始算法。
  2. 考虑到如果网络出现拥塞的话就不会收到好几个重复的确认,所以发送方现在认为网络可能没有出现拥塞。所以此时不执行慢开始算法,而是将cwnd设置为ssthresh的大小,然后执行拥塞避免算法

time_wait何时出现,大量出现时怎么发现和处理

timewait是主动关闭的一方会出现的状态,当收到对方发来的FIN包并返回一个ACK后,进入timewait。

time_wait存在的原因有两点

  1. 可靠地终止连接:若处于timewait的client发送给server确认报文段丢失的话,server将在此又一次发送FIN报文段,那么client必须处于一个可接收的状态就是timewait而不是close状态。
  2. 保证迟来的报文段有足够的时间被识别并丢弃:linux 中一个TCPport不能打开两次或两次以上。当client处于timewait状态时我们将无法使用此port建立新连接,假设不存在timewait状态,新连接可能会收到旧连接的数据。

timewait大量出现的场景,一般是服务端,因为一般是大量客户端连接少量服务端。虽然一般是客户端主动断开连接,但某些情况也可能是客户端向服务端发送一个信息,然后服务端主动关闭。这样就可能导致服务端短时间内出现大量timewait状态,而占用了资源致使不能创建更多的socket。

几个解决思路:

  1. 改为长链接
  2. 设计时尽量让客户端主动关闭
  3. 重用端口,即服务器设置SOREUSEADDR套接字选项来通知内核,如果端口忙,但TCP连接位于TIMEWAIT状态时可以重用端口
  4. 增加IP

close_wait何时出现,大量出现时怎么处理

close_wait是被动关闭的一方出现的状态,出现原因时,收到要关闭的信号后,自己这边还有些事情没处理完,导致迟迟不能发送FIN包给主动断开的一方。

所以说,一般大量出现都是我们的程序有问题,建议改代码。

通过netstat命令可以查看各种状态的连接数量,举个栗子:

➜  ~ netstat -an|awk '/tcp/ {print $6}'|sort|uniq -c
   1 CLOSED
   8 CLOSE_WAIT
   1 CLOSING
  42 ESTABLISHED
   1 FIN_WAIT_1
   2 FIN_WAIT_2
   2 LAST_ACK
  20 LISTEN
   3 SYN_SENT
   1 com.apple.network.tcp_ccdebug

本文分享自微信公众号 - 一杯82年的JAVA(acupjava),作者:acupt

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2019-09-09

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • JAVA中有趣的移位操作

    上次介绍了JAVA中有趣的位运算,知道了位运算是直接对一个整形的二进制位进行操作,效率上比起加减乘除高不少,因此常运用在对性能很敏感的场景。

    acupt
  • Linux实践 - 创建用户

    PS: 记不住ip,所以在个人电脑的hosts文件中设置了个别名,因为用的腾讯云服务器,就叫qqcloud。

    acupt
  • 在github搭建自己的博客

    在下以前也尝试过搭建个人主页,前端+后端+服务器+域名,等这些都弄过一遍后,发现系统设计的太挫没有使用的欲望,也没有写博客的欲望。某天突然醒悟了,别搞那些花里胡...

    acupt
  • 深入淘宝Diamond之客户端架构解析

    diamond是淘宝内部使用的一个管理持久配置的系统,它的特点是简单、可靠、易用,目前淘宝内部绝大多数系统的配置,由diamond来进行统一管理。 diamo...

    小程故事多
  • 从局部刷新到节省算力,微软在省钱上从不叨叨

    Power BI书签的应用场景是非常广泛的,比如实现翻页效果、界面选择系统、切换图和表等:

    陈学谦
  • 接口设计技巧和最佳实践

    你的响应应该是在代码中严格定义的嵌套数据业务模型,不要依赖数据库查询结果映射,或者其他操作

    公众号_松花皮蛋的黑板报
  • Python+Selenium基础篇之5-第一个完整的自动化测试脚本

    作者 | Anthony_tester,300w+访问量博主,Oracle测试开发工程师。

    测试开发社区
  • 编程小白 | 每日一练(149)

    这道理放在编程上也一并受用。在编程方面有着天赋异禀的人毕竟是少数,我们大多数人想要从编程小白进阶到高手,需要经历的是日积月累的学习,那么如何学习呢?当然是每天都...

    闫小林
  • 知识图谱中的推理技术及其在高考机器人中的应用

    用户1737318
  • Python3刷题系列(八)

    用户5473628

扫码关注云+社区

领取腾讯云代金券