【问题标题】:C socket programming: get IP address (IPv6 or IPv4) server bounds to, and client connected to?C套接字编程:获取IP地址(IPv6或IPv4)服务器绑定到,客户端连接到?
【发布时间】:2017-10-21 06:20:14
【问题描述】:

我编写了如下代码。此代码应该能够检索 PASSIVE_SOCKET(服务器绑定其套接字的位置)和 CONNECTION_SOCKET(客户端已连接到的位置)的 IP 地址。

result_t print_socket_address(int sockfd, socket_type_t socket_type) {

    char *ip_address; // address (passive) socket was binded to
    int port; // port (passive) socket was binded to

    switch(socket_type)
    {
        case PASSIVE_SOCKET:
            if(get_current_address_and_port(sockfd, &ip_address, &port) == FAILURE) {
                fprintf(stderr, "get_current_address_and_port: faild!\n");
                free(ip_address);
                return FAILURE;
            }
            printf("Created passive socket %d binded to %s:%d\n", sockfd, ip_address, port);
            break;
        case CONNECTION_SOCKET:
            if(get_peer_address_and_port(sockfd, &ip_address, &port) == FAILURE) {
                fprintf(stderr, "get_peer_address_and_port: faild!\n");
                free(ip_address);
                return FAILURE;
            }
            printf("Socket %d connected to %s:%d\n", sockfd, ip_address, port);
            break;
        default:
            fprintf(stderr, "Incorrect socket type!\n");
            free(ip_address);
            return FAILURE;
    }

    free(ip_address);
    return SUCCESS;
}

/**
 * function retrieves current ip address and port
 * socket is bound to for given socket file descriptor
 */
result_t get_current_address_and_port(int sockfd, char **ip_address, int *port) {

    struct sockaddr sockaddr;
    socklen_t sockaddrlen = sizeof(sockaddr);

    if(getsockname(sockfd, &sockaddr, &sockaddrlen) < 0) {
        fprintf(stderr, "getsockname: %s\n", strerror(errno));
        return FAILURE;
    }

    sockaddr.sa_family = AF_INET6;

    return get_address_and_port_from_sockaddr(&sockaddr, ip_address, port);
}

/**
 * function retrieves peer ip address and port
 * socket is connected to for given socket file descriptor
 */
result_t get_peer_address_and_port(int sockfd, char **ip_address, int *port) {

    struct sockaddr sockaddr;
    socklen_t sockaddrlen = sizeof(sockaddr);

    if(getpeername(sockfd, &sockaddr, &sockaddrlen) < 0) {
        fprintf(stderr, "getpeername: %s\n", strerror(errno));
        return FAILURE;
    }

    return get_address_and_port_from_sockaddr(&sockaddr, ip_address, port);
}

/**
 * function unwrap ip address and port from addrinfo structure
 */
result_t get_address_and_port_from_addrinfo(const struct addrinfo *addrinfo, char **ip_address, int *port) {

    return get_address_and_port_from_sockaddr((struct sockaddr *)addrinfo->ai_addr, ip_address, port);
}

/**
 * function unwrap ip address and port from sockaddr structure
 */
result_t get_address_and_port_from_sockaddr(const struct sockaddr *sockaddr, char **ip_address, int *port) {

    *ip_address = (char *) malloc(INET6_ADDRSTRLEN * sizeof(char));

    // converting network address to presentation address
    if(inet_ntop(sockaddr->sa_family, get_in_addr(sockaddr), *ip_address, INET6_ADDRSTRLEN * sizeof(char)) == NULL) {
        fprintf(stderr, "inet_ntop: %s\n", strerror(errno));
        return FAILURE;
    }

    // converting network port to host port
    *port = ntohs(get_in_port(sockaddr));

    return SUCCESS;
}

/**
 * function unwrap in_addr or in6_addr structure from
 * sockaddr structure depending on address family
 * AF_INET or AF_INET6
 */
void *get_in_addr(const struct sockaddr *sa) {

    if( sa->sa_family == AF_INET) // IPv4 address
        return &(((struct sockaddr_in*)sa)->sin_addr);
    // else IPv6 address
    return &(((struct sockaddr_in6*)sa)->sin6_addr);
}

/**
 * function unwrap in_port from sockaddr structure
 * depending on address family AF_INET or AF_INET6
 */
in_port_t get_in_port(const struct sockaddr *sa)
{
    if( sa->sa_family == AF_INET ) // IPv4 address
        return (((struct sockaddr_in*)sa)->sin_port);
    // else IPv6 address
    return (((struct sockaddr_in6*)sa)->sin6_port);
}

但是当我使用这段代码时,我得到了一些奇怪的行为。返回我的服务器绑定套接字的 IP 地址,例如:

创建绑定到 ::ba54:431c:f9:55401 的被动套接字 4

55401 是端口号,它是正确的。但是这个 ::ba54:431c:f9 是什么?我想这可能是一些 IPv6。但为什么?我的电脑在局域网的IP地址是192.168.8.102! 此外,当我尝试通过客户端程序连接此服务器时,我必须使用 192.168.8.102 IP 地址进行连接,否则使用此 ::ba54:431c:f9 会出现“找不到路由”之类的错误?当客户端使用 192.168.8.102 IP 地址与服务器连接时,它会打印它连接到的计算机的 IP 地址和端口号,我得到另一个像这样的 ODD IP 地址:

Socket 3 连接到 ::3300:5208:6d16:9c88:55401

所以这里只有端口号匹配,IP地址不正确! 在与服务器连接之前在本地绑定时,客户端套接字绑定的 IP 地址与它所连接的计算机的 IP 地址相同(它在我的 LAN 网络中是物理上不同的计算机),即 ::3300:5208:6d16:9c88:52040 ,其中 52040 是客户端绑定其套接字的端口。

我什至尝试将服务器计算机 192.168.8.1 的 IP 地址转换为 IPv6,但我得到的是这样的:0:0:0:0:0:ffff:c0a8:866 和这个地址在客户端程序中使用时连接到服务器工作正常!但是使用上述功能的客户端会打印出这个完全不同的 IP 地址,例如:::::3300:5208:6d16:9c88:52040

那么我应该如何将这个函数写成:

  1. 启用服务器打印它所绑定的 IP 地址和端口号(通常 IPv6 或 IPv4 可以连接到它选择的任何地址/任何端口)

  2. 使客户端能够打印其绑定和正在连接的 IP 地址和端口号(通常可以根据需要使用 IPv4 或 IPv6)。

我想在服务器程序中显示 IP 地址和端口号,然后我可以在客户端程序中使用它们来连接两者。现在我需要猜测它应该是我在网络首选项中找到的计算机地址,并假设端口号是正确的,然后尝试连接。

【问题讨论】:

  • 你想用这段代码实现什么:sockaddr.sa_family = AF_INET6;?看起来像您将获得的地址系列更改为 IPv6,这可能解释了您返回的意外 IPv6 地址。

标签: c sockets networking


【解决方案1】:

get_current_address_and_port 中删除这一行。

    sockaddr.sa_family = AF_INET6;

我不知道那行代码是如何写入您的代码的,但它是不正确的。

通过删除该行,这将解决您在 IPv4 方面的大部分问题。

您的代码硬连线使用struct sockaddr。 IIRC,sockaddr 无论如何都不足以容纳 IPv6 地址。我是否可以建议将其转换为 sockaddr_storage 以用于对 getsocknamegetpeername 的初始调用,以便您的代码可以更好地用于 IPv4 和 IPv6。

修复示例:

result_t get_current_address_and_port(int sockfd, char **ip_address, int *port) {

    struct sockaddr_storage address = {0};
    socklen_t sockaddrlen = sizeof(address);

    if(getsockname(sockfd, (struct sockaddr*)(&address), &sockaddrlen) < 0) {
        fprintf(stderr, "getsockname: %s\n", strerror(errno));
        return FAILURE;
    }

    return get_address_and_port_from_sockaddr((struct sockaddr*)(&address), ip_address, port);
}


result_t get_peer_address_and_port(int sockfd, char **ip_address, int *port) {
    struct sockaddr_storage address = {0};
    socklen_t sockaddrlen = sizeof(address);

    if(getpeername(sockfd, (struct sockaddr*)(&address), &sockaddrlen) < 0) {
        fprintf(stderr, "getpeername: %s\n", strerror(errno));
        return FAILURE;
    }

    return get_address_and_port_from_sockaddr((struct sockaddr*)(&address), ip_address, port);
}

【讨论】:

  • 这行`sockaddr.sa_family = AF_INET6;`只是为了测试而添加的,我记得在stackoverflow上发布这个问题之前删除它。
  • 现在我收到这样的消息:“创建的被动套接字 4 绑定到 :::55494”
  • 这对 IPv6 是正确的。您的本地套接字绑定到 INADDR_ANY (0),这意味着“侦听所有适配器”。作为字符串,这将转换为:: 作为 IPv6 地址。您没有显示您的套接字是如何创建或绑定的,但大概是您创建了一个 IPv6 套接字或在代码路径中保留了硬编码的 AF_INET6 标志。
  • 如果它是 IPv4 套接字,它会被打印为“0.0.0.0”。这在技术上也是正确的。
  • 现在在客户端打印正确的 IP 地址。 “套接字 3 绑定到 192.168.8.100:52622”和“套接字连接到 192.168.102:55536”。在服务器端,当与客户端连接时,也会出现类似“Socket 5 connected to ::ffff:192.168.8.100:52622”的信息。所以现在看来​​还可以。正如您所说,“创建的绑定到 :::55536 的被动套接字 4”似乎是正确的,这类似于 INADDR_ANY。但是我如何打印地址来通知客户端它如何与服务器连接(即服务器计算机的真实 IP 地址)。
猜你喜欢
  • 1970-01-01
  • 2013-05-25
  • 1970-01-01
  • 2015-12-25
  • 1970-01-01
  • 2021-09-25
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多