VC++ IPv6的支持

最近根据项目需要,要在产品中添加对IpV6的支持,因此研究了一下IPV6的相关内容,Ipv6 与原来最直观的改变就是地址结构的改变,IP地址由原来的32位扩展为128,这样原来的地址结构肯定就不够用了,根据微软的官方文档,只需要对原来的代码做稍许改变就可以适应ipv6。

修改地址结构

Windows Socket2 针对Ipv6的官方描述

根据微软官方的说法,要做到支持Ipv6首先要做的就是将原来的SOCKADDR_IN等地址结构替换为SOCKADDR_STORAGE 该结构的定义如下:

typedef struct sockaddr_storage {
    short ss_family;
    char __ss_pad1[_SS_PAD1SIZE];
    __int64 __ss_align;
    char __ss_pad2[_SS_PAD2SIZE];
} SOCKADDR_STORAGE,  *PSOCKADDR_STORAGE;
  • ss_family:代表的是地址家族,IP协议一般是AF_INET, 但是如果是IPV6的地址这个参数需要设置为 AF_INET6

后面的成员都是作为保留字段,或者说作为填充结构大小的字段,这个结构兼容了IPV6与IPV4的地址结构,跟以前的SOCKADDR_IN结构不同,我们现在不能直接从SOCKADDR_STORAGE结构中获取IP地址了。也没有办法直接往结构中填写IP地址。

使用兼容函数

除了地址结构的改变,还需要改变某些函数,有的函数是只支持Ipv4的,我们需要将这些函数改为即兼容的函数,根据官方的介绍,这些兼容函数主要是下面几个:

  • WSAConnectByName : 可以直接通过主机名建立一个连接
  • WSAConnectByList: 从一组主机名中建立一个连接
  • getaddrinfo: 类似于gethostbyname, 但是gethostbyname只支持IPV4所以一般用这个函数来代替
  • GetAdaptersAddresses: 这个函数用来代替原来的GetAdaptersInfo

WSAConnectByName函数:

函数原型如下:

BOOL PASCAL WSAConnectByName(
  __in     SOCKET s,
  __in     LPSTR nodename,
  __in     LPSTR servicename,
  __inout  LPDWORD LocalAddressLength,
  __out    LPSOCKADDR LocalAddress,
  __inout  LPDWORD RemoteAddressLength,
  __out    LPSOCKADDR RemoteAddress,
  __in     const struct timeval* timeout,
           LPWSAOVERLAPPED Reserved
);
  • s: 该参数为一个新创建的未绑定,未与其他主机建立连接的SOCKET,后续会采用这个socket来进行收发包的操作
  • nodename: 主机名,或者主机的IP地址的字符串
  • servicename: 服务名称,也可以是对应的端口号的字符串,传入服务名时需要传入那些知名的服务,比如HTTP、FTP等等, 其实这个字段本身就是需要传入端口的,传入服务名,最后函数会根据服务名称转化为这些服务的默认端口
  • LocalAddressLength, LocalAddress, 返回当前地址结构,与长度
  • RemoteAddressLength, RemoteAddress,返回远程主机的地址结构,与长度
  • timeout: 超时值
  • Reserved: 重叠IO结构

为了使函数能够支持Ipv6,需要在调用前使用setsockopt函数对socket做相关设置,设置的代码如下:

iResult = setsockopt(ConnSocket, IPPROTO_IPV6, IPV6_V6ONLY, (char*)&ipv6only, sizeof(ipv6only) );

调用函数的例子如下(该实例为微软官方的例子):

SOCKET OpenAndConnect(LPWSTR NodeName, LPWSTR PortName)
{
    SOCKET ConnSocket;
    DWORD ipv6only = 0;
    int iResult;
    BOOL bSuccess;
    SOCKADDR_STORAGE LocalAddr = {0};
    SOCKADDR_STORAGE RemoteAddr = {0};
    DWORD dwLocalAddr = sizeof(LocalAddr);
    DWORD dwRemoteAddr = sizeof(RemoteAddr);

    ConnSocket = socket(AF_INET6, SOCK_STREAM, 0);
    if (ConnSocket == INVALID_SOCKET){
        return INVALID_SOCKET;
    }

    iResult = setsockopt(ConnSocket, IPPROTO_IPV6,
        IPV6_V6ONLY, (char*)&ipv6only, sizeof(ipv6only) );
    if (iResult == SOCKET_ERROR){
        closesocket(ConnSocket);
        return INVALID_SOCKET;
    }

    bSuccess = WSAConnectByName(ConnSocket, NodeName,
            PortName, &dwLocalAddr,
            (SOCKADDR*)&LocalAddr,
            &dwRemoteAddr,
            (SOCKADDR*)&RemoteAddr,
            NULL,
            NULL);
    if (bSuccess){
        return ConnSocket;
    } else {
        return INVALID_SOCKET;
    }
}

WSAConnectByList

该函数从传入的一组hostname中选取一个建立连接,函数内部会调用WSAConnectByName,它的原型,使用方式与WSAConnectByName类似,这里就不再给出具体的原型以及调用方法了。

getaddrinfo

该函数的作用与gethostbyname类似,但是它可以同时支持获取V4、V6的地址结构,函数原型如下:

int getaddrinfo(
  const char FAR* nodename,
  const char FAR* servname,
  const struct addrinfo FAR* hints,
  struct addrinfo FAR* FAR* res
);
  • nodename: 主机名或者IP地址的字符串
  • servname: 知名服务的名称或者端口的字符串
  • hints:一个地址结构,该结构规定了应该如何进行地址转化。
  • res:与gethostbyname类似,它也是返回一个地址结构的链表。后续只需要遍历这个链表即可。

使用的实例如下:

char szServer[] = "www.baidu.com";
char szPort[] = "80";
addrinfo hints = {0};
struct addrinfo* ai = NULL;
getaddrinfo(szServer, szPort, NULL, &ai);
while (NULL != ai)
{
  SOCKET sConnect = socket(ai->ai_family, SOCK_STREAM, ai->ai_protocol);
  connect(sConnect, ai->ai_addr, ai->ai_addrlen);
  shutdown(sConnect, SD_BOTH);
  closesocket(sConnect);
  ai = ai->ai_next;
}

freeaddrinfo(ai); //最后别忘了释放链表

针对硬编码的情况

针对这种情况一般是修改硬编码,如果希望你的应用程序即支持IPV6也支持IPV4,那么就需要去掉这些硬编码的部分。微软提供了一个工具叫"Checkv4.exe" 这个工具一般是放到VS的安装目录中,作为工具一起安装到本机了,如果没有可以去官网下载。

工具的使用也非常简单

checkv4.exe 对应的.h或者.cpp 文件

这样它会给出哪些代码需要进行修改,甚至会给出修改意见,我们只要根据它的提示修改代码即可。

几个例子

因为IPV6 不能再像V4那样直接往地址结构中填写IP了,因此在IPV6的场合需要大量使用getaddrinfo函数,来根据具体的IP字符串或者根据主机名来自动获取地址信息,然后根据地址信息直接调用connect即可,下面是微软的例子

int ResolveName(char *Server, char *PortName, int Family, int SocketType)
{

    int iResult = 0;

    ADDRINFO *AddrInfo = NULL;
    ADDRINFO *AI = NULL;
    ADDRINFO Hints;

    memset(&Hints, 0, sizeof(Hints));
    Hints.ai_family = Family;
    Hints.ai_socktype = SocketType;
    iResult = getaddrinfo(Server, PortName, &Hints, &AddrInfo);
    if (iResult != 0) {
        printf("Cannot resolve address [%s] and port [%s], error %d: %s\n",
               Server, PortName, WSAGetLastError(), gai_strerror(iResult));
        return SOCKET_ERROR;
    }

    if(NULL != AddrInfo)
    {
        SOCKET sConnect = socket(AddrInfo->ai_family, SOCK_STREAM, AddrInfo->ai_protocol);
        connect(sConnect, AddrInfo->ai_addr, AddrInfo->ai_addrlen);

        shutdown(sConnect, SD_BOTH);
        closesocket(sConnect);
    }
    freeaddrinfo(AddrInfo);
    return 0;
}

这个例子需要传入额外的family参数来规定它使用何种地址结构,但是如果我只有一个主机名,而且事先并不知道需要使用何种IP协议来进行通信,这种情况下又该如何呢?

针对服务端,不存在这个问题,服务端是我们自己的代码,具体使用IPV6还是IPV4这个实现是可以确定的,因此可以采用跟上面类似的写法:

BOOL Create(int af_family)
{
    //这里不建议使用IPPROTO_IP 或者IPPROTO_IPV6,使用TCP或者UDP可能会好点,因为它们是建立在IP协议之上的
    //当然,具体情况具体分析
    s = socket(af_family, SOCK_STREAM, IPPROTO_TCP);
}

BOOL Bind(int family, UINT nPort)
{
    addrinfo hins = {0};
    hins.ai_family = family;
    hins.ai_flags = AI_PASSIVE; /* For wildcard IP address */
    hins.ai_protocol = IPPROTO_TCP;
    hins.ai_socktype = SOCK_STREAM;

    addrinfo *lpAddr = NULL;
    CString csPort = "";
    csPort.Format("%u", nPort);
    if (0 != getaddrinfo(NULL, csPort, &hins, &lpAddr))
    {
        closesocket(s);
        return FALSE;
    }

    int nRes = bind(s, lpAddr->ai_addr, lpAddr->ai_addrlen);
    freeaddrinfo(lpAddr);

    if(nRes == 0)
        return TRUE;

    return FALSE;
}

//监听,以及后面的收发包并没有区分V4和V6,因此这里不再给出跟他们相关的代码

针对服务端,我们自然没办法事先知道它使用的IP协议的版本,因此传入af_family参数在这里不再适用,我们可以利用getaddrinfo函数根据服务端的主机名或者端口号来提前获取它的地址信息,这里我们可以封装一个函数

int GetAF_FamilyByHost(LPCTSTR lpHost, int nPort, int SocketType)
{
    addrinfo hins = {0};
    addrinfo *lpAddr = NULL;
    hins.ai_family = AF_UNSPEC;
    hins.ai_socktype = SOCK_STREAM;
    hins.ai_protocol = IPPROTO_TCP;
    CString csPort = "";
    csPort.Format("%u", nPort);
    int af = AF_UNSPEC;
    char host[MAX_HOSTNAME_LEN] = "";
    if (lpHost == NULL)
    {
        gethostname(host, MAX_HOSTNAME_LEN);// 如果为NULL 则获取本机的IP地址信息
    }else
    {
        strcpy_s(host, MAX_HOSTNAME_LEN, lpHost);
    }

    if(0 != getaddrinfo(host, csPort, &hins, &lpAddr))
    {
        return af;
    }

    af = lpAddr->ai_family;
    freeaddrinfo(lpAddr);
    return af;
}

有了地址家族信息,后面的代码即可以根据地址家族信息来分别处理IP协议的不同版本,也可以使用上述服务端的思路,直接使用getaddrinfo函数得到的addrinfo结构中地址信息,下面给出第二种思路的部分代码:

if(0 != getaddrinfo(host, csPort, &hins, &lpAddr))
{
    connect(s, lpAddr->ai_addr, lpAddr->ai_addrlen);
}

当然,也可以使用前面提到的 WSAConnectByName 函数,不过它需要针对IPV6来进行特殊的处理,需要事先知道服务端的IP协议的版本。

VC中各种地址结构

在学习网络编程中,一个重要的概念就是IP地址,而巴克利套接字中提供了好几种结构体来表示地址结构,微软针对WinSock2 又提供了一些新的结构体,有的时候众多的结构体让人眼花缭乱,在这我根据自己的理解简单的回顾一下这些常见的结构

SOCKADD_IN 与sockaddr_in结构

在Winsock2 中这二者是等价的, 它们的定义如下:

struct sockaddr_in{
   short sin_family;
   unsigned short sin_port;
   struct in_addr sin_addr;
   char sin_zero[8];
};
  • sin_family: 地址协议家族
  • sin_port:端口号
  • sin_addr: 表示ip地址的结构
  • sin_zero: 用于与sockaddr 结构体的大小对齐,这个数组里面为全0

in_addr 结构如下:

struct in_addr {
    union {
        struct{
            unsigned char s_b1,
            s_b2,
            s_b3,
            s_b4;
        } S_un_b; 

        struct {
            unsigned short s_w1, s_w2;
        } S_un_w; 

        unsigned long S_addr;
    } S_un;
};

这个结构是一个公用体,占4个字节,从本质上将IP地址仅仅是一个占4个字节的无符号整型数据,为了方便读写才会采用点分十进制的方式。

仔细观察这个结构会发现,它其实定义了IP地址的几种表现形式,我们可以将IP地址以一个字节一个字节的方式拆开来看,也可以以两个字型数据的形式拆开,也可以简单的看做一个无符号长整型。

当然在写入的时候按照这几种方式写入,为了方便写入IP地址,微软定义了一个宏:

#define s_addr  S_un.S_addr

因此在填入IP地址的时候可以简单的使用这个宏来给S_addr这个共用体成员赋值

一般像bind、connect等函数需要传入地址结构,它们需要的结构为sockaddr,但是为了方便都会传入SOCKADDR_IN结构

sockaddr SOCKADDR结构

这两个结构也是等价的,它们的定义如下

struct sockaddr {

unsigned short sa_family;

char sa_data[14];

};

从结构上看它占16个字节与 SOCKADDR_IN大小相同,而且第一个成员都是地址家族的相关信息,后面都是存储的具体的IPV4的地址,因此它们是可以转化的,

为了方便一般是使用SOCKADDR_IN来保存IP地址,然后在需要填入SOCKADDR的时候强制转化即可。

sockaddr_in6

该结构类似于sockaddr_in,只不过它表示的是IPV6的地址信息,在使用上,由于IPV6是128的地址占16个字节,而sockaddr_in 中表示地址的部分只有4个字节,

所以它与之前的两个是不能转化的,在使用IPV6的时候需要特殊的处理,一般不直接填写IP而是直接根据IP的字符串或者主机名来连接。

sockaddr_storage

这是一个通用的地址结构,既可以用来存储IPV4地址也可以存储IPV6的地址,这个地址结构在前面已经说过了,这里就不再详细解释了。

各种地址之间的转化

一般我们只使用从SOCKADDR_IN到sockaddr结构的转化,而且仔细观察socket函数族发现只需要从其他结构中得到sockaddr结构,而并不需要从sockaddr转化为其他结构,因此这里重点放在如何转化为sockaddr结构

  1. 从SOCKADDR_IN到sockaddr只需要强制类型转化即可
  2. 从addrinfo结构中只需要调用其成员即可
  3. 从SOCKADDR_STORAGE结构到sockaddr只需要强制转化即可。

其实在使用上更常用的是将字符串的IP转化为对应的数值,针对IPV4有我们常见的inet_addrinet_ntoa 函数,它们都是在ipv4中使用的,

针对v6一般使用inet_pton,inet_ntop来转化,它们两个分别对应于inet_addrinet_ntoa。但是在WinSock中更常用的是WSAAddressToStringWSAStringToAddress

INT WSAAddressToString(
  LPSOCKADDR lpsaAddress,
  DWORD dwAddressLength,
  LPWSAPROTOCOL_INFO lpProtocolInfo,
  OUT LPTSTR lpszAddressString,
  IN OUT LPDWORD lpdwAddressStringLength
);
  • lpsaAddress: ip地址
  • dwAddressLength: 地址结构的长度
  • lpProtocolInfo: 协议信息的结构体,这个结构一般给NULL
  • lpszAddressString:目标字符串的缓冲
  • lpdwAddressStringLength:字符串的长度

而WSAStringToAddress定义与使用与它类似,这里就不再说明了。



VC++ IPv6的支持

原文地址:https://www.cnblogs.com/lanuage/p/10013064.html

时间: 2024-10-02 03:52:25

VC++ IPv6的支持的相关文章

安卓开启ipv6网络支持

安卓开启ipv6网络支持 (可在ipv6环境下访问ipv6网络, 如:ipv6.google.com, 或使用ipv6的代理访问ipv4网络) 注 需要root权限 只在 android4.1 环境测试成功 可能需要安装"ES 文件浏览器".打开Root工具箱,将文件系统挂载为"可读可写"(RW). 重启后失效, 需要再次运行脚本, 原因可能是开机的启动脚本有禁用ipv6.(可以尝试修改系统脚本-) 参考: –1– : –2– 安装 Android Terminal

iOS IPv6兼容支持和IPv6审核被拒收集整理

iOS IPv6兼容支持和IPv6审核被拒收集整理 最近遇到一个大坑:IPv6审核被拒问题,于是广寻解决方案,先把一些可以用资料文档收集起来备用.也希望同行能用得着. 官方文档说明:Supporting IPv6 DNS64/NAT64 Networks 官方关于支持IPv6的网络的开发指引文档 iOS-用手机网络测试Ipv6 用一台Mac,2个iPhone手机,1个数据线搭建本地IPv6测试网络环境 iOS应用支持IPV6,就那点事儿 参考官方文档的一些原理中文说明 专业处理AppStore审

unity增加对IPv6的支持

本文转自:http://blog.csdn.net/huutu/article/details/52155885 其实ipv4与ipv6之间的区别在建立socket时就有不同: ipv4的socket:socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); ipv6的socket:socket = new Socket(AddressFamily.InterNetworkV6, S

VC++ 编译libcurl 支持SSL,GZIP(有脚本)

由于网上下载的 libcurl 不支持 gzip,只好自己动手编译,期间走了很多弯路,下面是最终成功的记录. 我所使用的环境 Visual Studio 2010 . Windows 7 64 bit 1 下载文件 1.1 libcurl 下载页面 http://curl.haxx.se/download.html 下载地址 http://curl.haxx.se/download/curl-7.26.0.zip 1.2 zlib 下载页面 http://sourceforge.net/proj

VC++ 编译libcurl 支持SSL,GZIP

由于网上下载的 libcurl 不支持 gzip,只好自己动手编译,期间走了很多弯路,下面是最终成功的记录. 我所使用的环境 Visual Studio 2010 . Windows 7 64 bit 1 下载文件 1.1 libcurl 下载页面http://curl.haxx.se/download.html 下载地址 http://curl.haxx.se/download/curl-7.26.0.zip 1.2 zlib 下载页面http://sourceforge.net/projec

iOS应用支持IPV6

说了好久  ipv6, 擦, 似乎没遇到过呢 再次来 写下 同事们遇到的问题 一.IPV6-Only支持是啥? 首先IPV6,是对IPV4地址空间的扩充.目前当我们用iOS设备连接上Wifi.4G.3G等网络时,设备被分配的地址均是IPV4地址,但是随着运营商和企业逐渐部署IPV6 DNS64/NAT64网络之后,设备被分配的地址会变成IPV6的地址,而这些网络就是所谓的IPV6-Only网络,并且仍然可以通过此网络去获取IPV4地址提供的内容.客户端向服务器端请求域名解析,首先通过DNS64

[C] zlstdint(让VC、TC等编译器自动兼容C99的整数类型)V1.0。支持Turbo C++ 3等DOS下的编译器

作者:zyl910 以前我曾为了让VC++等编译器支持C99的整数类型,便编写了c99int库来智能处理(http://www.cnblogs.com/zyl910/p/c99int_v102.html).如今为了兼容Turbo C++ 3等DOS下的编译器,做了重大改变,不再适合沿用旧名,于是采用了zlstdint这个新名. 一.用法简介 用法很简单——把z_stdint.h.z_inttyp.h这2个文件放到你的项目中,便可以正常的使用C99整数类型及相关的宏了. 范例代码—— #defin

[C] zintrin.h : 智能引入intrinsic函数。支持VC、GCC,兼容Windows、Linux、Mac OS X

博客来源:http://blog.csdn.net/zyl910/article/details/8100744 现在很多编译器支持intrinsic函数,这给编写SSE等SIMD代码带来了方便.但是各个编译器略有差异,于是我编写了zintrin.h,智能引入intrinsic函数. 一.各种编译器的区别 1.1 Visual C++(Windows) 最早支持intrinsic函数的VC编译器是VC 6.0.它在装上Visual Studio 6.0 Service Pack 5.Visual

iOS应用支持IPV6,就那点事儿

本文转载至 http://www.jianshu.com/p/a6bab07c4062 果然是苹果打个哈欠,iOS行业内就得起一次风暴呀.自从5月初Apple明文规定所有开发者在6月1号以后提交新版本需要支持IPV6-Only的网络,大家便开始热火朝天的研究如何支持IPV6,以及应用中哪些模块目前不支持IPV6. 一.IPV6-Only支持是啥? 首先IPV6,是对IPV4地址空间的扩充.目前当我们用iOS设备连接上Wifi.4G.3G等网络时,设备被分配的地址均是IPV4地址,但是随着运营商和