一、sys_listen

对面向连接的协议,在调用 bind(2)后,进一步调用 listen(2),让套接字进入监听状态: int listen(int sockfd, int backlog);

backlog 表示新建连接请求时,最大的未处理的积压请求数。

这里说到让套接字进入某种状态,也就是说,涉及到套接字的状态变迁,前面 create 和bind 时,也遇到过相应的代码。

sock和 sk 都有相应的状态字段,先来看 sock 的:

typedef enum {

SS_FREE = 0,                        /*  套接字未分配                */

SS_UNCONNECTED,                        /*  套接字未连接        */

SS_CONNECTING,                        /*  套接字正在处理连接        */

SS_CONNECTED,                        /*  套接字已连接                */

SS_DISCONNECTING                /*  套接字正在处理关闭连接 */ } socket_state;

在创建套接字时,被初始化为 SS_UNCONNECTED。

对于面向连接模式的SOCK_TREAM来讲,这样描述状态显然是不够的。 这样, 在sk中, 使用sk_state维护了一个有限状态机来描述套接字的状态:

enum {

TCP_ESTABLISHED = 1,

TCP_SYN_SENT,

TCP_SYN_RECV,

TCP_FIN_WAIT1,

TCP_FIN_WAIT2,

TCP_TIME_WAIT,

TCP_CLOSE,

TCP_CLOSE_WAIT,

TCP_LAST_ACK,

TCP_LISTEN,

TCP_CLOSING,         /* now a valid state */

TCP_MAX_STATES /* Leave at the end! */

};

还有一个相应的用来进行状态位运算的枚举结构:

enum {

TCPF_ESTABLISHED = (1 << 1),

TCPF_SYN_SENT  = (1 << 2),

TCPF_SYN_RECV  = (1 << 3),

TCPF_FIN_WAIT1 = (1 << 4),

TCPF_FIN_WAIT2 = (1 << 5),

TCPF_TIME_WAIT = (1 << 6),

TCPF_CLOSE     = (1 << 7),

TCPF_CLOSE_WAIT = (1 << 8),

TCPF_LAST_ACK  = (1 << 9),

TCPF_LISTEN    = (1 << 10),

TCPF_CLOSING   = (1 << 11)

};

值得一提的是,sk 的状态不等于 TCP的状态,虽然 sk 是面向协议栈,但它的状态并不能同 TCP状态一一直接划等号。虽然这些状态值都用 TCP-XXX 来表式,但是只是因为 TCP协议状态非常复杂。sk 结构只是利用它的一个子集来抽像描述而已。

同样地,操作码 SYS_LISTEN的任务会落到 sys_listen()函数身上:

/* Maximum queue length specifiable by listen.  */

#define SOMAXCONN        128 int sysctl_somaxconn = SOMAXCONN;

asmlinkage long sys_listen(int fd, int backlog)

{

struct socket *sock;

int err;

if ((sock = sockfd_lookup(fd, &err)) != NULL) {

if ((unsigned) backlog > sysctl_somaxconn)

backlog = sysctl_somaxconn;

err = security_socket_listen(sock, backlog);

if (err) {

sockfd_put(sock);

return err;

}

err=sock->ops->listen(sock, backlog);

sockfd_put(sock);

}

return err;

}

同样地,函数会最终转向协议簇的 listen 函数,也就是 inet_listen():

/*

*        Move a socket into listening state.

*/

int inet_listen(struct socket *sock, int backlog)

{

struct sock *sk = sock->sk;

unsigned char old_state;

int err;

lock_sock(sk);

err = -EINVAL;

/* 在 listen 之前,sock 必须为未连接状态,且只有 SOCK_STREAM 类型,才有 listen(2)*/

if (sock->state != SS_UNCONNECTED || sock->type != SOCK_STREAM)

goto out;

/*  临时保存状态机状态 */

old_state = sk->sk_state;         /*  只有状态机处于 TCP_CLOSE  或者是 TCP_LISTEN  这两种状态时,才可能对其调用listen(2)  ,这个判断证明了 listen(2)是可以重复调用地(当然是在转向 TCP_LISTEN 后没有再进行状态变迁*/

if (!((1 << old_state) & (TCPF_CLOSE | TCPF_LISTEN)))

goto out;

/*  如果接口已经处理 listen 状态,只修改其 max_backlog,否则先调用 tcp_listen_start,继续设置协议的 listen 状态  */

if (old_state != TCP_LISTEN) {

err = tcp_listen_start(sk);

if (err)

goto out;

}

sk->sk_max_ack_backlog = backlog;

err = 0;

out:

release_sock(sk);

return err;

}

inet_listen 函数在确认 sock->state 和 sk->sk_state 状态后,会进一步调用 tcp_listen_start 函数,最且最后设置 sk_max_ack_backlog  。

tcp 的 tcp_listen_start 函数,完成两个重要的功能,一个是初始化 sk 的一些相关成员变量,另一方面是切换有限状态机的状态。 sk_max_ack_backlog表示监听时最大的 backlog 数量,它由用户空间传递的参数决定。而 sk_ack_backlog表示当前的的 backlog数量。

当 tcp 服务器收到一个 syn 报文时,它表示了一个连接请求的到达。内核使用了一个 hash 表来维护这个连接请求表:

struct tcp_listen_opt

{

u8                        max_qlen_log;        /* log_2 of maximal queued SYNs */

int                        qlen;

int                        qlen_young;

int                        clock_hand;

u32                        hash_rnd;

struct open_request        *syn_table[TCP_SYNQ_HSIZE];

};

syn_table,  是open_request结构,就是连接请求表,连中的最大项,也就是最大允许的 syn 报文的数量,由 max_qlen_log 来决定。当套接字进入 listen 状态,也就是说可以接收 syn 报文了,那么在此之前,需要先初始化这个表:

int tcp_listen_start(struct sock *sk)

{

struct inet_sock *inet = inet_sk(sk);                //获取 inet结构指针

struct tcp_sock *tp = tcp_sk(sk);                //获取协议指针

struct tcp_listen_opt *lopt;

//初始化 sk 相关成员变量

sk->sk_max_ack_backlog = 0;

sk->sk_ack_backlog = 0;

tp->accept_queue = tp->accept_queue_tail = NULL;

rwlock_init(&tp->syn_wait_lock);

tcp_delack_init(tp);

//初始化连接请求 hash 表

lopt = kmalloc(sizeof(struct tcp_listen_opt), GFP_KERNEL);

if (!lopt)

return -ENOMEM;

memset(lopt, 0, sizeof(struct tcp_listen_opt));

//初始化 hash 表容量,最小为 6,其实际值由 sysctl_max_syn_backlog 决定

for (lopt->max_qlen_log = 6; ; lopt->max_qlen_log++)

if ((1 << lopt->max_qlen_log) >= sysctl_max_syn_backlog)

break;

get_random_bytes(&lopt->hash_rnd, 4);

write_lock_bh(&tp->syn_wait_lock);

tp->listen_opt = lopt;

write_unlock_bh(&tp->syn_wait_lock);

/* There is race window here: we announce ourselves listening,

* but this transition is still not validated by get_port().

* It is OK, because this socket enters to hash table only

* after validation is complete.

*/

/*  修改状态机状态,表示进入 listen 状态,根据作者注释,当宣告自己进入 listening 状态后,但是这个状态转换并没有得到 get_port 的确  认。所以需要调用 get_port()函数。但是对于一点,暂时还没有完全搞明白,只有留待后面再来分析它 */

sk->sk_state = TCP_LISTEN;

if (!sk->sk_prot->get_port(sk, inet->num)) {

inet->sport = htons(inet->num);

sk_dst_reset(sk);

sk->sk_prot->hash(sk);

return 0;

}

sk->sk_state = TCP_CLOSE;

write_lock_bh(&tp->syn_wait_lock);

tp->listen_opt = NULL;

write_unlock_bh(&tp->syn_wait_lock);

kfree(lopt);

return -EADDRINUSE;

}

在切换了有限状态机状态后,调用了

sk->sk_prot->hash(sk);

也就是 tcp_v4_hash()函数。这里涉到到另一个 hash 表:TCP监听 hash 表。

二、TCP监听 hash表

所谓 TCP 监听表,指的就内核维护“当前有哪些套接字在监听”的一个表,当一个数据包进入 TCP栈的时候,内核查询这个表中对应的 sk,以找到相应的数据  结构。 (因为 sk 是面向网络栈调用的,找到了 sk,就找到了 tcp_sock,就找到了 inet_sock,就找到了 sock,就找到了 fd……就到了组  织了)。

TCP所有的 hash 表都用了tcp_hashinfo来封装,前面分析 bind已见过它:

extern struct tcp_hashinfo {

……

/* All sockets in TCP_LISTEN state will be in here.  This is the only

* table where wildcard'd TCP sockets can exist.  Hash function here

* is just local port number.

*/

struct hlist_head __tcp_listening_hash[TCP_LHTABLE_SIZE];

……

spinlock_t __tcp_portalloc_lock;

} tcp_hashinfo;

#define tcp_listening_hash (tcp_hashinfo.__tcp_listening_hash)

函数 tcp_v4_hash 将一个处理监听状态下的 sk 加入至这个 hash 表:

static void tcp_v4_hash(struct sock *sk)

{

if (sk->sk_state != TCP_CLOSE) {

local_bh_disable();                 __tcp_v4_hash(sk, 1);

local_bh_enable();

}

}

因为__tcp_v4_hash 不只用于监听 hash 表,它也用于其它 hash 表,其第二个参数 listen_possible 为真的时候,表示处理的是监听 hash表:

static __inline__ void __tcp_v4_hash(struct sock *sk, const int listen_possible)

{

struct hlist_head *list;

rwlock_t *lock;

BUG_TRAP(sk_unhashed(sk));

if (listen_possible && sk->sk_state == TCP_LISTEN) {

list = &tcp_listening_hash[tcp_sk_listen_hashfn(sk)];

lock = &tcp_lhash_lock;

tcp_listen_wlock();

} else {

……

}

__sk_add_node(sk, list);

sock_prot_inc_use(sk->sk_prot);

write_unlock(lock);

if (listen_possible && sk->sk_state == TCP_LISTEN)

wake_up(&tcp_lhash_wait);

}

else 中的部份用于另一个 hash 表,暂时不管它。代表很简单,如果确认是处理的是监听 hash 表。则先根据 sk计算一个 hash 值,在hash 桶中找到入口。再调用__sk_add_node 加入至该 hash 链。

tcp_sk_listen_hashfn()函数事实上是 tcp_lhashfn 的包裹,前面已经分析过了。

__sk_add_node()函数也就是一个简单的内核 hash处理函数 hlist_add_head()的包裹:

static __inline__ void __sk_add_node(struct sock *sk, struct hlist_head *list)

{

hlist_add_head(&sk->sk_node, list);

}

小结

一个套接字的 listen,主要需要做的工作有以下几件:

1、初始化 sk 相关的成员变量,最重要的是 listen_opt,也就是连接请求 hash 表。

2、将 sk 的有限状态机转换为 TCP_LISTEN,即监听状态;

3、将 sk 加入监听 hash表;

4、设置允许的最大请求积压数,也就是 sk 的成员 sk_max_ack_backlog 的值。

linux listen监听,Linux网络协议栈 -- socket listen监听相关推荐

  1. 网络编程0x04 Listen函数

    网络编程0x04 Listen函数 文章目录 网络编程0x04 Listen函数 1.套接字分类 2. listen函数 3. 监听过程 1.套接字分类 TCP socket分两种: 监听socket ...

  2. Linux内核网络协议栈8—socket监听

    几个问题  了解以下几个问题的同学可以直接忽略下文: 1.listen 库函数主要做了什么?  2. 什么是最大并发连接请求数?  3.什么是等待连接队列? socket 监听相对还是比较简单的,先看 ...

  3. Linux网络协议栈(二)——套接字缓存(socket buffer)

    Linux网络核心数据结构是套接字缓存(socket buffer),简称skb.它代表一个要发送或处理的报文,并贯穿于整个协议栈. 1.    套接字缓存 skb由两部分组成: (1)    报文数 ...

  4. kali linux wifi监听模式,无线渗透教程1:监听无线网络

    第一:配置管理无线网卡 1.1这里,我们使用tplink wn722n, kali linux插上即用,无需安装驱动. 1.2Vmare虚拟机配置如下: 如网卡插入到电脑后,先将虚拟机设置成桥接模式 ...

  5. linux监控某个端口流量抓包,tcpdump命令 – 监听网络流量

    tcpdump命令是一款sniffer工具,是linux上的抓包工具,嗅探器:它可以打印出所有经过网络接口的数据包的头信息. tcpdump命令工作时先要把网卡的工作模式切换到混杂模式.所以tcpdu ...

  6. Linux内核网络协议栈

    一.注册时机 1.在内核初始化时完成: 2.内核初始化过程(init/main.c):kernel_init()->do_basic_setup()->do_initcalls()-> ...

  7. 监视和调整Linux网络协议栈:接收数据

    Table of Contents 有关监视和调整Linux网络协议栈的建议 总览 详细外观 网络设备驱动程序 初始化 网络设备初始化 启动网络设备 监控网络设备 调整网络设备 SoftIRQ 什么是 ...

  8. Linux 内核网络协议栈运行原理

    封装:当应用程序用 TCP 协议传送数据时,数据首先进入内核网络协议栈中,然后逐一通过 TCP/IP 协议族的每层直到被当作一串比特流送入网络.对于每一层而言,对收到的数据都会封装相应的协议首部信息( ...

  9. linux 网络协议栈变化,ZZ Linux网络协议栈学习

    最近学习linux内核网络协议栈,把数据包接收流程大致理了一下, 前面也看了瀚海书香兄的总结,感觉总结的比我精炼,抓住了主干,是一目了然的那种 我的这篇本来是自己看得,因此把我自己学习中一些遇到的问题 ...

最新文章

  1. 通过 .gitlab-ci.yml配置任务-官方配置文件翻译
  2. linux bash中too many arguments问题的解决方法
  3. 架构设计贵在务实(转载)
  4. iOS NSNotificationCenter 使用姿势详解
  5. Steps to install Domino Server 8.5.1 on AIX 6
  6. LeetCode-321 Create Maximum Number
  7. python怎样填充颜色_python中如何给图形填充颜色
  8. 【转】解决 canvas 在高清屏中绘制模糊的问题
  9. Multisim调出时钟设置方波信号
  10. 支付那些事儿III---一个BD汪眼中的产品I
  11. HTML5+js+css3开心消消乐手机pc端通用源码|H5小游戏
  12. 台州银行登录显示服务器异常,手把手教你设置台州银行网上银行【处理办法】...
  13. 安卓APK文件结构解析 怎样去除内置广告 及修改图标和文字
  14. ArcGIS10.2 安装教程
  15. 1.1股票数据预处理练习
  16. 无视硬件检测直接运行Win10混合现实门户
  17. win7如何设置wifi热点_博世壁挂炉“盖世7200i”WiFi功能如何设置
  18. VMware Workstation 12 Pro 安装 mac最新系统版本10.12.3
  19. matlab带下标的字母,matlab的特殊字符(上下标和希腊字母等)
  20. linux opengl配置编译,Linux下OpenGL的安装与cmake编译OpenGL程序

热门文章

  1. 交换两个变量的值(三种方式、完整代码)
  2. Java-字符与字符串的转化
  3. kali dvwa php mysql,kali linux 2.0下搭建DVWA渗透测试演练平台
  4. MySQL checkpoint机制详解
  5. 深入解析:DB2 V10.5新特性列式存储表的优点与缺点
  6. STM32+华为云IoTDA,带你设计一个属于自己的动态密码锁
  7. 关于HTTPS认证,这里解决你所有疑惑
  8. 毕业季offer怎么拿?收下这份非典型求职面试指南
  9. 搭建亿级时间线数据的监控系统,我有绝招!
  10. 教你两种数据库覆盖式数据导入方法