Socket层实现系列 — listen()的实现
本文主要分析listen()的内核实现,包括它的系统调用、Socket层实现、半连接队列,以及监听哈希表。
内核版本:3.6
Author:zhangskd @ csdn blog
应用层
int listen(int sockfd, int backlog);
Accept incoming connections and a queue limit for incoming connections.
backlog的定义
Now it specifies the queue length for completely established sockets waiting to be accepted,
instead of the number of incomplete connection requests. The maximum length of the queue
for incomplete sockets can be set using the tcp_max_syn_backlog sysctl. When syncookies
are enabled there is no logical maximum length and this sysctl setting is ignored.
If the socket is of type AF_INET, and the backlog argument is greater than the constant SOMAXCONN
(128 default), it is silently truncated to SOMAXCONN.
全连接队列的最大长度:
backlog保存的是完成三次握手、等待accept的全连接,而不是半连接。
负载不高时,backlog不用太大。(For complete connections)
系统最大的、未处理的全连接数量为:min(backlog, somaxconn),net.core.somaxconn默认为128。
这个值最终存储于sk->sk_max_ack_backlog。
半连接队列的最大长度:
tcp_max_syn_backlog默认值为256。(For incomplete connections)
当使用SYN Cookie时,这个参数变为无效。
半连接队列的最大长度为backlog、somaxconn、tcp_max_syn_backlog的最小值。
系统调用
listen()是由glibc提供的,声明位于include/sys/socket.h中,实现位于sysdeps/mach/hurd/listen.c中,
主要是用来从用户空间进入名为sys_socketcall的系统调用,并传递参数。sys_socketcall()实际上是所
有socket函数进入内核空间的共同入口。
在sys_socketcall()中会调用sys_listen()。
SYSCALL_DEFINE2(socketcall, int, call, unsigned long __user *, args)
{
...
switch(call) {
...
case SYS_LISTEN:
err = sys_listen(a0, a1);
break;
...
}
return err;
}
经过了socket层的总入口sys_socketcall(),现在进入sys_listen()。
SYSCALL_DEFINE2(listen, int, fd, int, backlog)
{
struct socket *sock;
int err, fput_needed;
int somaxconn; /* 通过文件描述符fd,找到对应的socket。
* 以fd为索引从当前进程的文件描述符表files_struct中找到对应的file实例,
* 然后从file实例的private_data成员中获取socket实例。
*/
sock = sockfd_lookup_light(fd, &err, &fput_needed); if (sock) {
/* backlog不能超过系统参数somaxconn */
somaxconn = sock_net(sock->sk)->core.sysctl_somaxconn;
if ((unsigned int)backlog > somaxconn)
backlog = somaxconn; err = security_socket_listen(sock, backlog); if (! err)
/* socket层的操作函数,如果是SOCK_STREAM的话,proto_ops是inet_stream_ops,
* 接下来调用的是inet_listen()。
*/
err = sock->ops->listen(sock, backlog); fput_light(sock->file, fput_needed);
}
}
socket层
SOCK_STREAM套接口的socket层操作函数集实例为inet_stream_ops,其中监听函数为inet_listen()。
const struct proto_ops inet_stream_ops = {
.family = PF_INET,
.owner = THIS_MODULE,
...
.listen = inet_listen, /* socket层的实现 */
...
};
/*
* @sk_ack_backlog: current listen backlog
* @sk_max_ack_backlog: listen backlog set in listen()
*/
struct sock {
...
unsigned short sk_ack_backlog; /* 当前的backlog,当前全连接队列长度 */
unsigned short sk_max_ack_backlog; /* 最大的backlog,最大全连接队列长度 */
...
};
检查套接口的状态、当前连接的状态是否合法,然后调用inet_csk_listen_start()启动监听。
/* 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; /* 此时套接口状态需为SS_UNCONNECTED,套接口类型需为SOCK_STREAM */
if (sock->state != SS_UNCONNECTED || sock->type != SOCK_STREAM)
goto out; old_state = sk->sk_state; /* 当前连接状态 */ /* 当前的连接需为CLOSED或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.
*/
if (old_state != TCP_LISTEN) {
/* 启动监听 */
err = inet_csk_listen_start(sk, backlog);
if (err)
goto out;
} sk->sk_max_ack_backlog = backlog; /* 最大全连接队列长度 */
err = 0 out:
release_sock(sk);
return err;
}
启动监听时,做的工作主要包括:
1. 创建半连接队列的实例,初始化全连接队列。
2. 初始化sock的一些变量,把它的状态设为TCP_LISTEN。
3. 检查端口是否可用,防止bind()后其它进程修改了端口信息。
4. 把sock链接进入监听哈希表listening_hash中。
int inet_csk_listen_start(struct sock *sk, const in 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; /* 在返回inet_listen()时赋值 */
sk->sk_ack_backlog = 0;
inet_csk_delack_init(sk); /* icsk->icsk_ack结构清零 */ sk->sk_state = TCP_LISTEN; /* 把sock的状态置为LISTEN */ /* 检查端口是否仍然可用,防止bind()后其它进程修改了端口信息 */
if (! sk->sk_prot->get_port(sk, inet->inet_num)) {
inet->inet_sport = htons(inet->inet_num);
sk_dst_reset(sk);
sk->sk_prot->hash(sk); /* 把sock链接入监听哈希表中 */
return 0;
} sk->sk_state = TCP_CLOSE;
/* 如果端口不可用,则释放半连接队列 */
__reqsk_queue_destroy(&icsk->icsk_accept_queue);
return -EADDRINUSE;
}
半连接队列
listen_sock结构用于保存SYN_RECV状态的连接请求块,所以也叫半连接队列。
(1)创建
queue是连接请求控制块,nr_table_entries是半连接的最大个数,即backlog。
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; /* nr_table_entries必需在[8, sysctl_max_syn_backlog]之间,默认是[8, 256]
* 但实际上在sys_listen()中要求backlog <= sysctl_somaxconn(默认为128)
* 所以此时默认区间为[8, 128]
*/
nr_table_entries = min_t(u32, nr_table_entries, sysctl_max_syn_backlog);
nr_table_entries = max_t(u32, nr_table_entries, 8); /* 使nr_table_entries = 2^n,向上取整 */
nr_table_entries = roundup_pow_of_two(nr_table_entries + 1); /* 增加一个指针数组的长度 */
lopt_size += nr_table_entries * sizeof(struct request_sock *); if (lopt_size > PAGE_SIZE)
lopt = vzalloc(lopt_size); /* 如果申请内存大于1页,则申请虚拟地址连续的空间 */
else
lopt = kzalloc(lopt_size, GFP_KERNEL); /* 申请内存在1页内,则申请物理地址连续的空间 */ /* 相当于把max_qlen_log设置为nr_table_entries以2为底的对数 */
for (lopt->max_qlen_log = 3, (1 << lopt->max_qlen_log) < nr_table_entries; lopt->max_qlen_log++); 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;
}
/*
* Maximum number of SYN_RECV sockets in queue per LISTEN socket.
* One SYN_RECV socket costs about 80bytes on a 32bit machine.
* It would be better to replace it with a global counter for all sockets but then
* some measure against one socket starving all other sockets would be needed.
*
* The minimum value of it is 128. Experiments with real servers show that it is
* absolutely not enough even at 100conn/sec. 256 cures most of problems.
* This value is adjusted to 128 for low memory machines, and it will increase
* in proportion to the memory of machine.
* Note: Dont forget somaxconn that may limit backlog too.
*/
int sysctl_max_syn_backlog = 256;
(2)销毁
销毁连接请求块中的listen_sock实例,释放半连接队列。
void __reqsk_queue_destroy(struct request_sock_queue *queue)
{
struct listen_sock *lopt;
size_t lopt_size; lopt = queue->listen_opt;
lopt_size = sizeof(struct listen_sock) + lopt->nr_table_entries *
sizeof(struct request_sock *); if (lopt_size > PAGE_SIZE)
vfree(lopt);
else
kfree(lopt);
}
监听哈希表
inet_hash()用于把sock链入监听哈希表listening_hash,或者已建立连接的哈希表ehash。
struct proto tcp_prot = {
...
.hash = inet_hash,
.unhash = inet_unhash,
.get_port = inet_csk_get_port,
...
};
当sock状态为TCP_LISTEN时,inet_hash()把sock链接入tcp_hashinfo->listening_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)
{
struct inet_hashinfo *hashinfo = sk->sk_prot->h.hashinfo;
struct inet_listen_hashbucket *ilb; /* sock不处于listen状态时 */
if (sk->sk_state != TCP_LISTEN) {
__inet_hash_nolisten(sk, NULL); /* 这里对应的是已建立的连接 */
return;
} /* 要求sk->sk_node没被使用? */
WARN_ON(! sk_unhashed(sk)); /* 根据端口号,找到对应的监听哈希桶 */
lib = &hashinfo->listening_hash[inet_sk_listen_hashfn(sk)]; spin_lock(&lib->lock);
/* 把sock放入监听哈希桶的头,链接节点为sk->sk_nulls_node */
__sk_nulls_add_node_rcu(sk, &ilb->head); sock_prot_inuse_add(sock_net(sk), sk->sk_prot, 1); /* 此CPU上该协议的使用数加一 */
spin_lock(&lib->lock);
} static inline bool sk_unhashed(const struct sock *sk)
{
return hlist_unhashed(&sk->sk_node);
} static inline int hlist_unhashed(const struct hlist_node *h)
{
return ! h->pprev;
}
Socket层实现系列 — listen()的实现的更多相关文章
- Socket层实现系列 — send()类发送函数的实现
主要内容:socket发送函数的系统调用.Socket层实现. 内核版本:3.15.2 我的博客:http://blog.csdn.net/zhangskd 发送流程图 以下是send().sendt ...
- Socket层实现系列 — connect()的实现
主要内容:connect()的Socket层实现.期间进程的睡眠和唤醒. 内核版本:3.15.2 我的博客:http://blog.csdn.net/zhangskd 应用层 int connect( ...
- Socket层实现系列 — 信号驱动的异步等待
主要内容:Socket的异步通知机制. 内核版本:3.15.2 我的博客:http://blog.csdn.net/zhangskd 概述 socket上定义了几个IO事件:状态改变事件.有数据可读事 ...
- Socket层实现系列 — 睡眠驱动的同步等待
主要内容:Socket的同步等待机制,connect和accept等待的实现. 内核版本:3.15.2 我的博客:http://blog.csdn.net/zhangskd 概述 socket上定义了 ...
- Socket层实现系列 — accept()的实现(一)
本文主要介绍了accept()的系统调用.Socket层实现,以及TCP层实现. 内核版本:3.6 Author:zhangskd @ csdn blog 应用层 int accept(int soc ...
- Socket层实现系列 — getsockname()和getpeername()的实现
本文主要介绍了getsockname()和getpeername()的内核实现. 内核版本:3.6 Author:zhangskd @ csdn blog 应用层 int getsockname(in ...
- Socket层实现系列 — bind()的实现(一)
bind()函数的使用方法很简单,但是它是怎么实现的呢? 笔者从应用层出发,沿着网络协议栈,分析了bind()的系统调用.Socket层实现,以及它的TCP层实现. 本文主要内容:bind()的系统调 ...
- Socket层实现系列 — bind()的实现(二)
本文主要内容:bind()的TCP层实现.端口的冲突处理,以及不同内核版本的实现差异. 内核版本:3.6 Author:zhangskd @ csdn blog TCP层实现 SOCK_STREAM套 ...
- Socket层实现系列 — I/O事件及其处理函数
主要内容:Socket I/O事件的定义.I/O处理函数的实现. 内核版本:3.15.2 我的博客:http://blog.csdn.net/zhangskd I/O事件定义 sock中定义了几个I/ ...
随机推荐
- C语言如何在两个文件中访问同一个全局变量
方法一: 不使用头文件. 1.c 中 int var; 2.c 中 extern int var; 方法二: 使用头文件. 1.c 中 int var; 不必添加#include "1.h& ...
- 悲观的并发策略——Synchronized互斥锁
volatile既然不足以保证数据同步,那么就必须要引入锁来确保.互斥锁是最常见的同步手段,在并发过程中,当多条线程对同一个共享数据竞争时,它保证共享数据同一时刻只能被一条线程使用,其他线程只有等到锁 ...
- 给定一个数列a1,a2,a3,...,an和m个三元组表示的查询,对于每个查询(i,j,k),输出ai,ai+1,...,aj的升序排列中第k个数。
给定一个数列a1,a2,a3,...,an和m个三元组表示的查询,对于每个查询(i,j,k),输出ai,ai+1,...,aj的升序排列中第k个数. #include <iostream> ...
- 谷歌面试题:输入是两个整数数组,他们任意两个数的和又可以组成一个数组,求这个和中前k个数怎么做?
谷歌面试题:输入是两个整数数组,他们任意两个数的和又可以组成一个数组,求这个和中前k个数怎么做? 分析: "假设两个整数数组为A和B,各有N个元素,任意两个数的和组成的数组C有N^2个元素. ...
- Android Studio安装Genymotion插件
Android Studio安装Genymotion插件 Eclipse就不介绍了,谷歌都已经放弃Eclipse了,你还在坚持什么. 安装Genymotion 官网:https://www.genym ...
- Android简易实战教程--第二十七话《自定义View入门案例之开关按钮详细分析》
转载此博客请注明出处点击打开链接 http://blog.csdn.net/qq_32059827/article/details/52444145 对于自定义view,可能是一个比较大的 ...
- Android 深入理解Loader机制 让APP轻装上阵
本文简书同步发布,谢谢关注. http://blog.csdn.net/sk719887916/article/details/51540610 Android开发者都经历过APP UI开发不当 会造 ...
- 【Unity Shader实战】卡通风格的Shader(二)
写在前面 本系列其他文章: 卡通风格的Shader(一) 好久没写博客了,一定是因为课程作业比较多,一定不是因为我懒,恩恩. 三个月以前,在一篇讲卡通风格的Shader的最后,我们说到在Surface ...
- Android面试之高级篇
结合自己之前去很多大公司的面试经历和自己面别人的一些题,这里做一些总结,Android面试中常见的面试题. 1,Android的Handler运行机制 要解释Handler的运行机制就要讲几个对象:M ...
- Mybatis简单入门
MyBatis是一个支持普通SQL查询,存储过程和高级映射的优秀持久层框架.MyBatis消除了几乎所有的JDBC代码和参数的手工设置以及对结果集的检索封装.MyBatis可以使用简单的XML或注解用 ...