六一儿童节的大清早,竟然用这么一篇技术博客来总结童真,也挺好。我把早上的发的朋友圈文字附于文后,以应景。


周中写了一篇关于socket查找的文章,次日,也就是昨天上午,收到一封反馈邮件,好快,十分高兴能和大家一起进行技术讨论。

在这封邮件里,一位朋友提出了一种查询socket的优化方案,瞄准的是__inet_lookup函数,我以3.10内核为例,将该函数贴如下:

static inline struct sock *__inet_lookup(struct net *net,struct inet_hashinfo *hashinfo,const __be32 saddr, const __be16 sport,const __be32 daddr, const __be16 dport,const int dif)
{u16 hnum = ntohs(dport);// 1.首先查询establish hash表struct sock *sk = __inet_lookup_established(net, hashinfo,saddr, sport, daddr, hnum, dif);// 2.在查询establish hash表未果时再查询Listener hash表return sk ? : __inet_lookup_listener(net, hashinfo, saddr, sport,daddr, hnum, dif);
}

考虑到一台服务器支撑10万+的并发连接(所谓的并发连接均在establish hash表中)并不是什么难事,那么每一次新建连接都要先去10万+数量的hash表中去查询一遍,如果出现过长的hash冲突链表(如果你不使用理想中的完美hash,这几乎是不可避免的),便会严重影响scalable特性,于是能不能如下进行优化呢:

static inline struct sock *__inet_lookup(struct net *net,struct inet_hashinfo *hashinfo,const __be32 saddr, const __be16 sport,const __be32 daddr, const __be16 dport,const int dif,// 新增一个TCP头参数const struct tcphdr *th)
{u16 hnum = ntohs(dport);struct sock *sk;if (!th->syn) // 如果有syn标识则直接查询Listener hash,不再查询establish hash!sk = __inet_lookup_established(net, hashinfo,saddr, sport, daddr, hnum, dif);return sk ? : __inet_lookup_listener(net, hashinfo, saddr, sport,daddr, hnum, dif);
}

非常不错的想法,深入到了细节。但是如果你对TCP状态机有深入的理解,就会发现这里的这个优化是有问题的。

问题出在下面的两个细节上:

  • TCP连接主动断开后会进入timewait状态,该状态的连接依然在establish hash中;
  • TCP可以支持双向打开,没有Listener的情况下同时发送syn包建立连接。

在查询Listener hash之前先查询establish hash正是为了支持上述两种场景的,具体的支持方法如下:

  • 如果一个syn包命中了timewait状态的连接,必须检查其timestamp确认连接可重用,要么允许建立连接要么重置;
  • 如果一个syn包命中了一个已经发送过syn包的连接,则执行“同时打开”握手流程。

所以说,虽然这位朋友的这个优化思路是没有问题的,但是涉及到TCP协议的具体细节时却不可行。

嗯,结论就是这样,即在为一个TCP数据包查询socket的时候,必须首先查询establish hash表,然后再查Listener hash表。


有了结论并不意味着本文的结束,接下来的篇幅我将实验展示TCP同时打开的过程,给出一个观感上的印象。所需工具很简单,netcat和systemtap即可,能不编程就不编程,毕竟我编程编的不好…

好了,开始实验,准备两台机器,我这里分别是下面两台VMWare虚拟机:

Host 1:192.168.44.138/24
Host 2:192.168.44.248/24

两台机器直连在同一个网段,这样最简单,因为这个实验只是TCP层面的语义测试,与IP层无关。两台机器同时安装netcat这个“瑞士军刀”,然后在两台机器上分别确认没有TCP 1234这个端口在侦听,之后,两台机器上执行下面的命令:

# Host 1
root@debian:/home/zhaoya# while true; do nc -p 1234 192.168.44.248 1234 -v;done
192.168.44.248: inverse host lookup failed: No address associated with name
(UNKNOWN) [192.168.44.248] 1234 (?) : Connection refused
192.168.44.248: inverse host lookup failed: No address associated with name
# 连接建立!
(UNKNOWN) [192.168.44.248] 1234 (?) open
aaaa # 在一个终端敲入字符会在另一个机器的终端显示出来
bbbb
# Host 2
[root@localhost ~]# while true; do nc -p 1234 192.168.44.138 1234 -v;done
Ncat: Version 7.50 ( https://nmap.org/ncat )
Ncat: Connection refused.
Ncat: Version 7.50 ( https://nmap.org/ncat )
Ncat: Connection refused.
Ncat: Version 7.50 ( https://nmap.org/ncat )
# 连接建立!
Ncat: Connected to 192.168.44.138:1234.
aaaa # 在一个终端敲入字符会在另一个机器的终端显示出来
bbbb

这个时候,我们看一下连接状态:

[root@localhost ~]# ss -antp
State      Recv-Q Send-Q                                       Local Address:Port                                                      Peer Address:Port
...
ESTAB      0      0                                           192.168.44.248:1234                                                    192.168.44.138:1234                users:(("nc",pid=7925,fd=3))

可见,两边同时都是运行的TCP客户端在调用connect,两边均bind到了1234这个端口同时均没有Listen这个端口,最终连接还是建立了,状态为ESTABLISH。两边的时序图如下:

状态转换图如下:

似乎比较简单和清晰,然而这一切是怎么发生的,有必要确认一下以加深印象。

其实,现在就一个问题,作为TCP客户端调用connect系统调用主动打开连接,发送SYN包,该连接会在什么时候加入到establish hash表中呢?我们来detect一下究竟。依然基于3.10内核:

[root@localhost ~]# uname -r
3.10.0-862.2.3.el7.x86_64

静态分析代码的话,我们可以看到tcp_v4_connect中我们感兴趣的逻辑:

int tcp_v4_connect(struct sock *sk, struct sockaddr *uaddr, int addr_len)
{.../* Socket identity is still unknown (sport may be zero).* However we set state to SYN-SENT and not releasing socket* lock select source port, enter ourselves into the hash tables and* complete initialization after this.*/tcp_set_state(sk, TCP_SYN_SENT);err = inet_hash_connect(&tcp_death_row, sk);...
}

于是我们在inet_hash_connect处打桩,看看实验过程中是不是调用了这里,如果是的话__inet_hash_connect将会被调用,这个函数将socket加入到了establish hash表中。

systemtap是一个好工具,和crash侧重于静态分析不同,systemtap可以动态插桩,甚至可以搞一个trick,实现类似内核热补丁的功能,简直太强大,虽然其底层依赖的kprobe有点不太易用看起来让人觉得没什么玩头,但systemtap的封装完美解决了可玩性问题。

我们先看一下inet_hash_connect函数都有什么变量可用:

[root@localhost ~]# stap -L 'kernel.function("inet_hash_connect")'
kernel.function("inet_hash_connect@net/ipv4/inet_hashtables.c:593") $death_row:struct inet_timewait_death_row* $sk:struct sock*

然后我们确认逻辑进入到了这个函数,另起一个终端,执行下面的systemtap命令:

[root@localhost ~]# stap -e 'probe kernel.function("inet_hash_connect") {printf("OK\n");}'

重新做实验,观测systemtap命令终端的输出:

[root@localhost ~]# stap -e 'probe kernel.function("inet_hash_connect") {printf("OK\n");}'
OK
OK
OK
OK
OK
OK
OK
OK

嗯,确实印证了静态分析的结论,即socket在调用connect的时候就会把自己加入到establish hash表,虽然它此时连syn都还没有发送

为了实际确认,编写下面的systemtap脚本,探测tcp_v4_connect调用inet_hash_connect前后的sk->sk_hash值的变化。

#!/usr/bin/stap -g// 在inet_hash_connect调用前探测sk_hash的值
probe kernel.function("inet_hash_connect")
{printf("pre sk_hash value:%d\n", @cast($sk, "struct sock")->__sk_common->skc_hash);
}// 在inet_hash_connect调用结束返回前再次探测sk_hash的值
probe kernel.function("inet_hash_connect").return
{printf("post sk_hash value:%d\n", @cast($sk, "struct sock")->__sk_common->skc_hash);
}

重新做实验,以下是systemtap的结果:

[root@localhost network]# stap -u ./detect_skhash.stp
WARNING: confusing usage, consider @entry($sk) in .return probe: identifier '$sk' at ./init_cwnd:14:42source:        printf("post sk_hash value:%d\n", @cast($sk, "struct sock")->__sk_common->skc_hash);^
pre sk_hash value:0
post sk_hash value:610642400

确认了确实就是在inet_hash_connect中将该socket加入到establish hash表的,确切的讲,应该是在它调用的__inet_check_established函数中。


解释结束。这就是Linux关于TCP同时打开的实现细节的一部分,可以看出,整个流程没有Listener socket的参与,并且正确地处理了syn握手。

最后,我来展示一个好玩的TCP连接,TCP咬尾蛇,即自己连接自己bind的端口:

[root@localhost ~]# nc -p 2234 127.0.0.1 2234
aaaaa # 无论你敲入什么
aaaaa # 当前的终端就会回显什么...wwwwwwwwwwwwww
wwwwwwwwwwwwww

用ss命令确认一下:

[root@localhost ~]# ss -ntp
# 注意,只有一个2234到2234的TCP establish连接,没有Listener。
State      Recv-Q Send-Q        Local Address:Port      Peer Address:Port
...
ESTAB      0      0             127.0.0.1:2234          127.0.0.1:2234                users:(("nc",pid=3576,fd=3))
...                                                  

自己用前面描述的原理分析一下,应该能得知其所以然。


最后,除了TCP同时打开,之所以要先查establish hash还有一个原因,与timewait套接字相关,关于timewait的实验,请参见我在2013年写的一篇文章:
TCP的TIME_WAIT快速回收与重用:https://blog.csdn.net/dog250/article/details/13760985
这个文章里也设计了一系列的实验,目的是确认timewait的影响。

附:童趣和童真

今天六一儿童节,上班路上,我来说说在古城安阳度过的我的童年。

小时候过六一儿童节,人民公园是必须要去的,我一直都比较喜欢动物,所以进了公园一般都是先去动物园,然后顺着东边的长廊走到花卉园,最里面有一棵很大的树,由于分叉比较低我一般会躺在枝丫处假装休息一会儿,出来花卉园再沿着长廊走一会儿,随后就到了游乐园,有旱冰场,游泳池(这两样我至今都不会…),还有滑梯,转圈升降的飞机,那个疯狂老鼠是后来才有的,至少我小学一年级是没有的。对了,还有人工湖,上面有游船,一般可以自己划桨,我没有坐过,好像是因为太贵了。。。

玩够了就回家,在人民公园门口有卖小金鱼的,我每次去都会买,然而养不了几天。。。大概走路不到半小时就能到家,沿着东环城护城河拐进红庙街,路过我的小学也会像小小一样很自豪的唠唠叨叨介绍“这是我的学校,这里是一年级3班,那里是三年级2班,这个房子后是后操场。。。”,然后经过老板娘和姨儿的小摊,很快就到家了,我父母现在还住在那一块,只是周围都被规划了,人民公园成了新东区的边缘,周边都是高楼大厦,再也没了那份寂静。

还在安阳的朋友估计没我这般感慨,对一座城市的印象,往往当你离开了,才能感知,每一座城市,都是围城。

小时候每过六一,我都特别想去农村玩,但最多也就到郊区,纱厂铁路大桥下面那个郭家湾,也就是现在的洹园,我和老婆谈恋爱的地方,那里有小蝌蚪可以抓,小时候我妈每周末都会骑自行车带我去玩一下午。。。我很羡慕那些农村的孩子,每天都能抓鱼,捉小蝌蚪,逮蜻蜓蝈蝈蚂蚱螳螂,钓青蛙。。。还能肆意田间奔跑,这些在城市里不存在,所以我只能假装去营造氛围,比如会找个坑边湖边逮蜻蜓,有时还挽起裤管假装要下去的样子,其实我心里是不敢的,傍晚路灯刚开,我就去路灯下蹲点了,手里拎个瓶子,在墙角旮旯偷清洁工阿姨一把扫帚,煞有其事的捣鼓着逮那些会飞的昆虫,显得自己很专业,其实都是装的。。。

在课外读物里知道农村可以抓蛇,我也想玩,既然没法抓蛇,去菜市场买总是可以吧,自己跑到健康路市场,还真有,但太大了有点害怕,后来很久,路过一家饭店看到有蛇逃了,我看机会来了,用手掐住蛇拔腿就跑,这个时候,发现后面一群小孩子跟着我跑,脑海里竟然出现了我在那些散文里读到的农村的场景,我的内心是满足的,一直跑到我家附近的后仓坑,把蛇放了。。。

后来我爸妈给我买了小霸王游戏机,那种必须用电视当显示器的盒子游戏机,有点像现在的外置机顶盒,但必须插入游戏卡,说白了小霸王游戏机就是一台普通的通用处理机,真正的那些好玩的游戏逻辑全部都在卡带的存储芯片里,所以说游戏机本身才100多块钱,而好玩的6合1卡带要好几百。。。炎热的假期就基本就游戏搞起了,还记得那些经典游戏吗?还记得上上下下左右左右BA吗?超级马里奥,魂斗罗,双截龙,坦克大战,赤色要塞,绿色兵团,松鼠大作战,忍者神龟,忍者龙剑传,沙罗曼蛇,波斯王子,冒险岛,龙牙。。。对了,还有俄罗斯方块,这个最经典了。不过最后我把游戏机摔了,好像是因为一直没能通关,也够任性的。

至于大街上游戏厅的街机,基本上也就街霸,格斗三人组,三国,抢不到机器时偶尔玩玩雷电。离我家最近的就是铁狮口那里的游戏厅,经常去。

伴随我童年成长的一个不得不提的东西,那就是《七龙珠》(together with《圣斗士》),直到现在还在追剧,已经伴随了我30年,据说《龙珠超》的续集7月份又要开播了,爱奇艺会员已经买好!
。。。。
愿所有人都能保持一份童趣和童真,祝大家六一儿童节快乐!!

关于TCP同时打开-无需Listener的TCP连接建立过程相关推荐

  1. android studio安卓手机tcp通信打开app自动连接,连接失败自动重试8次

    最近在做一个项目,项目其中一个需求是打开app自动连接tcp服务端,连接失败后自动尝试8次.尝试成功继续,不成功提示服务器未响应. 我想到了用多线程实现,其中一个用于显示尝试状态,另一个线程循环尝试s ...

  2. TCP、UDP、TCP三次握手与四次挥手、TCP如何保证可靠传输、TCP异常分析、拆包和粘包等

    4.OSI模型 4.1.OSI七层模型 4.2.七层模型功能 ​ 物理层:利用传输介质为数据链路层提供物理连接,实现比特流的透明传输,如网线:网卡标准. ​ 数据链路层:接收来自物理层的位流形式的数据 ...

  3. tcp报文格式_面试必备TCP(一):三次握手

    TCP大家都知道是什么东西,这个协议的具体报文格式如下: 标志位 URG:指示报文中有紧急数据,应尽快传送(相当于高优先级的数据). PSH:为1表示是带有push标志的数据,指示接收方在接收到该报文 ...

  4. TCP/IP(四):TCP 与 UDP 协议简介

    从本章开始,我们开始介绍最重要的传输层.传输层位于 OSI 七层模型的第四层(由下往上).顾名思义,传输层的主要作用是实现应用程序之间的通信.网络层主要是保证不同数据链路下数据的可达性,至于如何传输数 ...

  5. TCP系列11—重传—1、TCP重传概述

    在最开始介绍TCP的时候,我们就介绍了TCP的三个特点,分别是面向连接.可靠.字节流式.前面内容我们已经介绍过了TCP的连接管理,接下来的这部分内容将会介绍与TCP可靠性强关联的TCP重传. 很多网络 ...

  6. 深入理解TCP三次握手与四次挥手过程以及抓包实验

    一.前言 最近,我正好在做socket相关的实验,发现现在对计算机网络知识有一点点模糊,借此机会,熟悉一下TCP连接过程并利用WireShark工具进行测试. 二.TCP报文首部 源端口号:占16比特 ...

  7. java tcp门禁_门禁控制器的TCP/IP协议功能

    原标题:门禁控制器的TCP/IP协议功能 门禁控制器的TCP/IP协议功能 Linux支持多种不同的网络协议,TCP/IP是Linux系统中最健壮.速度最快和最可靠的部分.TCP/IP协议包括了各个层 ...

  8. 深入分析Linux操作系统对于TCP/IP栈的实现原理与具体过程

    一.Linux内核与网络体系结构 在我们了解整个linux系统的网络体系结构之前,我们需要对整个网络体系调用,初始化和交互的位置,同时也是Linux操作系统中最为关键的一部分代码-------内核,有 ...

  9. 【重难点】【计算机网络 02】TCP 和 UDP 的区别、TCP 的三次握手和四次挥手、HTTP 和 HTTPS、HTTP 各版本之间的区别、HTTP 如何实现长连接

    [重难点][计算机网络 02]TCP 和 UDP 的区别.TCP 的三次握手和四次挥手.HTTP 和 HTTPS.HTTP 各版本之间的区别.HTTP 如何实现长连接 文章目录 [重难点][计算机网络 ...

最新文章

  1. 一文详尽系列之逻辑回归
  2. 手动修改Outlook 2007 邮件签名
  3. Python_模块介绍
  4. 2021云数据库RDS重磅升级发布会
  5. mongodb备份oplog_MongoDB 备份(mongodump)与恢复(mongorestore)
  6. 《认清C++语言》のrandom_shuffle()和transform()算法
  7. javaweb解决编码问题_学习编码? 首先,学会解决问题。
  8. 7-1 矩阵A乘以B (30 分)
  9. 云服务 华为p10 短信_苹果、小米、华为,手机云服务哪家强?
  10. 轻松八句话 教会你完全搞定MySQL数据库(基础)
  11. 米家小白增强固件_中考体育:男1000米/女800米想拿满分,掌握呼吸法是关键
  12. 【NOIP2001】【Luogu1027】Car的旅行路线
  13. 卡方分布、T分布和F分布
  14. 图片标注工具Labelme的安装及使用方法
  15. 常用实验设计方法有哪些?
  16. 恋物志(二):独居者的智能生活指南
  17. 今天来看一下云测平台的测试实验
  18. 介绍一些ddos产品的厂家
  19. 基于SSM或SpringBoot的JavaWeb项目——写作分析系统
  20. nginx 域名解析

热门文章

  1. FPGA价格划分和预估【转载】
  2. 在idea中往Kafka发送消息失败
  3. 23Java面试专题 八股文面试全套真题(含大厂高频面试真题)多线程
  4. Nwafu-Oj-1444 Problem l C语言实习题七——2.结构体数组的定义与引用
  5. Web容器(一):Web容器简介
  6. html页面加载有时没有网样式,页面css加载失败的原因有哪些?
  7. 【开源】基于Java+SpringBoot+Vue+ElementUI的超市管理系统
  8. 计算机企业社会实践活动鉴定范文,计算机专业社会实践自我鉴定范文
  9. Mondly怎么样,Mondly好用吗——Mondly使用评测+七折优惠购买+App Store退订
  10. 【图卷积网络】01-卷积神经网络:从欧氏空间到非欧氏空间