c - 为什么在 ~16370 个套接字连接后会有长时间的停顿?
问题描述
我一直在使用 sockets API 来了解它是如何工作的。
我写了两个小程序:
- 服务器在 8080 上侦听流连接。它向任何连接到它的人发送一条简单的消息。
- 客户端连接到 127.0.0.1:8080,并将接收到的内容转储到标准输出。它按顺序重复此 20000 次。
消息以极快的速度流向大约 16370 次,然后暂停数十秒,然后再次开始快速移动以完成 20000 个连接。
我把这个实验重复了几次,我在 16370、16371 和 16372 抓到了它。重复实验的结果出奇的一致。
我的问题是:为什么它需要在 ~16370 次迭代后暂停?这里的瓶颈是什么?
FWIW,我在 macOS Sierra 上。
我像这样运行服务器代码:
clang -Wall -Werror -Wpedantic server.c -o server.out && ./server.out
和这样的客户端代码:
clang -Wall -Werror -Wpedantic client.c -o client.out && time ./client.out
这是两个程序:
服务器.c
#include <errno.h>
#include <netdb.h>
#include <netinet/in.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <unistd.h>
#define PORT 8080
#define MAXMSG 512
int make_socket(int port) {
int sock;
struct sockaddr_in name;
sock = socket(PF_INET, SOCK_STREAM, 0);
if (sock < 0) {
perror("socket");
exit(1);
}
name.sin_family = AF_INET;
name.sin_port = htons(port);
name.sin_addr.s_addr = htonl(INADDR_ANY);
if (bind(sock, (struct sockaddr*) &name, sizeof(name)) < 0) {
perror("bind");
exit(1);
}
return sock;
}
int main(int argc, char** argv) {
const char hello[] = "Hello visitor ";
char buffer[MAXMSG];
int sk;
unsigned long count = 0;
strcpy(buffer, hello);
sk = make_socket(PORT);
listen(sk, 10);
printf("ready\n");
for (;;) {
count++;
sprintf(buffer + strlen(hello), "%lu", count);
int s = accept(sk, NULL, NULL);
if (send(s, buffer, strlen(buffer) + 1, 0) < 0) {
perror("send");
exit(1);
}
close(s);
printf("data socket (%d) message sent (%s)\n", s, buffer);
}
}
客户端.c
#include <arpa/inet.h>
#include <errno.h>
#include <netdb.h>
#include <netinet/in.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <unistd.h>
#define PORT 8080
#define MAXMSG 512
int make_socket() {
int sock;
sock = socket(PF_INET, SOCK_STREAM, 0);
if (sock < 0) {
perror("socket");
exit(1);
}
return sock;
}
int main(int argc, char** argv) {
char buffer[MAXMSG];
int sk;
size_t i;
struct sockaddr_in addr;
strcpy(buffer, "Hello world!");
for (i = 0; i < 20000; i++) {
sk = make_socket();
addr.sin_family = AF_INET;
addr.sin_port = htons(PORT);
addr.sin_addr.s_addr = inet_addr("127.0.0.1");
connect(sk, (struct sockaddr*) &addr, sizeof(addr));
recv(sk, buffer, strlen(buffer) + 1, 0);
close(sk);
printf("socket (%d) message = %s\n", sk, buffer);
}
}
这是我得到客户端的最后一个标准输出:
socket (3) message = Hello visitor 16369
socket (3) message = Hello visitor 16370
socket (3) message = Hello visitor 16371
socket (3) message = Hello visitor 16372
解决方案
您很可能在操作系统上遇到了一个称为临时端口范围的限制。相同的原则适用于所有基于 IP 的操作系统。
每当建立套接字连接时,都会为请求分配一个端口,并与建立连接的接口相关联。一旦此套接字关闭,端口就会进入称为 TIME_WAIT 的状态。有效地将端口放在工作台上一段时间,以确保不会过早重复使用。这是为了防止 Internet 中的潜在数据包迟到并导致问题。
临时端口范围在 Linux 上指定为/proc/sys/net/ipv4/ip_local_port_range
.
您可以使用以下命令在 MacOS 上显示这些:
sysctl net.inet.ip.portrange.first net.inet.ip.portrange.last
net.inet.ip.portrange.first: 49152
net.inet.ip.portrange.last: 65535
这是临时范围内的16,383 个可用端口。
要查看所有网络参数,您可以执行:
sysctl net.inet.tcp
您可以更改 TIME_WAIT 值,但对于您的高压力应用程序,它只会降低您的减速等待的阈值。
您可以使用 netstat -an 查看打开的连接数。如果您打开和关闭大量连接,套接字可能会卡在 TIME_WAIT 状态。在某些地方这是不可避免的,但如果是这种情况,您可能需要考虑是否需要连接池。
如果 TIME_WAIT 是问题,您可以调整系统设置。您可以设置net.ipv4.tcp_tw_reuse / net.ipv4.tcp_tw_recycle
以加快连接周转速度。
一个快速测试是切换到另一个界面并重试。如果您使用的是 localhost,然后遇到速度变慢的情况,您可以在另一个接口上切换到您的外部 IP,并且您应该像 gangbusters 一样运行,直到再次达到限制。
这不是您使用的语言的问题,而是基于套接字的网络使用的问题。
推荐阅读
- asp.net - Img 标签不显示图像
- html - 如何将 lottie 动画渲染为全屏或全容器宽度
- c - 使用指向结构数组的指针不会返回完整的数组
- linux-kernel - 在回调函数外使用 platform_device 指针
- terraform - aws_iam_policy_document On Count Enabled 资源的 DRY 解决方案
- reactjs - 在反应路由器路径道具中使用正则表达式有条件地渲染组件
- reactjs - handleChange 是如何接收 props 的?反应
- flutter - 获取颤振“缩放”小部件画布的 x,y
- reactjs - 有没有办法在 react-admin 的 matchSuggestion 中检查嵌套资源?
- parameters - 如何根据过滤器/参数隐藏/取消隐藏数据值(仅在一列中)?