目录
背景
linux下一切皆文件,所以一个tcp连接也是一个文件。
linux对每个进程打开的最大文件数是有限制的。通过ulimit -a查看:默认的大小是1024。一个打开的文件占用一个文件描述符,而且是顺序增加的整数值。
本文通过例子来验证一下。
验证方式
写一个TCP server端: 接收网络连接
写一个TCP client端:只建立连接
代码
sever端代码
#include <sys/socket.h>
#include <netinet/in.h>
#include <strings.h>
#include <stdio.h>
int main(int argc, char **argv) {
int listenFD = socket(AF_INET, SOCK_STREAM, 0);
struct sockaddr_in bindAddr;
bzero(&bindAddr, sizeof(bindAddr));
bindAddr.sin_family = AF_INET;
bindAddr.sin_port = htons(8080);
bindAddr.sin_addr.s_addr = htonl(INADDR_ANY);
int bindRet = bind(listenFD, (struct sockaddr *) &bindAddr, sizeof(bindAddr));
if (bindRet != 0) {
perror("bind失败");
return 1;
}
listen(listenFD, 10000);
int connCount = 0;
printf("已监听,等待连接.\n");
int failCount = 0;
while (1) {
struct sockaddr_in connAddr;
socklen_t connAddrSize = sizeof connAddr;
bzero(&connAddr, connAddrSize);
int connfd = accept(listenFD, (struct sockaddr *) &connAddr, &connAddrSize);
if (connfd < 0) {
failCount++;
perror("accept失败.");
if (failCount > 10) {
printf("accept失败次数大于10,程序退出\n");
return 2;
}
continue;
}
connCount++;
printf("接收第[%d]个连接%d,端口:%d\n", connCount, connfd, ntohs(connAddr.sin_port));
}
}
client端代码
#include <sys/socket.h>
#include <netinet/in.h>
#include <strings.h>
#include <stdio.h>
int main(int argc, char **argv) {
int listenFD = socket(AF_INET, SOCK_STREAM, 0);
struct sockaddr_in bindAddr;
bzero(&bindAddr, sizeof(bindAddr));
bindAddr.sin_family = AF_INET;
bindAddr.sin_port = htons(8080);
bindAddr.sin_addr.s_addr = htonl(INADDR_ANY);
int bindRet = bind(listenFD, (struct sockaddr *) &bindAddr, sizeof(bindAddr));
if (bindRet != 0) {
perror("bind失败");
return 1;
}
listen(listenFD, 10000);
int connCount = 0;
printf("已监听,等待连接.\n");
int failCount = 0;
while (1) {
struct sockaddr_in connAddr;
socklen_t connAddrSize = sizeof connAddr;
bzero(&connAddr, connAddrSize);
int connfd = accept(listenFD, (struct sockaddr *) &connAddr, &connAddrSize);
if (connfd < 0) {
failCount++;
perror("accept失败.");
if (failCount > 10) {
printf("accept失败次数大于10,程序退出\n");
return 2;
}
continue;
}
connCount++;
printf("接收第[%d]个连接%d,端口:%d\n", connCount, connfd, ntohs(connAddr.sin_port));
}
}
验证
默认(1024)配置
服务器端输出:
[root@localhost c]# ./a.out
已监听,等待连接.
接收第[1]个连接4,端口:3516
接收第[2]个连接5,端口:3517
接收第[3]个连接6,端口:3518
接收第[4]个连接7,端口:3519
接收第[5]个连接8,端口:3520
...省略中间的
接收第[1016]个连接1019,端口:54352
接收第[1017]个连接1020,端口:54353
接收第[1018]个连接1021,端口:54354
接收第[1019]个连接1022,端口:54355
接收第[1020]个连接1023,端口:54356
accept失败.: Too many open files
accept失败.: Too many open files
accept失败.: Too many open files
客户端输出:
connect函数返回小于0的值,并且perror的信息是:Connection refused(后面从报文看服务器端返回RST报文,重置了连接)
第1019个连接成功
第1020个连接成功
第1021个连接成功
连接失败!0
连接失败!1
连接失败...: Connection refused
连接失败...: Connection refused
连接失败...: Connection refused
分析
服务器端接收了1020个连接后就accept失败,最后一个连接的描述符是: 1023。之前通过ulimit -a查看的是1024。这里为什么只有1020个连接呢?
因为每个进程默认都会打开标准输入、标准输出、标准错误输出,占用了0、1、2。在c语言的标准库unistd.h中定义,代码如下:
/* Standard file descriptors. */
#define STDIN_FILENO 0 /* Standard input. */
#define STDOUT_FILENO 1 /* Standard output. */
#define STDERR_FILENO 2 /* Standard error output. */
加上这3个,也才1023个,还差一个呢?
差的那一个就是我们在server端的代码中创建的监听套接字,listenFD。
报文
通过抓包可以看到,服务端达到too many open files后,协议栈会发送RST报文,直接重置连接。
修改配置为1048
使用 ulimit -n 1048 临时修改
服务器端输出:
接收第[1040]个连接1043,端口:62788
接收第[1041]个连接1044,端口:62789
接收第[1042]个连接1045,端口:62790
接收第[1043]个连接1046,端口:62791
接收第[1044]个连接1047,端口:62792
accept失败.: Too many open files
accept失败.: Too many open files
accept失败.: Too many open files
accept失败.: Too many open files
这里为什么是1044而不是1048呢?和上面的原因是一样的,你能想起来是为什么?
总结
- linux下每个进程能创建的socket数是有限制的,可以通过ulimit命令来查看和临时修改,如果需要永久修改需要修改配置文件。
- 默认是1024.但是只能接受1020个客户端连接。因为标准输入、标准输出、标准错误输出、listen的socket会占用4个。
- 如果某个连接关闭了,那么连接描述符是可以复用的。
- 如果超过open files的阈值:
- 服务器端 accept函数,返回失败,错误信息是 Too many open files.
- 客户端connect函数,返回失败,错误信息是 Connection refused.
- 服务器端给客户端返回RST报文,重置连接。