2018年4月30日
近期AppStore上架被拒了,原因是不支持ipv6。
解决问题后,顺便复习了下socket编程。
一般情况下,我们的程序关于网络连接的创建都是写死的:
socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
如上,我们的网络一般都是ipv4,tcp协议的,按照如上方式创建socket即可。但如果需要连接ipv6,需要按照如下方式:
socket(AF_INET6, SOCK_STREAM, IPPROTO_TCP);
因此,有了如下方案:
1.先判断所处网络环境,获取环境对应的ip地址,完成发送
gethostbyname()返回对应于给定主机名的包含主机名字和地址信息的hostent结构的指针。结构的声明与gethostbyaddr()中一致。
该函数用于返回ipv4的地址信息,要返回ipv6,需要用gethostbyname2函数:
#include <netdb.h>
struct hostent * gethostbyname2(const char *hostname, int family);
成功返回非空指针,出错返回空指针,同时设置 h_errno。
该函数的逻辑依赖于参数family和解析器选项RES_USE_INET6,对于新的选项RES_USE_INET6,函数gethostbyname2操作如下:
- 如果参数family是AF_INET,则查询A记录。
- 若不成功,则返回一个空指针;
- 若成功,则返回地址的类型及大小依赖于新的解析器选项RES_USE_INET6:
- 若选项未设置,则返回IPv4地址,结构hostent的成员h_length的值为4;
- 若选项设置 ,则返回IPv4映射的IPv6地址,结构hostent的成员h_length的值为16。
- 如果参数family为AF_INET6,则查询AAAA记录,若成功,则返回IPv6地址,结构hostent的成员h_length的值为16,否则返回一个空指针。
hostent结构如下:
struct hostent
{
char *h_name;
char ** h_aliases;
short h_addrtype;
short h_length;
char ** h_addr_list;
};
2.Apple官方也给了适配方案,即使用getaddrinfo函数
gethostbyname和gethostbyaddr这两个函数仅仅支持IPv4,getaddrinfo函数能够处理名字到地址以及服务到端口这两种转换,返回的是一个sockaddr结构的链表而不是一个地址清单。这些sockaddr结构随后可由套接口函数直接使用。如此一来,getaddrinfo函数把协议相关性安全隐藏在这个库函数内部。应用程序只要处理由getaddrinfo函数填写的套接口地址结构。
函数原型:
int getaddrinfo( const char *hostname, const char *service, const struct addrinfo *hints, struct addrinfo **result );
参数说明
hostname:一个主机名或者地址串(IPv4的点分十进制串或者IPv6的16进制串)
service:服务名可以是十进制的端口号,也可以是已定义的服务名称,如ftp、http等
hints:可以是一个空指针,也可以是一个指向某个addrinfo结构体的指针,调用者在这个结构中填入关于期望返回的信息类型的暗示。举例来说:指定的服务既可支持TCP也可支持UDP,所以调用者可以把hints结构中的ai_socktype成员设置成SOCK_DGRAM使得返回的仅仅是适用于数据报套接口的信息。
result:本函数通过result指针参数返回一个指向addrinfo结构体链表的指针。
返回值:0——成功,非0——出错
要适配ipv6关键在于设置hints参数:
struct addrinfo hints, *res,*res0;
memset(&hints, 0, sizeof(hints));
hints.ai_family = PF_UNSPEC; //返回操作系统支持的协议族,如果同时支持ipv4和ipv6,就都返回
hints.ai_socktype = SOCK_STREAM;
hints.ai_flags = AI_DEFAULT;
error = getaddrinfo(ipv4_str, strPort.c_str(), &hints, &res0);
返回的结果集res0 包含了ipv4和ipv6,在处理是循环进行,哪一个能够连接上就用哪一个。
for (res = res0; res; res = res->ai_next) {
s = socket(res->ai_family, res->ai_socktype,
res->ai_protocol);
cocos2d::log("family: ipv%d",res->ai_family);
if (s < 0) {
cause = "socket";
continue;
}
if (connect(s, res->ai_addr, res->ai_addrlen) < 0) {
cause = "connect";
close(s);
s = -1;
continue;
}
break; /* okay we got one */
}
注意:
此处connect不能够从循环中拿出来。因为socket创建一般都会成功,只有在connect的时候才会因网络环境不同而出现连接失败的情况。