TCP是一个面向连接的、提供可靠传输的、全双工通信、面向字节流的传输层协议。本文先讲述TCP报文格式以及TCP连接的过程。
TCP报文段首部格式
源端口(2): 数据发送方的端口号。
目的端口(2): 数据接受方的端口号。
序号(4):seq序号,记录本报文段所发送的第一个字节的序号。
例如:一报文段字段值是301,携带数据共有100字节,因为在TCP连接中传送的字节流中的每一个字节都按顺序编号,所以本报文段的第一个字节的序号是301,最后一个字节的序号是400,那么下一个报文段应当从401开始
确认号(4):ack序号,即接收端希望收到的下一个数据报文中的第一个字节的序号。
只有ACK标志位为1时,确认序号字段才有效,ack=seq+1。(上面例子里确认号=401)
数据偏移(4位):表示TCP报文段的数据起始处距离TCP报文段的起始处有多远(即TCP报文段的首部长度)
数据偏移的单位是32bit,即4字节;因为4位二进制能够表是的最大十进制数是15,所以数据偏移的最大长度是15*4=60字节,除去固定部分的20字节,选项字段最大不能超过40字节
保留(6位)
紧急比特URG(1位):当值为1时表示次报文段中有需要紧急处理。
确认比特ACK(1位):确认序号是否有效,为1时有效,为0时无效。TCP规定连接建立后传送报文段ACK=1
推送比特PSH(1位):接收方应该尽快将这个报文交给应用层。
复位比特RST(1位):重置连接,值为1时表示TCP连接存在严重的错误,需要重新进行连接。
同步比特SYN(1位):建立连接,在连接建立时同步序号,SYN=1时表示这是一个连接请求或连接接受报文
当SYN=1且ACK=0时,表明这时一个连接请求报文段
当SYN=1且ACK=1时,表明对方同意建立连接
所以SYN=1时表示这是一个连接请求或连接接受报文
终止比特FIN(1位): 用于释放连接,值为1表示要发送的数据报已经发送完毕,需要释放传送连接。
窗口字段(2):接收端根据缓存空间的大小确定自己接受窗口的大小,限制发送放的窗口上限。
检验和:用来检验首部和数据两部分的正确性。
紧急指针字段:本报文紧急报文的最后一个字节的序号。
需要注意的是:不要将确认序号ack与标志位中的ACK搞混了,确认方ack=发起方seq+1,两端配对。
TCP的连接
每一条TCP连接有两个端点,端点叫socket(套接字)
socket是一个 五元组,包括:源IP、源端口、目的IP、目的端口、类型:TCP or UDP
这个五元组,即标识了一条可用的连接。例如,本地IP是180.172.35.150,在浏览器中连接某一个Web服务器,例如百度,这条socket连接的五元组可能就是:
[180.172.35.150:45678, tcp, 180.97.33.108:80]
源IP为你的出口IP地址 180.172.35.150,源端口为随机端口 45678,目的IP为百度的某一个负载均衡服务器IP 180.97.33.108,端口为HTTP标准的80端口
三次握手
三次握手的过程:
刚开始客户端处于 closed 的状态,服务端处于 listen 状态。然后
1、第一次握手:客户端给服务端发一个 SYN 报文,并指明客户端的初始化序列号seq=X(seq利用ISN生成)。此时客户端处于 SYN_Send (同步已发送) 状态。
2、第二次握手:服务器收到客户端的 SYN 报文后,会以自己的 SYN 报文作为应答,并且也是指定了自己的初始化序列号seq=Y,同时会把客户端的seq + 1 作为 Ack (确认序号)的值,表示自己已经收到了客户端的 SYN,此时服务器处于 SYN_REVD(同步收到)的状态。
3、第三次握手:客户端收到服务器 SYN 报文之后,会发送一个 ACK应答 报文,也是一样把服务器的seq + 1 作为 Ack 的值,表示已经收到了服务端的 SYN 报文,此时客户端处于 establised 状态。(此时客户端可以通过ACK报文段发送数据,但如果不发送数据则不消耗序号)
4、服务器收到 ACK 报文之后,也处于 establised 状态,此时,双方已建立连接
三次握手的作用
1、确认双方的接受、发送能力是否正常,是否可以传送数据。
2、同步双方的初始化序列号,为后面的可靠传送做准备。
3、协商窗口大小,同时接收方预留数据缓存区,为后面的可靠传送做准备。
(ISN)是固定的吗
三次握手的一个重要功能是客户端和服务端交换ISN(Initial Sequence Number), 以便让对方知道接下来接收数据的时候如何按序列号组装数据。如果ISN是固定的,攻击者很容易猜出后续的确认号,因此 ISN 是动态生成的。
1 | ISN = M + F(localhost, localport, remotehost, remoteport) |
ISN是随机的,所以序列号容易就会超过2^31-1,就出现了“tcp序列号回绕”问题 参考博文
四次挥手
由于socket是全双工的工作模式,一个socket的关闭,是需要四次握手来完成的。
四次挥手的过程:
刚开始双方都处于 establised 状态,假如是客户端主动发起关闭请求(调用close()方法),则:
1、第一次挥手:客户端发送一个 FIN 终止报文给服务器,报文中指定一个初始化序列号seq=U。此时客户端处于FIN_WAIT1状态。
2、第二次挥手:服务端收到 FIN 之后,因为还有数据未发送完,所以服务器会先发送一个 ACK 应答报文,指定一个初始化序列号seq=V,且把客户端的序列号值seq + 1 作为确认序号的值,表明已经收到客户端的报文了,此时服务端处于 CLOSE_WAIT状态。客户端等待服务器关闭,则进入FIN_WAIT_2状态;此时,客户端 等待 服务器 发起关闭请求
3、第三次挥手:服务器在完成所有数据发送后,主动发起关闭请求,和客户端的第一次挥手一样,发给 FIN 报文,且指定一个初始化序列号seq=W。此时服务端处于 LAST_ACK 的状态。
4、第四次挥手:客户端收到 FIN 之后,发送一个 ACK 报文作为应答,且把服务端的序列号值 + 1 作为自己 ACK 报文的序列号值,此时客户端处于 TIME_WAIT 状态。
5、服务端收到 ACK 报文之后,就处于关闭连接了,处于 CLOSED 状态。等待2MSL(最长报文段寿命),主动关闭的一方,结束TIME-WAIT,进入CLOSED状态
四次挥手的作用:确保数据能够完成传输,而不是发送FIN报文后对方立即关闭连接
参考博文:关于3次握手与4次挥手
TIME_WAIT的作用或2MSL的作用
防止前一个连接上延迟的数据包或丢失重传的数据包,被后面复用的连接错误接收(异常:数据丢失或传输慢)
SEQ=3的数据包丢失,重传第一次,没有得到ACK确认
如果没有TIME_WAIT,或TIME_WAIT时间非常短,那么关闭的连接马上被重用,并连续发送SEQ=1,2 的数据包
【180.172.35.150:45678, tcp, 180.97.33.108:80 的状态变为了CLOSED,源端口可被再次利用】【对180.97.33.108:80新建的连接,复用了之前的随机端口45678】
此时,前面的连接上的SEQ=3的数据包再次重传,同时,seq的序号刚好也是3(这个很重要,不然,SEQ的序号对不上,就会RST掉),此时,前面一个连接上的数据被后面的一个连接错误的接收
利用2MSL(最长报文段寿命)的等待时间,就可以**使本连接持续的时间内所产生的所有报文段都从网络中消失
确保连接方能在时间范围内,关闭自己的连接(也是因为丢包导致的),即为了保证主动关闭方发送的最后一个ACK报文能够到达被动关闭方。
总的来说就是为了解决网络的丢包和网络不稳定所带来的其他问题
参考博文:你所不知道的TIME_WAIT