支持 IPv6

IPv6 与 IPv4 是不兼容的, 但如果服务器使用 IPv6, 客户端使用 IPv4 也可以正常连接, 因为 IPv4 的地址会自动映射为 IPv6 的地址, 像 ::ffff:192.168.27.25

IPv6 移植工作主要涉及以下三类函数的修改

(1) 创建 Socket 函数: socket
(2) 从应用层 sockaddr 传递至 内核 的相关函数: bind, connect, sendto
(3) 从内核传递 sockaddr 至 应用层 的相关函数: accept, recvfrom, getpeername, getsockname

(1) 与 (2) 类函数的修改

不要再像这样去创建 Socket, socket(AF_INET, SOCK_STREAM, 0);
按 man 文档比较好的做法是调用 getaddrinfo 函数, 并获取 addrinfo 结构中的对应字段作为 socket() 的参数
如下所示:

struct addrinfo hints;
struct addrinfo *res, *p;
int sfd = -1;

memset(&hints, 0, sizeof(hints));
hints.ai_family = family;
hints.ai_socktype = SOCK_STREAM; /* Stream socket */
hints.ai_protocol = IPPROTO_TCP; /* TCP protocol */
int ret = getaddrinfo(host, service, &hints, &res);
if( ret != 0 ) {
	LOG_ERR("getaddrinfo() failed : %s", gai_strerror(ret));
	return -1;
}

/*
Try open socket with each address getaddrinfo returned, 
until we get a valid listening socket. 
*/
for(p = res; p != NULL; p = p->ai_next) {
	if( (sfd = socket(p->ai_family, p->ai_socktype, p->ai_protocol)) == -1 ) {
		LOG_ERRNO("socket() failed");
		continue;
	}

	int on = 1;
	if( setsockopt(sfd, SOL_SOCKET, SO_REUSEADDR, (char *)&on, sizeof(on)) < 0 )
	{
		LOG_ERRNO("setsockopt() - SO_REUSEADDR failed");
		goto _failed_listen_socket;
	}

	if( bind(sfd, p->ai_addr, p->ai_addrlen) == -1 ) {
		LOG_ERRNO("bind() failed :");
		goto _failed_listen_socket;
	}

	break;

_failed_listen_socket:
	close(sfd);
	continue;
}

freeaddrinfo(res);

完整代码: dual_stack_server.c

(3) 类函数的修改

传递给sockaddr *参数的结构使用struct sockaddr_storage,有些代码会使用sockaddr_in6sockaddr_in.

其它

(1) 关于IPV6_V6ONLY
Windows 默认的 IPV6_V6ONLY 设置为 true, 所以在 Windows 下如果需要同时监听 IPv6 和 IPv4 的话那么需要将它设置为 false.

参考资料