您的位置:首页 > 理论基础 > 计算机网络

客户端底层 Socket 实现IPV4 IPV6网络环境的兼容

2017-05-09 18:11 886 查看
先贴上苹果给出的IPV6相关介绍的地址,很全面也比较详细: https://developer.apple.com/library/content/documentation/NetworkingInternetWeb/Conceptual/NetworkingOverview/
UnderstandingandPreparingfortheIPv6Transition/UnderstandingandPreparingfortheIPv6Transition.html

苹果指出的IPV6是个什么鬼呢?

实际上是指IPV6 DNS64/NAT64网络。IPV4和IPV6本就是两个不同的协议,是不兼容的。而V6又不可能立刻完全取代V4,所以就需要过渡技术来让两者互通,DNS64/NAT64技术就是用来保证IPV6网络环境下的终端可以访问IPV4网络环境下的资源(注意,是单向的)。

DNS64/NAT64又是什么鬼呢?

实际上就是网络供应商在IPV6网络与IPV4网络之间架上DNS64/NAT64服务器来负责一个协议转换的工作。借苹果给出的图来形象的展示一下,如下图,终端在IPV6环境可直接访问IPV6环境下的服务器,而访问IPV4环境下的服务器时则需要经过DNS64/NAT64服务器来实现协议转换。



我们可以使用 OS X 10.11及以上系统的双网卡Mac(以太网口+无线网卡)来自己搭建一个IPV6 DNS64/NAT64网络环境。原理依然借用苹果的图来形象的展示一下,如下图,MAC电脑担任DNS64/NAT64服务器的角色,其共享的无线网络就是一个IPV6-only网络,终端连接该无线网后即处于IPV6环境。

具体的搭建步骤很简单,网上搜索一大堆,链接就不贴了。



OK,转入正题,看看客户端兼容V4 V6的socket用法。

IPV6 环境下客户端 Socket 编程与 IPV4 的区别主要在 socket 、connect 两个函数的使用上,其余的 read、write 等函数的使用都与 IPV4 一样。

socket() 的函数原型为 int socket(int domain, int type, int protocol),两者的区别在第一个参数的使用上,IPV4 为 AF_INET,IPV6 为 AF_INET6。

connect() 的函数原型为 int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen),两者的区别在第二个和第三个的使用上,尤其是第二个参数的结构体,而该结构体参数则需要使用函数 getaddrinfo() 来获得,以实现兼容。

getaddrinfo() 函数的信息很容易查到,不多解释,先贴上项目代码裁剪后deamon代码,代码详细介绍请查看注释。

int getconnectsocket_tcp(const char * ip_str, uint32_t ip, const char * port_str, uint16_t port)
{
int fd4 = -1, fd6 = -1;
int ret;
struct addrinfo hits;
struct addrinfo * results, * aitmp;
char host[40], portstr[6];

//任意一个fd创建成功,都有机会成功连接服务器
fd4 = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
fd6 = socket(AF_INET6, SOCK_STREAM, IPPROTO_TCP);
if (fd4 == -1 && fd6 == -1){
printf("socket create error:%s\n", strerror(errno));
return -1;
}

//设置非阻塞
set_sock_nonblocking(fd4);
set_sock_nonblocking(fd6);

//初始化 getaddrinfo 所需参数 hits
memset((void *)&hits, 0, sizeof(hits));
hits.ai_family = AF_UNSPEC;     //注意这里不要指定协议,这样getaddrinfo函数会根据网络环境返回得到的全部地址(包含ipv4和ipv6)
hits.ai_socktype = SOCK_STREAM;
hits.ai_protocol = 0;
hits.ai_flags = 0;

//获取服务器的地址和端口的字符串,域名IP均可
if(ip_str != NULL){
memcpy(host, ip_str, sizeof(host));
}else{
snprintf(host, sizeof(host), "%d", ip);
}
if(port_str != NULL){
memcpy(portstr, port_str, sizeof(portstr));
}else{
snprintf(portstr, sizeof(portstr), "%d", port);
}

//调用getaddrinfo函数来获取服务器的IP地址,结果存放在以第四个参数results为首地址的链表中
//需要注意的是:只有IOS9.2和OS X 10.11.2及以上系统支持IPV6地址的合成,即第一个参数host传入IPV4地址,也能得到对应的IPV6地址
//            IOS9.2和OS X 10.11.2以下系统只有第一个参数传入域名时才能得到IPV6地址,若传入IPV4地址则只能得到IPV4地址
ret = getaddrinfo(host, portstr, &hits, &results);
if(ret != 0){
printf("getaddrinfo error:%s\n", strerror(errno));
return -2;
}

//results链表中会存在ipv4和ipv6地址,所以遍历该链表,当有一个connect成功时即可结束遍历直接返回
//注意两个connect第二个参数虽然都是aitmp->ai_addr,却指的不同的结构体,第三个参数应该传入对应结构体的大小
for(aitmp = results; aitmp != NULL; aitmp = aitmp->ai_next){
switch(aitmp->ai_family){
case AF_INET:
ret = connect(fd4, aitmp->ai_addr, sizeof(struct sockaddr_in));
if(ret == 0 || errno == EINPROGRESS || errno == EWOULDBLOCK){
if(fd6 >= 0)
close(fd6);
return fd4;
}
printf("IPV4 connect error:%s\n", strerror(errno));
break;
case AF_INET6:
ret = connect(fd6, aitmp->ai_addr, sizeof(struct sockaddr_in6));
if(ret == 0 || errno == EINPROGRESS || errno == EWOULDBLOCK){
if(fd4 >= 0)
close(fd4);

9c2e
return fd6;
}
printf("IPV6 connect error:%s\n", strerror(errno));
break;
}
}

printf("connect fail!\n");
if(fd4 >= 0)
close(fd4);
if(fd6 >= 0)
close(fd6);
return -3;
}


connect成功以后其他操作都无需改变,所以IPV4 IPV6环境的兼容其实就这么简单。

简单提一下,其中 getaddrinfo 函数实际上就是前往上文提到的DNS64服务器来获取IP地址(特殊一点的DNS解析)。依然借用苹果的图,原理如下图:

需要注意的是编程中会涉及到几个结构体的相互关系(sockaddr、sockaddr_in、sockaddr_in6等)。(其中sockaddr *比较迷惑人,而实际上把这里sockaddr * 理解成void *后,很多问题就明了了)

几个结构体的信息可以参考以下两个地址:
http://blog.csdn.net/an_zhenwei/article/details/8591115 http://xyliufeng.iteye.com/blog/718862
除文中提到得链接,本文额外参考地址:
http://www.jianshu.com/p/a6bab07c4062
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: