目前ipv6的应用越来越广泛,慢慢将变成强制的标准,做网络应用开发的,在未来会越来越的要开始支持ipv6,但是ipv6肯定不是一下子就可以大范围应用的,必然有一段时间Ipv4与ipv6共存,要做到对ipv4和ipv6的同时支持。
关于如何让程序支持ipv6 可以参考 https://blog.csdn.net/v6543210/article/details/106927210
示例代码见: https://github.com/alongL/ipv6_demo
这个文章只讲如何兼容ipv4和ipv6
如果使用asio这样成熟的网络库,就无需再看此文,asio已经将底层的差别屏蔽。
这里只讨论从socket层面自已开发网络库时,支持ipv4 和ipv6会遇到的情况。
1.地址表示
使用这样的结构体既可以表示ipv4的地址,又可以表示ipv6地址
struct myaddrinfo{
int ai_family;
union
{
struct sockaddr addr;
struct sockaddr_in addr4;
struct sockaddr_in6 addr6;
};
};
union联合体中的三个结构体 大概如下所示(与标准有一些区别,主要方便理解): 源自https://blog.csdn.net/albertsh/article/details/80991684
/* Structure describing a generic socket address. */
struct sockaddr
{
uint16 sa_family; /* Common data: address family and length. */
char sa_data[14]; /* Address data. */
};
/* Structure describing an Internet socket address. */
struct sockaddr_in
{
uint16 sin_family; /* Address family AF_INET */
uint16 sin_port; /* Port number. */
uint32 sin_addr.s_addr; /* Internet address. */
unsigned char sin_zero[8]; /* Pad to size of `struct sockaddr'. */
};
/* Ditto, for IPv6. */
struct sockaddr_in6
{
uint16 sin6_family; /* Address family AF_INET6 */
uint16 sin6_port; /* Transport layer port # */
uint32 sin6_flowinfo; /* IPv6 flow information */
uint8 sin6_addr[16]; /* IPv6 address */
uint32 sin6_scope_id; /* IPv6 scope-id */
};
2.地址使用
此处由于windows平台下和linux平台下对sockaddr_in6的定义有些区别,所以需要尽量使用两个平台下定义相同的变量,避免使用系统中定义名称的变量。
获取ip地址
//返回ipv4 地址
static uint32_t get_ipv4(const myaddrinfo &addr)
{
return addr.addr4.sin_addr.s_addr;
}
//返回ipv6 地址
static in6_addr get_ipv6(const myaddrinfo &addr)
{
return addr.addr6.sin6_addr;
}
ip地址字符串
//返回ipv4或v6地址字符串
static inline std::string get_ipstr(const myaddrinfo &addr)
{
if (addr.ai_family == AF_INET)
{
char buf[INET_ADDRSTRLEN] = "";
inet_ntop(AF_INET, (void*) &(addr.addr4.sin_addr), buf, INET_ADDRSTRLEN);
return std::string(buf);
}
else if (addr.ai_family == AF_INET6)
{
char buf[INET6_ADDRSTRLEN] = "";
inet_ntop(AF_INET6, (void*) &(addr.addr6.sin6_addr), buf, INET6_ADDRSTRLEN);
return std::string(buf);
}
else return std::string("");
}
获取端口号
static inline uint16_t get_port(const myaddrinfo &addr)
{
if (addr.ai_family == AF_INET)
{
return ntohs(addr.addr4.sin_port);
}
else if (addr.ai_family == AF_INET6)
{
return ntohs(addr.addr6.sin6_port);
}
else return 0;
}
4. ipv4到ipv6的地址映射
如果程序监听的是 ::0 地址,即IPv6地址, 对于同时具有ipv4地址和 ipv6地址的 双栈服务器来讲, 会同时监听ipv4和ipv6协议栈上的某个端口,客户端连接服务器的时候,如果使用的是ipv4连接的,显示出的结果是 ::ff:192.168.3.8这样经过映射的ipv6的地址。
如果要判断是否是ipv4映射的地址,可以用如下办法
//是否是v4映射的v6地址
static bool is_v4mapped(const myaddrinfo &addr)
{
struct Prefix {
int64_t a = 0x0;
int16_t b = 0x0000;
int16_t c = 0xFFFF;
};
static const Prefix prefix;
static const int PREFIX_SIZE = 12;
// [00:00:00:00:00 :00:00:00:00:00 :ff:ff] 共12个字节
if (memcmp(&prefix, &(addr.addr6.sin6_addr), PREFIX_SIZE) == 0)
return true;
else
return false;
}
//ipv4地址,映射前的地址 (网络字节序)
static uint32_t get_ipv4_inv6(const myaddrinfo &addr)
{
if (addr.ai_family == AF_INET)
return addr.addr4.sin_addr.s_addr;
int32_t v4 = *(int32_t*)(addr.addr6.sin6_addr.s6_addr + 12);
return v4;
}
更详细的例子可以参考:
https://github.com/alongL/ipv6_demo
参考:
nginx 的实现 https://github.com/nginx/nginx/blob/912fb44e25c6ab2598e36b4544c709b871251b2e/src/core/ngx_inet.h#L44