我一直在使用套接字API,以了解它的工作原理。
我写了两个小程序:
消息的传输速度高达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
这是两个程序:
server.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);
}
}
client.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
答案 0 :(得分:2)
您很可能在操作系统上达到了称为Ephemeral Port Range的限制。相同的原则适用于所有基于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,并且应该像狂暴分子一样运行,直到再次达到极限为止。
这不是您使用的语言的问题,而是基于套接字的网络使用情况的问题。
答案 1 :(得分:0)
您可能正在处理套接字的TIME_WAIT状态。在主动关闭服务器中的每个连接套接字后,该套接字会长时间保持状态(数十秒)。因此,正如@Blaze所建议的那样,您的程序正在达到资源限制,必须等待此套接字确定关闭。
此功能可以防止两种情况,第一种是将一个连接中发送的延迟数据包解释为以后连接的一部分的机会。第二个机会是连接的被动关闭方未接收到连接关闭的最后一个ACK,从而导致其重新发送FIN / ACK。如果发生这种情况,并且活动的关闭端已经关闭了套接字,它将用RST进行响应,即使所有信息都已正确发送,另一端也会收到错误消息。
如果您确实要完全关闭套接字(有发生先前事件的风险),则必须在尝试关闭SO_LINGER选项之前对其进行调整。
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css">
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css" integrity="sha384-Gn5384xqQ1aoWXA+058RXPxPg6fy4IWvTNh0E263XmFcJlSAwiGgFAW/dAiS6JXm" crossorigin="anonymous">
<label class="btn custom-input-btn">
<input type="file" name="photo" style="display:none" accept="*" multiple>
<i class="fa fa-cloud-upload"></i> Upload Files
</label>