【问题标题】:How to get IP address from sockaddr如何从 sockaddr 获取 IP 地址
【发布时间】:2010-12-21 21:50:31
【问题描述】:

我想在调用accept后尝试获取客户端的IP地址。这是我到目前为止所拥有的,但我最终得到了一些显然不是 IP 地址的长数字。有什么问题?

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

sockaddr_in client;
client.sin_family = AF_INET;
socklen_t c_len = sizeof(client);

int acc_tcp_sock = accept(tcp_sock, (sockaddr*)&client, &c_len);
cout << "Connected to: " << client.sin_addr.s_addr << endl;

【问题讨论】:

    标签: c++ sockets ip-address


    【解决方案1】:

    来自http://beej.us/guide/bgnet/examples/client.c

    // get sockaddr, IPv4 or IPv6:
    void *get_in_addr(struct sockaddr *sa)
    {
        if (sa->sa_family == AF_INET)
            return &(((struct sockaddr_in*)sa)->sin_addr);
        return &(((struct sockaddr_in6*)sa)->sin6_addr);
    }
    
    // [...]
    
    struct addrinfo *p;
    char s[INET6_ADDRSTRLEN];
    inet_ntop(p->ai_family, get_in_addr((struct sockaddr *)p->ai_addr), s, sizeof s);
    

    它使用inet_ntop,优于inet_ntoa(非线程安全),因为它处理IPv4和IPv6(AF_INETAF_INET6),我认为应该是线程安全的。

    【讨论】:

      【解决方案2】:

      那个长数字整数形式的IP地址(毕竟IP地址只是一个整数;当我们将八位字节分开并将其放入点符号)。

      You can use inet_ntoa 将整数值转换为标准点表示法。

      【讨论】:

      • 请注意,现在inet_ntoa 已被视为已弃用。您应该改用支持更多格式的inet_ntop
      【解决方案3】:

      您得到的是 IP 地址的原始 32 位整数表示。 要获得熟悉的点分隔字符串,请使用以下函数:

       char * inet_ntoa(struct in_addr addr);
      

      这会将整数转换为静态字符串。

      【讨论】:

        【解决方案4】:

        以下摘自示例https://banu.com/blog/2/how-to-use-epoll-a-complete-example-in-c/epoll-example.c

                      struct sockaddr in_addr;
                      socklen_t in_len;
                      int infd;
                      char hbuf[NI_MAXHOST], sbuf[NI_MAXSERV];
        
                      in_len = sizeof in_addr;
                      infd = accept (sfd, &in_addr, &in_len);
                      if (infd == -1)
                        {
                          if ((errno == EAGAIN) ||
                              (errno == EWOULDBLOCK))
                            {
                              /* We have processed all incoming
                                 connections. */
                              break;
                            }
                          else
                            {
                              perror ("accept");
                              break;
                            }
                        }
        
                         s = getnameinfo (&in_addr, in_len,
                         hbuf, sizeof hbuf,
                         sbuf, sizeof sbuf,
                         NI_NUMERICHOST | NI_NUMERICSERV);
                         if (s == 0){
                             printf("Accepted connection on descriptor %d "
                                 "(host=%s, port=%s)\n", infd, hbuf, sbuf);
                         }
        

        【讨论】:

          【解决方案5】:

          虽然这些是很好的答案,但他们用-pedantic -std=c99 -Werror 破坏了我的编译。

          来自man 7 socket

          为了允许将任何类型的套接字地址传递给套接字 API 中的接口,定义了 struct sockaddr 类型。的目的 这种类型纯粹是为了允许转换特定于域的套接字地址 类型为“通用”类型,以避免编译器关于类型的警告 对套接字 API 的调用不匹配。

          从glibc-2.17 (RHEL7) 中获取此页面中的所有相关数据,我看到了

          /* Structure describing a generic socket address.  */
          struct sockaddr
            {
              __SOCKADDR_COMMON (sa_);    /* Common data: address family and length.  */
              char sa_data[14];       /* Address data.  */
            };
          

          其中 SOCKADDR_COMMON 是 uint16_t。所以总大小是16B。

          IP(互联网协议)域特定,来自 man 7 ip

          struct sockaddr_in {
              sa_family_t    sin_family; /* address family: AF_INET */
              in_port_t      sin_port;   /* port in network byte order */
              struct in_addr sin_addr;   /* internet address */
          };
          
          /* Internet address. */
          struct in_addr {
              uint32_t       s_addr;     /* address in network byte order */
          };
          

          第一次尝试

          inet_ntoa( ((struct sockaddr_in) peer_addr).sin_addr )
          

          问题

          error: conversion to non-scalar type requested
          

          第二次尝试

           inet_ntoa( ((struct sockaddr_in *) &peer_addr)->sin_addr ) ));
          

          问题

          error: dereferencing type-punned pointer might break strict-aliasing rules [-Werror=strict-aliasing]
          

          第三次尝试:inet_pton,无论如何更现代,线程安全,占用 void*

          char peer_addr_str[ INET_ADDRSTRLEN ];
          inet_ntop( AF_INET, &peer_addr, peer_addr_str, INET_ADDRSTRLEN );
          

          好的,工作。人类可读的十进制和点字符串位于peer_addr_str

          【讨论】:

            【解决方案6】:

            您可以使用适用于 Linux 和 Windows 的 getnameinfo 函数。来自 Linux 手册页https://linux.die.net/man/3/getnameinfo

            getnameinfo - 独立于协议的地址到名称转换

            C/C++ (Linux) 示例:

            #include <sys/socket.h>
            #include <netdb.h>
            #include <stdio.h>
            
            char host[NI_MAXHOST]; // to store resulting string for ip
            if (getnameinfo((sockaddr*)&client, c_len,
                              host, NI_MAXHOST, // to try to get domain name don't put NI_NUMERICHOST flag
                              NULL, 0,          // use char serv[NI_MAXSERV] if you need port number
                              NI_NUMERICHOST    // | NI_NUMERICSERV
                           ) != 0) {
               //handle errors
            } else {
                printf("Connected to: %s\n", host);     
            }
            

            这是对@热心的回答的清理。他的回答有一个接受电话的工作示例。

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 2011-08-22
              • 2010-11-19
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2010-12-26
              相关资源
              最近更新 更多