几个问题
了解以下几个问题的同学可以直接忽略下文:
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);
}
了解到这里,回答文初提出的
3
个问题,应该没什么问题了吧
:)
分享到:
相关推荐
详解Linux协议栈的数据流向,SOCKET的操作流程,unicast multicast等等的区别。
很清楚的分析了一个数据包如何通过查询路由进入内核ipsec协议栈的处理、Linux 内核ipsec协议栈详细的加解密流程以及加解密完后如何将数据包发送出去。 文档中前半部分主要介绍一些关键的数据结构,及其相互之间的...
《Linux内核网络栈源代码情景分析》主要对Linux1.2.13内核协议栈的全部源代码做了详细的分析,该版本所有代码都在一个文件夹中,每种协议的实现都只有一个文件与之对应,分析该版本源代码可以方便读者迅速掌握Linux...
本书主要对 Linux 1.2.13 内核协议栈的全部源代码做了详细的分析, 该版本所有代码都在一个文件夹中,每种协议的实现都只有一个文件与之对应,分析该版本源代码可以方便读者迅速掌握 Linux 网络协议结构。...
主要对Linux1.2.13内核协议栈的全部源代码做了详细的分析,该版本所有代码都在一个文件夹中,每种协议的实现都只有一个文件与之对应,分析该版本源代码可以方便读者迅速掌握Linux网络协议结构。 压缩包最后一部分。
3.3 timestack数据包-Wireshark3.4 内核协议栈相关主要源码Time_wait状态生成及快速回收相关代码:开启timestamps引起的丢
详细描述了linux内核协议栈的实现原理及相关数据结构,为linux内核协议栈分析人员提供了重要参考。
基于Linux内核的用户态网络协议栈的实现.pdf
深入,对于想深入了解linux内核协议栈的有帮助
Linux网络体系结构 Linux内核中网络协议的设计与实现,Linux网络体系结构 Linux内核中网络协议的设计与实现
Linux内核网络栈源代码情景分析
想学习linux内核的朋友不能错过的好书 强烈推荐
---------------------------------------------------Linux 中的各种栈:进程栈 线程栈 内核栈 中断栈
SM3算法及在Linux内核IPSEC协议栈的实现.pdf
全面分析linux内核的网络协议栈,非常适合对linux网络协议栈源码的学习
linux-2.6.18内核基础上分析网络协议栈,适合初学者看
linux内核TCP协议栈培析部分,下册,能打开的,讲TCP怎么启动,怎么控制发送,不开dsack等
很详细的一个原理图,看明白之后对linux网络协议栈有很深的理解和认知,有关socket、ip报文、ip分片、转发、桥等图解
文档中简要的描述了linux内核中SCTP实现的基础以及相关代码流程,欢迎大家互相交流
基于 linux 2.6.18 内核源码,对ip协议栈分析。 下载后,评个分。谢谢哈。