TCP通信协议( 三 )


如果有携带数据:下次客户端发送的报文,seq=服务器发回的ACK号
如果没有携带数据:第三次握手的报文不消耗seq,下次客户端发送的报文,seq序列号为x+1
① 服务端SYN-RECV流程

TCP通信协议

文章插图
② 客户端SYN-SEND流程
TCP通信协议

文章插图
场景1:sk->sk_write_pending != 0
这个值默认是0的,那什么情况会导致不为0呢?答案是协议栈发送数据的函数遇到socket状态不是ESTABLISHED的时候,会对这个变量做++操作,并等待一小会时间尝试发送数据 。
场景2:icsk->icsk_accept_queue.rskq_defer_accept != 0
客户端先bind到一个端口和IP,然后setsockopt(TCP_DEFER_ACCEPT),然后connect服务器,这个时候就会出现rskq_defer_accept=1的情况,这时候内核会设置定时器等待数据一起在回复ACK包 。
场景3:icsk->icsk_ack.pingpong != 0
pingpong这个属性实际上也是一个套接字选项,用来表明当前链接是否为交互数据流,如其值为1,则表明为交互数据流,会使用延迟确认机制 。
为什么是三次握手?不是两次、四次?
TCP建立连接时,通过三次握手能防止历史连接的建立,能减少双方不必要的资源开销,能帮助双方同步初始化序列号 。序列号能够保证数据包不重复、不丢弃和按序传输 。不使用「两次握手」和「四次握手」的原因:
两次握手:无法防止历史连接的建立,会造成双方资源的浪费,也无法可靠的同步双方序列号
四次握手:三次握手就已经理论上最少可靠连接建立,所以不需要使用更多的通信次数
接下来以三个方面分析三次握手的原因:
三次握手才可以阻止重复历史连接的初始化(主要原因)
三次握手才可以同步双方的初始序列号
三次握手才可以避免资源浪费
原因一:避免历史连接
客户端连续发送多次 SYN 建立连接的报文,在网络拥堵情况下:
一个「旧 SYN 报文」比「最新的 SYN 」 报文早到达了服务端
那么此时服务端就会回一个 SYN + ACK 报文给客户端
客户端收到后可以根据自身的上下文,判断这是一个历史连接(序列号过期或超时),那么客户端就会发送 RST 报文给服务端,表示中止这一次连接
TCP通信协议

文章插图
如果是两次握手连接,就不能判断当前连接是否是历史连接,三次握手则可以在客户端(发送方)准备发送第三次报文时,客户端因有足够的上下文来判断当前连接是否是历史连接:
如果是历史连接(序列号过期或超时),则第三次握手发送的报文是 RST 报文,以此中止历史连接
如果不是历史连接,则第三次发送的报文是 ACK 报文,通信双方就会成功建立连接
所以,TCP 使用三次握手建立连接的最主要原因是防止历史连接初始化了连接 。
原因二:同步双方初始序列号
TCP 协议的通信双方,都必须维护一个「序列号」,序列号是可靠传输的一个关键因素,它的作用:
接收方可以去除重复的数据
接收方可以根据数据包的序列号按序接收
可以标识发送出去的数据包中,哪些是已经被对方收到的
可见,序列号在 TCP 连接中占据着非常重要的作用,所以当客户端发送携带「初始序列号」的 SYN 报文的时候,需要服务端回一个 ACK 应答报文,表示客户端的 SYN 报文已被服务端成功接收,那当服务端发送「初始序列号」给客户端的时候,依然也要得到客户端的应答回应,这样一来一回,才能确保双方的初始序列号能被可靠的同步 。
TCP通信协议

文章插图
四次握手其实也能够可靠的同步双方的初始化序号,但由于第二步和第三步可以优化成一步,所以就成了「三次握手」 。而两次握手只保证了一方的初始序列号能被对方成功接收,没办法保证双方的初始序列号都能被确认接收 。
原因三:避免资源浪费
如果只有「两次握手」,当客户端的 SYN 请求连接在网络中阻塞,客户端没有接收到 ACK 报文,就会重新发送 SYN,由于没有第三次握手,服务器不清楚客户端是否收到了自己发送的建立连接的 ACK 确认信号,所以每收到一个 SYN 就只能先主动建立一个连接,这会造成什么情况呢?如果客户端的 SYN 阻塞了,重复发送多次 SYN 报文,那么服务器在收到请求后就会建立多个冗余的无效链接,造成不必要的资源浪费 。


推荐阅读