我试图了解backlog参数在int listen(int sockfd, int backlog);中是如何影响如何处理新连接的。
这是我的服务器程序。
/* server.c */
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netdb.h>
int main()
{
int sockfd;
int ret;
int yes = 1;
struct addrinfo hints, *ai;
memset(&hints, 0, sizeof hints);
hints.ai_family = AF_INET;
hints.ai_socktype = SOCK_STREAM;
hints.ai_flags = AI_PASSIVE;
if ((ret = getaddrinfo(NULL, "8000", &hints, &ai)) == -1) {
fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(ret));
return 1;
}
sockfd = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol);
if (sockfd == -1) {
perror("server: socket");
return 1;
}
if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof yes) == -1) {
perror("server: setsockopt");
close(sockfd);
return 1;
}
if (bind(sockfd, ai->ai_addr, ai->ai_addrlen) == -1) {
perror("server: bind");
close(sockfd);
return 1;
}
freeaddrinfo(ai);
if (listen(sockfd, 2) == -1) {
perror("server: listen");
close(sockfd);
return 1;
}
printf("server: listening ...\n");
printf("server: sleep() to allow multiple clients to connect ...\n");
sleep(10);
printf("server: accepting ...\n");
while (1) {
int connfd;
struct sockaddr_storage client_addr;
socklen_t client_addrlen = sizeof client_addr;
char buffer[1024];
int bytes;
connfd = accept(sockfd, (struct sockaddr *) &client_addr, &client_addrlen);
if (connfd == -1) {
perror("server: accept");
continue;
}
if ((bytes = recv(connfd, buffer, sizeof buffer, 0)) == -1) {
perror("server: recv");
continue;
}
printf("server: recv: %.*s\n", (int) bytes, buffer);
close(connfd);
}
return 0;
}这是我的客户程序。
/* client.c */
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netdb.h>
int main(int argc, char **argv)
{
int sockfd;
int ret;
struct addrinfo hints, *ai;
if (argc != 2) {
fprintf(stderr, "usage: %s MSG\n", argv[0]);
return 1;
}
memset(&hints, 0, sizeof hints);
hints.ai_family = AF_INET;
hints.ai_socktype = SOCK_STREAM;
if ((ret = getaddrinfo(NULL, "8000", &hints, &ai)) == -1) {
fprintf(stderr, "client: getaddrinfo: %s\n", gai_strerror(ret));
return 1;
}
sockfd = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol);
if (sockfd == -1) {
perror("client: socket");
return 1;
}
if (connect(sockfd, ai->ai_addr, ai->ai_addrlen) == -1) {
perror("client: connect");
close(sockfd);
return -1;
}
printf("client: connected\n");
if (send(sockfd, argv[1], strlen(argv[1]), 0) == -1) {
perror("client: send");
close(sockfd);
return -1;
}
printf("client: send: %s\n", argv[1]);
freeaddrinfo(ai);
close(sockfd);
return 0;
}我用下面的脚本编译和运行这些程序。
# run.sh
gcc -std=c99 -Wall -Wextra -Wpedantic -D_DEFAULT_SOURCE server.c -o server
gcc -std=c99 -Wall -Wextra -Wpedantic -D_DEFAULT_SOURCE client.c -o client
./server &
sleep 1
./client hello1 &
sleep 1
./client hello2 &
sleep 1
./client hello3 &
sleep 1
./client hello4 &
sleep 1
./client hello5 &
sleep 5
pkill server当我运行上面的脚本时,我得到了这个输出。
$ sh run.sh
server: listening ...
server: sleep() to allow multiple clients to connect ...
client: connected
client: send: hello1
client: connected
client: send: hello2
client: connected
client: send: hello3
client: connected
client: send: hello4
client: connected
client: send: hello5
server: accepting ...
server: recv: hello1
server: recv: hello2
server: recv: hello3输出显示,当服务器处于listen()和accept()之间时,所有五个客户端都可以成功地将connect()和send()连接到服务器。但是,服务器只能使用accept()和recv()三个客户端。
我不明白以下几点。
backlog参数作为2调用2。那么,为什么五个客户都在connect()-ing上成功了呢?我原以为只有两个connect()才能成功。accept()和recv()?发布于 2017-01-05 15:45:41
服务器程序调用listen(),参数为2。为什么所有五个客户端都在connect()-ing中成功?
backlog参数只是listen()的一个提示。来自POSIX doc
backlog参数为实现提供了提示,实现将用来限制套接字侦听队列中未完成连接的数量。实现可以对待办事项进行限制,并悄悄地减少指定的值。通常,较大的待定参数值将导致侦听队列的更大或相同长度。中定义的SOMAXCONN之前,实现应支持待办事项的值。
发布于 2017-01-05 20:09:54
当客户端连接到侦听端口时,取决于套接字堆栈的实现,它可以:
accept()从积压中删除该客户端时,才能完成3路TCP握手。这是您所期望的行为,也是旧系统的行为方式。accept()将其删除。这是你的榜样所表现出来的行为,在现代系统中并不少见。根据Linux listen()
在Linux2.2中,TCP套接字上的待定参数的行为发生了变化。现在指定等待被接受的完全建立的套接字的队列长度,而不是不完整的连接请求的数量。可以使用
/proc/sys/net/ipv4/tcp_max_syn_backlog设置不完全套接字的最大队列长度。启用syncookie时,不存在逻辑最大长度,因此将忽略此设置。有关详细信息,请参阅tcp(7)。
因此,在您的示例中,所有5个连接都可能是在开始调用accept()之前在后台完成的,从而允许客户端调用send() (并且在它们能够检测到一些连接正在被删除之前),但并不是所有的连接都能够保持在积压中,因为它的大小很小。
发布于 2017-01-17 02:07:59
实际上,问题似乎在于,测试并没有隔离它假定正在测试的backlog。
问题中的测试代码似乎使用了“阻塞”套接字,并通过妖魔化客户端测试来调用并发,这可能解释了另一个客户机是如何“进入”的。
为了正确地测试这个问题,重要的是要有一个并发模型,在这个模型中我们知道在任何时间点对系统施加了多大的压力。
同样重要的是,我们只清除backlog 一次,而不等待内核重新填充我们分配给内核层积压的待办事项。
附加的是一个并发(线程) client+server,它同时侦听、连接(到自身)并输出消息。
这个设计清楚了服务器同时经历了多大的压力(5个连接)。
为了使它更清楚一点,我选择避免对服务器线程“阻塞”套接字。这样,我们就可以在积压文件中的所有内容中accept,并在积压文件为空时获得通知(一个错误值)。
在我的平台(macOS)上,结果表明只有两个客户端能够连接到服务器,这符合listen(socked, 2)积压规范。
所有其他客户端都会失败,因为当内核无法将连接推入(完全)待办事项时,它会丢弃连接.虽然我们不知道连接是在尝试read之前被删除的.另外,我的一些错误检查并不完美):
server: listening ...
server: sleep() to allow multiple clients to connect ...
client: connected
client: connected
client: connected
client: connected
client: connected
client: read error: Connection reset by peer
client: read error: Connection reset by peer
client: read error: Connection reset by peer
server: accepting ...
client 3: Hello World!
client 5: Hello World!连接的客户端(本例中为3&5)依赖于线程调度程序,因此每次执行测试时,都会有不同的客户端设法连接。
诚然,connect成功返回,但connect似乎是由接受内核实现的,正如@RemyLebeau的答案所指出的。在某些系统(即Linux和macOS)上,内核将在尝试将连接附加到侦听套接字的待办事项之前完成TCP/IP握手(如果积压文件已满,则将其删除)。
这很容易在我的系统输出中看到,其中的“服务器:接受.”消息在“连接”确认和“由对等方重置连接”事件之后到达。
测试的代码是:
#include <limits.h>
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <fcntl.h>
#include <netdb.h>
#include <sys/socket.h>
void *server_threard(void *arg);
void *client_thread(void *arg);
int main(void) {
/* code */
pthread_t threads[6];
if (pthread_create(threads, NULL, server_threard, NULL))
perror("couldn't initiate server thread"), exit(-1);
sleep(1);
for (size_t i = 1; i < 6; i++) {
if (pthread_create(threads + i, NULL, client_thread, (void *)i))
perror("couldn't initiate client thread"), exit(-1);
}
for (size_t i = 0; i < 6; i++) {
pthread_join(threads[i], NULL);
}
return 0;
}
/* will start listenning, sleep for 5 seconds, then accept all the backlog and
* finish */
void *server_threard(void *arg) {
(void)(arg);
int sockfd;
int ret;
struct addrinfo hints, *ai;
memset(&hints, 0, sizeof hints);
hints.ai_family = AF_INET;
hints.ai_socktype = SOCK_STREAM;
hints.ai_flags = AI_PASSIVE;
if ((ret = getaddrinfo(NULL, "8000", &hints, &ai)) == -1) {
fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(ret));
exit(1);
}
sockfd = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol);
if (sockfd == -1) {
perror("server: socket");
exit(1);
}
ret = 1;
if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &ret, sizeof ret) == -1) {
perror("server: setsockopt");
close(sockfd);
exit(1);
}
if (bind(sockfd, ai->ai_addr, ai->ai_addrlen) == -1) {
perror("server: bind");
close(sockfd);
exit(1);
}
freeaddrinfo(ai);
/* Set the server to non_blocking state */
{
int flags;
if (-1 == (flags = fcntl(sockfd, F_GETFL, 0)))
flags = 0;
// printf("flags initial value was %d\n", flags);
if (fcntl(sockfd, F_SETFL, flags | O_NONBLOCK) < 0) {
perror("server: to non-block");
close(sockfd);
exit(1);
}
}
if (listen(sockfd, 2) == -1) {
perror("server: listen");
close(sockfd);
exit(1);
}
printf("server: listening ...\n");
printf("server: sleep() to allow multiple clients to connect ...\n");
sleep(5);
printf("server: accepting ...\n");
int connfd;
struct sockaddr_storage client_addr;
socklen_t client_addrlen = sizeof client_addr;
/* accept up all connections. we're non-blocking, -1 == no more connections */
while ((connfd = accept(sockfd, (struct sockaddr *)&client_addr,
&client_addrlen)) >= 0) {
if (write(connfd, "Hello World!", 12) < 12)
perror("server write failed");
close(connfd);
}
close(sockfd);
return NULL;
}
void *client_thread(void *arg) {
(void)(arg);
int sockfd;
int ret;
struct addrinfo hints, *ai;
memset(&hints, 0, sizeof hints);
hints.ai_family = AF_INET;
hints.ai_socktype = SOCK_STREAM;
if ((ret = getaddrinfo(NULL, "8000", &hints, &ai)) == -1) {
fprintf(stderr, "client: getaddrinfo: %s\n", gai_strerror(ret));
exit(1);
}
sockfd = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol);
if (sockfd == -1) {
perror("client: socket");
exit(1);
}
if (connect(sockfd, ai->ai_addr, ai->ai_addrlen) < 0) {
perror("client: connect error");
close(sockfd);
fprintf(stderr, "client number %lu FAILED\n", (size_t)arg);
return NULL;
}
printf("client: connected\n");
char buffer[128];
if (read(sockfd, buffer, 12) < 12) {
perror("client: read error");
close(sockfd);
} else {
buffer[12] = 0;
fprintf(stderr, "client %lu: %s\n", (size_t)arg, buffer);
}
return NULL;
}https://stackoverflow.com/questions/41488549
复制相似问题