【问题标题】:accept() call doesn't initialize sockaddr structaccept() 调用不初始化 sockaddr 结构
【发布时间】:2016-06-04 15:10:29
【问题描述】:

我用 C 语言编写了一个简短的 TCP 服务器示例,它不断侦听端口 12701 上的连接,并将对等方的 sockaddr.sa_family 的值输出到标准输出。程序在无限循环中调用accept(),它应该在struct sockaddr中填写连接的详细信息,包括sa_family。

但是,对于第一个 TCP 连接,这不会正确发生; sockaddr.sa_family 始终为零。所有后续连接都提供正确的值 2 - 只有第一个是错误的。为什么会这样?我还没有找到任何类似问题的报告,但我怀疑我初始化不正确或误解了 accept() 的参数。

这是程序:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/socket.h>
#include <arpa/inet.h>

int main(void)
{
    struct sockaddr_in saddr =
    {
        .sin_family      = AF_INET,
        .sin_addr.s_addr = htonl(INADDR_ANY),
        .sin_port        = htons(12701),
    };

    // open socket to accept() tcp connections
    int accept_fd = socket(AF_INET, SOCK_STREAM, 0);
    bind(accept_fd, (struct sockaddr *) &saddr, sizeof(struct sockaddr_in));
    listen(accept_fd, SOMAXCONN);

    socklen_t addrlen;
    struct sockaddr * addr;
    for(;;)
    {
        addr = (struct sockaddr *) malloc(sizeof(struct sockaddr));
        int child_fd = accept(accept_fd, addr, &addrlen);

        // why is this always zero the first time?
        printf("addr->sa_family = %d\n", addr->sa_family);

        close(child_fd);
        free(addr);
    }

    return 0;
}

【问题讨论】:

  • 在调用accept之前必须初始化addrlen。另外,为什么要动态分配?这可能只会导致内存泄漏(就像你现在一样)。
  • 当调用任何内存分配函数(malloc、calloc、realloc)时,1)不要转换返回值。返回值的类型为void*,因此可以分配给任何指针。强制转换只会使代码混乱,使理解、调试和维护变得更加困难。 2) 始终检查 (!=NULL) 返回值以确保操作成功。注意:始终将内存分配返回的指针传递给函数free(),以避免内存泄漏(在当前情况下,最终会导致系统崩溃)建议在调用close后立即调用free()
  • 您对内存泄漏的看法是正确的;这段代码是从一个更大的程序复制而来的,稍后将释放内存。为了将来清楚起见,我已在示例中修复了该问题。

标签: c sockets unix


【解决方案1】:

这假定AF_INET 用于设置连接。

您想将适合您绑定侦听套接字的结构的地址传递给accept(),即struct sockaddr_in

还需要将addrlen 设置为传递给accept() 的地址的大小。

  for (;;)
  {
    struct sockaddr_in * addr = malloc(sizeof *addr);
    socklen_t addrlen = sizeof *addr;

    if (NULL == addr)
    {
       perror("malloc() failed");
       /* exit(), break, continue, whatever ... */
    }

    int child_fd = accept(accept_fd, (struct sockaddr *) addr, &addrlen);
    if (-1 == child_fd)
    {
       perror("accept() failed");
       free(addr);
       addr = NULL;
       /* exit(), break, continue, whatever ... */
    }

    /* Do stuff. */

    close(child_fd);
    free(addr);
  }

【讨论】:

  • 一个小问题:变量addrlen需要在每次调用accept()之前重新初始化,因为调用会修改值
  • @user3629249:确实!谢谢。
  • 我认为你有一个错误 - (-1 = child_fd) 应该有 ==
  • 我赞成这个答案,因为它是唯一一个为每次调用accept() 分配sockaddr_in 而不是sockaddr 的答案。由于监听套接字是AF_INET,所以必须使用sockaddr_in
【解决方案2】:

没有正确处理对accept() 的调用。

在发布的代码中:

  1. 必须检查对accept() 的调用是否失败。
  2. 必须检查对 malloc() 的调用是否失败
  3. 分配的内存必须是空闲的
  4. 必须在每次调用 accept() 之前正确初始化 accept()addrlen 参数。

发布的代码一次只能处理一个连接。允许多个同时连接是一个好主意 将每个连接路由到thread。但是,生成/销毁线程需要很长时间,因此建议在将线程标记为“正在使用”之后,将线程“池”最初设置为“未使用”并将每个连接传递给“当前未使用”线程线程返回,再次将其标记为“未使用”。

在下面的代码中,没有考虑允许多个同时连接。

for(;;)
{
    addr = malloc(sizeof(struct sockaddr));
    if( !addr )
    {
        perror( "malloc failed" );
        exit( EXIT_FAILURE );  // exit() and EXIT_FAILURE from stdlib.h
    }

    // implied else, malloc successful

    addrlen = sizeof( struct sockaddr );
    int child_fd = accept(accept_fd, addr, &addrlen);

    if( -1 == child_fd )
    { // then an error occurred
        perror( "accept failed" );
    }

    else
    { // else, accept successful
        printf("addr->sa_family = %d\n", addr->sa_family);
        close(child_fd);
    }

    free( addr ); // to avoid memory leak
    addr = NULL;  // for safety
}

【讨论】:

  • 请注意,实际上不需要调用malloc()free(),因为实际上只需要addr 变量的一个实例。
【解决方案3】:

例如看看这个文档:http://man7.org/linux/man-pages/man2/accept.2.html

更具体地说是 addrlen 参数的描述。 这是一个 inout 参数,您必须正确初始化它。

addrlen 参数是一个值结果参数:调用者必须 初始化它以包含指向的结构的大小(以字节为单位) 通过地址;返回时它将包含对等方的实际大小 地址。'

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-08-30
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多