点击右侧关注,了解黑客的世界!
点击右侧关注,掌握进阶之路!
点击右侧关注,探讨技术话题!
作者丨一叶知秋0830
链接:
https://juejin.im/post/5e13f8b95188253aa83e2e35
TCP是一种面向连接的、可靠的、基于字节流的传输层通信协议,在发送数据前,通信双方必须在彼此间建立一条连接。所谓的“连接”,其实是客户端和服务端保存的一份关于对方的信息,如ip地址、端口号等。
一个TCP连接通常分为三个阶段:连接、数据传输、退出(关闭)。通过三次握手建立一个链接,通过四次挥手来关闭一个连接。
1.TCP报文的头部结构
在了解TCP连接之前先来了解一下TCP报文的头部结构。
1.1 端口号(16位)
源端口号
标示这段报文来自哪里;目的端口号
标示这段报文要发往哪里。进行tcp通信时,一般客户端是通过系统自动选择的临时端口号,而服务器一般是使用指定的端口号。
1.2 序列号seq(32位)
因为TCP使用IP来传输报文段,而IP不能过滤掉重复的报文,也不能保证报文的顺序。比如客户端给服务端发送一条5kb的数据,如果tcp一次只能发1kb,那就要将数据分成5段分5次来发,将这5段数据分别编号为1、2、3、4、5,发送的时候按照这个顺序进行发送,服务端收到的顺序可能就是13254,
也就是说服务端收到的顺序和发送的顺序可能是不一致的,甚至因为某些因素(比如客户端没收到服务端回复的ACK数据包就会重发数据)导致服务端收到多个编号相同的数据。
那这样的话服务端收到全部数据后要如何将它们拼接成正确的数据呢?这里就用到了序列号。首先序列号被系统初始化为一个随机值ISN
,一个报文的序列号就是ISN
+这个报文携带的数据的第一个字节的偏移量。
比如上面所说的例子要发5个报文,第一个报文的第一个字节的偏移量为0,序列号就是ISA+0;由于第一个报文携带的数据大小是1kb(1024),所以第二个报文的第一个字节的偏移量就是1024,序列号就是ISA+1024,以此类推。
(注意实际上三次握手时是会占据一个序列号的,所以实际上正式发送数据时第一个报文的序列号是ISA+1+0,这里为了方便理解就不考虑三次握手时占据的那个序列号)。
1.3 确认号ack(32位)
还是用前面的例子,服务端收到客户端发过来的报文后需要给客户端回复一个ack数据包,回复报文的确认号的值等于服务端收到的报文的序列号的值+1,比如服务端当前收到的报文的序列号是ISN+2048
(也就是第3段数据),那么它回复客户端的报文的确认号的值就是ISN+2048+1
,其作用就是告诉客户端ISN+2048+1
之前的所有数据都已经收到了。
关于ack回复有几点需要说明(下面客户端为发送端,服务端为接收端):
a.客户端发送一个报文后并不需要等服务端的ack回复就可以接着发下一条报文。
b.服务端回复ack时,必须要确保确认号之前的数据全部已经收到了,比如上面的例子,如果序列号是ISN+2048
的报文收到了,但是ISN+1024
的报文还没收到,那就不能回复ISN+2048+1
的ack。
c.服务端在收到数据后不是立即给客户端发送ack的,一般会有200ms的延迟(系统有个定时器每隔200ms来检查是否需要发送ack包)。
这么做是因为TCP数据包到达的顺序是不保证的,就比如上面必须要等ISN+1024
的数据收到了才能回复ISN+2048+1
的ack,这个时候就只用回复ISN+2048+1
这一个ack就可以了,不需要回复ISN+1024+1
的ack了,因为回复ISN+2048+1
的ack就已经告诉客户端ISN+2048+1
之前的数据已经全部收到了,这样做还可以减少网络流量。
当然如果ISN+1024
的数据丢包导致服务端一直没收到,那客户端也就一直收不到ack回复,客户端就会从上次收到的ack回复开始重发数据,包括服务端已经收到的ISN+2048
数据也会重发。另外如果服务端刚好也有数据要发给客户端,那么就会在发送数据的TCP数据包里带上ack信息。
1.4 头部长度(4位)
头部长度是以32bit(4字节)为一个单位,头部长度的值表示TCP头部总共多少个32bit,4位的最大是二进制1111(十进制15),TCP头部最大为15*32/8
个字节,也就是60字节。TCP头部前面20个字节是固定的,TCP头部最小也就是20个字节。
1.5 六个标志位(每个标志占1位,每个标志只有0和1两种状态)
URG
:为1时表示紧急指针有效。
ACK
:为1时表示确认号是有效的,携带ACK标志的报文段也称确认报文段。
PSH
:为1时是提示接收端应用程序应该立即从TCP接受缓冲区中读走数据,为后续接收的数据让出空间。
RST
:为1时表示通知对方关闭连接或重新建立连接。带RST标志的TCP报文段也叫复位报文段。很多异常情况都会导致无法建立TCP连接或者TCP连接异常终止,比如客户端请求中使用了一个不存在的端口,那服务端就可以发送RST报文段拒绝这个请求;
再比如TCP连接很久没有传输数据了,可以发送RST报文段来终止这个连接;
还比如TCP连接出现了一次,然后服务端希望终止这条异常的链接,可以发送RST报文段来终止这个连接。注意一旦发送了复位报文段,发送端所有排队等待发送的数据都将被丢弃,而且发送完RST报文后TCP连接就关闭了,所以接收端收到RST报文后也就没有必要发送ACK包来确认了。
SYN
:为1表示建立一个连接,携带SYN标志的报文段为同步报文段。SYN标志位只有在TCP建立连接时(也就是三次握手的时候)才会被置为1,客户端请求建立连接时(第一次握手)的报文就携带SYN标志和初始化的序列号(也就是起始序列号),SYN标志是提醒服务端记住客户端的起始序列号。
然后服务端也会初始化自己的起始序列号并回复客户端一条报文(第二次握手),这条报文包含服务端的起始序列号、SYN标志位和ACK标志位,其中SYN标志位用来提醒客户端记住服务端的起始序列号。
FIN
:为1时用来告知对方本端要关闭连接了。
1.6 窗口大小(16位)
窗口大小可以理解为自己接收数据的缓存区大小,用来告诉对方自己最大还能接收多少数据,如果对方发送的数据超过了窗口大小,那这个数据会被丢弃。当窗口大小为0时对方会暂停发送数据,直到窗口大小大于0时才会恢复发送数据。
1.7 校验和(16位)
发送端对TCP报文(包括TCP头部和数据部分)执行CRC算法得到校验和,接收端收到报文后对报文执行同样的算法,将结果与校验和进行比对,两者一致说明TCP报文段在传输过程中没有损坏,如果不一致那么这段TCP报文段会被直接丢弃。
1.8 紧急指针(16位)
只有当URG标志位为1时紧急指针才是有意义的,紧急指针指向紧急数据最后一个字节的下一位。准确来说紧急指针的值其实是一个相对于序列号的偏移量,比如说紧急指针的值是5,如果这个报文段的序列号seq=10,那就表示紧急指针指向的是15这个位置的字节,这就意味着10到14这段数据是紧急数据。
紧急数据是不进入缓存区的。(注:关于紧急指针的指向和紧急数据的长度网上有不同的说法,我这里也不能确定哪种是正确的)。
1.9 TCP头部选项
TCP头部选项时长度可变的可选信息,最多包含40个字节。典型的TCP头部选项包含kind
、length
和info
三个部分,kind表示选项的类型,length表示选项的长度,info表示选项的具体内容。
2. 三次握手
第一次握手
:客户端要向服务端发起连接请求,首先客户端随机生成一个起始序列号ISN(比如是100),那客户端向服务端发送的报文段包含SYN标志位(也就是SYN=1),序列号seq=100。第二次握手
:服务端收到客户端发过来的报文后,发现SYN=1,知道这是一个连接请求,于是将客户端的起始序列号100存起来,并且随机生成一个服务端的起始序列号(比如是300)。然后给客户端回复一段报文,回复报文包含SYN和ACK标志(也就是SYN=1,ACK=1)、序列号seq=300、确认号ack=101(客户端发过来的序列号+1)。第三次握手
:客户端收到服务端的回复后发现ACK=1并且ack=101,于是知道服务端已经收到了序列号为100的那段报文;同时发现SYN=1,知道了服务端同意了这次连接,于是就将服务端的序列号300给存下来。然后客户端再回复一段报文给服务端,报文包含ACK标志位(ACK=1)、ack=301(服务端序列号+1)、seq=101(第一次握手时发送报文是占据一个序列号的,所以这次seq就从101开始,需要注意的是不携带数据的ACK报文是不占据序列号的,所以后面第一次正式发送数据时seq还是101)。
当服务端收到报文后发现ACK=1并且ack=301,就知道客户端收到序列号为300的报文了,就这样客户端和服务端通过TCP建立了连接。
3. 四次挥手
比如客户端初始化的序列号ISA=100,服务端初始化的序列号ISA=300。TCP连接成功后客户端总共发送了1000个字节的数据,服务端在客户端发FIN报文前总共回复了2000个字节的数据。
第一次挥手
:当客户端的数据都传输完成后,客户端向服务端发出连接释放报文(当然数据没发完时也可以发送连接释放报文并停止发送数据),释放连接报文包含FIN标志位(FIN=1)、序列号seq=1101(100+1+1000,其中的1是建立连接时占的一个序列号)。需要注意的是客户端发出FIN报文段后只是不能发数据了,但是还可以正常收数据;另外FIN报文段即使不携带数据也要占据一个序列号。第二次挥手
:服务端收到客户端发的FIN报文后给客户端回复确认报文,确认报文包含ACK标志位(ACK=1)、确认号ack=1102(客户端FIN报文序列号1101+1)、序列号seq=2300(300+2000)。此时服务端处于关闭等待状态,而不是立马给客户端发FIN报文,这个状态还要持续一段时间,因为服务端可能还有数据没发完。第三次挥手
:服务端将最后数据(比如50个字节)发送完毕后就向客户端发出连接释放报文,报文包含FIN和ACK标志位(FIN=1,ACK=1)、确认号和第二次挥手一样ack=1102、序列号seq=2350(2300+50)。第四次挥手
:客户端收到服务端发的FIN报文后,向服务端发出确认报文,确认报文包含ACK标志位(ACK=1)、确认号ack=2351、序列号seq=1102。注意客户端发出确认报文后不是立马释放TCP连接,而是要经过2MSL(最长报文段寿命的2倍时长)后才释放TCP连接。而服务端一旦收到客户端发出的确认报文就会立马释放TCP连接,所以服务端结束TCP连接的时间要比客户端早一些。
4. 常见面试题
4.1 为什么TCP连接的时候是3次?2次不可以吗?
因为需要考虑连接时丢包的问题,如果只握手2次,第二次握手时如果服务端发给客户端的确认报文段丢失,此时服务端已经准备好了收发数(可以理解服务端已经连接成功)据,而客户端一直没收到服务端的确认报文,所以客户端就不知道服务端是否已经准备好了(可以理解为客户端未连接成功),这种情况下客户端不会给服务端发数据,也会忽略服务端发过来的数据。
如果是三次握手,即便发生丢包也不会有问题,比如如果第三次握手客户端发的确认ack报文丢失,服务端在一段时间内没有收到确认ack报文的话就会重新进行第二次握手,也就是服务端会重发SYN报文段,客户端收到重发的报文段后会再次给服务端发送确认ack报文。
4.2 为什么TCP连接的时候是3次,关闭的时候却是4次?
因为只有在客户端和服务端都没有数据要发送的时候才能断开TCP。而客户端发出FIN报文时只能保证客户端没有数据发了,服务端还有没有数据发客户端是不知道的。
而服务端收到客户端的FIN报文后只能先回复客户端一个确认报文来告诉客户端我服务端已经收到你的FIN报文了,但我服务端还有一些数据没发完,等这些数据发完了服务端才能给客户端发FIN报文(所以不能一次性将确认报文和FIN报文发给客户端,就是这里多出来了一次)。
4.3 为什么客户端发出第四次挥手的确认报文后要等2MSL的时间才能释放TCP连接?
这里同样是要考虑丢包的问题,如果第四次挥手的报文丢失,服务端没收到确认ack报文就会重发第三次挥手的报文,这样报文一去一回最长时间就是2MSL,所以需要等这么长时间来确认服务端确实已经收到了。
4.4 如果已经建立了连接,但是客户端突然出现故障了怎么办?
TCP设有一个保活计时器,客户端如果出现故障,服务器不能一直等下去,白白浪费资源。服务器每收到一次客户端的请求后都会重新复位这个计时器,时间通常是设置为2小时,若两小时还没有收到客户端的任何数据,服务器就会发送一个探测报文段,以后每隔75秒钟发送一次。若一连发送10个探测报文仍然没反应,服务器就认为客户端出了故障,接着就关闭连接。
推荐↓↓↓
长
按
关
注
????【16个技术公众号】都在这里!
涵盖:程序员大咖、源码共读、程序员共读、数据结构与算法、黑客技术和网络安全、大数据科技、编程前端、Java、Python、Web编程开发、Android、iOS开发、Linux、数据库研发、幽默程序员等。
万水千山总是情,点个 “在看” 行不行