先贴上苹果给出的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代码,代码详细介绍请查看注释。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83int 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:%sn", 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:%sn", 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:%sn", 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); return fd6; } printf("IPV6 connect error:%sn", 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
最后
以上就是迷人朋友最近收集整理的关于客户端底层 Socket 实现IPV4 IPV6网络环境的兼容的全部内容,更多相关客户端底层内容请搜索靠谱客的其他文章。
发表评论 取消回复