代码之家  ›  专栏  ›  技术社区  ›  Nexuss

在循环中调用“accept”系统调用会创建损坏的套接字

  •  1
  • Nexuss  · 技术社区  · 2 年前

    我正在尝试用C为自己构建一个网络应用程序。到目前为止,我已经让HTTP服务器正常工作,但由于某种原因,当我有需要其他CSS和JavaScript文件的HTML文件时,浏览器(Firefox)会非常快地处理多个请求,导致服务器和客户端之间的套接字连接损坏。我已经尝试了很多不同的方法来让HTTP服务器在不生成损坏的TCP连接的情况下工作。

    目前,我有一个循环调用 accept ,并为每个新连接创建一个新线程:

    for(;;)
    {
        int newsockfd = accept(sockfd, (struct sockaddr *)&host_addr, (socklen_t *)&host_addrlen);
        if(newsockfd < 0)
        {
            sendf(stderr, LOG_ERROR, "Failed to accept connection...\n");
            continue;
        }
        pthread_t thread;
        if(pthread_create(&thread, NULL, handler, (void *)&newsockfd) != 0)
        {
            sendf(stderr, LOG_ERROR, "Failed to create a new thread...\n");
            return -4;
        }
    }
    

    我创建线程,同时将套接字文件描述符(表示客户端和服务器之间的新连接)发送到新线程。

    处理新创建的线程的函数如下所示:

    void * handler(void * argument)
    {
        int sockfd = *((int *)argument);
    
        struct sockaddr_in client_addr;
        int client_addrlen = sizeof(client_addr);
    
        int sockn = getpeername(sockfd, (struct sockaddr *)&client_addr, (socklen_t *)&client_addrlen);
        if(sockn < 0)
        {
            sendf(stderr, LOG_ERROR, "Failed to get client's address, got \x1b[35msockn\x1b[0m \x1b[33;3m%d\x1b[0m...\n", sockn);
            pthread_exit(NULL);
        }
    
        char * input = malloc(sizeof(char) * BUFFER_SIZE);
    
        int valread = read(sockfd, input, BUFFER_SIZE);
        if(valread < 0)
        {
            sendf(stderr, LOG_ERROR, "Failed to read from socket, got \x1b[35mvalread\x1b[0m \x1b[33;3m%d\x1b[0m...\n", valread);
            free(input);
            pthread_exit(NULL);
        }
    
        char * method = malloc(sizeof(char) * BUFFER_SIZE);
        char * url = malloc(sizeof(char) * BUFFER_SIZE);
        char * version = malloc(sizeof(char) * BUFFER_SIZE);
        sscanf(input, "%s %s %s", method, url, version);
        sendf(stdout, LOG_DEBUG, "Client \x1b[33;3m%s:%u\x1b[0m has sent a \x1b[33;3m%s\x1b[0m request to \x1b[33;3m%s\x1b[0m!\n", inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port), method, url);
    
        ...
    

    当连接损坏时,我得到:

    [ERROR]: Failed to get client's address, got sockn -1...
    

    使用一些调试工具,我检查并查看 method , url verison 设置为 NULL .功能 getpeername 也返回-1。 函数 sendf 只是类似printf的基本函数,但似乎不是问题的原因。

    有人发现这里有什么问题吗?

    2 回复  |  直到 2 年前
        1
  •  3
  •   Remy Lebeau    2 年前

    你在传球 handler 指向 局部变量 newsockfd 当线程开始运行时,它已不在作用域中。

    您需要:

    • 分配一个新的 int 动态地 处理程序 将自由,如:
    int *newsockfd = malloc(sizeof(int));
    if (!newsockfd) { ... }
    *newsockfd = ...;
    ...
    pthread_t thread;
    if (pthread_create(..., newsockfd) != 0) {
        free(newsockfd);
        ...
    }
    
    void * handler(void * argument)
    {
        int *sockfd = (int*)argument;
        ...
        free(sockfd);
    }
    
    • 只需通过 价值 属于 newsockfd 而不是 指针 对它,例如:
    int newsockfd = ...;
    ...
    pthread_t thread;
    pthread_create(..., (void *)(intptr_t)newsockfd)
    
    void * handler(void * argument)
    {
        int sockfd = (int)(intptr_t)argument;
        ...
    }
    
        2
  •  2
  •   Ted Lyngmo    2 年前

    newsockfd 是循环的本地指针,您将指向它的指针发送到线程,但指针在循环结束时将无效,循环结束后 pthread_create

    for(;;)
    {
        int newsockfd = accept(...);
        pthread_create(..., (void *)&newsockfd);
    } // <- here &newsockfd is a "dangling" pointer
    

    在线程中 int sockfd = *((int *)argument); 因此不能保证工作。

    您需要永久存储套接字描述符,以便指向它的指针保持有效,或者通过在线程确认已读取值之前不让它超出范围来同步对它的访问。

    另一个解决方法是 int void* 并通过这样做将文件描述符传递给 pthread_create 按价值:

    pthread_create(..., (void *)(uintptr_t)newsockfd);
    
    void *handler(void * argument) {
        int sockfd = (uintptr_t)argument;
        //
    }
    
    推荐文章