为什么具有listen(sockfd,2)的服务器能够接受3个连接?

Lon*_*ner 9 c sockets linux connection

我试图了解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;
}
Run Code Online (Sandbox Code Playgroud)

这是我的客户端程序.

/* 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 Code Online (Sandbox Code Playgroud)

我使用以下脚本编译和运行这些程序.

# 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
Run Code Online (Sandbox Code Playgroud)

当我运行上面的脚本时,我得到了这个输出.

$ 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
Run Code Online (Sandbox Code Playgroud)

输出显示,当服务器在listen()和之间休眠时accept(),所有五个客户端都可以成功connect()并且send()到服务器.但是,服务器只能accept()recv()三个客户端.

我不明白以下几点.

  1. 服务器程序listen()使用backlog参数as 调用2.为什么所有五个客户都成功了connect()?我期待只有2 connect()秒才能成功.
  2. 为什么服务器能够accept()recv()来自3个客户端,而不是2?

ks1*_*322 5

服务器程序使用backlog参数调用listen()为2.为什么所有五个客户端都成功连接() - 然后呢?

backlog参数只是一个提示listen().来自POSIX doc:

backlog参数提供了实现的提示,实现将使用该提示来限制套接字侦听队列中未完成连接的数量.实现可能会对积压施加限制并以静默方式减少指定值.通常,较大的积压参数值将导致监听队列的长度更大或相等.实现应支持积压到SOMAXCONN的值,定义于.


Rem*_*eau 5

当客户端连接到侦听端口时,根据套接字堆栈的实现,它可能是:

  1. 在待办事项中保留挂起的连接,并仅在accept()调用从待办事项中删除该客户端时完成3路TCP握手.这是您期望的行为,也是旧系统的行为方式.

  2. 在后台立即完成握手,然后将完全连接的连接存储在待办事项中,直到accept()将其删除.这是您的示例似乎展示的行为,并且在现代系统中并不罕见.

根据Linux手册listen():

使用Linux 2.2更改了TCP套接字上的backlog参数的行为. 现在它指定了等待接受的完全建立的套接字的队列长度,而不是未完成的连接请求的数量.可以使用设置不完整套接字的队列的最大长度/proc/sys/net/ipv4/tcp_max_syn_backlog.启用syncookies时,没有逻辑最大长度,并忽略此设置.有关更多信息,请参阅tcp(7).

因此,在您的情况下,所有5个连接可能在您开始调用之前在后台完成accept(),从而允许客户端调用send()(并且可以在他们检测到某些连接被丢弃之前这样做),但不是全部由于其尺寸小,连接能够保留在积压中.


Set*_*top 1

当您的代码在每个客户端之间休眠一秒钟时,客户端就有时间在下一个客户端到来之前完成并关闭其连接。

因此服务器端的队列(这是 backlog 参数控制的)始终为空。

在没有“睡眠”语句的情况下重试。