几个问题 
了解以下几个问题的同学可以直接忽略下文:

1、listen 库函数主要做了什么? 
2、 什么是最大并发连接请求数? 
3、什么是等待连接队列?

socket 监听相对还是比较简单的,先看下应用程序代码:

listen( server_sockfd, 5);

其中,第一个参数  server_sockfd 为服务端  socket 所对应的文件描述符,第二个参数5   代表监听socket   能处理的最大并发连接请求数,在2.6.26   内核中,该值为  256  ;

listen   库函数调用的主要工作可以分为以下几步:  
1   、根据  socket 文件描述符找到内核中对应的  socket 结构体变量;这个过程在《socket地址绑定 》   一文中描述过,这里不再重述;  
2   、设置  socket 的状态并初始化等待连接队列;  
3   、将  socket 放入listen   哈希表中;

listen 调用代码跟踪 
下面是  listen 库函数对应的内核处理函数:

asmlinkage long sys_listen(int fd, int backlog)
{  struct socket *sock;  int err, fput_needed;  int somaxconn;  // 根据文件描述符取得内核中的socket  sock = sockfd_lookup_light(fd, &err, &fput_needed);  if (sock) {  // 根据系统中的设置调整参数backlog  somaxconn = sock_net(sock->sk)->core.sysctl_somaxconn;  if ((unsigned)backlog > somaxconn)  backlog = somaxconn;  err = security_socket_listen(sock, backlog);  // 调用相应协议簇的listen函数  if (!err)  err = sock->ops->listen(sock, backlog);  fput_light(sock->file, fput_needed);  }  return err;
}  

根据《创建socket 》   一文的介绍,例子中,这里sock->ops->   listen(sock, backlog)   实际上调用的是 net/ipv4/Af_inet.c:inet_listen()   函数:

int inet_listen(struct socket *sock, int backlog)
{  struct sock *sk = sock->sk;  unsigned char old_state;  int err;  lock_sock(sk);  err = -EINVAL;  // 1 这里首先检查socket的状态和类型,如果状态或类型不正确,返回出错信息  if (sock->state != SS_UNCONNECTED || sock->type != SOCK_STREAM)  goto out;  old_state = sk->sk_state;  // 2 这里检查sock的状态是否是TCP_CLOSE或TCP_LISTEN,如果不是,返回出错信息  if (!((1 << old_state) & (TCPF_CLOSE | TCPF_LISTEN)))  goto out;  /* Really, if the socket is already in listen state * we can only allow the backlog to be adjusted. */  // 3 当sock的状态不是TCP_LISTEN时,做监听相关的初始化  if (old_state != TCP_LISTEN) {  err = inet_csk_listen_start(sk, backlog);  if (err)  goto out;  }  // 4 设置sock的最大并发连接请求数  sk->sk_max_ack_backlog = backlog;  err = 0;
out:  release_sock(sk);  return err;
} 

上面的代码中,有点值得注意的是,当   sock   状态已经是   TCP_LISTEN   时,也可以继续调用   listen()   库函数,其作用是设置   sock   的最大并发连接请求数;  
下面看看   inet_csk_listen_start()   函数:

int inet_csk_listen_start(struct sock *sk, const int nr_table_entries)
{  struct inet_sock *inet = inet_sk(sk);  struct inet_connection_sock *icsk = inet_csk(sk);  // 初始化连接等待队列  int rc = reqsk_queue_alloc(&icsk->icsk_accept_queue, nr_table_entries);  if (rc != 0)  return rc;  sk->sk_max_ack_backlog = 0;  sk->sk_ack_backlog = 0;  inet_csk_delack_init(sk);  // 设置sock的状态为TCP_LISTEN  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;  __reqsk_queue_destroy(&icsk->icsk_accept_queue);  return -EADDRINUSE;
} 

这里 nr_table_entries 是参数 backlog 经过最大值调整后的值;

相关数据结构 
先看下接下来的代码中提到了几个数据结构,一起来看一下:

1 、 request_sock

struct request_sock {  struct request_sock *dl_next; /* Must be first */  u16 mss;  u8 retrans;  u8 cookie_ts; /* syncookie: encode tcpopts in timestamp */  /* The following two fields can be easily recomputed I think -AK */  u32 window_clamp; /* window clamp at creation time */  u32 rcv_wnd; /* rcv_wnd offered first time */  u32 ts_recent;  unsigned long expires;  const struct request_sock_ops *rsk_ops;  struct sock *sk;  u32 secid;  u32 peer_secid;
};

socket 在侦听的时候,那些来自其它主机的 tcp socket 的连接请求一旦被接受(完成三次握手协议),便会建立一个 request_sock ,建立与请求 socket 之间的一个 tcp 连接。该 request_sock 会被放在一个先进先出的队列中,等待 accept 系统调用的处理;
2 、 listen_sock

struct listen_sock {  u8 max_qlen_log;  /* 3 bytes hole, try to use */  int qlen;  int qlen_young;  int clock_hand;  u32 hash_rnd;  u32 nr_table_entries;  struct request_sock *syn_table[0];
};

新建立的 request_sock 就存放在 syn_table 中;这是一个哈希数组,总共有 nr_table_entries 项;

成员 max_qlen_log 以 2 的对数的形式表示 request_sock 队列的最大值;

qlen 是队列的当前长度;

hash_rnd 是一个随机数,计算哈希值用;

3 、 request_sock_queue

struct request_sock_queue {  struct request_sock *rskq_accept_head;  struct request_sock *rskq_accept_tail;  rwlock_t syn_wait_lock;  u16 rskq_defer_accept;  /* 2 bytes hole, try to pack */  struct listen_sock *listen_opt;
}; 

结构体 struct request_sock_queue 中的 rskq_accept_head 和 rskq_accept_tail 分别指向 request_sock 队列的队列头和队列尾;

等待连接队列初始化

先看下 reqsk_queue_alloc() 的源代码:

int reqsk_queue_alloc(struct request_sock_queue *queue,unsigned int nr_table_entries)
{  size_t lopt_size = sizeof(struct listen_sock);  struct listen_sock *lopt;  // 1 控制nr_table_entries在8~ sysctl_max_syn_backlog之间  nr_table_entries = min_t(u32, nr_table_entries, sysctl_max_syn_backlog);  nr_table_entries = max_t(u32, nr_table_entries, 8);  // 2 向上取2的幂  nr_table_entries = roundup_pow_of_two(nr_table_entries + 1);  // 3 申请等待队列空间  lopt_size += nr_table_entries * sizeof(struct request_sock *);  if (lopt_size > PAGE_SIZE)  lopt = __vmalloc(lopt_size,GFP_KERNEL | __GFP_HIGHMEM | __GFP_ZERO,PAGE_KERNEL);  else  lopt = kzalloc(lopt_size, GFP_KERNEL);  if (lopt == NULL)  return -ENOMEM;  // 4 设置listen_sock的成员max_qlen_log最小为3,最大为nr_table_entries的对数  for (lopt->max_qlen_log = 3;(1 << lopt->max_qlen_log) < nr_table_entries;lopt->max_qlen_log++);  // 5 相关字段赋值  get_random_bytes(&lopt->hash_rnd, sizeof(lopt->hash_rnd));  rwlock_init(&queue->syn_wait_lock);  queue->rskq_accept_head = NULL;  lopt->nr_table_entries = nr_table_entries;  write_lock_bh(&queue->syn_wait_lock);  queue->listen_opt = lopt;  write_unlock_bh(&queue->syn_wait_lock);  return 0;
}  

整个过程中,先计算 request_sock 的大小并申请空间,然后初始化 request_sock_queue 的相应成员的值;

TCP_LISTEN 的 socket 管理

在《端口管理》一文中提到管理 socket 的哈希表结构 inet_hashinfo ,其中的成员listening_hash[INET_LHTABLE_SIZE] 用于存放处于 TCP_LISTEN 状态的 sock ;

当 socket 通过 listen() 调用完成等待连接队列的初始化后,需要将当前 sock 放到该结构体中:

if (!sk->sk_prot->get_port(sk, inet->num)) {  // 这里再次判断端口是否被占用  inet->sport = htons(inet->num);  sk_dst_reset(sk);  // 将当前socket哈希到inet_hashinfo中  sk->sk_prot->hash(sk);  return 0;
}  

这里调用了   net/ipv4/Inet_hashtables.c:inet_hash()   方法:

void inet_hash(struct sock *sk)
{  if (sk->sk_state != TCP_CLOSE) {  local_bh_disable();  __inet_hash(sk);  local_bh_enable();  }
}
static void __inet_hash(struct sock *sk)
{  // 取得inet_hashinfo结构  struct inet_hashinfo *hashinfo = sk->sk_prot->h.hashinfo;  struct hlist_head *list;  rwlock_t *lock;  // 状态检查  if (sk->sk_state != TCP_LISTEN) {  __inet_hash_nolisten(sk);  return;  }  BUG_TRAP(sk_unhashed(sk));  // 计算hash值,取得链表  list = &hashinfo->listening_hash[inet_sk_listen_hashfn(sk)];  lock = &hashinfo->lhash_lock;  inet_listen_wlock(hashinfo);  // 将sock添加到链表中  __sk_add_node(sk, list);  sock_prot_inuse_add(sock_net(sk), sk->sk_prot, 1);  write_unlock(lock);  wake_up(&hashinfo->lhash_wait);
}  

Linux内核网络协议栈8—socket监听相关推荐

  1. Linux内核网络协议栈:udp数据包发送(源码解读)

    <监视和调整Linux网络协议栈:接收数据> <监控和调整Linux网络协议栈的图解指南:接收数据> <Linux网络 - 数据包的接收过程> <Linux网 ...

  2. Linux内核网络协议栈流程及架构

    文章目录 Linux内核网络报文处理流程 Linux内核网络协议栈架构 Linux内核网络报文处理流程 linux网络协议栈是由若干个层组成的,网络数据的处理流程主要是指在协议栈的各个层之间的传递. ...

  3. 深入浅出Linux内核网络协议栈|结构sk_buff|Iptables|Netfilter丨内核源码丨驱动开发丨内核开发丨C/C++Linux服务器开发

    深入浅出Linux内核网络协议栈 视频讲解如下,点击观看: 深入浅出Linux内核网络协议栈|结构sk C/C++Linux服务器开发高级架构师知识点精彩内容包括:C/C++,Linux,Nginx, ...

  4. Linux内核网络协议栈

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

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

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

  6. linux内核网络协议栈--linux网络设备理解(十三)

    网络层次 linux网络设备驱动与字符设备和块设备有很大的不同. 字符设备和块设备对应/dev下的一个设备文件.而网络设备不存在这样的设备文件.网络设备使用套接字socket访问,虽然也使用read, ...

  7. Linux内核网络协议栈7-socket端口管理

    一.前情回顾 上一节<socket 地址绑定 >中提到,应用程序传递过来的端口在内核中需要检查端口是否可用: if (sk->sk_prot->get_port(sk, snu ...

  8. Linux内核网络协议栈3-创建socket(1)

    1.示例及函数入口: 1) 示例代码如下: C代码   int server_sockfd = socket(AF_INET, SOCK_STREAM, 0); 2) 入口: net/Socket.c ...

  9. Linux内核网络协议栈5-socket地址绑定

    一.socket绑定入口 1.示例代码 struct sockaddr_in server_address; server_address.sin_family = AF_INET; server_a ...

最新文章

  1. 《最受欢迎的女友职业排行榜Top10》
  2. DependentLayout相对布局
  3. VTK:隐藏线移除用法实战
  4. oracle备份集注册,OracleRMAN将备份集重新注册到控制文件说明
  5. [转] Java, 使用 Reactor 进行反应式编程
  6. ruby scala python_解释一个基准在C,Clojure,Python,Ruby,Scala和其他
  7. 迅雷游戏盒子下载|迅雷游戏盒子下载
  8. GaussDB(for Redis)揭秘:Redis存算分离架构最全解析
  9. fast rcnn 论文解读(附代码链接)
  10. Ubuntu21.04设置国内镜像源
  11. Python实现对给定的列表中连续数字的寻找
  12. c600 raid linux,华硕Z9PA-D8 (-C600 INTEL RAID)主板驱动3.8.0.1108版下载,适用于Win8-64,win7,Win7-64,winxp-驱动精灵...
  13. 苹果电脑怎么打开计算机管理,mac打开的软件怎么关 苹果电脑系统怎么关闭打开的程序...
  14. Word一行有空白格,却无法输入新的文字
  15. (18)python字符串的使用
  16. html怎么解压缩文件,压缩包7z如何解压
  17. 总结HTMLT5高级的新特性
  18. 异贝,通过移动互联网技术,为中小微实体企业联盟、线上链接、线上线下自定义营销方案推送。案例62
  19. Android实现可录音/暂停录音/播放录音的录音软件
  20. 项目实践系列-点击生成自定义设置的二维码

热门文章

  1. 文本标注工具——doccano
  2. 解决zookeeper启动失败Could not find or load main class org.apache.zookeeper.server.quorum.QuorumPeerMain报错
  3. 采集浏览器访问某网站时产生的流量,并保存为pcap文件
  4. Mac安装sshpass同时解决Calling Non-checksummed download of sshpass formula file from an arbitrary URL报错
  5. dijikstra 旅行商问题_第27期:基于旅行商问题(TSP)的配送网络优化—R实现
  6. 【机器学习】梯度下降中矩阵的迹的求导证明
  7. 四川第七届 C Censor (字符串哈希)
  8. cmd切换为administrator用户
  9. STM32L之可编程电压检测(PVD)
  10. 利用面向对象的方式来使用JS