前几天实验室的群里扔出了这样一个问题: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内核源码分析笔记》,不过因为内核版本的不同,可能会有些许差异。毕竟我没研究过协议栈,就不敢再说什么了。

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

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

    TCP是主机对主机层的传输控制协议,提供可靠的连接服务,采用三次握手确认建立一个连接: 每一次TCP连接都需要三个阶段:连接建立.数据传送和连接释放.“三次握手”就发生在连接建立阶段. 1.三次握手( ...

  2. TCP协议:三次握手过程详解

    本文通过图来梳理TCP-IP协议相关知识.TCP通信过程包括三个步骤:建立TCP连接通道,传输数据,断开TCP连接通道.如图1所示,给出了TCP通信过程的示意图. 上图主要包括三部分:建立连接.传输数 ...

  3. 详解TCP连接的“三次握手”与“四次挥手”(上)

    一.TCP connection 客户端与服务器之间数据的发送和返回的过程当中需要创建一个叫TCP connection的东西: 由于TCP不存在连接的概念,只存在请求和响应,请求和响应都是数据包,它 ...

  4. TCP为什么要三次握手?

    在<计算机网络>一书中其中有提到,三次握手的目的是“为了防止已经失效的连接请求报文段突然又传到服务端,因而产生错误”,这种情况是: 一端(client)A发出去的第一个连接请求报文并没有丢 ...

  5. TCP的三次握手过程与四次挥手

    TCP握手协议 在TCP/IP协议中,TCP协议提供可靠的连接服务,采用三次握手建立一个连接.第一次握手:建立连接时,客户端发送syn包(syn=j)到服务器,并进入SYN_SEND状态,等待服务器确 ...

  6. 【转】简述TCP的三次握手过程

    TCP握手协议 在TCP/IP协议中,TCP协议提供可靠的连接服务,采用三次握手建立一个连接.第一次握手:建立连接时,客户端发送syn包(syn=j)到服务器,并进入SYN_SEND状态,等待服务器确 ...

  7. Java网络编程学习A轮_02_抓包分析TCP三次握手过程

    参考资料: https://huoding.com/2013/11/21/299 https://hpbn.co/building-blocks-of-tcp/#three-way-handshake ...

  8. TCP协议的三次握手和四次挥手过程

    TCP是一种面向连接(连接导向)的.可靠的基于字节流的传输层通信协议.TCP将用户数据打包成报文段,它发送后启动一个定时器,另一端收到的数据进行确认.对失序的数据重新排序.丢弃重复数据. 1.TCP/ ...

  9. tcp三次握手过程

    TCP握手协议 在TCP/IP协议中,TCP协议提供可靠的连接服务,采用三次握手建立一个连接.第一次握手:建立连接时,客户端发送syn包(syn=j)到服务器,并进入SYN_SEND状态,等待服务器确 ...

随机推荐

  1. spring boot 中文文档翻译地址

    https://github.com/qibaoguang/Spring-Boot-Reference-Guide/blob/master/SUMMARY.md

  2. linux下安装多个mysql实例(摘自国外:How to create multiple mysql instance in CentOS 6.4 and Red Hat 6.4)

    How to create multiple mysql instance in CentOS 6.4 and Red Hat 6.4 from:http://sharadchhetri.com/20 ...

  3. iOS消息推送机制

    iOS消息推送的工作机制可以简单的用下图来概括: Provider是指某个iPhone软件的Push服务器,APNS是Apple Push Notification Service的缩写,是苹果的服务 ...

  4. jquery自适应布局

    代码整理 - uix.layout.js /** * Grace [jQuery.js] * * UIX页面布局 * 290353142@qq.com * exp: * $.uix.layout(); ...

  5. hibernate中一对多 多对多 inverse cascade

    ----------------------------一对多------------------------------------------- inverse属性:是在维护关联关系的时候起作用的 ...

  6. C++和MATLAB混合编程-DLL

    先小话一下DLL,DLL是动态链接库,是源代码编译后的二进制库文件和程序接口,和静态链接库不同的是,程序在编译时并不链接动态链接库的执行体,而是在文件中保留一个调用标记,在程序运行时才将动态链接库文件 ...

  7. 配置Struts2的异常处理

    最好的方式是通过声明管理异常处理 1Action里面的execute()方法抛出所有异常:public String execute() throws Exception{} 2声明异常捕捉     ...

  8. session与cookie的区别,有哪些不同之处

    session与cookie的区别,根据自己的理解总结如下: (1)cookie是一种客户端的状态管理技术,将状态写在 浏览器端,而session是一种服务器端的状态管理技术,将 状态写在web服务器 ...

  9. Winbind authentication against active directory

    Winbind authentication against active directory Description This tip will describe how to configure ...

  10. Sql Server2000,2005,2008各版本主要区别

    Emerson回来之后,在过程中遇到的一些问题,再次做一些整理,包括本篇的Sql Server各版本之间的区别和另一篇数据库函数. (博文内容来自网络) 数据类型 SQL Server 2008 数据 ...