socket
目标进程的标识(套接字)由两部分组成:
- IP地址:标识出计算机所在的网络位置。
- 端口号:标识出计算机上特定的进程。
字节序
字节序定义了多字节数据在内存中的存储顺序,是处理器架构的特性。字节序有两种:
- 大端字节序:最高地址存储最低有效字节。
- 小端字节序:最低地址存储最低有效字节。
eg. 检测本机字节序
#include <stdio.h>
// 函数用于检测字节序
void byteorder() {
// 定义一个共用体,用于检查字节序
union {
int value; // 整型变量
char union_bytes[sizeof(int)]; // 存储整型变量的字节数组
} test;
// 最高有效字节是4 最低有效字节是1
test.value = 0x04030201;
// 根据字节数组的内容判断字节序
if (test.union_bytes[0] == 4) {
printf("big endian\n"); // 大端序:最低字节地址存储最高有效字节
} else if (test.union_bytes[0] == 1) {
printf("little endian\n"); // 小端序:最低字节地址存储最低有效字节
} else {
printf("unknown\n");
}
}
int main() {
byteorder();
return 0;
}
无论什么字节序,最高有效字节总在最左边,最低有效字节总在最右边。一个32位整数0x04030201,最高有效字节是4,最低有效字节是1。若将字符指针cp强行转换到这个整型数地址,在小端序处理器上,cp[0]指向1;在大端序处理器上,cp[0]指向4。
网络字节序
在网络协议中(如 TCP/IP)通常使用大端序来确保不同系统间的数据一致性。Linux提供4个函数完成主机字节序和网络字节序的转换。
#include <netinet/in.h>
// 将32位主机字节序整数转换为网络字节序
uint32_t htonl(uint32_t hostint32);
// 将16位主机字节序整数转换为网络字节序
uint16_t htons(uint16_t hostint16);
// 将32位网络字节序整数转换为主机字节序
uint32_t ntohl(uint32_t netint32);
// 将16位网络字节序整数转换为主机字节序
uint16_t ntohs(uint16_t netint16);
长整型函数通常用来转换IP地址,短整型函数用来转换端口号。
地址格式
常见地址族
套接字地址是用来标识特定套接字的通信端点,并且它的格式取决于所使用的地址族。每个地址族(如 IPv4、IPv6、Unix 域等)有其特定的地址格式和解析规则。常见的地址族包括:
描述 | 协议族 | 地址族 | 地址值含义和长度 |
---|---|---|---|
UNIX本地协议族 | PF_UNIX | AF_UNIX | 文件系统中的路径,长度可达108字节 |
TCP/IPv4协议族 | PF_INET | AF_INET | 16bit端口号和32bitIPv4地址,共6字节 |
TCP/IPv6协议族 | PF_INET6 | AF_INET6 | 16bit端口号,32bit流标识和128bitIPv6地址,共26字节 |
Linux系统通用socket地址结构体
#include <bits/socket.h>
// 通用套接字地址结构体
struct sockaddr {
sa_family_t sa_family; // 地址族,例如 AF_INET 或 AF_INET6
char sa_data[14]; // 地址数据,存储地址信息
};
为了简化不同协议族(如IPv4、IPv6、UNIX域套接字等)的地址操作,Linux提供了专门的套接字地址结构体。这些结构体封装了协议族特定的信息,使得设置和获取IP地址、端口号等操作更直观。
UNIX本地协议族
#include <sys/un.h>
// UNIX域套接字地址结构体
struct sockaddr_un
{
sa_family_t sun_family; // 地址族,通常设置为 AF_UNIX
char sun_path[108]; // 套接字路径,用于本地通信的文件系统路径
};
TCP/IPv4协议族
#include <netinet/in.h>
#include <stdint.h>
// IPv4套接字地址结构体
struct sockaddr_in {
sa_family_t sin_family; // 地址族,通常设置为 AF_INET
in_port_t sin_port; // 端口号,16位无符号整数,网络字节序
struct in_addr sin_addr; // IP地址,结构体类型为 struct in_addr
};
// IPv4地址结构体
struct in_addr {
in_addr_t s_addr; // IPv4地址,32位无符号整数,网络字节序
};
TCP/IPv6协议族
#include <stdint.h>
#include <netinet/in.h>
// IPv6 地址的结构体
struct sockaddr_in6 {
sa_family_t sin6_family; // 地址族,IPv6 通常为 AF_INET6
in_port_t sin6_port; // 端口号(网络字节序)
uint32_t sin6_flowinfo; // 流量信息(通常用于 QoS)
struct in6_addr sin6_addr; // IPv6 地址
uint32_t sin6_scope_id; // 地址作用域 ID(用于链路本地地址和站点本地地址)
};
// IPv6 地址的结构体
struct in6_addr {
uint8_t s6_addr[16]; // IPv6地址(16 字节,128 位)
};
IP地址转换函数
适用于IPv4的函数
in_addr_t inet_addr(const char* strptr);
用于将IPv4地址从点分十进制格式的字符串转换为网络字节序的32位二进制格式,一般改用inet_aton了。
strptr:点分十进制格式的 IPv4 地址字符串。
返回值:转换后的in_addr_t类型的地址(网络字节序)。如果字符串无效或地址无法解析,则返回INADDR_NONE(通常是-1)。
int inet_aton(const char* cp, struct in_addr* inp);
用于将一个IPv4地址的字符串表示形式转换为网络字节序的二进制格式,并将结果存储在struct in_addr结构体中。
cp:点分十进制格式的 IPv4 地址字符串。
inp:指向struct in_addr结构体的指针,用于存储转换后的二进制地址。
返回值:成功返回1,表示地址字符串成功转换;失败返回0,表示地址字符串无法转换。
char* inet_ntoa(struct in_addr in);
inp:结构体in_addr类型,包含一个 32 位的 IPv4 地址(以网络字节序存储)。
返回值:指向静态缓冲区的指针,该缓冲区包含转换后的点分十进制字符串表示形式。请注意,这个缓冲区是静态的,所以每次调用inet_ntoa时它的内容可能会被覆盖。
eg. 缓冲区覆盖案例
#include <stdio.h>
#include <arpa/inet.h>
int main() {
struct in_addr addr1;
struct in_addr addr2;
addr1.s_addr = htonl(0xc0a80101); // 192.168.1.1
addr2.s_addr = htonl(0xc0a80102); // 192.168.1.2
char* str1 = inet_ntoa(addr1); // addr1转换为字符串 存储在静态缓冲区中
char* str2 = inet_ntoa(addr2); // addr2转换为字符串 存储在同一个静态缓冲区中 覆盖了之前的内容
printf("address 1: %s\n", str1);
printf("address 2: %s\n", str2);
}
适用于IPv4和IPv6的函数
int inet_pton(int af, const char *src, void *dst);
用于将一个IP地址字符串转换为网络字节序的二进制格式。
af:地址族,指定要转换的IP地址类型,如AF_INET,AF_INET6。
src:指向包含IP地址文本表示的字符串的指针。
dst:指向一个缓冲区的指针,用于存储转换后的二进制格式的IP地址。对于IPv4,指向struct in_addr结构体;对于IPv6,指向struct in6_addr结构体。
返回值:1,成功;0,输入的IP地址字符串格式无效;-1,发生错误,错误可通过errno获取。
const char* inet_ntop(int af, const void* src, char* dst, socklen_t cnt);
用于将网络字节序的IP地址转换为文本字符串表示。
af:地址族,指定要转换的IP地址类型,如AF_INET,AF_INET6。
src:指向包含网络字节序IP地址的二进制数据的指针。对于IPv4,指向struct in_addr结构体;对于IPv6,指向struct in6_addr结构体。
dst:指向一个字符数组的指针,用于存储转换后的文本格式的IP地址。
cnt:dst指向的缓冲区的大小,以字节为单位。
两个宏可以用于指定cnt大小:
#define INET_ADDRSTRLEN 16
#define INET6_ADDRSTRLEN 46
返回值:成功,返回dst指针,指向包含文本格式IP地址的缓冲区;失败返回NULL,错误可通过errno获取。
推荐一下
https://github.com/0voice