我编写的以下c函数返回一个接受IPv4连接但不接受IPv6的文件描述符。 有人能帮我弄清楚出了什么问题吗? 我怀疑我没有正确使用getaddrinfo()。
打开侦听连接的文件描述符。
/*
* open_listenfd - open and return a listening socket on port
* Returns -1 and sets errno on Unix error.
*/
int open_listenfd(int port)
{
const char* hostname=0;
// Converts port to string
char* pName = malloc(numPlaces(port) + 1);
sprintf(pName, "%d", port);
const char* portname= pName;
struct addrinfo hints;
memset(&hints,0,sizeof(hints));
hints.ai_family=AF_UNSPEC;
hints.ai_socktype=SOCK_STREAM;
hints.ai_protocol= 0;
hints.ai_flags=AI_PASSIVE|AI_ADDRCONFIG;
struct addrinfo* res=0;
int err=getaddrinfo(hostname,portname,&hints,&res);
free(pName);
if (err!=0) {
return -1;
}
int listenfd, optval=1;
struct sockaddr_in serveraddr;
/* Create a socket descriptor */
if ((listenfd = socket(res->ai_family,res->ai_socktype, res->ai_protocol)) < 0)
return -1;
/* Eliminates "Address already in use" error from bind. */
if (setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR,
(const void *)&optval , sizeof(int)) < 0)
return -1;
/* Listenfd will be an endpoint for all requests to port
on any IP address for this host */
bzero((char *) &serveraddr, sizeof(serveraddr));
serveraddr.sin_family = /*AF_INET;*/ AF_UNSPEC;
serveraddr.sin_addr.s_addr = htonl(INADDR_ANY);
serveraddr.sin_port = htons((unsigned short)port);
if (bind(listenfd, res->ai_addr, res->ai_addrlen/*(SA *)&serveraddr, sizeof(serveraddr)*/) < 0)
return -1;
freeaddrinfo(res);
/* Make it a listening socket ready to accept connection requests */
if (listen(listenfd, 1024) < 0)
return -1;
return listenfd;
}
答案 0 :(得分:3)
如果您希望服务器监听IPV4和IPV6地址,需要可以设置两个套接字来监听。
getaddrinfo()
可能会返回给定主机和/或服务的多个互联网地址的信息。
传递的ai_family
结构的成员hints
指定调用者感兴趣的地址族。如果指定AF_UNSPEC
,则可能返回IPV4和IPV6地址。
要查明是否有这样的地址,您可能希望修改代码,如下所示:
int open_listenfd(int port, int * pfdSocketIpV4, int * pfdSocketIpV6)
{
...
struct addrinfo hints = {0};
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_STREAM;
hints.ai_flags = AI_PASSIVE|AI_ADDRCONFIG;
hints.ai_next = NULL;
struct addrinfo * res = NULL;
int err=getaddrinfo(hostname, portname, &hints, &res);
free(pName);
if (err)
{
return -1;
}
struct addrinfo * pAddrInfoIpV4 = NULL;
struct addrinfo * pAddrInfoIpV6 = NULL;
{
struct addrinfo * pAddrInfo = res;
/* Loop over all address infos found until a IPV4 and a IPV6 address is found. */
while (pAddrInfo)
{
if (!pAddrInfoIpV4 && AF_INET == pAddrInfo->ai_family)
{
pAddrInfoIpV4 = pAddrInfo; /* Take first IPV4 address available */
}
else if (!pAddrInfoIpV6 && AF_INET6 == pAddrInfo->ai_family)
{
pAddrInfoIpV6 = pAddrInfo; /* Take first IPV6 address available */
}
else
{
break; /* Already got an IPV4 and IPV6 address, so skip the rest */
}
pAddrInfo= pAddrInfo->ai_next; /* Get next address info, if any */
}
}
if (pAddrInfoIpV4)
{
... /* create, bind and make IPV4 socket listen */
int fdSocketIpV4 = socket(pAddrInfoIpV4->ai_family,...
*pfdSocketIpV4 = fdSocketIpV4;
}
if (pAddrInfoIpV6)
{
/* create, bind and make IPV6 socket listen */
int fdSocketIpV6 = socket(pAddrInfoIpV6->ai_family,...
*pfdSocketIpV6 = fdSocketIpV6;
}
freeaddrinfo(res);
...
然后这样称呼它:
...
int fdSocketIpV4 = -1;
int fdSocketIpV6 = -1;
if (0 > open_listenfd(port, &fdSocketIpV4, &fdSocketIpV6))
{
printf("Error executing 'open_listenfd()'\n");
}
else
{
... /* go for accepting connectings on 'fdSocketIpV4' and 'fdSocketIpV6' */
<强>更新强>
如 Per Johansson 所评论的另一种方法是建立一个双栈插槽,支持Ipv4和Ipv6,如本答案所述:How to support both IPv4 and IPv6 connections