TCP连接建立的三次握手过程可以携带数据吗?

前几天实验室的群里扔出了这样一个问题:TCP连接建立的三次握手过程可以携带数据吗?突然发现自己还真不清楚这个问题,平日里用tcpdump或者Wireshark抓包时,从来没留意过第三次握手的ACK包有没有数据。于是赶紧用nc配合tcpdump抓了几次包想检验一下。但是经过了多次实验,确实都发现第三次握手的包没有其它数据(后文解释)。后来的探究中发现这个过程有问题,遂整理探究过程和结论汇成本文,以供后来者参考。

先来张三次握手的图(下面这张图来自网络,若侵犯了作者权利,请联系我删除):

RFC793文档里带有SYN标志的过程包是不可以携带数据的,也就是说三次握手的前两次是不可以携带数据的(逻辑上看,连接还没建立,携带数据好像也有点说不过去)。重点就是第三次握手可不可以携带数据。

先说结论:TCP协议建立连接的三次握手过程中的第三次握手允许携带数据

对照着上边的TCP状态变化图的连接建立部分,我们看下RFC793文档的说法。RFC793文档给出的说法如下(省略不重要的部分):

重点是这句 “Data or controls which were queued for transmission may be included”,也就是说标准表示,第三次握手的ACK包是可以携带数据。那么Linux的内核协议栈是怎么做的呢?侯捷先生说过,“源码面前,了无秘密”。最近恰逢Kernel 4.0正式版发布,那就追查下这个版本的内核协议栈的源码吧。

在探索源码前,我们假定读者对Linux的基本socket编程很熟悉,起码对连接的流程比较熟悉(可以参考这篇文章《浅谈服务端编程》最前边的socket连接过程图)。至于socket接口和协议栈的挂接,可以参阅《socket接口与内核协议栈的挂接》

首先, 第三次握手的包是由连接发起方(以下简称客户端)发给端口监听方(以下简称服务端)的,所以只需要找到内核协议栈在一个连接处于SYN-RECV(图中的SYN_RECEIVED)状态时收到包之后的处理过程即可。经过一番搜索后找到了,位于 net\ipv4目录下tcp_input.c文件中的tcp_rcv_state_process函数处理这个过程。如图:

这个函数实际上是个TCP状态机,用于处理TCP连接处于各个状态时收到数据包的处理工作。这里有几个并列的switch语句,因为函数很长,所以比较容易看错层次关系。下图是精简了无需关注的代码之后SYN-RECV状态的处理过程:

一定要注意这两个switch语句是并列的。所以当TCP_SYN_RECV状态收到合法规范的二次握手包之后,就会立即把socket状态设置为TCP_ESTABLISHED状态,执行到下面的TCP_ESTABLISHED状态的case时,会继续处理其包含的数据(如果有)。

上面表明了,当客户端发过来的第三次握手的ACK包含有数据时,服务端是可以正常处理的。那么客户端那边呢?那看看客户端处于SYN-SEND状态时,怎么发送第三次ACK包吧。如图:

tcp_rcv_synsent_state_process函数的实现比较长,这里直接贴出最后的关键点:

一目了然吧?if 条件不满足直接回复单独的ACK包,如果任意条件满足的话则使用inet_csk_reset_xmit_timer函数设置定时器等待短暂的时间。这段时间如果有数据,随着数据发送ACK,没有数据回复ACK。

之前的疑问算是解决了。

但是,那三个条件是什么?什么情况会导致第三次握手包可能携带数据呢?或者说,想抓到一个第三次握手带有数据的包,需要怎么做?别急,本博客向来喜欢刨根问底,且听下文一一道来。

条件1:sk->sk_write_pending != 0

这个值默认是0的,那什么情况会导致不为0呢?答案是协议栈发送数据的函数遇到socket状态不是ESTABLISHED的时候,会对这个变量做++操作,并等待一小会时间尝试发送数据。看图:

net/core/stream.c里的sk_stream_wait_connect函数做了如下操作:

sk->sk_write_pending递增,并且等待socket连接到达ESTABLISHED状态后发出数据。这就解释清楚了。

Linux socket的默认工作方式是阻塞的,也就是说,客户端的connect调用在默认情况下会阻塞,等待三次握手过程结束之后或者遇到错误才会返回。那么nc这种完全用阻塞套接字实现的且没有对默认socket参数进行修改的命令行小程序会乖乖等待connect返回成功或者失败才会发送数据的,这就是我们抓不到第三次握手的包带有数据的原因。

那么设置非阻塞套接字,connect后立即send数据,连接过程不是瞬间连接成功的话,也许有机会看到第三次握手包带数据。不过开源的网络库即便是非阻塞socket,也是监听该套接字的可写事件,再次确认连接成功才会写数据。为了节省这点几乎可以忽略不计的性能,真的不如安全可靠的代码更有价值。

条件2:icsk->icsk_accept_queue.rskq_defer_accept != 0

这个条件好奇怪,defer_accept是个socket选项,用于推迟accept,实际上是当接收到第一个数据之后,才会创建连接。tcp_defer_accept这个选项一般是在服务端用的,会影响socket的SYN和ACCEPT队列。默认不设置的话,三次握手完成,socket就进入accept队列,应用层就感知到并ACCEPT相关的连接。当tcp_defer_accept设置后,三次握手完成了,socket也不进入ACCEPT队列,而是直接留在SYN队列(有长度限制,超过内核就拒绝新连接),直到数据真的发过来再放到ACCEPT队列。设置了这个参数的服务端可以accept之后直接read,必然有数据,也节省一次系统调用。

SYN队列保存SYN_RECV状态的socket,长度由net.ipv4.tcp_max_syn_backlog参数控制,accept队列在listen调用时,backlog参数设置,内核硬限制由 net.core.somaxconn 限制,即实际的值由min(backlog,somaxconn) 来决定。

有意思的是如果客户端先bind到一个端口和IP,然后setsockopt(TCP_DEFER_ACCEPT),然后connect服务器,这个时候就会出现rskq_defer_accept=1的情况,这时候内核会设置定时器等待数据一起在回复ACK包。我个人从未这么做过,难道只是为了减少一次ACK的空包发送来提高性能?哪位同学知道烦请告知,谢谢。

条件3:icsk->icsk_ack.pingpong != 0

pingpong这个属性实际上也是一个套接字选项,用来表明当前链接是否为交互数据流,如其值为1,则表明为交互数据流,会使用延迟确认机制。

好了,本文到此就应该结束了,上面各个函数出现的比较没有条理。具体的调用链可以参考这篇文章《TCP内核源码分析笔记》,不过因为内核版本的不同,可能会有些许差异。毕竟我没研究过协议栈,就不敢再说什么了。

时间: 2025-01-02 05:48:16

TCP连接建立的三次握手过程可以携带数据吗?的相关文章

深入浅出TCP协议的三次握手过程

TCP是主机对主机层的传输控制协议,提供可靠的连接服务,采用三次握手确认建立一个连接: 每一次TCP连接都需要三个阶段:连接建立.数据传送和连接释放."三次握手"就发生在连接建立阶段. 1.三次握手(three times handshake) 所谓的"三次握手"即对每次发送的数据量跟踪进行协商使数据段的发送和接收同步,以及根据所接收到的数据量来确定数据发送.接收完毕后何时撤消联系,并建立虚连接. 第一次握手:主机A发送位码为syn=1,随机产生seq number

TCP中的三次握手和四次挥手

一.文章来由 三次握手只记得ack.ack+1这些零碎片段了~~~特此总结 二.总图 明显三次握手是建立连接,四次挥手是断开连接,总图如下: 三.握手 (1)首先,Client端发送连接请求报文(SYN=1,seq=client_isn) (2)Server段接受连接后回复ACK报文,并为这次连接分配资源.(SYN=1,seq=client_isn,ack = client_isn+1) (3)Client端接收到ACK报文后也向Server段发生ACK报文,并分配资源,这样TCP连接就建立了.

结合Wireshark捕获分组深入理解TCP/IP协议栈之TCP协议(TCP报文格式+三次握手实例)

摘要:     本文简单介绍了TCP面向连接理论知识,详细讲述了TCP报文各个字段含义,并从Wireshark俘获分组中选取TCP连接建立相关报文段进行分析.   一.概述     TCP是面向连接的可靠传输协议,两个进程互发数据之前需要建立连接,这里的连接只不过是端系统中分配的一些缓存和状态变量,中间的分组交换机不维护任何连接状态信息.连接建立整个过程如下(即三次握手协议): 首先,客户机发送一个特殊的TCP报文段: 其次,服务器用另一个特殊的TCP报文段来响应: 最后,客户机再用第三个特殊报

【转载】理解TCP为什么需要进行三次握手(白话)

理解TCP为什么需要进行三次握手(白话)   首先简单介绍一下TCP三次握手   www.2cto.com       在TCP/IP协议中,TCP协议提供可靠的连接服务,采用三次握手建立一个连接.   第一次握手:建立连接时,客户端发送syn包(syn=j)到服务器,并进入SYN_SEND状态,等待服务器确认:   第二次握手:服务器收到syn包,必须确认客户的SYN(ack=j+1),同时自己也发送一个SYN包(syn=k),即SYN+ACK包,此时服务器 进入SYN_RECV状态:   第

tcp为什么要三次握手,而不能二次握手?

谢希仁版<计算机网络>中的例子是这样的,"已失效的连接请求报文段"的产生在这样一种情况下:client发出的第一个连接请求报文段并没有丢失,而是在某个网络结点长时间的滞留了,以致延误到连接释放以后的某个时间才到达server.本来这是一个早已失效的报文段.但server收到此失效的连接请求报文段后,就误认为是client再次发出的一个新的连接请求.于是就向client发出确认报文段,同意建立连接.假设不采用"三次握手",那么只要server发出确认,新的

TCP连接的三次握手(基于WIRESHARK抓包分析)

作为一个IT人士,如果能TCP的三次握手都不知道长什么样子, 那也算是白混了.它或许只是一种掉书袋的知识,对真正解决问题帮助不是最大.(异常数据包才是分析重点) 这次记录一下它的样子吧. 记住两次ACK值都是在SEQ的基础上+1来实现认证的.还有,哪些过程有SYN标记,哪些过程同时有SYN+ACK标记,哪些过程只有ACK标记. 此处略过前面的DNS请求和后面的GET/HTTP1.1过程..  

TCP三次握手应用及原理

TCP/IP是很多的不同的协议组成,实际上是一个协议组,TCP用户数据报表协议(也称作TCP传输控制协议,Transport Control Protocol.可靠的主机到主机层协议.这里要先强调一下,传输控制协议是OSI网络的第四层的叫法,TCP传输控制协议是TCP/IP传输的6个基本协议的一种.两个TCP意思非相同. ).TCP是一种可靠的面向连接的传送服务.它在传送数据时是分段进行的,主机交换数据必须建立一个会话.它用比特流通信,即数据被作为无结构的字节流. 通过每个TCP传输的字段指定顺

TCP ,UDP概念和TCP三次握手连接 的知识点总结

OSI 计算机网络7层模型 TCP/IP四层网络模型 传输层提供应用间的逻辑通信(端到端),网络层提供的是主机到主机的通信,传输层提供的是可靠服务. TCP 中常说的握手指的是:连接的定义和连接的建立的过程.IP 协议是无连接的,但是 TCP 是有链接的. 端口:数据链路层依靠 mac 地址寻址,网络接口层依靠 ip 地址寻址,传输层依靠端口号寻址,端口就是应用层的各种协议进程和传输实体之间进行层间交换的地址. 端口号:标识不同进程的号码,16位,2的16次方个,只在本地有意义.一共有三类,一是

关于TCP的三次握手和四次分手 专题

    客户端TCP状态迁移:CLOSED->SYN_SENT->ESTABLISHED->FIN_WAIT_1->FIN_WAIT_2->TIME_WAIT->CLOSED服务器TCP状态迁移:CLOSED->LISTEN->SYN收到->ESTABLISHED->CLOSE_WAIT->LAST_ACK->CLOSED 各个状态的意义如下: LISTEN - 侦听来自远方TCP端口的连接请求: SYN-SENT -在发送连接请求后