C 中的套接字服务器中的缓慢 accept()

Slow accept() in socket server in C

我在 C 中为我的教育套接字服务器编写了以下代码。

#include <arpa/inet.h>
#include <netinet/in.h>
#include <stdio.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <time.h>
#include <unistd.h>

double get_wall_time()
{
    struct timeval time;
    if (gettimeofday(&time, NULL)){
        return 0;
    }
    return (double)time.tv_sec + (double)time.tv_usec * 0.000001;
}

double get_cpu_time()
{
    return (double)clock() / CLOCKS_PER_SEC;
}

int main()
{
    double wall = get_wall_time();
    double cpu = get_cpu_time();

    int sfd = socket(AF_INET, SOCK_STREAM, 0);

    struct sockaddr_in own_addr = {0};
    own_addr.sin_family = AF_INET;
    own_addr.sin_port = htons(5678);

    bind(sfd, (struct sockaddr *)&own_addr, sizeof(own_addr));
    listen(sfd, 5);

    static char message[] = "hello from server\n";

    double wall_accept = 0;
    double cpu_accept = 0;

    int count = 0;
    while (1) {
        if (count++ == 1000) {
            break;
        }

        double wall_start = get_wall_time();
        double cpu_start = get_cpu_time();
        int client_sfd = accept(sfd, NULL, NULL);
        wall_accept += get_wall_time() - wall_start;
        cpu_accept += get_cpu_time() - cpu_start;

        send(client_sfd, message, sizeof(message), 0);
        close(client_sfd);
    }

    wall = get_wall_time() - wall;
    cpu = get_cpu_time() - cpu;

    printf("wall accept: %lf\n", wall_accept);
    printf("cpu accept: %lf\n", cpu_accept);

    printf("wall: %lf\n", wall);
    printf("cpu: %lf\n", cpu);
}

为了测试,我使用 seq 1000 | time parallel -j 1 -n0 'nc 127.0.0.1 5678' | wc -l 结果

wall accept: 6.436480
cpu accept: 0.010000
wall: 6.456266
cpu: 0.020000

对于 10000 个请求,结果是

wall accept: 55.434541
cpu accept: 0.080000
wall: 55.633679
cpu: 0.260000

accept()慢还是我做错了什么?或者这可能是单线程实现的正常结果?

UPD. 我还写了一个服务器,用 pthreads 在不同的线程中发送消息。

#include <arpa/inet.h>
#include <netinet/in.h>
#include <stdio.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <time.h>
#include <unistd.h>
#include <pthread.h>
#include <stdlib.h>
#include <sys/time.h>

double get_wall_time()
{
    struct timeval time;
    if (gettimeofday(&time, NULL)){
        return 0;
    }
    return (double)time.tv_sec + (double)time.tv_usec * 0.000001;
}

double get_cpu_time()
{
    return (double)clock() / CLOCKS_PER_SEC;
}

void *send_message(void *pclient_sfd)
{
    int client_sfd = *(int *)pclient_sfd;
    free(pclient_sfd);

    static char message[] = "hello from server\n";
    send(client_sfd, message, sizeof(message), 0);
    close(client_sfd);

    return NULL;
}

int main()
{
    double wall = get_wall_time();
    double cpu = get_cpu_time();

    int sfd = socket(AF_INET, SOCK_STREAM, 0);

    struct sockaddr_in own_addr = {0};
    own_addr.sin_family = AF_INET;
    own_addr.sin_port = htons(5678);

    bind(sfd, (struct sockaddr *)&own_addr, sizeof(own_addr));
    listen(sfd, 5);

    double wall_accept = 0;
    double cpu_accept = 0;

    int count = 0;
    while (1) {
        if (count++ == 10000) {
            break;
        }

        int *pclient_sfd = malloc(sizeof(*pclient_sfd));

        double wall_start = get_wall_time();
        double cpu_start = get_cpu_time();

        *pclient_sfd = accept(sfd, NULL, NULL);

        wall_accept += get_wall_time() - wall_start;
        cpu_accept += get_cpu_time() - cpu_start;

        pthread_t tid;
        pthread_create(&tid, NULL, send_message, (void *)pclient_sfd);
    }

    wall = get_wall_time() - wall;
    cpu = get_cpu_time() - cpu;

    printf("wall accept: %lf\n", wall_accept);
    printf("cpu accept: %lf\n", cpu_accept);

    printf("wall: %lf\n", wall);
    printf("cpu: %lf\n", cpu);

    return 0;
}

然后我用seq 10000 | time parallel -j 4 -n0 'nc 127.0.0.1 5678' | wc -l,用了58秒。

看看你的 listen() 第二个参数。尝试增加它。 这是来自 man 2 listen

的文字

The backlog argument defines the maximum length to which the queue of pending connections for sockfd may grow. If a connection request arrives when the queue is full, the client may receive an error with an indication of ECONNREFUSED or, if the underlying protocol supports retransmission, the request may be ignored so that a later reattempt at connection succeeds.

这是您测试它的方式。当你使用这个

seq 10000 | time parallel -j 4 -n0 'nc 127.0.0.1 5678' | wc -l

这实际上会影响测试,因为您正在生成大量进程等,也就是说,您实际上并没有在测试 C 应用程序,而是在测试生成进程的能力。

如果我们将其更改为简单的 python 脚本,即

#!/usr/bin/env python
import socket
TCP_IP = '127.0.0.1'
TCP_PORT = 5678
BUFFER_SIZE = 1024
msg = "1" 

for i in range(0, 1000):
  s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
  s.connect((TCP_IP, TCP_PORT))
  s.send(msg.encode('utf-8'))
  data = s.recv(BUFFER_SIZE)
  s.close()
  print("received data: %s" % data)

和运行测试结果相差甚远

real    0m0.269s
user    0m0.074s
sys     0m0.114s

如果你真的想测试它,看看它有多快,你需要使用单独的机器等,通常你可能需要使用 C,或者你的限制因素可能是客户端。我见过的最好的工具(特定于 HTTP)是 wrk

,您可能会在其中找到一些好的代码

https://github.com/wg/wrk