详解 win32 api getsockname

Win32 API 中的 getsockname 函数用于获取套接字(socket)的本地地址信息,也就是套接字绑定的 IP 地址和端口号。它通常用于获取一个已经绑定的套接字的本地地址,特别是在动态分配端口的情况下,这个函数可以用来获取分配的端口。

函数原型

在 Windows 上,getsockname 的定义如下:

int getsockname(
    SOCKET s,
    struct sockaddr *name,
    int *namelen
);

参数详解

  • s:类型为 SOCKET,是一个有效的套接字描述符,表示要查询的套接字。该套接字必须已经通过 socket 函数创建并绑定(可以通过 bind 函数绑定地址)。

  • name:指向 sockaddr 类型的指针,用于接收本地地址信息。sockaddr 是一个通用的地址结构体,通过强制转换可以与特定的地址结构体(例如 sockaddr_in)兼容。在调用 getsockname 之前,需要将 name 指向一个已分配的 sockaddr 结构体空间。

  • namelen:指向一个 int 类型的指针,用于传递 name 缓冲区的大小。在调用函数之前,应将其设置为 sockaddr 结构体的大小,函数执行后,会将 namelen 设置为实际地址结构的长度。

返回值

  • 成功:返回 0
  • 失败:返回 SOCKET_ERROR(通常为 -1),并且可以通过 WSAGetLastError() 获取具体的错误代码。

常见错误代码

  • WSANOTINITIALISED:表示 Winsock 库未初始化。在使用 socket 之前必须调用 WSAStartup
  • WSAENOTSOCK:参数 s 不是一个有效的套接字。
  • WSAEFAULT:参数 namenamelen 指针无效或未分配足够的内存。
  • WSAEINPROGRESS:阻塞的 Winsock 调用正在进行中。

示例代码

下面是一个使用 getsockname 获取本地地址和端口的示例代码:

#include <winsock2.h>
#include <ws2tcpip.h>
#include <iostream>

#pragma comment(lib, "ws2_32.lib")

int main() {
    WSADATA wsaData;
    SOCKET s;
    struct sockaddr_in serverAddr, localAddr;
    int localAddrLen = sizeof(localAddr);

    // 初始化 Winsock
    if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) {
        std::cerr << "WSAStartup failed: " << WSAGetLastError() << std::endl;
        return 1;
    }

    // 创建套接字
    s = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    if (s == INVALID_SOCKET) {
        std::cerr << "Socket creation failed: " << WSAGetLastError() << std::endl;
        WSACleanup();
        return 1;
    }

    // 设置服务器地址
    serverAddr.sin_family = AF_INET;
    serverAddr.sin_addr.s_addr = inet_addr("127.0.0.1"); // 本地地址
    serverAddr.sin_port = htons(0); // 使用端口 0,让系统自动分配端口

    // 绑定套接字
    if (bind(s, (struct sockaddr*)&serverAddr, sizeof(serverAddr)) == SOCKET_ERROR) {
        std::cerr << "Bind failed: " << WSAGetLastError() << std::endl;
        closesocket(s);
        WSACleanup();
        return 1;
    }

    // 获取本地地址信息
    if (getsockname(s, (struct sockaddr*)&localAddr, &localAddrLen) == SOCKET_ERROR) {
        std::cerr << "getsockname failed: " << WSAGetLastError() << std::endl;
    } else {
        // 输出本地 IP 地址和端口
        char ipStr[INET_ADDRSTRLEN];
        inet_ntop(AF_INET, &localAddr.sin_addr, ipStr, sizeof(ipStr));
        std::cout << "Local IP Address: " << ipStr << std::endl;
        std::cout << "Local Port: " << ntohs(localAddr.sin_port) << std::endl;
    }

    // 清理
    closesocket(s);
    WSACleanup();

    return 0;
}

示例代码解析

  1. 初始化 Winsock:首先调用 WSAStartup 初始化 Winsock 库。
  2. 创建套接字:使用 socket 函数创建一个 TCP 套接字 s
  3. 设置和绑定服务器地址:设置 serverAddr 的 IP 地址为 127.0.0.1(本地地址),端口设置为 0,即由系统自动分配端口。通过 bind 函数将套接字绑定到该地址。
  4. 获取本地地址:通过 getsockname 获取套接字的本地地址信息,返回的地址存储在 localAddr 中。
  5. 输出本地 IP 地址和端口:使用 inet_ntoplocalAddr.sin_addr 转换为字符串形式,并使用 ntohs 将端口号转换为主机字节序进行打印。
  6. 清理资源:调用 closesocket 关闭套接字,并调用 WSACleanup 清理 Winsock 库。

注意事项

  • 动态分配端口:在绑定时使用 0 作为端口号,表示让系统自动选择一个可用端口。在这种情况下,getsockname 用于获取实际分配的端口号。
  • 字节序转换:从 sockaddr_in 中获取端口号时,需要使用 ntohs 转换为主机字节序;而 IP 地址转换可以直接使用 inet_ntop
  • 跨平台兼容性getsockname 函数在不同平台上可能略有差异。例如,Linux 平台上 sockaddr 结构的定义略有不同。
  • 在代码中,getsockname 的作用是获取套接字 sServer 的本地 IP 地址和端口号。尽管已经手动传入了 IP 和端口号进行绑定,但调用 getsockname 可能仍然有一些合理的原因,主要包括以下几点:

1. 确认实际绑定的本地 IP 和端口

即使在代码中指定了 IP 和端口,调用 getsockname 仍然可以确认实际绑定的本地地址。特别是在多网卡或多 IP 的环境中,即使指定了 IP 地址,操作系统可能会选择绑定到一个具体的接口,这样可以通过 getsockname 确认实际绑定的 IP 地址。

此外,有时端口号可能设置为 0(即让系统自动分配一个可用端口)。在这种情况下,通过 getsockname 可以获取到系统分配的具体端口号。

2. 获取真实的本地地址(在 NAT 或代理环境中)

在 NAT(网络地址转换)或代理服务器环境中,即使绑定了本地地址,应用程序可能无法知道 NAT 后的外部地址。在某些特定网络栈实现中,调用 getsockname 可以获取到 NAT 后的真实本地地址。这种情况在 Windows 系统上不常见,但在一些复杂网络环境中仍可能有帮助。

3. 用于日志或调试

调用 getsockname 获取并打印本地地址信息,可能只是为了记录日志或进行调试,确认套接字的状态。例如,在调试和测试环境中,开发者可能希望检查和记录实际绑定的 IP 和端口号,以便分析连接行为。这可以帮助开发者验证程序是否绑定到预期的地址和端口。

4. 确保跨平台兼容性

有时代码需要兼容不同的操作系统或网络环境。在一些操作系统上,即使绑定了特定 IP 地址,实际的绑定信息可能会有所不同。调用 getsockname 可以确保获得准确的本地地址,特别是在处理跨平台应用时。

示例分析

在以下示例代码中,即使已指定本地 IP 地址和端口号,getsockname 仍然被调用以获取实际的绑定信息:

SOCKADDR_IN addrServ;
addrServ.sin_family = AF_INET;
addrServ.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
addrServ.sin_port = htons(12345); // 指定了端口 12345

if (bind(sServer, (struct sockaddr*)&addrServ, sizeof(addrServ)) == SOCKET_ERROR) {
    qDebug() << "Bind failed: " << WSAGetLastError();
    return;
}

getsockname(sServer, (struct sockaddr*)&clientAddr, &clientAddrLen);
qDebug() << "Actual bound IP: " << inet_ntoa(clientAddr.sin_addr);
qDebug() << "Actual bound Port: " << ntohs(clientAddr.sin_port);

即使我们在 bind 中指定了 127.0.0.1:12345,调用 getsockname 仍然可以确保实际绑定的 IP 地址和端口确实是我们预期的。

总结

尽管 getsockname 在手动设置了 IP 和端口的情况下看起来有些多余,但调用它可能基于以下原因:

  1. 确认绑定的地址和端口。
  2. 适应 NAT 或代理环境,获取真实的本地地址。
  3. 记录日志或进行调试。
  4. 增强代码的跨平台兼容性。

这些原因使得 getsockname 的调用在某些场景下依然有意义,帮助开发者确认程序绑定的网络地址是否符合预期。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

心瞳几何造型

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值