我使用的是一个做一些网络工作的库,当客户端连接时,该库提供了一个“struct sockaddr *”,用于保存客户端套接字。我只是想提取IP和端口,而我目前是这样进行的:
#include <sys/socket.h>
#include <netdb.h>
const std::string Client::prepareIPandPort(struct sockaddr *hostaddr) {
assert(hostaddr != nullptr);
std::string ipport;
char clienthost[NI_MAXHOST];
char clientport[NI_MAXSERV];
int result = getnameinfo(hostaddr, sizeof(*hostaddr),
clienthost, sizeof(clienthost),
clientport, sizeof(clientport),
NI_NUMERICHOST | NI_NUMERICSERV);
if (result != 0) {
std::cerr << "Error: " << gai_strerror(result) << std::endl;
ipport = "unknown";
} else {
switch (hostaddr->sa_family) {
case AF_INET:
ipport = std::string {clienthost} + ":"
+ std::string {clientport};
break;
case AF_INET6:
ipport = "[" + std::string {clienthost} + "]:"
+ std::string {clientport};
break;
default:
ipport = "unknown";
}
}
return ipport;
}
在Mac上使用IPv4时,它可以工作。如果我在具有完全IPv6支持的Gentoo Linux服务器上使用此应用程序,则只会得到:
错误:不支持ai_family
连接客户端具有AAAA和IP6记录。
我添加了一些提示,并打印了hostaddr-> sa_family,它返回10,即AF_INET6。
为什么这不起作用? :-)
最佳答案
您不能使用sizeof(*hostaddr)
,因为hostaddr
是通用的sockaddr*
指针。不同的地址族使用不同的sockaddr_...
类型,它们的大小与sockaddr
本身并不完全相同。 getnameinfo()
需要根据其地址族了解sockaddr_...
实际指向的hostaddr
结构的真实大小。
根据Linux getnameinfo()
documentation:
sockaddr_in
(IPv4)的大小与sockaddr
相同,这就是为什么getnameinfo()
对于IPv4“起作用”的原因。但是sockaddr_in6
(IPv6)大于sockaddr
,这就是getnameinfo()
失败的原因。
最好的解决方案是使 call 者传递正确的大小:
const std::string Client::prepareIPandPort(struct sockaddr *hostaddr, int hostaddrlen) {
...
int result = getnameinfo(hostaddr, hostaddrlen, ...);
...
}
sockaddr_in ipv4host;
...
client.prepareIPandPort((sockaddr*)&ipv4host, sizeof(ipv4host));
sockaddr_in6 ipv6host;
...
client.prepareIPandPort((sockaddr*)&ipv6host, sizeof(ipv6host));
否则,您必须计算它:
const std::string Client::prepareIPandPort(struct sockaddr *hostaddr) {
...
int hostaddrlen;
switch (hostaddr->sa_family) {
case AF_INET:
hostaddrlen = sizeof(sockaddr_in);
break;
case AF_INET6:
hostaddrlen = sizeof(sockaddr_in6);
break;
default:
std::cerr << "Error: " << gai_strerror(EAI_FAMILY) << std::endl;
return "unknown";
}
int result = getnameinfo(hostaddr, hostaddrlen, ...);
...
}