三次握手(three-way handshaking)
1.背景:TCP位于传输层,作用是提供可靠的字节流服务,为了准确无误地将数据送达目的地,TCP协议采纳三次握手策略。
2.原理:
1)发送端首先发送一个带有SYN(synchronize)标志地数据包给接收方。
2)接收方接收后,回传一个带有SYN/ACK标志的数据包传递确认信息,表示我收到了。
3)最后,发送方再回传一个带有ACK标志的数据包,代表我知道了,表示’握手‘结束。
通俗的说法
1)Client:嘿,李四,是我,听到了吗?
2)Server:我听到了,你能听到我的吗?
3)Client:好的,我们互相都能听到对方的话,我们的通信可以开始了。

TCP 建立连接为什么要三次握手而不是两次?
网上大多数资料对这个问题的回答只有简单的一句:防止已过期的连接请求报文突然又传送到服务器,因而产生错误,这既不够全面也不够具体。下面给出比较详细而全面的回答:
防止已过期的连接请求报文突然又传送到服务器,因而产生错误
在双方两次握手即可建立连接的情况下,假设客户端发送 A 报文段请求建立连接,由于网络原因造成 A 暂时无法到达服务器,服务器接收不到请求报文段就不会返回确认报文段,客户端在长时间得不到应答的情况下重新发送请求报文段 B,这次 B 顺利到达服务器,服务器随即返回确认报文并进入 ESTABLISHED 状态,客户端在收到 确认报文后也进入 ESTABLISHED 状态,双方建立连接并传输数据,之后正常断开连接。此时姗姗来迟的 A 报文段才到达服务器,服务器随即返回确认报文并进入 ESTABLISHED 状态,但是已经进入 CLOSED 状态的客户端无法再接受确认报文段,更无法进入 ESTABLISHED 状态,这将导致服务器长时间单方面等待,造成资源浪费。
三次握手才能让双方均确认自己和对方的发送和接收能力都正常
第一次握手:客户端只是发送处请求报文段,什么都无法确认,而服务器可以确认自己的接收能力和对方的发送能力正常;
第二次握手:客户端可以确认自己发送能力和接收能力正常,对方发送能力和接收能力正常;
第三次握手:服务器可以确认自己发送能力和接收能力正常,对方发送能力和接收能力正常;
可见三次握手才能让双方都确认自己和对方的发送和接收能力全部正常,这样就可以愉快地进行通信了。
告知对方自己的初始序号值,并确认收到对方的初始序号值
TCP 实现了可靠的数据传输,原因之一就是 TCP 报文段中维护了序号字段和确认序号字段,也就是图中的 seq 和 ack,通过这两个字段双方都可以知道在自己发出的数据中,哪些是已经被对方确认接收的。这两个字段的值会在初始序号值得基础递增,如果是两次握手,只有发起方的初始序号可以得到确认,而另一方的初始序号则得不到确认。
TCP 建立连接为什么要三次握手而不是四次?
答:相比上个问题而言,这个问题就简单多了。因为三次握手已经可以确认双方的发送接收能力正常,双方都知道彼此已经准备好,而且也可以完成对双方初始序号值得确认,也就无需再第四次握手了。
有一种网络攻击是利用了 TCP 建立连接机制的漏洞,你了解吗?这个问题怎么解决?
答:在三次握手过程中,服务器在收到了客户端的 SYN 报文段后,会分配并初始化连接变量和缓存,并向客户端发送 SYN + ACK 报文段,这相当于是打开了一个“半开连接 (half-open connection)”,会消耗服务器资源。如果客户端正常返回了 ACK 报文段,那么双方可以正常建立连接,否则,服务器在等待一分钟后会终止这个“半开连接”并回收资源。这样的机制为 SYN洪泛攻击 (SYN flood attack)提供了机会,这是一种经典的 DoS攻击 (Denial of Service,拒绝服务攻击),所谓的拒绝服务攻击就是通过进行攻击,使受害主机或网络不能提供良好的服务,从而间接达到攻击的目的。在 SYN 洪泛攻击中,攻击者发送大量的 SYN 报文段到服务器请求建立连接,但是却不进行第三次握手,这会导致服务器打开大量的半开连接,消耗大量的资源,最终无法进行正常的服务。
解决方法:SYN Cookies,现在大多数主流操作系统都有这种防御系统。SYN Cookies 是对 TCP 服务器端的三次握手做一些修改,专门用来防范 SYN 洪泛攻击的一种手段。它的原理是,在服务器接收到 SYN 报文段并返回 SYN + ACK 报文段时,不再打开一个半开连接,也不分配资源,而是根据这个 SYN 报文段的重要信息 (包括源和目的 IP 地址,端口号可一个秘密数),利用特定散列函数计算出一个 cookie 值。这个 cookie 作为将要返回的SYN + ACK 报文段的初始序列号(ISN)。当客户端返回一个 ACK 报文段时,服务器根据首部字段信息计算 cookie,与返回的确认序号(初始序列号 + 1)进行对比,如果相同,则是一个正常连接,然后分配资源并建立连接,否则拒绝建立连接。
同时打开
这是 TCP 建立连接的特殊情况,有时会出现两台机器同时执行主动打开的情况,不过概率非常小,这种情况大家仅作了解即可。在这种情况下就无所谓发送方和接收方了,双放都可以称为客户端和服务器,同时打开的过程如下:
如图所示,双方在同一时刻发送 SYN 报文段,并进入 SYN-SENT 状态,在收到 SYN 后,状态变为 SYN-RECEIVED,同时它们都再发送一个 SYN + ACK 的报文段,状态都变为 ESTABLISHED,连接成功建立。在此过程中双方一共交换了4个报文段,比三次握手多一个。
四次挥手(Four-Way-Wavehand)
1.意义:当被动方收到主动方的FIN报文通知时,它仅仅表示主动方没有数据再发送给被动方了。但未必被动方所有的数据都完整的发送给了主动方,所以被动方不会马上关闭SOCKET,它可能还需要发送一些数据给主动方后,再发送FIN报文给主动方,告诉主动方同意关闭连接,所以这里的ACK报文和FIN报文多数情况下都是分开发送的。
2.原理:
1)第一次挥手:Client发送一个FIN,用来关闭Client到Server的数据传送,Client进入FIN_WAIT_1状态。
2)第二次挥手:Server收到FIN后,发送一个ACK给Client,确认序号为收到序号+1(与SYN相同,一个FIN占用一个序号),Server进入CLOSE_WAIT状态。
3)第三次挥手:Server发送一个FIN,用来关闭Server到Client的数据传送,Server进入LAST_ACK状态。
4)第四次挥手:Client收到FIN后,Client进入TIME_WAIT状态,接着发送一个ACK给Server,确认序号为收到序号+1,Server进入CLOSED状态,完成四次挥手
通俗的说法
1)Client:我所有东西都说完了
2)Server:我已经全部听到了,但是等等我,我还没说完
3)Server:好了,我已经说完了
4)Client:好的,那我们的通信结束

详解
面试官直呼TCP三次握手和四次挥手问题答得完美 - 腾讯云开发者社区-腾讯云 (tencent.com)
三次握手建立链接,四次挥手断开链接。这个问题算非常经典的问题,也是面试官非常喜欢问的问题。
不夸张的说,龙叔在校招面试的时候每一家公司都问到过关于三次握手和四次挥手相关的问题,相信大家也都差不多被面试官各种怼。

这个问题的重要性,已经意识到。不说废话了,接下来就是听龙叔给你安排的明明白白。
先画个图,看下TCP的建立连接 和 断开连接的整体过程。

tcp三次握手四次挥手
看完这个图相信聪明的你在整体对三次握手和四次挥手有了一些基本把控。但是,里面的细节肯定是会有些生疏或者模糊的,接下来就一个一个问题的揭露本质。
在解释之前先看点基础知识做做铺垫。
TCP状态转移解释
| 状态 | 描述 |
|---|---|
| CLOSED | 阻塞或关闭状态,表示主机当前没有正在传输或者建立的链接 |
| LISTEN | 监听状态,表示服务器做好准备,等待建立传输链接 |
| SYN RECV | 收到第一次的传输请求,还未进行确认 |
| SYN SENT | 发送完第一个SYN报文,等待收到确认 |
| ESTABLISHED | 链接正常建立之后进入数据传输阶段 |
| FIN WAIT1 | 主动发送第一个FIN报文之后进入该状态 |
| FIN WAIT2 | 已经收到第一个FIN的确认信号,等待对方发送关闭请求 |
| TIMED WAIT | 完成双向链接关闭,等待分组消失 |
| CLOSING | 双方同时关闭请求,等待对方确认时 |
| CLOSE WAIT | 收到对方的关闭请求并进行确认进入该状态 |
| LAST ACK | 等待最后一次确认关闭的报文 |
再看下TCP的报文格式

TCP报文格式
首部有20字节的固定长度,含义如下:
- 源端口和目的端口
各占2字节,就是存储源端口号和目的端口的
- 序号seq
占4字节,表示的范围就是整形的范围[0~2^32]。序号使用在给数据部分每个字节进行编号的,编号方式是mod 2^32 。
- 确认号ack
占4字节,范围也是无符号整数的范围。使用在对端传输给我的数据最后一个字节序号,例如A传输给B 101—500,此时B返回的确认号一定是小于等于501的。当B段正确接收数据之后才会返回确认号,换句话说确认号之前的数据已经全部接收。
- 数据偏移
占4bit,数据偏移很多人很容易想到是不是表示数据的长度,那就错了。偏移嘛,指的是TCP起始位置到数据部分的起始位置的偏移,也就是TCP首部的长度。
- 保留
占6bit,保留字段顾名思义,就是为今后使用,默认置为0。
- 紧急URG控制位
占用1bit,URG=1,表示紧急指针有效,此时tcp数据优先传输。相当于生活中的紧急通道,特殊情况时使用。 在网络中也会有特殊情况,例如,发送一个很长的程序在远程服务器上运行,此时发现程序有bug,需要中断运行,因此我们从键盘输入Ctrl c,假如不使用紧急数据,需要在缓冲区里排队,都知道是bug了,还要排队,这怕是要出锅啊。 此时使用紧急数据传输,不需要排队,直接中断程序是不是更符合我们的预期。 需要注意一点是,即使窗口为0时,也可以发送紧急数据。
如何使用紧急URG控制位,在socket编程中send函数flag参数
send(int socket, const void *buffer, size_t length, int flags);flags参数传MSG_OOB宏时,表示此时有紧急数据。MSG_OOB是个宏,
- 确认ACK
占1bit,当ACK=1时生效。TCP有条硬性规定,当建立链接成功后所有传输的数据报文都必须把ACK置为1。
- 推送PSH
占1bit,发送方把PSH置为1时 会立即发送该数据包,接收方收到PSH=1的报文会立即处理交付给应用层处理。是不是感觉和URG很像,其实还是有些区别的。
- 两者相同点:
URG与PSH两者都使用于紧急处理的情况,用来快速传输紧急数据。
- 两者不同点
URG置为1时,对于发送发,“带外数据”与正常情况下应该发送的消息数据一起,封装成数据报发送,省去了在队列中等待的时间。 在接收方,解析报文后,获取数据之后还是要放在缓存区中,等待满了之后在向上往应用层交付。
PSH置为1时,对于发送方,表明这些数据不需要等向下发送的缓存区满,立刻封装成报文,发送,省去了等待发送缓存区到达满的状态的时间。 在接收方,也不需要等接受缓存区满,直接向上交付给应用层。
- 复位RST
占1bit,当RST=1时,TCP会主动释放链接,两种情况会用上。 TCP出现严重差错时,会主动释放连接,重建链接,传输数据。 遇到非法报文或者拒绝连接时会把RST置为1.
- 同步SYN
占1bit,同步控制位,用来在传输连接建立时同步传输连接序号。 SYN=1时,表示这是一个连接请求或连接确认报文。 SYN=1,ACK=0,表明这是一个连接请求数据段,如果对方同意建立连接,则对方会返回一个SYN=1、ACK=1的确认。
- FIN控制位
占1bit,用于释放一个传输连接。 FIN=1时,表示数据已全部传输完成,发送端没有数据要传输了,要求释放当前连接,但是接收端仍然可以继续接收还没有接收完的数据。 FIN=0,正常传输数据。
- 窗口大小
占16bit,2byte,用于表示发送方可以接受的最大数据大小。 该窗口是动态变化的,用作流量控制时使用。
- 检验和
占16bit,2byte,用于对TCP头部,伪头部,数据三个部分进行校验。
- 紧急指针
占16bit,2byte,用于记录紧急数据的末尾在数据段中的位置。 当URG=1时,该指针才生效。
- 可选项
可选项最长可达40byte,是可选的,可以没有。当可选项不存在时,TCP头部长度为20byte。 可选项可以包括窗口缩放选项(Window ScaleOption,WSopt)、MSS(最大数据段大小)选项、SACK(选择性确认)选项、时间戳(Timestamp)选项等。
- 数据
TCP数据部分,由应用层应用程序提交的数据。
TCP头部是基础知识,必须了解才能更好的理解TCP数据如何封装和传输,以及在建立链接和断开链接时都在操作那些地方。
三次握手建立连接
三次握手如何建立连接?

三次握手建立链接
从图中可以清楚的看到,三次握手的过程,我在在把过程清楚的解释一遍,顺便说下每个过程容易被问到的知识点。
采用C/S模式解释,假设C端发起传输请求。
在发送建立链接请求之前,C端是保持CLOSED状态,S端最开始也是处于CLOSED状态,当执行listen函数套接字进入被动监听状态。
所谓被动监听,是指当没有客户端请求时,套接字处于“睡眠”状态,只有当接收到客户端请求时,套接字才会被“唤醒”来响应请求。
第一次:C端发送SYN=1的请求报文,此时C端进入SYN SENT状态,等待服务器确认。
此时如果报文丢失发送不到对端会如何?
C端发送报文之后会启动一个定时器,在超时之后未收到S端的确认,会再次发送SYN请求,每次尝试的时间会是第一次的二倍,如果总的总尝试时间为75秒,此次建立链接失败。
第二次:S端收到C端发送的SYN报文(建立链接请求)后,S端必须返回确认号并且同时发送一条SYN报文,此时进入SYN RCVD状态。
为啥要连带发送SYN报文?
TCP是全双工通信,协议规定当收到建立链接请求后必须返回序列号,同时建立本端到对端的通信链接。这也叫做捎带应答机制。
如果第二次报文丢失怎么办?
在发送完ACK+SYN报文后会启动一个定时器,超时没有收到ACK确认,会再次发送,会进行多次重试。超时时间依旧每次翻倍,重试次数可设置。 修改
/proc/sys/net/ipv4/tcp_synack_retries的值

第三次:C端收到S端发的ACK+SYN报文,需要返回一个应答ACK的报文,此时该连接会进入半连接状态的队列,当S端收到ACK后,一条完整的全双工TCP链接建立完成,双方进入ESTABLISHED状态。
这里有个常用攻击手段,攻击者伪造一个SYN请求发送给服务端,服务端响应之后,会收不到C端的ACK确认,服务端会不断的重试,默认会重试五次。 此时服务端会维持这个链接的所有资源,如果有大量这样的请求,服务端的资源会被耗完。 这就是DOS攻击。
如果第三次报文丢失怎么办?
S端在发出ACK+SYN报文后会启动一个定时器,在超时触发还没收到ACK就确认是丢失了,会重试一次发送。
这里面的每个状态都必须搞明白,面试官也超级爱问上面的状态转移。
龙叔还遇到过一个面试官问我用过socket编程么?问我用过哪些socket函数?
C端socket编程代码
//C端
#define PORT 8080
#define BUFFER_SIZE 1024
int main(int argc, char **argv)
{
//定义IPV4的TCP连接的套接字描述符
int sock_cli = socket(AF_INET,SOCK_STREAM, 0);
//定义sockaddr_in
struct sockaddr_in servaddr;
memset(&servaddr, 0, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = inet_addr(argv[1]);
servaddr.sin_port = htons(PORT);
//连接服务器,成功返回0,错误返回-1
int ret = connect(sock_cli, (struct sockaddr *)&servaddr, sizeof(servaddr));
//客户端将控制台输入的信息发送给服务器端,服务器原样返回信息,阻塞
while (fgets(sendbuf, sizeof(sendbuf), stdin) != NULL)
{
ret=send(sock_cli, sendbuf, strlen(sendbuf),0); ///发送
recv(sock_cli, recvbuf, sizeof(recvbuf),0); ///接收
fputs(recvbuf, stdout);
}
close(sock_cli); // 关闭连接
return 0;
}2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
复制
S端socket编程代码
int main(int argc, char **argv)
{
//定义IPV4的TCP连接的套接字描述符
int server_sockfd = socket(AF_INET,SOCK_STREAM, 0);
//定义sockaddr_in
struct sockaddr_in server_sockaddr;
server_sockaddr.sin_family = AF_INET;
server_sockaddr.sin_addr.s_addr = htonl(INADDR_ANY);
server_sockaddr.sin_port = htons(PORT);
//bind成功返回0,出错返回-1
if(bind(server_sockfd,(struct sockaddr *)&server_sockaddr,sizeof(server_sockaddr))==-1)
//listen成功返回0,出错返回-1,允许同时监听的连接数为QUEUE_SIZE
if(listen(server_sockfd,QUEUE_SIZE) == -1)
for(;;)
{
struct sockaddr_in client_addr;
socklen_t length = sizeof(client_addr);
//进程阻塞在accept上,成功返回非负描述字,出错返回-1
int conn = accept(server_sockfd, (struct sockaddr*)&client_addr,&length);
//处理数据部分
...
}
close(server_sockfd);
return 0;
}2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
复制
为什么需要三次握手建立链接,2次可以么,4次行不行?
这问题问的,面试官是咋了?在这明知故问的,整些有的没的。肯定是不行啊,RFC 标准就是这样写的啊。
可不敢这样回答啊,标准是说的三次握手建立链接,可没说四次不行啊。要是这样答,妥妥的会收到,同学我们今天的面试到此基本结束了,你回家等消息...
龙叔来说说这个问题,为什么不能两次?
如果第二次不发送SYN+ACK,只是发送确认应答消息ACK,会造成只能建立单向通信,而且不能应答。而TCP是全双工通信的,而且必须保证可靠性。
如果第二发送SYN+ACK,不用应答。此时会出现三种情况
一、二次握手失败,C端会重复发送SYN报文,等待对端发送确认报文,S端会保存tcp连接的所有资源,大量的这种情况会导致S资源耗尽。
二、二次握手成功,S收不到ACK会重复发送SYN+ACK报文。
三、二次握手完以后,双方以为连接建立成功,即可开始通信。假如此时连接并没有真的建立成功,S端开始发送消息,会造成网络拥堵发生。
为什么不能是四次?
四次其实原则上来说是可以的,就是把第二次的ACK和SYN分两次发送。在理论上是完全可以行得通的,但是TCP本着节约网络网络资源的前提。
还有一种是不拆开二次握手的捎带应答,三次握手之后C端继续发送SYN报文,其时这是徒劳的。第三次完成以后链接已经建立,后面无论多少次都是徒劳。
如果双方同时建立连接,会发生什么情况?

TCP同时建立链接
这就是双方同时建立链接的情况,情况还不错,反正能建立成功,这点是肯定的。但是要注意两点
第一、此时只会建立一条全双工的TCP链接,不是两条。
第二、双方没有CS之分,两端都是同时承担两个角色,客户端和服务器。
四次挥手断开链接
先整个图看下四次挥手的整个过程和状态转移。状态转移会考看仔细点。

四次挥手断开链接
依旧采用C/S模式解释此过程。
第一次:当C端的应用程序结束数据传输是,会向S端发送一个带有FIN附加标记的报文段(FIN表示英文finish),此时C端进入FIN_WAIT1状态,C端不能在发送数据到S端。
第二次:S端收到FIN报文会响应一个ACK报文,S端进入CLOSE_WAIT状态。进入此状态后S端把剩余未发送的数据发送到C端,C端收到S端的ACK之后,进入FIN_WAIT2状态。
同时继续接受S端传输的其他数据包。
第三次:S端处理完自己待发送的数据之后,也会发送FIN断开链接的请求,S端进入LAST_ACK状态。
第四次:C端收到S端的断开链接请求后会启动一个定时器,该定时器时长是2MSL(最大段报文生存时间),同时发送最后一次ACK报文。
为什么要四次挥手?
TCP是全双工的通信机制,每个方向必须单独进行关闭。 TCP传输连接关闭的原则如下: 当一端完成它的数据发送任务后就可以发送一个FIN字段置1的数据段来终止这个方向的数据发送;当另一端收到这个FIN数据段后,必须通知它的应用层 对端已经终止了那个方向的数据传送。
为什么不能用三次握手中捎带应答机制减少一次握手?
这点到是很迷惑人,但是掌握了TCP传输的一些细节就会发现并不难。 TCP是全双工通信的,S收到断开链接请求后只是表示C端不会传输数据到S端了,但是并不表示S端不传输数据到C端。 如果采用捎带应答,S端将无法把剩余的数据传输到C端。
为何最后一次ACK之后需要等待2MSL的时间?
网络是不可靠的,TCP是可靠协议,必须保证最后一次报文送达之后才能断开链接,否则会再次收到S端的FIN报文信息。 而等待2MSL时间就是为了保证最后最后一次报文丢失时还能重新发送。
为何是2MSL的时间?
2MSL是报文一个往返的最长时间,假设小于这个时间会发生,ACK丢了,但是还没接收到对方重传的FIN我方就重新发送了ACK。
如果已经建立了连接,但是客户端突然出现故障了怎么办?
这个不难TCP自己做了保证,TCP默认有个定时器,每次收到客户端的请求后会把定时器设置好,通常设置两小时,超过两小时还没收到数据。 服务端会发送一个探测报文,以后每隔75秒钟发送一次。若一连发送10个探测报文仍然没反应,服务器就认为客户端出了故障,接着就关闭连接。
总结
三次握手和四次挥手的知识基本告一段落了,就讲到这里了,如果有什么不明白的地方可以加我微信探讨。
后面还会出一篇网络编程常用的linux命令行工具,比如ping、tcpdump、netstat、nc等等,在出一篇计算机网络的总结文章。计算机网络这部分基本完结了,如果又不懂得可以看看公号里面前面的文章。