掌握TCP三次握手和四次挥手

云鱼Cloudy

引语

最近在重温计算机网络TCP/IP协议簇,TCP的三次握手四次挥手可以说是面试中有关计算机网络部分经常被拿出来问的部分,所以这里就做一个整理。

TCP概念与特点

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

TCP 是面向连接的传输层协议,面向连接是指发送数据之前必须在两端建立连接。建立连接的方法是“三次握手”。

TCP 提供可靠传输服务,实行“顺序控制”或“重发控制”机制。

TCP提供全双工通信,允许通信双方的应用程序在任何时候都能发送数据。TCP连接的两端都设有缓存,用来临时存放双向通信的数据。

TCP还具备“流控制(流量控制)”、“拥塞控制”、提高网络利用率等众多功能。当网络出现拥塞的时候,TCP能够减小向网络注入数据的速率和数量,缓解拥塞。

TCP仅支持单播传输,每条TCP传输连接只能有两个端点,只能进行点对点的数据传输,不支持多播和广播传输方式。

许多应用层协议都是基于TCP协议:

  • FTP:文件传输协议;
  • SSH:安全登录、文件传送(SCP)和端口重定向;
  • Telnet:不安全的文本传送;
  • SMTP:简单邮件传输协议Simple Mail Transfer Protocol (E-mail);
  • HTTP:超文本传送协议 (WWW);
  • 其他...

TCP报文段首部

TCP/IP协议的每个分层中,都会对所发送的数据附加一个首部,在这个首部中包含了该层必要的信息,如发送的目标地址以及协议相关信息。通常,为协议提供的信息为包首部,所要发送的内容为数据。在下一层的角度看,从上一层收到的包全部都被认为是本层的数据。

d1d5720c658a4479a7894bdaa072a05b.jpg

虽然TCP以字节流方式进行传输,但TCP传输的数据单元却是报文段。TCP报文段分为TCP首部和数据部分,TCP报文段首部的前20个字节是固定的,后面有4*n字节根据需要动态添加的选项,最大长度为40字节。

所以要理解TCP协议,首先要了解TCP报文段的首部。

page76image58697056.jpg

TCP报文段首部中各部分的含义:

  • 源端口和目的端口: 各占两个字节,TCP的分用功能也是通过端口实现的。
  • 序号 占4个字节,范围是[0,2^32],TCP是面向字节流的,每个字节都是按顺序编号。例如一个报文段,序号字段是201,携带数据长度是100,那么第一个数据的序号就是201,最后一个就是300。当达到最大范围,又从0开始。
  • 确认号: 占4个字节,是期望收到对方下一个报文段的第一个字节的序号。若确认号=N,则表示序号N前所有的数据已经正确收到了。
  • 数据偏移: 占4位,表示报文段的数据部分的起始位置,距离整个报文段的起始位置的距离。间接的指出首部的长度。
  • 保留: 占6位,保留使用,目前为0.
  • 标志位:共6个,即URG、ACK、PSH、RST、SYNFIN等,具体含义

    • URG(紧急): 当URG=1,表明紧急指针字段有效,该报文段有紧急数据,应尽快发送。
    • ACK(确认): 仅当ACK=1时,确认号才有效,连接建立后,所有的报文段ACK都为1。
    • PSH(推送): 接收方接收到PSH=1的报文段,会尽快交付接收应用经常,不再等待整个缓存填满再交付。实际较少使用。
    • RST(复位): RST=1时,表明TCP连接中出现严重差错,必须是否连接,再重连。
    • SYN(同步): 在建立连接时用来同步序号。当SYN=1,ACK=0,则表明是一个连接请求报文段。SYN=1,ACK=1则表示对方同意连接。TCP建立连接用到。
    • FIN(终止): 用来释放一个连接窗口。当FIN=1时,表明此报文段的发送方不再发送数据,请求释放单向连接。TCP断开连接用到。
  • 窗口: 占2个字节,表示发送方自己的接收窗口,窗口值用来告诉对方允许发送的数据量。
  • 校验和: 占2字节,检验和字段查验范围包括首部和数据部分。
  • 紧急指针: 占2字节,URG=1时,紧急指针指出本报文段中的紧急数据的字节数(紧急字节数结束后为普通字节)。
  • 选项: 长度可变,最长可达40字节。例如最大报文段长度MSS。MSS指的是数据部分的长度而不是整个TCP报文段长度,MSS默认为536字节长。窗口扩大,时间戳选项等。

需要注意的是

  1. 不要将确认序号Ack与标志位中的ACK搞混了。
  2. 确认方Ack=发起方Req+1,两端配对。

TCP的三次握手与四次挥手

开始之前,我们先来讲两个故事吧。

故事一:三次牵手
男:我喜欢你。
女:我知道了,我也喜欢你。
男:我知道了,那我们在一起吧。

故事二:四次分手
男:我不喜欢你了,我要和你分手。
女:我知道到了,等我先打完这把王者荣耀再说。
女:我打完了,我也不喜欢你了,我要和你分手。
男:我知道了,同意分手。

这真是两个有意思的故事,那么接下去转入正题。

序列号与确认号的含义

ACK=1:确认序号有效;
SYN=1:发起一个新连接;
FIN=1:释放一个连接;
seq = x:本报文段发送的数据的第一个字节的序号
ACK=1, ack = y:期待收到对方下一个报文段的第一个字节的序号。

举个例子:

  1. 序列号为2000且长度为100的数据包,在此连接上包含第2000-2099个字节,发送一个seq = 2000;
  2. 当接收器接收到包括第2099字节在内的所有字节时,它发送一个确认ack = 2100,表示它已接收到序号2000-2099的所有字节,并期待接下去接收到起始序号为2100的字节。

三次握手

注意,不是三次牵手 : )

所谓三次握手是指建立一个 TCP 连接时需要客户端和服务器端总共发送三个包以确认连接的建立。在socket编程中,这一过程由客户端执行connect来触发。
page77image65112304.jpg

第一次握手:

客户端将标志位SYN置为1,随机产生一个值seq=x,并将该数据包发送给服务器端,客户端进入SYN-SENT状态,等待服务器端确认。

第二次握手:

服务器端收到数据包后由标志位SYN=1知道客户端请求建立连接,服务器端将标志位SYNACK都置为1,ack=x+1,随机产生一个值seq=y,并将该数据包发送给客户端以确认连接请求,服务器端进入SYN-RCVD状态。

第三次握手:

客户端收到确认后,检查ack是否为x+1ACK是否为1,如果正确则将标志位ACK置为1ack=y+1,并将该数据包发送给服务器端,服务器端检查ack是否为y+1ACK是否为1,如果正确则连接建立成功,客户端和服务器端进入ESTABLISHED状态,完成三次握手,随后客户端与服务器端之间可以开始传输数据了。

连接建立总结:

SYN = 1,  seq = x;

SYN = 1,  ACK = 1, seq = y; ack = x + 1;

ACK = 1, seq = x + 1, ack = y + 1;

四次挥手

注意,不是四次分手 : )

四次挥手即终止TCP连接,就是指断开一个TCP连接时,需要客户端和服务端总共发送4个包以确认连接的断开。在socket编程中,这一过程由客户端或服务端任一方执行close来触发。

page77image65112096.jpg

中断连接端可以是客户端,也可以是服务器端。

第一次挥手:

客户端进程发出连接释放报文,并且停止发送数据。释放数据报文首部,FIN=1,其序列号为seq=u等于前面已经传送过来的数据的最后一个字节的序号加1,并将该数据包发送给服务器端,用来关闭客户端到服务器端的数据传送,客户端进入FIN-WAIT-1状态。意思是说:“我客户端没有数据要发给你了,但是如果你服务器端还有数据没有发送完成,则不必急着关闭连接,可以继续发送数据”

第二次挥手:

服务器端收到FIN=1后,发出确认报文,ACK=1ack=u+1,并且带上自己的序列号seq=v,告诉客户端:“你的请求我收到了,但是我还没准备好,请继续你等我的消息”。这个时候客户端就进入FIN-WAIT-2 状态,继续等待服务器端的FIN报文。客户端收到服务器的确认请求后,此时,客户端就进入FIN-WAIT-2状态,等待服务器发送连接释放报文(在这之前还需要接受服务器发送的最后的数据)。

第三次挥手:

当服务器端确定数据已发送完成,就向客户端发送连接释放报文。置FIN=1ACK=1ack=u+1,由于在半关闭状态,服务器很可能又发送了一些数据,假定此时的序列号为seq=w,告诉客户端:“好了,我这边数据发完了,准备好关闭连接了,请求关闭连接”。服务器端进入LAST-ACK状态。

第四次挥手:

客户端收到服务器端的连接释放报文后,必须发出确认ACK=1ack=w+1,而自己的序列号是seq=u+1,此时,客户端就进入TIME-WAIT状态,如果Server端没有收到ACK则可以重传。注意此时TCP连接还没有释放,必须经过2MSL(最长报文段寿命)的时间后,当客户端撤销相应的TCB后,才进入CLOSED状态。服务器只要收到了客户端发出的确认,立即进入CLOSED状态。同样,撤销TCB后,就结束了这次的TCP连接。可以看到,服务器结束TCP连接的时间要比客户端早一些。

连接断开总结:

FIN = 1, seq = u;

ACK = 1, seq = v; ack = u + 1;

FIN = 1, ACK = 1,    seq = w, ack = u + 1;

ACK = 1, seq = u + 1, ack = w + 1

这里列出每个状态所包含的含义以供参考:

LISTEN - 侦听来自远方TCP端口的连接请求;
SYN-SENT -在发送连接请求后等待匹配的连接请求;
SYN-RECEIVED - 在收到和发送一个连接请求后等待对连接请求的确认;
ESTABLISHED- 代表一个打开的连接,数据可以传送给用户;
FIN-WAIT-1 - 等待远程TCP的连接中断请求,或先前的连接中断请求的确认;
FIN-WAIT-2 - 从远程TCP等待连接中断请求;
CLOSE-WAIT - 等待从本地用户发来的连接中断请求;
CLOSING -等待远程TCP对连接中断的确认;
LAST-ACK - 等待原来发向远程TCP的连接中断请求的确认;
TIME-WAIT -等待足够的时间以确保远程TCP接收到连接中断请求的确认;
CLOSED - 没有任何连接状态;

TCP三次握手四次挥手相关面试问题

为什么要进行三次握手?

为了防止已失效的连接请求报文段突然又传送到了服务端,因而产生错误,同时确认双方的接收与发送能力是否正常。

为什么要 TCP 四次分手?

由于TCP连接是全双工的,因此,每个方向都必须要单独进行关闭,这一原则是当一方完成数据发送任务后,发送一个FIN来终止这一方向的连接,收到一个FIN只是意味着这一方向上没有数据流动了,即不会再收到数据了,但是在这个TCP连接上仍然能够发送数据,直到这一方向也发送了FIN。首先进行关闭的一方将执行主动关闭,而另一方则执行被动关闭。

为什要有 2MSL 等待延迟?
MSL是Maximum Segment Lifetime的英文缩写,可译为“最长报文段寿命”,它是任何报文在网络上存在的最长时间,超过这个时间报文将被丢弃。
为了保证客户端发送的最后一个ACK报文段能够到达服务器。因为这个ACK有可能丢失,从而导致处在LAST-ACK状态的服务器收不到对FIN-ACK的确认报文。服务器会超时重传这个FIN-ACK,接着客户端再重传一次确认,重新启动时间等待计时器。最后客户端和服务器都能正常的关闭。假设客户端不等待2MSL,而是在发送完ACK之后直接释放关闭,一但这个ACK丢失的话,服务器就无法正常的进入关闭连接状态。

两个理由:

  1. 保证客户端发送的最后一个ACK报文段能够到达服务端。 这个ACK报文段有可能丢失,使得处于LAST-ACK状态的B收不到对已发送的FIN+ACK报文段的确认,服务端超时重传FIN+ACK报文段,而客户端能在2MSL时间内收到这个重传的FIN+ACK报文段,接着客户端重传一次确认,重新启动2MSL计时器,最后客户端和服务端都进入到CLOSED状态,若客户端在TIME-WAIT状态不等待一段时间,而是发送完ACK报文段后立即释放连接,则无法收到服务端重传的FIN+ACK报文段,所以不会再发送一次确认报文段,则服务端无法正常进入到CLOSED状态。
  2. 防止“已失效的连接请求报文段”出现在本连接中。 客户端在发送完最后一个ACK报文段后,再经过2MSL,就可以使本连接持续的时间内所产生的所有报文段都从网络中消失,使下一个新的连接中不会出现这种旧的连接请求报文段。
什么是SYN攻击?

服务器端的资源分配是在二次握手时分配的,而客户端的资源是在完成三次握手时分配的,所以服务器容易受到SYN洪泛攻击。SYN攻击就是Client在短时间内伪造大量不存在的IP地址,并向Server不断地发送SYN包,Server则回复确认包,并等待Client确认,由于源地址不存在,因此Server需要不断重发直至超时,这些伪造的SYN包将长时间占用未连接队列,导致正常的SYN请求因为队列满而被丢弃,从而引起网络拥塞甚至系统瘫痪。SYN 攻击是一种典型的 DoS/DDoS 攻击。

检测 SYN 攻击非常的方便,当你在服务器上看到大量的半连接状态时,特别是源IP地址是随机的,基本上可以断定这是一次SYN攻击。在 Linux/Unix 上可以使用系统自带的 netstats 命令来检测 SYN 攻击。

netstat -n -p TCP | grep SYN_RECV

常见的防御 SYN 攻击的方法有如下几种:

  • 缩短超时(SYN Timeout)时间
  • 增加最大半连接数
  • 过滤网关防护
  • SYN cookies技术
(ISN)是固定的吗?

三次握手的一个重要功能是客户端和服务端交换ISN(Initial Sequence Number), 以便让对方知道接下来接收数据的时候如何按序列号组装数据。
如果ISN是固定的,攻击者很容易猜出后续的确认号,因此 ISN 是动态生成的。

什么是三次握手的半连接队列和全连接队列?

服务器第一次收到客户端的 SYN 之后,就会处于 SYN-RCVD 状态,此时双方还没有完全建立其连接,服务器会把此种状态下请求连接放在一个队列里,我们把这种队列称之为半连接队列

全连接队列指已经完成三次握手,建立起连接的就会放在全连接队列中。如果队列满了就有可能会出现丢包现象。

三次握手中SYN-ACK 重传次数

服务器发送完SYN-ACK包,如果未收到客户确认包,服务器进行首次重传,等待一段时间仍未收到客户确认包,进行第二次重传,如果重传次数超 过系统规定的最大重传次数,系统将该连接信息从半连接队列中删除。注意,每次重传等待的时间不一定相同,一般会是指数增长,例如间隔时间为 1s, 2s, 4s, 8s, ….

三次握手过程中可以携带数据吗?

第三次握手的时候可以携带数据的。第一次、第二次握手不可以携带数据。

对于第三次握手来说,此时客户端已经处于 established 状态,也就是说,对于客户端来说,他已经建立起连接了,并且也已经知道服务器的接收、发送能力是正常的了,所以能携带数据页没啥毛病。

结语

最近一段时间应该会比较系统性地重新整理一些TCP/IP协议簇有关的知识,目前正在打算开一个计算机基础相关的专题,可能主要会涉及操作系统计算机网络算法设计数据结构有关的内容。

俗话说的好,基础不牢,地动山摇,我们励志要成一只有梦想的优质前端攻城狮!

参考
《计算机网络 第7版 谢希仁》
终于把TCP/IP 协议讲的明明白白了,再也不怕被问三次握手了
「真香警告」重学 TCP/IP 协议 与三次握手
https://blog.csdn.net/freekin...
TCP和UDP比较
关于三次握手与四次挥手面试官想考我们什么?— 不看后悔系列
如果你还看不懂这篇TCP/IP协议的话,就可以来打我了


推荐阅读:
【专题:JavaScript进阶之路】
ES6 Promise
JavaScript之深入理解闭包
ES6 尾调用和尾递归
JavaScript之函数柯理化
浅谈 MVC 和 MVVM 模型


我是Cloudy,年轻的前端攻城狮一枚,爱专研,爱技术,爱分享。
个人笔记,整理不易,感谢关注、阅读、点赞和收藏。
文章有任何问题欢迎大家指出,也欢迎大家一起交流前端各种问题!
阅读 2.1k

感谢阅读、浏览和关注!

2.8k 声望
384 粉丝
0 条评论

感谢阅读、浏览和关注!

2.8k 声望
384 粉丝
文章目录
宣传栏