TCP和UDP连接管理
TCP 协议和 UDP 协议都是工作在传输层,是为了在程序之间传递数据,数据可以是多种形式,比如视频,文本,图片等。对于 TCP 协议和 UDP 协议来说,都是一堆二进制数,只是 TCP 基于连接,而 UDP 基于非连接。
1 名词解释
名词 | 解释 |
---|---|
SYN | 同步序号,用于建立连接过程,在连接请求中,SYN=1 和 ACK=0 表示该数据段没有使用捎带的确认域,而连接应答捎带一个确认,即 SYN=1 和 ACK=1 |
FIN | finish 标志,用于释放连接,为 1 时表示发送方已经没有数据发送了,即关闭本方数据流 |
ACK | 确认序号标志,为 1 时表示确认号有效,为 0 表示报文中不含确认信息,忽略确认号字段 |
PSH | push 标志,为 1 表示是带有 push 标志的数据,指示接收方在接收到该报文段以后,应尽快将这个报文段交给应用程序,而不是在缓冲区排队 |
RST | 重置连接标志,用于重置由于主机崩溃或其他原因而出现错误的连接。或者用于拒绝非法的报文段和拒绝连接请求 |
序列号 seq | 占 4 个字节,用来标记数据段的顺序,TCP 把连接中发送的所有数据字节都编上一个序号,第一个字节的编号由本地随机产生;给字节编上序号后,就给每一个报文段指派一个序号;序列号 seq 就是这个报文段中的第一个字节的数据编号 |
确认号 ack | 占 4 个字节,期待收到对方下一个报文段的第一个数据字节的序号;序列号表示报文段携带数据的第一个字节的编号;而确认号指的是期望接收到下一个字节的编号;因此当前报文段最后一个字节的编号 +1 (ACK 会占一个序号)即为确认号 |
ACK
、SYN
和 FIN
这些大写的单词表示标志位,其值要么是 1,要么是 0;ack
、seq
小写的单词表示序号。ACK
是可能与 SYN
,FIN
等同时使用的。比如 SYN
和ACK
可能同时为 1,它表示的就是建立连接之后的响应搜索 如果只是单个的一个SYN
,它表示的只是建立连接。
SYN
与FIN
是不会同时为 1 的,因为前者表示的是建立连接,而后者表示的是断开连接。
RST
一般是在FIN
之后才会出现为 1 的情况,表示的是连接重置。
一般,当出现FIN
包或RST
包时,便认为客户端与服务器端断开了连接;而当出现SYN
和SYN+ACK
包时,我们认为客户端与服务器建立了一个连接。
PSH
为 1 的情况,一般只出现在DATA
内容不为 0 的包中,也就是说PSH
为1表示的是有真正的 TCP 数据包内容被传递。
2 三次握手
三次握手是建立请求的过程,当客户端向服务端发送请求时,会先发一包连接请求数据,这包数据称为SYN
包,如果服务端同意连接,则会回复一包SYN+ACK
包,客户端收到后回复一包ACK
包,连接建立成功。在连接建立的过程中,客户端和服务端互相发送了 3 包数据,所以把这个过程称为 3 次握手。
2.1 第一次握手
2.1.1 客户端
主动打开(active open),向服务端发送 SYN
报文段SYN=1, SN=client_isn, OPT=client_mss
,请求建立连接。
client_isn
是客户端初始序号,动态生成,用于实现可靠传输,client_sn-client_isn
等于客户端已发送字节数。
SYN
报文段虽然不能携带数据,但是会消耗一个序号(相当于发送了1个字节的有效数据),下次客户端再向服务端发送的报文段中 SN=client_isn+1
。 除了 SYN
报文段和 ACK-SYN
报文段,其他所有后续报文段的序号 SN
值都等于上次接收的 ACK
报文段中的确认号 AN
值。
client_mss
是客户端最大报文段长度,在 TCP
首部的选项和填充部分,会在客户端与服务端的 MSS
中选择一个较小值使用。
客户端变为 SYN_SENT
状态,然后等待服务端 ACK
报文段。
2.2 第二次握手
2.2.1 服务端
接收来自客户端的 SYN
报文段,得知客户端发送能力正常。
被动打开passive open
,向客户端发送 SYN-ACK
报文段ACK=1, AN=client_isn+1, SYN=1, SN=server_isn, OPT=server_mss
,应答来自客户端的建立连接请求并向客户端发起建立连接请求。
SN=server_isn
是服务端初始序号,ACK-SYN
报文段虽然不能携带数据,但是会消耗一个序号(相当于发送了1个字节的有效数据),下次服务端再向客户端发送的报文中 SN=server_isn+1
。
OPT=server_mss
是服务端最大报文段长度。
AN=client_isn+1
是确认号,表明服务端接下来要开始接收来自客户端的第 client_isn+1
个字节的有效数据。
服务端变为 SYN_RCVD
状态,并等待客户端 ACK
报文段。
2.3 第三次握手
2.3.1 客户端
接收来自服务端的 SYN-ACK
报文段,得知服务端发送能力和接收能力都正常。
向客户端发送 ACK
报文段ACK=1, AN=server_isn+1, SN=client_isn+1, MESSAGE=message
,应答来自服务端的建立连接请求。
SN=client_isn+1
是序号,表明当前报文段发送的有效数据首字节是从请求建立连接到现在为止客户端向服务端发送的第 (client_isn+1)-clien_isn+1=2
个字节的有效数据。
有效数据:一般有效数据指的是应用层的报文数据,不过 SYN
报文段、 ACK-SYN
报文段和 FIN
报文段虽然没有携带报文数据,但认为发送了1个字节的有效数据。
AN=server_isn+1
是确认号,表明客户端接下来要开始接收来自服务端的第 server_isn+1
个字节的有效数据。
MESSAGE=message
此时可以在报文段中携带客户端到服务端的报文数据;该 ACK
报文段消耗的序号个数等于 message_length
(注意 message_length
可以等于0,即不携带有效数据,此时 ACK
报文段不消耗序号),下次客户端再向服务端发送的报文段中 SN=client_isn+1+message_length
。
客户端变为 ESTABLISHED
状态,client——>server
数据流建立。
2.3.2 服务端
接收来自客户端的 ACK
报文段,得知客户端接收能力正常。
变为 ESTABLISHED
状态,server——>client
数据流也建立。
3 丢包问题
一包数据有可能被拆成多包发送,如何处理丢包问题❓这些数据包到达的先后顺序不同,如何处理乱序问题❓
TCP 协议为每一个连接建立了一个发送缓冲区,从建立连接后的第一个字节的序列号为 0,后面每个字节的序列号就会增加 1,发送数据时,从发送缓冲区取一部分数据组成发送报文,在其 TCP 协议头中会附带序列号和长度,接收端在收到数据后,需要回复确认报文,确认报文中的ACK
等于
接收序列号加上长度
,也就是下一包数据需要发送的起始序列号,这样的发送方式,能够使发送端确认发送的数据,已经被对方收到,发送端也可以一次发送,连续的多包数据,接收端只需要回复一次ACK
就可以了,这样发送端可以把待发送的数据分隔成一系列的碎片,发送到对端,对端根据序列号和长度,在接收后重构出来完整的数据。
假设其中丢失了某些数据,则接收端可以要求发送端重传,比如丢失了 100-199 这 100 个字节,接收端向发送端发送ACK=100
的报文,发送端收到后重传这一包数据,接收端进行补齐。
4 四次挥手
断开连接前,客户端和服务端都处于 ESTABLISHED
状态,两者谁都可以先发起断开连接请求。以下假设客户端先发起断开连接请求。
客户端向服务端发送一包FIN
包,表示要关闭连接,客户端自己进入终止等待1 状态,这是第一次挥手。
服务端收到FIN
包,发送一包ACK
包,表示自己进入了关闭等待状态,客户端进入终止等待2 状态,这是第二次挥手。
服务端此时还可以发送未发送的数据,而客户端还可以接收数据,待服务端发送完数据之后,发送一包FIN
包,进入最后确认状态
,这是第三次挥手。
客户端收到之后回复ACK
包,进入超时等待状态,经过超时时间后关闭连接,而服务端收到ACK
包后立即关闭连接,这是第四次挥手。
4.1 第一次挥手
4.1.1 客户端
向服务端发送 FIN
报文段FIN=1, SN=client_sn
,请求断开连接。
SN=client_sn
是序号,表明当前报文段发送的有效数据首字节是从请求建立连接到现在为止客户端向服务端发送的第 client_sn-clien_isn+1
个字节的有效数据。
FIN
报文段虽然不能携带数据,但是会消耗一个序号(相当于发送了1个字节的有效数据),下次客户端再向服务端发送的报文中 SN=client_isn+1
。
客户端变为 FIN_WAIT1
状态,等待服务端 ACK
报文段。
4.2 第二次挥手
4.2.1 服务端
接收来自客户端的 FIN
报文段。
向客户端发送 ACK
报文段ACK=1, AN=client_sn+1, SN=server_sn_wave2
,应答客户端的断开连接请求。
SN=server_sn_wave2
是序号,表明当前报文段发送的有效数据首字节是从请求建立连接到现在为止服务端向客户端发送的第 server_sn_wave2-client_isn+1
个字节的有效数据。
AN=client_sn+1
是确认号,表明服务端接下来要开始接收来自客户端的第 client_sn+1
个字节的有效数据。
此时服务端变为 CLOSE_WAIT
状态。
4.2.2 客户端
接收来自服务端的 ACK
包。
变为 FIN_WAIT2
状态,等待服务端关闭连接请求FIN
报文段。
4.3 第三次挥手
4.3.1 服务端
(服务端想断开连接时)向客户端发送 FIN
报文段FIN=1, SN=server_sn
,请求断开连接。
SN=server_sn
是序号,表明当前报文段发送的有效数据首字节是从请求建立连接到现在为止服务端向客户端发送的第 server_sn-clien_isn+1
个字节的有效数据。
FIN
报文段虽然不能携带数据,但是会消耗一个序号(相当于发送了1个字节的有效数据),下次服务端再向客户端发送的报文中 SN=client_isn+2
(若断开连接成功,则服务端不会再向客户端发送下一个报文段)。
第二次挥手和第三次挥手之间,服务端又向客户端发送了 server_sn - server_sn_wave2
个字节的有效数据。
服务端变为 LAST_ACK
状态,等待客户端的 ACK
报文段。
4.4 第四次挥手
4.4.1 客户端
接收来自服务端的 FIN
报文段。
向服务端发送 ACK
报文段ACK=1, AN=server_sn+1, SN=client_sn+1
,应答服务端断开连接请求。
client_sn+1
是序号,表明当前报文段发送的有效数据首字节是从请求建立连接到现在为止客户端向客户端发送的第 client_isn+1)-clien_isn+1
个字节的有效数据。AN=server_sn+1
是确认号,表明服务端接下来要开始接收来自客户端的第 client_sn+1
个字节的有效数据。
客户端变为 TIME_WAIT
状态,等待2MSL时间后进入 CLOSED
状态,至此 client——>server
数据流被关闭。
4.4.2 服务端
接收来自客户端的 ACK
报文段。
变为 CLOSED
状态,至此 server——>client
数据流被关闭。
FIN
报文时,仅仅表示对方不再发送数据了但是还能接收数据。5 UDP协议
UDP 协议是进行非连接的,发送数据就是简单的把数据包封装一下,然后从网卡发出去,数据包之间并没有状态上的联系。由于 UDP 这种简单的处理方式,所以它的性能损耗非常少,对于 CPU 内存资源的占用也远小于 TCP,但是对于网络传输过程中的丢包,UDP 协议并不能保证,所以 UDP 在传输稳定性上弱于 TCP。
6 常见问题
- ❓ 为什么建立连接需要三次握手而不是二次
客户端和服务端之间建立的 TCP 是全双工通信,双方都要确保对方发送能力和接收能力正常。
一次握手后,服务端得知客户端发送能力正常。
二次握手后,客户端得知服务端接收能力和发送能力正常。
三次握手后,服务端得知客户端接收能力正常。
如果在第二次也就是服务端回复SYN+ACK
就建立连接的话,那么可能存在已经失效的请求报文,突然又传到服务器引起错误。
假设采用两次握手建立连接,客户端向服务端发送了一个SYN
包来请求建立连接,由于某些未知的原因,并没有到达服务器,在中间某个网络节点产生了滞留,为了建立连接,客户端会重新发送SYN
包,这次的数据包正常送达,服务端回复SYN+ACK
之后建立了连接,但是此时第一次数据阻塞的网络节点突然恢复,第一包SYN
又送达到服务器,这时服务端会误认为客户端又发起了一个新的连接,又向客户端发送了SYN+ACK
包,但事实是此时客户端并没有发出建立请求的连接,因此不会理睬服务端的确认,也不会向服务端发送数据,但服务端却以为新的连接已经建立了,并一直等待客户端发来的数据,从而在两次握手之后进入等待数据状态。此时服务端认为是两个连接,而客户端认为是一个连接,造成了状态不一致。如果在三次握手的情况下,服务端收不到最后的ACK
包,便不会认为连接建立成功,所以三次握手是为了解决网络不可靠的问题。
- ❓ 为什么第四次挥手时客户端需要等待超时时间再进入CLOSED状态
MSL,报文段最大生存时间,是一个未被接受的报文段在网络中被丢弃前存活的最大时间。
保证建立新连接时网络中不存在上次连接时发送的数据包,进入CLOSED
状态意味着可以建立新连接,等待 >MSL 的时间再进入CLOSED
状态可以保证建立新连接后,网络中不会存在上次连接时发送出去的数据包。若网络中同时存在发送端在两次连接中发出的数据包,对接收端接收数据可能会有影响。
客户端需要等待超时时间,这是为了保证对方已收到ACK
包。
假设客户端发送最后一包ACK
包后就CLOSED释放了连接,一旦ACK
包在网络中丢失,服务端将一直停留在最后确认状态,如果客户端发送最后一包ACK
包后,等待一段时间,这时服务端因为没有收到ACK
包,会重发FIN
包,客户端会响应这个FIN
包,重发ACK
包并刷新超时时间。这是为了在不可靠的网络中,进行可靠的连接断开确认。
- ❓ 已经建立了连接,客户端突然出现故障怎么办
TCP 设有一个保活计时器,客户端如果出现故障,服务器不能一直等下去,白白浪费资源。服务器每收到一次客户端的请求后都会重新复位这个计时器,时间通常是设置为 2 小时,若两小时还没有收到客户端的任何数据,服务器就会发送一个探测报文段,以后每隔 75 秒钟发送一次。若一连发送 10 个探测报文仍然没反应, 服务器就认为客户端出了故障,接着就关闭连接。
- ❓ TCP VS UDP
TCP 传输数据稳定可靠,适用于对网络通讯质量要求较高的场景,需要准确无误的传输给对方,比如传输文件,发送邮件,浏览网页等。
UDP 优点是速度快,但是可能产生丢包,所以适用于对实时性要求较高,但是对少量丢包并没有太大要求的场景,比如域名查询,语音通话,视频直播等。