实现一个模块,可以通过hostname获取目标的IP地址,并且保存IPv4和IPv6地址,
用户可以选择IPv4和或Pv6地址进行通信,模块来创建socket,返回给用户进行操作。
结构体sockaddr、sockaddr_in、sockaddr_in6之间的区别和联系
正文部分,参照了以上文章,这篇文章对源码结构的定义的分析很好。
Socket编程中,一般地址管理有3种结构体:
sockaddr、sockaddr_in、sockaddr_in6
sockaddr_in结构用于IPv4,sockaddr_in6结构用于IPv6通信。
他们结构内部成员是不一样的。
他们的结构如下:
/* /usr/include/bits/socket.h */
/* Structure describing a generic socket address. */
struct sockaddr
{
__SOCKADDR_COMMON (sa_); /* Common data: address family and length. */
char sa_data[14]; /* Address data. */
};
↑三个结构中,__SOCKADDR_COMMON 部分是一样的,只是宏参数不同,其实都是short类型(2字节),表示family;
sockaddr 独有的sa_data数组,14字节。
/* /usr/include/netinet/in.h */
/* Structure describing an Internet socket address. */
struct sockaddr_in
{
__SOCKADDR_COMMON (sin_);
in_port_t sin_port; /* Port number. */
struct in_addr sin_addr; /* Internet address. */
/* Pad to size of `struct sockaddr'. */
unsigned char sin_zero[sizeof (struct sockaddr) -
__SOCKADDR_COMMON_SIZE -
sizeof (in_port_t) -
sizeof (struct in_addr)];
};
↑__SOCKADDR_COMMON,2字节;
sin_port,表示port,2字节
sin_addr,表示IP地址,4字节
sin_zero,8字节,填充尾部
/* /usr/include/netinet/in.h */
#ifndef __USE_KERNEL_IPV6_DEFS
/* Ditto, for IPv6. */
struct sockaddr_in6
{
__SOCKADDR_COMMON (sin6_);
in_port_t sin6_port; /* Transport layer port # */
uint32_t sin6_flowinfo; /* IPv6 flow information */
struct in6_addr sin6_addr; /* IPv6 address */
uint32_t sin6_scope_id; /* IPv6 scope-id */
};
#endif /* !__USE_KERNEL_IPV6_DEFS */
↑ __SOCKADDR_COMMON ,2字节
sin6_port,表示port,2字节
sin6_flowinfo,4字节
sin6_addr,16字节 (in6_addr 是一个联合体,占16字节)
sin6_scope_id,4字节
所以sockaddr和sockaddr_in 大小相同,16字节,sockaddr_in6 28字节。
因为sockaddr和sockaddr_in的大小相同,在IPv4编程的时候,才可以把sockaddr_in的指针转换成sockaddr的指针,例如:
//ipv4 dup
sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = inet_addr("255.255.255.255");
addr.sin_port = htons(9100);
int res = sendto(m_local_fd, buff, strlen(buff), 0,
(sockaddr *)&addr, sizeof(addr));
IPv6编程的时候,也会把sockaddr_in6的指针转换为sockaddr的指针(例如下面代码),但是这里应该只是为了标准化,
sockaddr_in6的结构和sockaddr在开头的__SOCKADDR_COMMON 部分以外,有很大不同,所以应该是有些额外的处理的,而不是想sockaddr_in指针到sockaddr指针,这么轻易的转换。
//ipv6 udp
sockaddr_in6 addr;
addr.sin6_family = AF_INET6;
inet_pton(AF_INET6, buff, &addr.sin6_addr);
addr.sin6_port = htons(9100);
addr.sin6_scope_id = if_nametoindex("ens33");
res = sendto(m_local_fd, buff, strlen(buff), 0,
(sockaddr *)&addr, sizeof(addr));
sockaddr_in和sockaddr_in6的结构不一样,地址存储在结构中的位置也不一样。
所以设计的模块中,必有要兼容sockaddr_in和sockaddr_in6两个结构。
用getaddrinfo()来获取。
[参考文章[()
地址类的设计:
它可以保存IPv4地址和IPv6地址、hostname。
添加了bIsIPv4addr和bIsIPv6addr标识,标记Ipv4地址和Ipv6地址是否有效。
class Target_IPaddr
{
public:
Target_IPaddr():bIsIPv4addr(false), bIsIPv6addr(false), bISIpv6Selected(false){}
~Target_IPaddr() {}
public:
void SetHostname(const char* hostname);
void SetIpv4Addr(sockaddr_in& a_IPv4_addr);
void SetIpv6Addr(sockaddr_in6& a_IPv6_addr);
sockaddr_in GetIpv4Addr();
sockaddr_in6 GetIpv6Addr();
std::string GetHostname();
bool SetUserSelecIpv6(bool b);
bool IsUserSelecIpv6();
private:
bool bIsIPv4addr;
bool bIsIPv6addr;
bool bISIpv6Selected;
sockaddr_in m_IPv4_addr;
sockaddr_in6 m_IPv6_addr;
std::string m_hostname;
};
管理模块的设计
在它内部包含了Target_IPaddr对象用来进行地址管理。
他的public接口,用来供用户调用,用户需要输入目标设备的hostname。
IPaddr_Controller通过getaddrinfo()来获取目标设备的IPv4和Ipv6地址,控制台输出。
等待用户选择是用IPv4地址还是IPv6地址通信。
用户选择后,会发送一条用户示例消息“hello client”出去。
class IPaddr_Controller
{
protected:
IPaddr_Controller() {
WSADATA lpWSAData;
WSAStartup(MAKEWORD(2, 2), &lpWSAData);
bIsLocalSupportIPv6 = IsSupportIpv6();
pIPaddr = new Target_IPaddr();
conn_socket = INVALID_SOCKET;
local_ipv6_scope_id = 0;
}
~IPaddr_Controller() {
if (pIPaddr)
delete pIPaddr;
WSACleanup();
}
public:
static IPaddr_Controller& CreateIPaddrCtrl()
{
static IPaddr_Controller IPaddrCtl;
return IPaddrCtl;
}
public:
bool GetIPaddrbyHostname(const char* hostname);
bool UserChoice();
bool SendMsg(const char* msg, int length);
private:
bool IsSupportIpv6();
bool IsSysSupportIpv6();
bool IsGetLocalIpv6Addr();
bool InitSocket();
private:
bool bIsLocalSupportIPv6;
Target_IPaddr* pIPaddr;
char data[256] = { 0x00 };
private:
int conn_socket;
public:
ULONG local_ipv6_scope_id;
};
bool IPaddr_Controller::GetIPaddrbyHostname(const char* hostname)
{
bool ret = false;
struct addrinfo hints, * res = NULL, * p = NULL;
memset(&hints, 0, sizeof(hints));
int e;
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_STREAM;
e = getaddrinfo(hostname, "9100", &hints, &res);
if (e != 0)
{
std::cout << "get host:" << hostname << " addrinfo error:" << WSAGetLastError() << std::endl;
return false;
}
p = res;
//这里假设只会获取到一个Ipv4地址和一个Ipv6地址
bool bNotFindIpv6 = true;;
while (p != NULL)
{
if (p->ai_family == AF_INET)
{
this->pIPaddr->SetIpv4Addr(*(sockaddr_in*)p->ai_addr);
}
else if (p->ai_family == AF_INET6)
{
this->pIPaddr->SetIpv6Addr(*(sockaddr_in6*)p->ai_addr);
bNotFindIpv6 = false;
}
p = p->ai_next;
}
if (bNotFindIpv6)
{
const char* buff = "2409:8a1e:6a62:4f80:20c:29ff:fe22:ff89";
sockaddr_in6 tmp_addr;
tmp_addr.sin6_family = AF_INET6;
inet_pton(AF_INET6, buff, &tmp_addr.sin6_addr);
tmp_addr.sin6_port = htons(9100);
tmp_addr.sin6_scope_id = this->local_ipv6_scope_id;
this->pIPaddr->SetIpv6Addr(tmp_addr);
}
this->pIPaddr->SetHostname(hostname);
freeaddrinfo(res);
if (UserChoice())
return InitSocket();
else
return false;
}
bool IPaddr_Controller::IsSupportIpv6()
{
if (!IsSysSupportIpv6())
return false;
if (!IsGetLocalIpv6Addr())
return false;
return true;
}
bool IPaddr_Controller::IsSysSupportIpv6()
{
bool bIPv6Env = FALSE;
OSVERSIONINFO VersionInformation;
VersionInformation.dwOSVersionInfoSize = sizeof(OSVERSIONINFO);
GetVersionEx(&VersionInformation);
//VER_PLATFORM_WIN32_NT:
//操作系统是 Windows 7、Windows Server 2008、Windows Vista、Windows Server 2003、Windows XP 或 Windows 2000。
if (VersionInformation.dwPlatformId == VER_PLATFORM_WIN32_NT) {
//windows xp及以后
if ((VersionInformation.dwMajorVersion >= 5 && VersionInformation.dwMinorVersion >= 1) ||
//Windows Vista 以后
VersionInformation.dwMajorVersion >= 6)
bIPv6Env = TRUE;
}
return bIPv6Env;
}
//获取本地的地址信息,如果包含AFINET6 family就判定支持IPv6
#define USE_LOCAL_HOST_NAME 0
//1:获取本地hostname 根据host name 查询IP family
//0:制定一个端口号,和AI_PASSIVE,获取本地任意地址,查询IP family
bool IPaddr_Controller::IsGetLocalIpv6Addr()
{
bool ret = false;
#if USE_LOCAL_HOST_NAME
get local host name
//char errmsg[256] = { 0x00 };
//struct in_addr addr;
//char local_host_name[256] = { 0x00 };
//if (gethostname(local_host_name, 256))
//{
// std::cout << "Get local host name failed, err:" <<
// strerror_s(errmsg, 256, WSAGetLastError()) << std::endl;
// return false;
//}
//std::cout << "local_host_name:" << local_host_name << std::endl;
//std::cout << std::endl;
#endif
struct addrinfo hints, * res = NULL, * p = NULL;
memset(&hints, 0, sizeof(hints));
int e;
#if USE_LOCAL_HOST_NAME
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_STREAM;
e = getaddrinfo(local_host_name, NULL, &hints, &res);
#else
hints.ai_flags = AI_PASSIVE;
e = getaddrinfo(NULL, "8080", &hints, &res);
#endif
if (e != 0)
{
std::cout << "get local addrinfo error:" << WSAGetLastError() << std::endl;
return false;
}
p = res;
char bufff[512] = { 0x00 };
while (p != NULL)
{
if (p->ai_family == AF_INET)
{
std::cout << "support IPv4" << std::endl;
memset(bufff, 0x00, sizeof(bufff));
inet_ntop(AF_INET, &((sockaddr_in*)p->ai_addr)->sin_addr, bufff, sizeof(bufff));
std::cout << "local support IPv4 addr:" << bufff << std::endl;
}
else if (p->ai_family == AF_INET6)
{
std::cout << "support IPv6" << std::endl;
memset(bufff, 0x00, sizeof(bufff));
inet_ntop(AF_INET6, &((sockaddr_in6*)p->ai_addr)->sin6_addr, bufff, sizeof(bufff));
std::cout << "local support IPv4 addr:" << bufff << std::endl;
std::cout << "ipv6 socopeid:" << ((sockaddr_in6*)p->ai_addr)->sin6_scope_id << std::endl;
this->local_ipv6_scope_id = ((sockaddr_in6*)p->ai_addr)->sin6_scope_id;
ret = true;
}
p = p->ai_next;
}
freeaddrinfo(res);
return ret;
}
添加到附件资源了。
我用这个代码,输入我本地ubuntu虚拟机的hostname,只能获取到IPv4地址,获取不到IPv6地址。
但是输入“www.baidu.com”的域名,是可以获取到IPv4地址和IPv6地址的。
我不知道虚拟机有什么设置,导致无法获取IPv6地址,其实在本机用Ping -6 命令是可以ping通虚拟机的。
结构体sockaddr、sockaddr_in、sockaddr_in6之间的区别和联系
如何实现在一个 Socket 应用程序中同时支持 IPv4 和 IPv6
c++对ipv4和ipv6地址的兼容处理
让你的Socket应用兼容IPv6
C++ IPv4与IPv6的兼容编码
如何让C++程序代码支持IPv6?