TCP第三次握手传数据过程图解

 更新时间:2020年12月07日 10:48:33   作者:林锅  
这篇文章主要介绍了TCP第三次握手传数据过程图解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下

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

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

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

重点是这句 “Data or controls which were queued for transmission may be included”,也就是说标准表示,第三次握手的ACK包是可以携带数据。

首先, 第三次握手的包是由连接发起方(以下简称客户端)发给端口监听方(以下简称服务端)的,所以只需要找到内核协议栈在一个连接处于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,则表明为交互数据流,会使用延迟确认机制。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持脚本之家。

相关文章

  • rsync server服务端配置文件 rsyncd.conf参数详解

    rsync server服务端配置文件 rsyncd.conf参数详解

    前两篇文章我们已经探讨了rsync的原理及基本使用,今天我们来介绍一下rsync server端的配置文件,如果你还没有学习rsync的原理及安装使用,那么就可以参考下面的文章
    2024-06-06
  • Rsync 服务安全加固方法

    Rsync 服务安全加固方法

    Rsync 是一个通过检查文件的时间戳和大小,来跨计算机系统高效地传输和同步文件的工具,建议您在使用 Rsync 服务端时,参考本文对 Rsync 服务进行安全加固,保障数据安全
    2018-02-02
  • Kubernetes常用命令大全近期总结

    Kubernetes常用命令大全近期总结

    Kubernetes是用于大规模部署和管理这些容器的开源软件 - 在希腊语中,这个词还有“舵手”或“飞行员”的意思,使用 Kubernetes(有时被称为“k8s”或“k-eights”)可更快地构建、交付和缩放容器化应用,这篇文章介绍了最新版Kubernetes常用命令大全
    2025-01-01
  • 通过伪静态解决中文乱码问题

    通过伪静态解决中文乱码问题

    当在网站使用伪静态的时候.显示中文出现乱码.不妨在你的伪静态页面中加上[QSA,NU,PT,L]试试
    2013-01-01
  • Vestacp免费VPS主机控制面板的安装与使用教程

    Vestacp免费VPS主机控制面板的安装与使用教程

    Vestacp除了为我们搭建网站提供简洁易用的管理面板外,还为我们提供了免费邮局功能和VPS性能监控,帮助我们更好地管理VPS服务器,提供可视化的网站管理面板,非常适合多用户使用。
    2017-07-07
  • Keepalived+HAProxy高可用集群K8S实现

    Keepalived+HAProxy高可用集群K8S实现

    这篇文章主要为大家介绍了Keepalived+HAProxy实现高可用集群K8S的构建方式,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步
    2022-03-03
  • 在Linux下用软件实现RAID功能的实现方法

    在Linux下用软件实现RAID功能的实现方法

    安装程式实现软件RAID代替硬件RAID的方法,今天再进一步谈谈手动创建软RAID和日常维护的方法。
    2011-04-04
  • 阿里云盾网站安全防御(WAF)的使用方法(图文)

    阿里云盾网站安全防御(WAF)的使用方法(图文)

    这篇文章主要介绍了阿里云盾网站安全防御(WAF)的正确使用方法,需要的朋友可以参考下
    2015-09-09
  • Apache Hudi性能提升三倍的查询优化

    Apache Hudi性能提升三倍的查询优化

    这篇文章主要为大家介绍了Apache Hudi性能提升三倍的查询优化,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步早日升职加薪
    2022-03-03
  • 服务器SVN搭建图文安装过程

    服务器SVN搭建图文安装过程

    这篇文章主要介绍了服务器SVN搭建图文过程,本文通过图文并茂的形式给大家介绍详细安装过程,需要的朋友可以参考下
    2022-06-06

最新评论