linux ip相关结构体和转换函数

1. IP相关结构体

1.1 IPv4: struct sockaddr_in结构16字节

struct sockaddr_in固定占16个字节,内容和头文件如下

#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/socket.h>

struct in_addr {
    unsigned long s_addr;          // 4 bytes load with inet_pton()	按照网络字节顺序存储IP地址
};
struct sockaddr_in {
	short int sin_family;              /* AF_INET */
	unsigned short int sin_port;       /* Port number存储端口号(使用网络字节顺序) */
	struct in_addr sin_addr;           /* Internet address存储IP地址,使用in_addr这个数据结构 */
	unsigned char sin_zero[8];         /* Same size as struct sockaddr */
};//16字节

注意:sin_port和sin_addr都必须是网络字节序(NBO),一般可视化的数字都是主机字节序(HBO)。

sin_family常用取值:

  • AF_UNIX:表示本地通讯;
  • AF_INET:表示TCP/IP协议;

sin_family详细取值列表可查看:glibc源码bits/socket.h头文件。

实例

struct sockaddr_in addr_dst;
//addr_dst.sin_addr.s_addr =  htonl(3232235887);		//方式一
addr_dst.sin_addr.s_addr =  inet_addr("192.168.1.111");	//方式二
addr_dst.sin_port = htons(80);
addr_dst.sin_family = AF_INET;

1.2 IPv6: struct sockaddr_in6结构28字节

struct sockaddr_in6 固定占28个字节,内容和头文件如下

#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/socket.h>
typedef unsigned short int sa_family_t;
typedef uint16_t in_port_t;

struct in6_addr {
    union {
        uint8_t u6_addr8[16];
        uint16_t u6_addr16[8];
        uint32_t u6_addr32[4];
    } in6_u;

    #define s6_addr                 in6_u.u6_addr8
    #define s6_addr16               in6_u.u6_addr16
    #define s6_addr32               in6_u.u6_addr32
};
struct sockaddr_in6 {
    sa_family_t sin6_family;    /* AF_INET6 */
    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 */
};

1.3 IPv4内核使用: struct sockaddr结构16个字节

#include <sys/socket.h>

typedef unsigned short	sa_family_t;
struct sockaddr {  
     	sa_family_t sin_family;	/* 地址族address family, AF_xxx	*/
    	char sa_data[14]; 		/* 14 bytes of protocol address	*/
 }; 

1.4 IPv6内核使用: struct sockaddr_storage结构128字节

#include <sys/socket.h>

#if __WORDSIZE == 64
# define __ss_aligntype	__uint64_t
#else
# define __ss_aligntype	__uint32_t
#endif
#define _SS_PADSIZE \
  (_SS_SIZE - __SOCKADDR_COMMON_SIZE - sizeof (__ss_aligntype))

struct sockaddr_storage
{
   sa_family_t ss_family;      /* Address family */
    char __ss_padding[_SS_PADSIZE];
    __ss_aligntype __ss_align;	/* Force desired alignment.  */
};

实例:根据socket fd获取对端地址

/**@brief IP地址:包括IPv4和IPv6格式的地址
 */
typedef struct
{		/* 24 bytes */
	struct in_addr	v4;							///< IPv4地址
	struct in6_addr	v6;							///< IPv6地址
	unsigned char	res[4];
}U_IN_ADDR;

/**@brief: 从fd中获取对端地址,该地址是网络地址
 *@param[in] fd socket描述符
 *@param[out] U_IN_ADDR *ip 网络地址
 *@return 成功0     失败 -1
*/
int get_peer_addr(int fd, U_IN_ADDR *ip)
{
	socklen_t namelen = sizeof(struct sockaddr_storage);
	struct sockaddr_in peer4;
	struct sockaddr_in6 peer6;
	struct sockaddr_storage peer;

	bzero(&peer4, sizeof(struct sockaddr_in));
	bzero(&peer6, sizeof(struct sockaddr_in6));
	bzero(&peer, sizeof(struct sockaddr_storage));

	if ((fd <= 0) || (nullptr == ip))
	{
		return -1;
	}
	
	if(getpeername(fd, (struct sockaddr*)&peer, &namelen))
	{
		return -1;
	}
	if(AF_INET == peer.ss_family)
	{
		memcpy(&peer4, &peer, sizeof(struct sockaddr_in));
		ip->v4 = peer4.sin_addr;
	}
	else if(AF_INET6 == peer.ss_family)
	{
		memcpy(&peer6, &peer, sizeof(struct sockaddr_in6));
		
		if(IN6_IS_ADDR_V4MAPPED(&peer6.sin6_addr))
		{
			memcpy(&ip->v4.s_addr, (char *)peer6.sin6_addr.s6_addr+12, 4);
		}
		else
		{
			ip->v6 = peer6.sin6_addr;
		}
	}
	//printf("peer_addr: %s\n", inet_ntoa(ip->v4));
	
	return 0;
}

1.5 自定义U_IN_ADDR结构24字节

自定义U_IN_ADDR结构体包括IPv4和IPv6格式的地址占24个字节

/**@brief IP地址:包括IPv4和IPv6格式的地址
 */
typedef struct
{	/* 24 bytes */
	struct in_addr	v4;							///< IPv4地址,4 bytes
	struct in6_addr	v6;							///< IPv6地址,16 bytes
	unsigned char	res[4];
}U_IN_ADDR;

1.6 自定义SOCKET_ADDR_T结构28字节

自定义SOCKET_ADDR_T结构体兼容IP地址结构体占28个字节,union联合体同时只能用其中一种结构

/**@brief	兼容IP地址结构体
 */
typedef struct _ADDR_T
{   /* 28 bytes */
    union
    {
        struct sockaddr_in sin4;    /**< IPV4 地址 16字节*/
        struct sockaddr_in6 sin6;  	/**< IPV6 地址 28字节*/
    }SA;
}SOCKET_ADDR_T;

1.7 sockaddr和sockaddr_in的区别

https://blog.csdn.net/will130/article/details/53326740
https://www.cnblogs.com/zihaowang/p/4649548.html

程序员不应操作sockaddr,sockaddr是给操作系统用的。

程序员应使用sockaddr_in来表示地址,sockaddr_in区分了地址和端口,使用更方便。

2. IP相关转换函数

2.1 htonl()、htons()、ntohl()、ntohs()

描述

htonl() “Host to Network Long” // 将主机的无符号长整形数转换成网络字节顺序
htons() “Host to Network Short” // 将主机的无符号短整形数转换成网络字节顺序
ntohl() “Network to Host Long” // 将一个无符号长整形数从网络字节顺序转换为主机字节顺序
ntohs() “Network to Host Short” // 将一个无符号短整形数从网络字节顺序转换为主机字节顺序

声明

#include <arpa/inet.h>
uint32_t htonl(uint32_t hostlong); 
uint16_t htons(uint16_t hostshort); 
uint32_t ntohl(uint32_t netlong); 
uint16_t ntohs(uint16_t netshort);

注意

  1. 主机序(小端)和网络序(大端)是两种不同的存储方式,与系统架构有关;
  2. 本质上执行 htonl() 和 ntohl() 是一样的,同理 htons() 和 ntohs();
  3. 在小端序主机上,执行一次 htonl()、htons()、ntohl()、ntohs()就进行一次转换;
  4. 在大端序主机上,执行 htonl()、htons()、ntohl()、ntohs()本质上无效,始终等于原值;

参考
https://blog.csdn.net/ktpd_pro/article/details/56276994

2.2 inet_addr()点分十进制转网络字节序

描述
将点分十进制IP转化为网络字节序(二进制位的大端存储),Linux下优先用inet_aton代替;

声明

#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
typedef uint32_t in_addr_t;
in_addr_t inet_addr(const char *cp);

返回值
成功:返回32位二进制的网络字节序地址;
失败:返回INADDR_NONE;

/* Address indicating an error return. */
#define	INADDR_NONE		((unsigned long int) 0xffffffff)

注意:inet_addr失败时返回INADDR_NONE,但这个值会和255.255.255.255这个合法地址混淆

建议:linux下,可以用inet_aton代替,vs2005里没有这个函数。

2.3 inet_network()点分十进制转主机字节序

描述
将点分十进制IP转化为主机字节序(二进制位小端存储)。

声明

#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
typedef uint32_t in_addr_t;
in_addr_t inet_network(const char *cp);

返回值
成功:返回主机字节序对应的数;
失败:返回INADDR_NONE;

注意:当IP是255.255.255.255时,会认为这是个无效的IP地址。

2.4 inet_aton()点分十进制转网络字节序

描述
将点分十进制IP转化为网络字节序存放在addr中,并返回该网络字节序对应的整数。

声明

#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
int inet_aton(const char *cp, struct in_addr *inp);

返回值
成功:返回非零,地址有效;
失败:返回零;

2.5 inet_ntoa()网络序IP转点分十进制串

描述
该函数将一个网络字节顺序的IP地址转换为它所对应的点分十进制串。常用于打印ip地址信息。

注意:对inet_aton的调用传递的是指向结构的指针,而对inet_ntoa的调用传递的是结构本身;

声明

#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
char *inet_ntoa(struct in_addr in);

返回值
返回指向点分十进制字符串的指针。
注:该字符串在一个stati‐cally分配的缓冲区中返回,随后的调用将覆盖该缓冲区。

实例

struct sockaddr_in sock;
sock.sin_family = AF_INET;
//将字符串转换为in_addr类型
sock.sin_addr.S_un.S_addr =  inet_addr("192.168.1.111");
sock.sin_port = htons(5000);

//将in_addr类型转换为字符串
printf("inet_ntoa ip = %s\n",inet_ntoa(sock.sin_addr));

//结果输出:
//inet_ntoa ip = 192.168.1.111

2.6 inet_pton()字符串IP转网络地址结构

描述
该函数将字符串src转换为af地址族中的网络地址结构,然后将网络地址结构复制到dst。

inet_pton和inet_ntop对IPv4和IPv6地址都能进行处理,字母p代表presentation,字母n代表numeric;

https://www.cnblogs.com/warren-liuquan/p/3555945.html

声明

#include <arpa/inet.h>
int inet_pton(int af, const char *src, void *dst);

参数
af必须是AF_INET或AF_INET6。

返回值
成功:若函数成功,则返回1;
失败:如果src中不包含表示有效网络地址的字符串,则返回0指定的地址族。如果af不包含有效的地址家族,则返回-1并将errno设置为EAFNOSUPPORT。

实例

struct in_addr  ip;
struct sockaddr_in6 ipv6;
if(inet_pton(AF_INET, (char *)ddns_cfg.serverAddr, &ip) > 0)
{	
}
else if(inet_pton(AF_INET6, (char *)ddns_cfg.serverAddr, &ipv6) > 0)
{
}
else
{ 
	p_ddns->addr_type = PSIA_ADDR_TYPE_DOMAIN; /* host name */ 
}

原先使用inet_addr函数进行转换,inet_addr函数无法区别255.255.255.255和错误值,且错误时返回值不一定为-1,inet_pton无此问题 */
ret = inet_pton(AF_INET, (const char *)ip, (void *)&addr);
if(1 != ret)

2.7 inet_ntop()网络地址结构转字符串IP

描述
此函数将af地址家族中的网络地址结构src转换为字符串。结果字符串被复制到dst指向的缓冲区中,它必须是非空指针。调用者在参数大小中指定此缓冲区中可用的字节数。对于IPV4会将网络字节顺序的IP地址转换为它所对应的点分十进制串。

inet_pton和inet_ntop对IPv4和IPv6地址都能进行处理,字母p代表presentation,字母n代表numeric;

声明

#include <arpa/inet.h>
const char *inet_ntop(int af, const void *src,
                             char *dst, socklen_t size);

返回值
成功:如果成功,inet_ntop()将返回一个指向dst的非空指针。
失败:如果出现错误,则返回NULL,并设置errno表示错误。

实例

char *ptr = NULL;
char ipstr[64] = {0};
struct in_addr ipAddress;
ptr = inet_ntop(AF_INET, &ipAddress, ipstr, sizeof(ipstr));
if(ptr) 
	printf("ip:%s\n",ptr);

注意
inet_pton()和inet_ntop()两个函数的参数family既可以是AF_INET,也可以是AF_INET6。如果以不被支持的地址族作为family参数,两个函数都返回错误,并将errno置为EAFNOSUPPORT。

2.8 inet_addr与inet_aton比较

inet_addr与inet_aton不同在于,他的返回值为转换后的32位网络字节序二进制值,而不是作为出参返回,这样存在一个问题,他的返回值返回的有效IP地址为0.0.0.0到255.255.255.255,如果函数出错,返回常量值INADDR_NONE(这个值一般为一个32位均为1的值),这意味着点分二进制数串255.255.255.255(IPv4的有限广播地址)不能由此函数进行处理。

2.9 IP地址转换汇总

点十进制字符串IP—>整数形式:IPv4
inet_addr() 将点分十进制IP字符串转化为一个网络字节序的整数值(二进制位的大端存储);
inet_network() 将点分十进制IP字符串转化为一个主机字节序的整数值(二进制位小端存储);
inet_aton() 将点分十进制IP字符串转化为一个网络字节序的整数值(二进制位的大端存储);

区别1:
inet_addr和inet_aton转换后的整数是网络字节序;
inet_network转换后的整数是主机字节序;

区别2:
inet_addr和inet_network将255.255.255.255作为无效的IP地址
inet_aton将255.255.255.255作为有效的IP地址(大多数路由器认为255.255.255.255是有效地址)

整数形式–>点十进制字符串:
char *inet_ntoa(unsigned in_addr addr);//此函数的返回值会被下次调用返回值覆盖

3.三种给socket赋值地址的方法

struct sockaddr_in myadd;
inet_aton("10.175.25.100",&myaddr.sin_addr);		//方式1
myadd.sin_addr.s_addr = inet_addr("10.175.25.100");	//方式2
INADDR_ANY转不转NBO随便
myadd.sin_addr_s_addr = htonl(INADDR_ANY);			//方式3,非INADDR_ANY要用htonl,非协议转一遍使用第二种
myadd.sin_addr_s_addr = INADDR_ANY;

4. 参考资料

https://www.cnblogs.com/tanghuimin0713/p/3425936.html
https://www.cnblogs.com/shijingxiang/articles/4693208.html
https://blog.csdn.net/c_base_jin/article/details/60469622
https://www.cnblogs.com/warren-liuquan/p/3555945.html

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值