目录
主机字节序和网络字节序
在 Linux 系统中,主机字节序(Host Byte Order)和网络字节序(Network Byte Order)是两个重要的概念。
- 主机字节序是指 CPU 直接处理数据时使用的字节序。在 x86 架构的 Linux 系统中,主机字节序通常是小端字节序(Little Endian),即最低有效字节(Least Significant Byte)位于最低内存地址。
- 网络字节序是指在网络通信中使用的字节序。在 TCP/IP 协议中,网络字节序通常是大端字节序(Big Endian),即最高有效字节(Most Significant Byte)位于最低内存地址。
在进行网络编程时,经常需要在主机字节序和网络字节序之间进行转换。例如,在发送数据时,需要将主机字节序转换为网络字节序;在接收数据时,需要将网络字节序转换为主机字节序。
在 Linux 系统中,可以使用htonl()
、htons()
、ntohl()
和ntohs()
等函数进行字节序转换。这些函数的命名规则如下:
h
表示主机(Host)。n
表示网络(Network)。l
表示长整型(Long)。s
表示短整型(Short)。
例如,htonl()
函数将一个 32 位的整数从主机字节序转换为网络字节序,ntohl()
函数将一个 32 位的整数从网络字节序转换为主机字节序。
下面是一个简单的示例,展示了如何在 Linux 中进行字节序转换:
#include <iostream>
#include <arpa/inet.h>
int main() {
// 定义一个32位整数
uint32_t host = 0x12345678;
// 将主机字节序转换为网络字节序
uint32_t network = htonl(host);
// 将网络字节序转换为主机字节序
uint32_t host2 = ntohl(network);
// 打印结果
std::cout << "主机字节序:" << std::hex << host << std::endl;
std::cout << "网络字节序:" << std::hex << network << std::endl;
std::cout << "转换后的主机字节序:" << std::hex << host2 << std::endl;
return 0;
}
在上面的示例中,我们定义了一个 32 位的整数host整数
,然后使用htonl()
函数将其转换为网络字节序,并使用ntohl()
函数将其转换回主机字节序。最后,我们打印出转换前后的结果。
判断机器字节序
#include <iostream>
#include <endian.h>
// 判断机器字节序函数
bool isLittleEndian() {
return __BYTE_ORDER__ == LITTLE_ENDIAN;
}
void isLittleEndian02() {
union {
short value;
char union_bytes[sizeof(short)];
} test;
test.value = 0x0102;
if (test.union_bytes[0] == 1 && test.union_bytes[1] == 2) {
std::cout << "这台机器是大端字节序(big endian)" << std::endl;
} else if (test.union_bytes[0] == 2 && test.union_bytes[1] == 1) {
std::cout << "这台机器是小端字节序(little endian)" << std::endl;
} else {
std::cout << "unknown..." << std::endl;
}
}
int main() {
if (isLittleEndian()) {
std::cout << "这台机器是小端字节序(little endian)" << std::endl;
} else {
std::cout << "这台机器是大端字节序(big endian)" << std::endl;
}
isLittleEndian02();
return 0;
}
/*
这台机器是小端字节序(little endian)
这台机器是小端字节序(little endian)
*/
函数isLittleEndian(),它使用__BYTE_ORDER__和__LITTLE_ENDIAN__宏来判断机器的字节序。如果__BYTE_ORDER__等于__LITTLE_ENDIAN__,则说明机器是小端字节序;否则,机器是大端字节序。
函数isLittleEndian02(),在这个代码中,使用了一个union
(联合体)来同时访问一个 16 位整数的不同字节。在这个union
中,value
成员是一个 16 位整数,而union_bytes
成员是一个字符数组,长度为sizeof(short)
,即 2。
首先,将 0x0102 赋值给value
成员。然后,检查union_bytes
成员中的第一个和第二个字节的值。如果第一个字节的值为 1,第二个字节的值为 2,那么这台机器是大端字节序;如果第一个字节的值为 2,第二个字节的值为 1,那么这台机器是小端字节序;否则,字节序是未知的。
通用socket地址
在 Linux 中,sockaddr
结构体用于表示网络套接字的地址信息。它是一个通用的数据结构,用于指定套接字连接的源或目标地址。
以下是sockaddr
结构体的定义:
struct sockaddr {
sa_family_t sa_family; /* 地址家族 */
char sa_data[14]; /* 协议地址 */
};
其中,sa_family_t sa_family
字段表示地址家族,它指定了使用的网络协议族(如 IPv4、IPv6 等)。char sa_data[14]
字段是一个字符数组,用于存储具体的协议地址。
专用socket地址
对于不同的地址家族,sockaddr
结构体可能会有不同的扩展结构体。例如,对于 IPv4 地址,使用struct sockaddr_in
结构体;对于 IPv6 地址,使用struct sockaddr_in6
结构体。这些扩展结构体包含了更详细的地址信息。
以下是 unix 本地域协议族的struct sockaddr_un
结构体的定义:
#include <sys/un.h>
// unix本地域协议族
struct sockaddr_un {
sa_family_t sin_family; /* 地址族:AF_UNIX */
char sun_path[108]; /* 文件路径名 */
};
以下是 IPv4 地址的struct sockaddr_in
结构体的定义:
#include </usr/include/netinet/in.h>
// ipv4专用结构体
struct sockaddr_in {
sa_family_t sin_family; /* 地址族:AF_INET */
u_int16_t sin_port; /* 端口号,要用网络字节序表示 */
struct in_addr sin_addr; /* ipv4地址结构体,见下面 */
};
/* Internet address. */
typedef uint32_t in_addr_t;
struct in_addr {
in_addr_t s_addr; /* ipv4地址,要用网络字节序表示 */
};
其中,sin_family
字段仍然表示地址家族,sin_port
字段表示端口号,sin_addr
字段表示 IPv4 地址。
以下是 IPv6 地址的struct sockaddr_in6
结构体的定义:
#include </usr/include/netinet/in.h>
// ipv6专用结构体
struct sockaddr_in6 {
sa_family_t sin_family; /* 地址族:AF_INET6 */
u_int16_t sin6_port; /* 端口号,要用网络字节序表示 */
u_int32_t sin6_flowinfo; /* 流信息,应设置为0 */
struct in6_addr sin6_addr; /* ipv6地址结构体,见下面 */
u_int32_t sin6_scope_id; /* scpoe ID,尚处于实验阶段*/
};
#if !__USE_KERNEL_IPV6_DEFS
/* IPv6 address */
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
#ifdef __USE_MISC
# define s6_addr16 __in6_u.__u6_addr16
# define s6_addr32 __in6_u.__u6_addr32
#endif
};
#endif /* !__USE_KERNEL_IPV6_DEFS */
这段代码定义了用于IPv6的两个结构体:sockaddr_in6
和in6_addr
。以下是它们的具体说明:
-
sockaddr_in6 结构体:
sa_family_t sin_family
: 这是一个表示地址族的数据类型。对于IPv6地址,这个字段的值应该是AF_INET6
。u_int16_t sin6_port
: 这是一个16位的无符号整数,表示端口号。注意,这个值通常应该使用网络字节序,而不是主机字节序。u_int32_t sin6_flowinfo
: 这是一个32位的无符号整数,用于表示IPv6流标签。在大多数情况下,这个字段的值应该设置为0。struct in6_addr sin6_addr
: 这是一个in6_addr
结构体,用于表示IPv6地址。u_int32_t sin6_scope_id
: 这是一个32位的无符号整数,用于表示作用域ID。这个字段在一些特殊的网络环境中使用,比如多点传送地址。需要注意的是,这个字段在某些系统或库中可能还处于实验阶段。
-
in6_addr 结构体:
这个结构体表示一个IPv6地址,由16个字节组成。为了方便访问这些字节,它提供了一个联合(union),使得可以通过多种方式访问这16个字节:
* `__u6_addr8[16]`: 这是一个包含16个元素的数组,每个元素是一个8位的无符号整数。通过这个数组,可以直接访问IPv6地址中的每一个字节。
* `__u6_addr16[8]`: 这是一个包含8个元素的数组,每个元素是一个16位的无符号整数。通过这个数组,可以按16位为一组访问IPv6地址。
* `__u6_addr32[4]`: 这是一个包含4个元素的数组,每个元素是一个32位的无符号整数。通过这个数组,可以按32位为一组访问IPv6地址。
此外,这个结构体还定义了一些宏,用于简化对这些字段的访问:
* `s6_addr`: 这是一个宏,用于访问`__u6_addr8`数组。
* `s6_addr16`: 这是一个宏,用于访问`__u6_addr16`数组。需要注意的是,这个宏在`__USE_MISC`被定义时才可用。
* `s6_addr32`: 这是一个宏,用于访问`__u6_addr32`数组。同样,这个宏也在`__USE_MISC`被定义时才可用。
注意:__USE_KERNEL_IPV6_DEFS
和__USE_MISC
是预处理器宏,它们可能在不同的系统或编译环境中有不同的定义。这些宏通常用于控制哪些代码片段被包含或排除在编译过程中。
ip地址转换函数
在Linux下,如果你想要进行IP地址的转换,通常涉及的操作包括点分十进制(dotted-decimal)表示法和整数表示法之间的转换,或者进行网络地址和主机地址的计算等。这里提供一些常见的转换方法。
-
点分十进制到整数的转换:
你可以使用标准的库函数,如inet_aton
和inet_ntoa
,但这些函数主要用于IPv4地址。对于IPv6地址,你需要使用inet_pton
和inet_ntop
。inet_aton
: 将点分十进制的IPv4地址转换为网络字节序的整数形式。inet_ntoa
: 将网络字节序的整数形式转换为点分十进制的IPv4地址。inet_pton
: 将点分十进制(IPv4)或冒号分隔的十六进制(IPv6)转换为二进制格式。inet_ntop
: 将二进制格式转换为点分十进制(IPv4)或冒号分隔的十六进制(IPv6)。
/* Convert from presentation format of an Internet number in buffer
starting at CP to the binary network format and store result for
interface type AF in buffer starting at BUF. */
extern int inet_pton (int __af, const char *__restrict __cp,
void *__restrict __buf) __THROW;
/* Convert a Internet address in binary network format for interface
type AF in buffer starting at CP to presentation form and place
result in buffer of length LEN astarting at BUF. */
extern const char *inet_ntop (int __af, const void *__restrict __cp,
char *__restrict __buf, socklen_t __len)
__THROW;
示例代码(使用inet_pton
和inet_ntop
):
#include <stdio.h>
#include <string.h>
#include <arpa/inet.h>
int main() {
char ip_str[] = "192.0.2.1";
struct in_addr ip_addr;
// 点分十进制到整数(网络字节序的整数形式)的转换
if (inet_pton(AF_INET, ip_str, &ip_addr) != 1) {
perror("inet_pton");
return 1;
}
printf("%x\n", ip_addr.s_addr);
// 整数(网络字节序的整数形式)到点分十进制的转换
char buffer[INET_ADDRSTRLEN];
if (inet_ntop(AF_INET, &ip_addr, buffer, sizeof(buffer)) == NULL) {
perror("inet_ntop");
return 1;
}
printf("%s\n", buffer);
return 0;
}
/*
10200c0
192.0.2.1
*/
- 自定义函数进行转换:
如果你想要手动进行转换,而不使用库函数,你可以编写自己的函数来处理字符串和数字之间的转换。
例如,一个简单的点分十进制到整数的转换函数可以这样写:
#include <stdint.h>
#include <stdio.h>
uint32_t ip_string_to_int(const char *ip) {
uint32_t result = 0;
char *end;
for (int i = 0; i < 4; ++i) {
result <<= 8;
unsigned long part = strtoul(ip, &end, 10);
if (end == ip || part > 255) {
return 0; // 无效IP
}
result |= part;
ip = end + 1;
}
return result;
}
int main() {
const char *ip_str = "192.0.2.1";
uint32_t ip_int = ip_string_to_int(ip_str);
printf("IP as integer: %u\n", ip_int);
return 0;
}
标签:__,addr,ip,地址,API,字节,inet,socket
From: https://www.cnblogs.com/yubo-guan/p/18010217