winsock2:服务器端代码调用`accept()`后如何获取已连接客户端的ipv4/ipv6地址

Posted

技术标签:

【中文标题】winsock2:服务器端代码调用`accept()`后如何获取已连接客户端的ipv4/ipv6地址【英文标题】:winsock2: How to get the ipv4/ipv6 address of a connected client after server side code calls `accept()` 【发布时间】:2021-12-11 00:03:42 【问题描述】:

此站点上还有其他类似的问题,但它们要么与 winsock2 无关,要么仅适用于 ipv4 地址空间。 Visual Studio 2019 的默认编译器在使用 ntoa 函数时会产生错误,因此需要 ipv4 和 ipv6 解决方案。

我曾经为 Linux 系统编写过代码来执行此操作,但是我目前正在工作并且无法访问该代码。它可能会或可能不会“复制和粘贴”到带有winsock2 的Windows 环境中。 (编辑:我当然会在今晚晚些时候添加该代码,但当然它可能没有用。)

以下包含一个示例,但是这是客户端代码的示例,不是服务器端代码。

https://www.winsocketdotnetworkprogramming.com/winsock2programming/winsock2advancedInternet3c.html

这里,getaddrinfo() 函数用于获取包含匹配 ipv4 和 ipv6 地址的结构。要获取此信息,需要与 DNS 进行一些交互,在这种情况下不需要。

我有一些服务器代码调用accept()(在绑定和监听之后)接受客户端连接。我希望能够将客户端 IP 地址和端口打印到标准输出。

这个网站上最相关的问题是here。但是答案使用ntoa,并且仅与 ipv4 兼容。

到目前为止我所拥有的:

到目前为止,我的草图如下:

SOCKET acceptSocket = INVALID_SOCKET;
SOCKADDR_IN addr; // both of these are NOT like standard unix sockets
    // I don't know how they differ and if they can be used with standard
    // unix like function calls (eg: inet_ntop)

int addrlen = sizeof addr;
acceptSocket = accept(listenSocket, (SOCKADDR*)&addr, &addrlen);
if(acceptSocket == INVALID_SOCKET)

    // some stuff

else

    const std::size_t addrbuflen = INET6_ADDRSRTLEN;
    char addrbuf[addrbuflen] = '\0'
    inet_ntop(AF_INET, (void*)addr.sin_addr, (PSTR)addrbuf, addrbuflen);
    // above line does not compile and mixes unix style function calls
    // with winsock2 structures
    std::cout << addrbuf << ':' << addr.sin_port << std::endl;

getpeername()

int ret = getpeername(acceptSocket, addrbuf, &addrbuflen);
    // addrbuf cannot convert from char[65] to sockaddr*
if(ret == ???)

    // TODO

【问题讨论】:

但接受直接返回这样的地址。在什么问题/问题上? @RbMm accept() 返回某种类型的套接字结构,而不是 ascii 字符串 是的,当然是socket结构。那又怎样? SOCKADDR_IN addr 这里你猜地址是 ipv4。你硬编码这个。如果更通用 - 使用SOCKADDR_INET addr。看起来你混淆了地址和它的字符串表示。地址可以通过WSAAddressToString转换为字符串。 @RbMm 对不起,我完全不知道你在说什么。是的,我使用 ipv4 结构来保存地址。我问这个的唯一原因是因为 VS2019 不支持 ntoa。该解决方案不必实际接受 ipv6 地址,只需使用 inet_ntop 或等效函数即可。 【参考方案1】:

您需要访问SOCKADDR。这实际上是一个有区别的工会。第一个字段告诉您它是 IPv4 (==AF_INET) 还是 IPv6 (==AF_INET6) 地址。根据您将addr 指针转换为struct sockaddr_in*struct sockaddr_in6*,然后从相关字段中读取IP 地址。

【讨论】:

什么时候接受已经返回这个? 查看我添加的最后一段,尽管在这里查看文档docs.microsoft.com/en-us/windows/win32/api/winsock2/…,但我不确定如何使用此功能 不,accept 给你一个套接字,它是一个类似于文件描述符的本地结构。 getPeerName在socket代表的连接的另一端查找机器的详细信息,这就是你想要的。 @PaulJohnson - getpeernameaccept 具有相同的输出 - 因为这个调用是毫无意义的 啊,抱歉,我错过了accept 中的输出 sockaddr。我会再试一次。【参考方案2】:

vs2019中的C++代码sn-p:

    char* CPortListener::get_ip_str(struct sockaddr* sa, char* s, size_t maxlen)
        
            switch (sa->sa_family) 
            case AF_INET:
                inet_ntop(AF_INET, &(((struct sockaddr_in*)sa)->sin_addr),
                    s, maxlen);
                break;
        
            case AF_INET6:
                inet_ntop(AF_INET6, &(((struct sockaddr_in6*)sa)->sin6_addr),
                    s, maxlen);
                break;
        
            default:
                strncpy(s, "Unknown AF", maxlen);
                return NULL;
            
        
            return s;
        

Example:


   
   
         ...
         
         char s[INET6_ADDRSTRLEN];
         sockaddr_storage ca;
         socklen_t al = sizeof(ca);
         SOCKET recv = accept(sd, (sockaddr*)&ca, &al);
         pObj->m_ip = get_ip_str(((sockaddr*)&ca),s,sizeof(s));
    

【讨论】:

以上是关于winsock2:服务器端代码调用`accept()`后如何获取已连接客户端的ipv4/ipv6地址的主要内容,如果未能解决你的问题,请参考以下文章

TSINGSEE青犀视频编译Winsock2 websocket服务端连接异常断开问题排查

accept函数

Netty——NIO(Selector处理accept事件)代码示例

Netty——NIO(Selector处理accept事件)代码示例

C++ WinSock2:连接()调用上的 WSA_INVALID_HANDLE

Netty——网络编程 NIO(Selector处理accept事件)代码示例