深入探索 Linux listen() 函数 backlog 的含义
1:listen()回顾以及问题引入
2:正确的解释
3:实验验证
1:listen()回顾以及问题引入
listen()
函数是网络编程中用来使服务器端开始监听端口的系统调用,首先来回顾下listen()
函数的定义:
有关于第二个参数含义的问题网上有好几种说法,我总结了下主要有这么3种:
- Kernel会为
LISTEN状态
的socket维护一个队列,其中存放SYN RECEIVED
和ESTABLISHED
状态的套接字,backlog
就是这个队列的大小。 - Kernel会为
LISTEN状态
的socket维护两个队列,一个是SYN RECEIVED
状态,另一个是ESTABLISHED
状态,而backlog
就是这两个队列的大小之和。 - 第三种和第二种模型一样,但是
backlog
是队列ESTABLISHED
的长度。
有关上面说的两个状态SYN RECEIVED
状态和ESTABLISHED
状态,是TCP三次握手
过程中的状态转化,具体可以参考下面的图(在新窗口打开图片):
2:正确的解释
那上面三种说法到底哪个是正确的呢?我下面的说法翻译自这个链接:
http://veithen.github.io/2014/01/01/how-tcp-backlog-works-in-linux.html
下面我翻译下作者的文章:
When an application puts a socket into LISTEN state using the listen syscall, it needs to specify a backlog for that socket. The backlog is usually described as the limit for the queue of incoming connections.
当一个应用使用listen
系统调用让socket
进入LISTEN
状态时,它需要为该套接字指定一个backlog
。backlog
通常被描述为连接队列的限制。
Because of the 3-way handshake used by TCP, an incoming connection goes through an intermediate state SYN RECEIVED before it reaches the ESTABLISHED state and can be returned by the accept syscall to the application (see the part of the TCP state diagram reproduced above). This means that a TCP/IP stack has two options to implement the backlog queue for a socket in LISTEN state:
由于TCP使用的3次握手,连接在到达ESTABLISHED
状态之前经历中间状态SYN RECEIVED
,并且可以由accept
系统调用返回到应用程序。这意味着TCP / IP
堆栈有两个选择来为LISTEN
状态的套接字实现backlog
队列:
(备注:一种就是两种状态在一个队列,一种是分别在一个队列)
1 : The implementation uses a single queue, the size of which is determined by the backlog argument of the listen syscall. When a SYN packet is received, it sends back a SYN/ACK packet and adds the connection to the queue. When the corresponding ACK is received, the connection changes its state to ESTABLISHED and becomes eligible for handover to the application. This means that the queue can contain connections in two different state: SYN RECEIVED and ESTABLISHED. Only connections in the latter state can be returned to the application by the accept syscall.
1:使用单个队列实现,其大小由listen syscall
的backlog
参数确定。 当收到SYN
数据包时,它发送回SYN/ACK
数据包,并将连接添加到队列。 当接收到相应的ACK
时,连接将其状态改变为已建立。 这意味着队列可以包含两种不同状态的连接:SYN RECEIVED
和ESTABLISHED
。 只有处于后一状态的连接才能通过accept syscall
返回给应用程序。
2 : The implementation uses two queues, a SYN queue (or incomplete connection queue) and an accept queue (or complete connection queue). Connections in state SYN RECEIVED are added to the SYN queue and later moved to the accept queue when their state changes to ESTABLISHED, i.e. when the ACK packet in the 3-way handshake is received. As the name implies, the accept call is then implemented simply to consume connections from the accept queue. In this case, the backlog argument of the listen syscall determines the size of the accept queue.
2 : 使用两个队列
实现,一个SYN
队列(或半连接队列)和一个accept
队列(或完整的连接队列)。 处于SYN RECEIVED
状态的连接被添加到SYN
队列,并且当它们的状态改变为ESTABLISHED
时,即当接收到3次握手中的ACK
分组时,将它们移动到accept
队列。 显而易见,accept
系统调用只是简单地从完成队列中取出连接。 在这种情况下,listen syscall
的backlog参数表示完成队列
的大小。
Historically, BSD derived TCP implementations use the first approach. That choice implies that when the maximum backlog is reached, the system will no longer send back SYN/ACK packets in response to SYN packets. Usually the TCP implementation will simply drop the SYN packet (instead of responding with a RST packet) so that the client will retry.
历史上,BSD 派生系统实现的TCP
使用第一种方法。 该选择意味着当达到最大backlog
时,系统将不再响应于SYN分组
发送回SYN/ACK
分组。 通常,TCP
的实现将简单地丢弃SYN分组
,使得客户端重试。
On Linux, things are different, as mentioned in the man page of the listen syscall:
The behavior of the backlog argument on TCP sockets changed with Linux 2.2. 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 /proc/sys/net/ipv4/tcp_max_syn_backlog.
在Linux上,是和上面不同的。如在listen
系统调用的手册中所提到的:
在Linux内核2.2之后,socket backlog
参数的形为改变了,现在它指等待accept
的完全建立
的套接字的队列长度,而不是不完全连接请求的数量。 不完全连接的长度可以使用/proc/sys/net/ipv4/tcp_max_syn_backlog
设置。
This means that current Linux versions use the second option with two distinct queues: a SYN queue with a size specified by a system wide setting and an accept queue with a size specified by the application.
这意味着当前Linux版本使用上面第二种说法,有两个队列:具有由系统范围设置指定的大小的SYN队列
和 应用程序(也就是backlog参数)指定的accept
队列。
OK,说到这里,相信backlog含义已经解释的非常清楚了,下面我们用实验验证下这种说法:
3:实验验证
验证环境:
RedHat 7
Linux version 3.10.0-514.el7.x86_64
验证思路:
1:客户端开多个线程分别创建socket去连接服务端。
2:服务端在listen
之后,不去调用accept
,也就是不会从已完成队列
中取走socket
连接。
3:观察结果,到底服务端会怎么样?处于ESTABLISHED
状态的套接字个数是不是就是backlog
参数指定的大小呢?
我们定义backlog
的大小为5
:
# define BACKLOG 5
看下我系统上默认的SYN
队列大小:
也就是我现在两个队列的大小分别是 :
SYN
队列大小:256
ACCEPT
队列大小:5
看看我们的服务端程序 server.c
:
#include<stdio.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<sys/time.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<errno.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#define PORT 8888 //端口号
#define BACKLOG 5 //BACKLOG大小
void my_err(const char* msg,int line)
{
fprintf(stderr,"line:%d",line);
perror(msg);
}
int main(int argc,char *argv[])
{
int conn_len;
int sock_fd,conn_fd;
struct sockaddr_in serv_addr,conn_addr;
if((sock_fd = socket(AF_INET,SOCK_STREAM,0)) == -1) {
my_err("socket",__LINE__);
exit(1);
}
memset(&serv_addr,0,sizeof(struct sockaddr_in));
serv_addr.sin_family = AF_INET;
serv_addr.sin_port = htons(PORT);
serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
if(bind(sock_fd,(struct sockaddr *)&serv_addr,sizeof(struct sockaddr_in)) == -1) {
my_err("bind",__LINE__);
exit(1);
}
if(listen(sock_fd,BACKLOG) == -1) {
my_err("sock",__LINE__);
exit(1);
}
conn_len = sizeof(struct sockaddr_in);
sleep(10); //sleep 10s之后接受一个连接
printf("I will accept one\n");
accept(sock_fd,(struct sockaddr *)&conn_addr,(socklen_t *)&conn_len);
sleep(10); //同理,再接受一个
printf("I will accept one\n");
accept(sock_fd,(struct sockaddr *)&conn_addr,(socklen_t *)&conn_len);
sleep(10); //同理,再次接受一个
printf("I will accept one\n");
accept(sock_fd,(struct sockaddr *)&conn_addr,(socklen_t *)&conn_len);
while(1) {} //之后进入while循环,不释放连接
return 0;
}
客户端程序 client.c
:
#include<stdio.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<string.h>
#include<strings.h>
#include<stdlib.h>
#include<unistd.h>
#include<pthread.h>
#define PORT 8888
#define thread_num 10 //定义创建的线程数量
struct sockaddr_in serv_addr;
void *func()
{
int conn_fd;
conn_fd = socket(AF_INET,SOCK_STREAM,0);
printf("conn_fd : %d\n",conn_fd);
if( connect(conn_fd,(struct sockaddr *)&serv_addr,sizeof(struct sockaddr_in)) == -1) {
printf("connect error\n");
}
while(1) {}
}
int main(int argc,char *argv[])
{
memset(&serv_addr,0,sizeof(struct sockaddr_in));
serv_addr.sin_family = AF_INET;
serv_addr.sin_port = htons(PORT);
inet_aton("192.168.30.155",(struct in_addr *)&serv_addr.sin_addr); //此IP是局域网中的另一台主机
int retval;
//创建线程并且等待线程完成
pthread_t pid[thread_num];
for(int i = 0 ; i < thread_num; ++i)
{
pthread_create(&pid[i],NULL,&func,NULL);
}
for(int i = 0 ; i < thread_num; ++i)
{
pthread_join(pid[i],(void*)&retval);
}
return 0;
}
编译运行程序,并用netstat
命令监控服务端8888
端口的情况:
$ gcc server.c -o server
$ gcc client.c -o client -lpthread -std=c99
# watch -n 1 “netstat -natp | grep 8888” //root执行
//watch -n 1 表示每秒显示一次引号中命令的结果
//netstatn: 以数字化显示 a:all t:tcp p:显示pid和进程名字
然后我们grep
端口号8888
就行了。$ ./server
$ ./client
结果如下:
首先是watch
的情况:
- 因为我们客户端用10个线程去连接服务器,因此服务器上有10条连接。
- 第一行的
./server
状态是LISTEN
,这是服务器进程。 - 倒数第三行的
./server
是服务器已经执行了一次accept
。 - 6条
ESTABLISHED
状态比我们的BACKLOG
参数5
大1。 - 剩余的
SYN_RECV
状态即使收到了客户端第三次握手回应的ACK
也不能成为ESTABLISHED
状态,因为BACKLOG
队列中没有位置。
然后过了10s
左右,等到服务器执行了第二个accept
之后,服务器情况如下,它执行了第二个accept
:
此时watch
监控的画面如下:
- 和上面相比,服务器再次
accept
之后,多了一条./server
的连接。 - 有一条连接从
SYN_RECV
状态转换到了ESTABLISHED
状态,原因是accept
函数从BACKlOG
完成的队列中取出了一个连接,接着有空间之后,SYN
队列的一个链接就可以转换成ESTABLISHED
状态然后放入BACKlOG
完成队列了。
好了,分析到这里,有关BACKLOG
的问题已经解决了,至于继续上面的实验将backlog
的参数调大会怎么样呢?我试过了,就是ESTABLISHED
状态的数量也会增大,值会是BACKLOG+1
,至于为什么是BACKLOG+1
呢???我也没有搞懂。欢迎指教。
当然,还有别的有意思的问题是 : 如果ESTABLISHED
队列满了,可是有连接需要从SYN
队列转移过来时会发生什么?
一边在喊:让我过来!我满足条件了。
一边淡淡的说:过个毛啊,没看没地方‘ 住 ‘ 吗?~
改天再细说吧,欢迎评论交流~
深入探索 Linux listen() 函数 backlog 的含义的更多相关文章
- listen()函数中backlog参数分析
实例分析1 将服务器端的listen函数backlog设置为2,用20个客户端与服务器建立连接,查看连接的建立情况. 服务器代码: #include <stdio.h> #include& ...
- linux tcp listen函数的参数backlog
1 listen函数(http://man7.org/linux/man-pages/man2/listen.2.html) int listen(int sockfd, int backlog); ...
- tcp/ip协议listen函数中backlog參数的含义
listen函数的定义例如以下所看到的: #include <sys/socket.h> int accept(int sockfd, struct sockaddr * restrict ...
- 曹工说Redis源码(4)-- 通过redis server源码来理解 listen 函数中的 backlog 参数
文章导航 Redis源码系列的初衷,是帮助我们更好地理解Redis,更懂Redis,而怎么才能懂,光看是不够的,建议跟着下面的这一篇,把环境搭建起来,后续可以自己阅读源码,或者跟着我这边一起阅读.由于 ...
- socket编程listen函数限制连接数的解决方案
函数原型: int listen(int sockfd, int backlog); 当服务器编程时,经常需要限制客户端的连接个数,下面为问题分析以及解决办法: 下面只讨论TCP UDP不做讨论(很 ...
- listen函数
listen函数仅仅由TCP服务器调用,它做2件事: 1)当socket函数创建一个套接字时,它被假设为一个主动套接字,也就是说,它是一个将调用connect发起连接的客户套接字 listen函数把一 ...
- 网络编程socket之listen函数
摘要:listen函数使用主动连接套接口变为被连接套接口,使得一个进程可以接受其它进程的请求,从而成为一个服务器进程.在TCP服务器编程中listen函数把进程变为一个服务器,并指定相应的套接字变为被 ...
- 探索C++虚函数在g++中的实现
本文是我在追查一个诡异core问题的过程中收获的一点心得,把公司项目相关的背景和特定条件去掉后,仅取其中通用的C++虚函数实现部分知识记录于此. 在开始之前,原谅我先借用一张图黑一下C++: “无敌” ...
- listen() 函数
声明:本文来自网络博文的合并,文后有链接. 一.listen函数仅由TCP服务器调用 它做两件事: 1.当socket函数创建一个套接字时,它被假设为一个主动套接字,也就是说,它是一个将调用conne ...
随机推荐
- Nginx系列(7)- Nginx安装 | Linux
step-1 安装gcc 安装 nginx 需要先将官网下载的源码进行编译,编译依赖 gcc 环境,如果没有 gcc 环境,则需要安装: [root@localhost ~]# yum install ...
- Nginx系列(3)- 负载均衡
负载均衡 Nginx提供的负载均衡策略有两种: 内置策略为轮询.加权轮询.ip hash 扩展策略,就天马行空了,只有你想不到的没有它做不到的 轮询 加权轮询(根据权重来) iphash对客户端请求 ...
- linux系统运维操作规范
1.1安装流程 1.1.1 系统如无特殊要求一律采用小化安装方式进行安装. 1.1.2 安装过程开始之前需要根据实际情况进行CPU数量.磁盘容量.内存分配.文件系统.目录结构.磁盘分区规划.磁盘管理方 ...
- 华为云计算IE面试笔记-云磁盘和普通磁盘的区别。
1. 定义 云硬盘:一种虚拟块存储服务,主要为ECS和BMS提供块存储空间 普通磁盘:也称本地硬盘,指挂载在计算实例物理机上的本地硬盘 2. 性能 吞吐量具体情况具体分析.(若云磁盘用的SSD本地磁盘 ...
- Spring,AOP实现功能级别权限验证
1. 首先是问题出现的原因 对于一个我的一个个人博客网站,我希望游客可以浏览我的博客,但是评论功能是需要登录才能使用 这就需要对某个功能进行权限验证 对于过滤器,拦截器,AOP的区别日后再讨论,现在是 ...
- LaTeX Vscode 配置
安装:https://www.latexstudio.net/archives/51801.html LaTeX 安装 & 宏包升级 & 入门:https://blog.csdn.ne ...
- 梦幻西游H5游戏超详细图文架设教程
前言 想体验经典Q版西游霸服快乐吗?想体验满级VIP的尊贵吗?想体验一招秒杀的爽快吗?各种极品装备.翅膀.宠物通通给你,就在梦幻西游! 本文讲解梦幻西游H5游戏的架设教程,想研究H5游戏如何实现,体验 ...
- Python读取网页表格数据
学会了从网格爬取数据,就可以告别从网站一页一页复制表格数据的时代了. 说个亲身经历的事: 以前我的本科毕业论文是关于"燃放烟花爆竹和空气质量"之间关系的,就要从环保局官网查资料. ...
- 洛谷4208 JSOI2008最小生成树计数(矩阵树定理+高斯消元)
qwq 这个题目真的是很好的一个题啊 qwq 其实一开始想这个题,肯定是无从下手. 首先,我们会发现,对于无向图的一个最小生成树来说,只有当存在一些边与内部的某些边权值相同的时候且能等效替代的时候,才 ...
- 试题 算法训练 二进制数数 java解题
资源限制 时间限制:1.0s 内存限制:256.0MB 问题描述 给定L,R.统计[L,R]区间内的所有数在二进制下包含的"1"的个数之和. 如5的二进制为101,包含2个&q ...