c++对ipv4和ipv6地址的兼容处理

目前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

 

 

 

参考:

https://stackoverflow.com/questions/26531531/efficient-way-to-store-ipv4-ipv6-addresses/63842120#63842120

nginx 的实现 https://github.com/nginx/nginx/blob/912fb44e25c6ab2598e36b4544c709b871251b2e/src/core/ngx_inet.h#L44

ipv6编程指引 http://www.6ip.cu/pdf/long_trans_app_ipv6.pdf

https://stackoverflow.com/questions/14272656/how-to-resolve-ipv4-address-from-ipv4-mapped-ipv6-address

  • 7
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
以下是一个使用gsoap实现同时支持ipv4和ipv6的设备发现的C++代码示例: ```c++ #include "soapH.h" #include "soapStub.h" #include <iostream> #include <cstring> #include <netdb.h> #include <arpa/inet.h> using namespace std; int main(int argc, char *argv[]) { struct soap soap; soap_init(&soap); // 设置设备发现的多播地址 const char *multicast_addr = "239.255.255.250"; // ipv4多播地址 const char *multicast_addr_v6 = "ff02::c"; // ipv6多播地址 const int multicast_port = 1900; // 设备发现使用的端口号 // 创建socket int sockfd = socket(AF_INET6, SOCK_DGRAM, IPPROTO_UDP); if (sockfd < 0) { perror("socket"); return -1; } // 设置IPV6_MULTICAST_IF选项,指定通过哪个网络接口来发送多播数据包 struct sockaddr_in6 sin6; memset(&sin6, 0, sizeof(sin6)); sin6.sin6_family = AF_INET6; sin6.sin6_port = htons(multicast_port); sin6.sin6_addr = in6addr_any; if (setsockopt(sockfd, IPPROTO_IPV6, IPV6_MULTICAST_IF, &sin6, sizeof(sin6)) < 0) { perror("setsockopt"); close(sockfd); return -1; } // 设置IPV6_V6ONLY选项,表示socket同时支持ipv4和ipv6 int opt = 0; if (setsockopt(sockfd, IPPROTO_IPV6, IPV6_V6ONLY, &opt, sizeof(opt)) < 0) { perror("setsockopt"); close(sockfd); return -1; } // 加入多播组,以接收设备发现响应 struct ip_mreq mreq; memset(&mreq, 0, sizeof(mreq)); mreq.imr_multiaddr.s_addr = inet_addr(multicast_addr); mreq.imr_interface.s_addr = htonl(INADDR_ANY); if (setsockopt(sockfd, IPPROTO_IP, IP_ADD_MEMBERSHIP, &mreq, sizeof(mreq)) < 0) { perror("setsockopt"); close(sockfd); return -1; } // 加入ipv6多播组,以接收设备发现响应 struct ipv6_mreq mreq_v6; memset(&mreq_v6, 0, sizeof(mreq_v6)); if (inet_pton(AF_INET6, multicast_addr_v6, &mreq_v6.ipv6mr_multiaddr) < 0) { perror("inet_pton"); close(sockfd); return -1; } mreq_v6.ipv6mr_interface = 0; if (setsockopt(sockfd, IPPROTO_IPV6, IPV6_JOIN_GROUP, &mreq_v6, sizeof(mreq_v6)) < 0) { perror("setsockopt"); close(sockfd); return -1; } // 设置socket为非阻塞模式 int flags = fcntl(sockfd, F_GETFL, 0); if (flags < 0) { perror("fcntl"); close(sockfd); return -1; } flags |= O_NONBLOCK; if (fcntl(sockfd, F_SETFL, flags) < 0) { perror("fcntl"); close(sockfd); return -1; } // 发送设备发现请求 struct sockaddr_in6 dest_addr; memset(&dest_addr, 0, sizeof(dest_addr)); dest_addr.sin6_family = AF_INET6; dest_addr.sin6_port = htons(multicast_port); if(inet_pton(AF_INET6, multicast_addr_v6, &dest_addr.sin6_addr) < 0) { perror("inet_pton"); close(sockfd); return -1; } if (sendto(sockfd, "M-SEARCH * HTTP/1.1\r\n\r\n", 25, 0, (struct sockaddr*)&dest_addr, sizeof(dest_addr)) < 0) { perror("sendto"); close(sockfd); return -1; } // 接收设备发现响应 const int buf_size = 1024; char buf[buf_size]; struct sockaddr_storage src_addr; socklen_t src_addr_len; src_addr_len = sizeof(src_addr); while (1) { int n = recvfrom(sockfd, buf, buf_size - 1, 0, (struct sockaddr*)&src_addr, &src_addr_len); if (n < 0) { if (errno == EAGAIN || errno == EWOULDBLOCK) { // 没有数据可接收,继续等待 continue; } else { perror("recvfrom"); close(sockfd); return -1; } } buf[n] = '\0'; // 解析设备发现响应中的IP地址和端口号 char ip_str[INET6_ADDRSTRLEN]; int port; if (src_addr.ss_family == AF_INET) { struct sockaddr_in *sin = (struct sockaddr_in *)&src_addr; inet_ntop(AF_INET, &(sin->sin_addr), ip_str, INET_ADDRSTRLEN); port = ntohs(sin->sin_port); } else if (src_addr.ss_family == AF_INET6) { struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *)&src_addr; inet_ntop(AF_INET6, &(sin6->sin6_addr), ip_str, INET6_ADDRSTRLEN); port = ntohs(sin6->sin6_port); } else { continue; } // 输出设备发现响应中的IP地址和端口号 cout << "Device found: " << ip_str << ":" << port << endl; } // 关闭socket close(sockfd); soap_destroy(&soap); soap_end(&soap); soap_done(&soap); return 0; } ``` 这个示例代码使用了IPv6的socket来同时支持IPv4和IPv6。使用setsockopt()函数分别设置IPV6_MULTICAST_IF和IPV6_V6ONLY选项,然后分别加入IPv4和IPv6多播组,以接收设备发现响应。在接收响应时,需要根据地址族(IPv4或IPv6)来解析IP地址和端口号。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

路边闲人2

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值