我是靠谱客的博主 迷人朋友,最近开发中收集的这篇文章主要介绍客户端底层 Socket 实现IPV4 IPV6网络环境的兼容,觉得挺不错的,现在分享给大家,希望可以做个参考。

概述

先贴上苹果给出的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:%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网络环境的兼容的全部内容,希望文章能够帮你解决客户端底层 Socket 实现IPV4 IPV6网络环境的兼容所遇到的程序开发问题。

如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(39)

评论列表共有 0 条评论

立即
投稿
返回
顶部