首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >为什么具有listen(sockfd,2)调用的服务器能够接受3个连接?

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

Stack Overflow用户
提问于 2017-01-05 15:20:25
回答 6查看 1.9K关注 0票数 9

我试图了解backlog参数在int listen(int sockfd, int backlog);中是如何影响如何处理新连接的。

这是我的服务器程序。

代码语言:javascript
复制
/* 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;
}

这是我的客户程序。

代码语言:javascript
复制
/* 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;
}

我用下面的脚本编译和运行这些程序。

代码语言:javascript
复制
# 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

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

代码语言:javascript
复制
$ 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()三个客户端。

我不明白以下几点。

  1. 服务器程序以backlog参数作为2调用2。那么,为什么五个客户都在connect()-ing上成功了呢?我原以为只有两个connect()才能成功。
  2. 为什么服务器能够从3个客户端而不是2个客户端accept()recv()
EN

回答 6

Stack Overflow用户

发布于 2017-01-05 15:45:41

服务器程序调用listen(),参数为2。为什么所有五个客户端都在connect()-ing中成功?

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

backlog参数为实现提供了提示,实现将用来限制套接字侦听队列中未完成连接的数量。实现可以对待办事项进行限制,并悄悄地减少指定的值。通常,较大的待定参数值将导致侦听队列的更大或相同长度。中定义的SOMAXCONN之前,实现应支持待办事项的值。

票数 5
EN

Stack Overflow用户

发布于 2017-01-05 20:09:54

当客户端连接到侦听端口时,取决于套接字堆栈的实现,它可以:

  1. 在待办事项处理中保持挂起的连接,只有在调用accept()从积压中删除该客户端时,才能完成3路TCP握手。这是您所期望的行为,也是旧系统的行为方式。
  2. 在后台立即完成握手,然后将完全连接的连接存储在待办事项处理中,直到accept()将其删除。这是你的榜样所表现出来的行为,在现代系统中并不少见。

根据Linux listen()

在Linux2.2中,TCP套接字上的待定参数的行为发生了变化。现在指定等待被接受的完全建立的套接字的队列长度,而不是不完整的连接请求的数量。可以使用/proc/sys/net/ipv4/tcp_max_syn_backlog设置不完全套接字的最大队列长度。启用syncookie时,不存在逻辑最大长度,因此将忽略此设置。有关详细信息,请参阅tcp(7)

因此,在您的示例中,所有5个连接都可能是在开始调用accept()之前在后台完成的,从而允许客户端调用send() (并且在它们能够检测到一些连接正在被删除之前),但并不是所有的连接都能够保持在积压中,因为它的大小很小。

票数 5
EN

Stack Overflow用户

发布于 2017-01-17 02:07:59

实际上,问题似乎在于,测试并没有隔离它假定正在测试的backlog

问题中的测试代码似乎使用了“阻塞”套接字,并通过妖魔化客户端测试来调用并发,这可能解释了另一个客户机是如何“进入”的。

为了正确地测试这个问题,重要的是要有一个并发模型,在这个模型中我们知道在任何时间点对系统施加了多大的压力。

同样重要的是,我们只清除backlog 一次,而不等待内核重新填充我们分配给内核层积压的待办事项。

附加的是一个并发(线程) client+server,它同时侦听、连接(到自身)并输出消息。

这个设计清楚了服务器同时经历了多大的压力(5个连接)。

为了使它更清楚一点,我选择避免对服务器线程“阻塞”套接字。这样,我们就可以在积压文件中的所有内容中accept,并在积压文件为空时获得通知(一个错误值)。

在我的平台(macOS)上,结果表明只有两个客户端能够连接到服务器,这符合listen(socked, 2)积压规范。

所有其他客户端都会失败,因为当内核无法将连接推入(完全)待办事项时,它会丢弃连接.虽然我们不知道连接是在尝试read之前被删除的.另外,我的一些错误检查并不完美):

代码语言:javascript
复制
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握手(如果积压文件已满,则将其删除)。

这很容易在我的系统输出中看到,其中的“服务器:接受.”消息在“连接”确认和“由对等方重置连接”事件之后到达。

测试的代码是:

代码语言:javascript
复制
#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;
}
票数 3
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/41488549

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档