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

来源:互联网 发布:珠海知想科技官网 编辑:程序博客网 时间:2024/05/22 16:56

先贴上苹果给出的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);                    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

0 0