深入socket编程--ipv6适配

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的时候才会因网络环境不同而出现连接失败的情况。

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

程序员柒叔

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

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

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

打赏作者

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

抵扣说明:

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

余额充值