TCP 的backlog详解
来源:互联网 发布:软件开发项目计划书 编辑:程序博客网 时间:2024/05/16 12:10
注:本文分析基于3.10.107内核版本
问题1:backlog是什么?
问题2:backlog怎么设置?
问题3:backlog怎么影响TCP的建链?
问题4:如何验证backlog的设置?
以上就是我的疑问,因此我开始去从代码中了解backlog。
man手册
首先,要知道这个backlog是listen()函数里的第二个参数。
int listen(int sockfd, int backlog);
因此我们看下man手册里的解释:
所以说backlog控制的是一个队列的长度,至于控制的是什么队列那就得从TCP的三次握手说起了。
TCP三次握手的队列
在服务端要接收多个客户端发起的连接,因此必不可少要使用队列来管理这些连接。其中在TCP三次握手中有两个队列,分别是半连接状态队列和全连接队列。
半连接状态队列:每个客户端发来的SYN报文,服务器都会把这个报文放到队列里管理,这个队列就是半连接队列,即SYN队列,此时服务器端口处于SYN_RCVD状态。之后服务器会向客户端发送SYN+ACK报文。
全连接状态队列:当服务器接收到客户端的ACK报文后,就会将上述半连接队列里面对应的报文转移(注:其实不是同一个结构,会新建一个结构挂到全连接队列里)到另一个队列里管理,这个队列就是全连接队列,及ACCEPT队列,此时服务器端口处于ESTABLISHED状态。
用代码说话
1、listen()函数
从listen中来,到listen中去。我们先看下listen()系统调用的实现。
SYSCALL_DEFINE2(listen, int, fd, int, backlog){ struct socket *sock; int err, fput_needed; int somaxconn; sock = sockfd_lookup_light(fd, &err, &fput_needed); if (sock) { somaxconn = sock_net(sock->sk)->core.sysctl_somaxconn; if ((unsigned int)backlog > somaxconn) backlog = somaxconn; err = security_socket_listen(sock, backlog); if (!err) err = sock->ops->listen(sock, backlog); fput_light(sock->file, fput_needed); } return err;}
可以看到,listen()函数里传递的backlog并不是直接使用,而是取的min(backlog, somaxconn),其中somaxconn就是/proc/sys/net/core/somaxconn的值,默认为128。
再往下调用listen指针指向的函数,在ipv4里调用的是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; if (sock->state != SS_UNCONNECTED || sock->type != SOCK_STREAM) goto out; old_state = sk->sk_state; if (!((1 << old_state) & (TCPF_CLOSE | TCPF_LISTEN))) goto out; //此时socket的状态仍为TCP_CLOSE状态 if (old_state != TCP_LISTEN) { //暂不分析这个分支 if ((sysctl_tcp_fastopen & TFO_SERVER_ENABLE) != 0 && inet_csk(sk)->icsk_accept_queue.fastopenq == NULL) { if ((sysctl_tcp_fastopen & TFO_SERVER_WO_SOCKOPT1) != 0) err = fastopen_init_queue(sk, backlog); else if ((sysctl_tcp_fastopen & TFO_SERVER_WO_SOCKOPT2) != 0) err = fastopen_init_queue(sk, ((uint)sysctl_tcp_fastopen) >> 16); else err = 0; if (err) goto out; } //这个函数里有一些关键的初始化操作,我们稍后再分析 err = inet_csk_listen_start(sk, backlog); if (err) goto out; } //请注意这个赋值,又一次使用到了backlog入参 sk->sk_max_ack_backlog = backlog; err = 0;out: release_sock(sk); return err;}
2、SYN报文的接收
我们再看服务端收到客户端SYN报文的处理流程。在TCP层的处理大概如下:
tcp_v4_do_rcv |--> tcp_rcv_state_process |--> tcp_v4_conn_request
那我们就来看下这个tcp_v4_conn_request()函数。
int tcp_v4_conn_request(struct sock *sk, struct sk_buff *skb){ .... //这里就是在判断半连接状态队列是否满 if (inet_csk_reqsk_queue_is_full(sk) && !isn) { want_cookie = tcp_syn_flood_action(sk, skb, "TCP"); if (!want_cookie) goto drop; } //这里便是全连接队列是否满的判断 if (sk_acceptq_is_full(sk) && inet_csk_reqsk_queue_young(sk) > 1) { NET_INC_STATS_BH(sock_net(sk), LINUX_MIB_LISTENOVERFLOWS); goto drop; } ....}
按图索骥,我们接着看到底是怎么判断这两个队列满的,这样我们就能知道这两个队列的长度到底是由什么参数决定的。
半连接队列:
static inline int inet_csk_reqsk_queue_is_full(const struct sock *sk){ return reqsk_queue_is_full(&inet_csk(sk)->icsk_accept_queue);}static inline int reqsk_queue_is_full(const struct request_sock_queue *queue){ //重点在max_qlen_log return queue->listen_opt->qlen >> queue->listen_opt->max_qlen_log;}
由此可以看出半连接队列的长度上限和max_qlen_log相关。其实这个值就是在listen()函数流程里设置的,在上节梳理listen()函数时inet_csk_listen_start()没有继续往下梳理,放到这说明比较连贯。
3、inet_csk_listen_start()的初始化内容
//由listen()函数可知,这里的nr_table_entries入参就是backlogint 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); //分配请求的socket结构,并进行初始化操作 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); sk->sk_state = TCP_LISTEN;//设置socket状态为listen,所以之前的状态应该是closed 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); return 0; } sk->sk_state = TCP_CLOSE; __reqsk_queue_destroy(&icsk->icsk_accept_queue); return -EADDRINUSE;}//这个函数进行的初始化操作和半连接队列有密切关系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; //可以看到这里又进行了取值,其中sysctl_max_syn_backlog就是/proc/sys/net/ipv4/tcp_max_syn_backlog的值 //因此 8 <= nr_table_entries <= tcp_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); //这里做的是取与(nr_table_entries + 1)最接近的2的指数次幂, //如果nr_table_entries=7,那该函数的返回值便是8,如果nr_table_entries=8,那么就返回16,以此类推。 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); else lopt = kzalloc(lopt_size, GFP_KERNEL); if (lopt == NULL) return -ENOMEM; //这个for循环就有意思了,求的其实就是2的几次方开始大于nr_table_entries //最终结果保存在max_qlen_log变量中 //即log2(nr_table_entries),结果向上取整 //因为nr_table_entries至少为8,所以指数从3开始 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;}
4、半连接队列长度
知道max_qlen_log怎么来的,这下就可以重新回到半连接队列上。
queue->listen_opt->qlen >> queue->listen_opt->max_qlen_log
所以可以得出结论,半连接队列的长度为
roundup_pow_of_two(max(8, min(tcp_max_syn_backlog, min(backlog, somaxconn))) + 1)
其中backlog为listen()函数的第二个参数,somaxconn为/proc/sys/net/core/somaxconn的值,tcp_max_syn_backlog为/proc/sys/net/ipv4/tcp_max_syn_backlog的值。
5、全连接队列长度
全连接队列的长度就比较简单了。
static inline bool sk_acceptq_is_full(const struct sock *sk){ return sk->sk_ack_backlog > sk->sk_max_ack_backlog;}
有上可知accept队列的长度上限为sk_max_ack_backlog,这个值在上面inet_listen()函数里有标注,这个值就是
min(backlog, somaxconn)
其中backlog为listen()函数的第二个参数,somaxconn为/proc/sys/net/core/somaxconn的值。
至此,对于backlog这个参数我们已经分析清楚了,下面我们就来实际验证一下我们的分析结果。
6、实例验证
首先写一个很简单的服务端程序,然后用hping工具来发起泛洪攻击,这样可以很方便的观察到半连接状态的连接。
服务端程序如下:
/* server.c */#include <stdio.h>#include <string.h>#include <unistd.h>#include <sys/types.h>#include <sys/socket.h>#include <netinet/in.h>#include <arpa/inet.h>#include <stdlib.h>#define PORT 5000#define BACKLOG 5#define BUFLEN 1024int main(int argc, char *argv[]){ int listenfd, connfd; struct sockaddr_in servaddr; char buf[BUFLEN]; if ((listenfd = socket(AF_INET, SOCK_STREAM, 0)) < 0) { printf("socket() failed\n"); exit(1); } memset(&servaddr, 0, sizeof(servaddr)); servaddr.sin_family = AF_INET; servaddr.sin_port = htons(PORT); servaddr.sin_addr.s_addr = htonl(INADDR_ANY); if (bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr)) < 0) { printf("bind() failed\n"); exit(1); } if(listen(listenfd, BACKLOG) < -1) { printf("listen() failed\n"); exit(1); } while(1){ if((connfd = accept(listenfd, NULL, NULL)) < 0) { printf("accept() failed\n"); exit(1); } } close(listenfd); close(connfd); return 0;}
此次测试端口号设置为5000,listen()函数的backlog设置为500,此时系统内核参数如下:
linux-yuk6:/ # cat /proc/sys/net/ipv4/tcp_max_syn_backlog512linux-yuk6:/ # cat /proc/sys/net/core/somaxconn128
按照上述总结的公式可得半连接队列上限为:`
roundup_pow_of_two(max(8, min(512, min(500, 128))) + 1)即,roundup_pow_of_two(129)=256
开启三个终端窗口,一个执行服务器端程序,等待客户端连接;一个执行hping3程序,发起泛洪攻击;剩下一个用netstat命令查看处于SYN_RECV状态的连接数。
hping3程序的安装请参考:
http://blog.csdn.net/u010039418/article/details/78380329
此次测试使用具体命令为:
./hping3 -d 120 -S -w 64 -p 5000 -i u100 --rand-source 192.168.2.116
测试结果如下图:
由图可知,与我们的分析结果一致。
以下几组数据供大家参考:
参考资料:
1、http://blog.csdn.net/raintungli/article/details/37913765
2、http://www.cnblogs.com/Orgliny/p/5780796.html
- TCP 的backlog详解
- TCP listen() Backlog 参数详解
- TCP listen() Backlog 参数详解
- Linux中TCP listen()的backlog参数详解
- Linux中TCP listen()的backlog参数详解
- tcp backlog
- linux里的backlog详解
- linux里的backlog详解
- linux里的backlog详解
- linux里的backlog详解
- linux里的backlog详解
- TCP/IP编程之listen函数backlog参数详解(linux)
- TCP receive_queue prequeue backlog
- 再理解tcp backlog
- TCP之listen&backlog
- TCP SOCKET中backlog
- tcp之backlog参数
- tcp/ip协议listen函数中的backlog参数的含义
- json的使用
- 欢迎使用CSDN-markdown编辑器
- my stl study-road<template>
- 插入排序法
- 三次握手、四次挥手
- TCP 的backlog详解
- Java mysql(1)----jdbc连接mysql之url书写
- 【Leetcode】Merge Two Binary Trees 合并两个二叉树
- Hadoop必备
- 第八周
- 第8章 表单处理
- Semi-supervised Learning for Network-Based Cardiac MR Image Segmentation文章解读
- Ubuntu16.04编译安装BusyBox
- 码云使用说明