一、Socket地址的本质Socket最初的含义是一个IP地址和端口对ip, port它唯一地表示了使用TCP通信的一端这就是Socket地址。在网络编程中需要一种标准化的数据结构来描述这个地址对这就引出了各种地址结构体。二、通用Socket地址结构2.1 sockaddr结构体Socket网络编程接口中表示socket地址的是结构体sockaddr其定义如下#include bits/socket.h struct sockaddr { sa_family_t sa_family; // 地址族类型 char sa_data[14]; // 存放socket地址值14字节 };sa_family地址族类型通常与协议族类型对应如AF_INET表示IPv4协议sa_data用于存放具体的socket地址值但只有14字节2.2 为什么需要通用地址结构socket网络编程接口中使用的是sockaddr作为地址参数类型但不同的协议族如IPv4、IPv6、UNIX域有不同的地址格式和长度。14字节的sa_data根本无法完全容纳多数协议族的地址值例如IPv6地址就需要26字节。因此Linux定义了新的通用socket地址结构体sockaddr_storage#include bits/socket.h struct sockaddr_storage { sa_family_t sa_family; unsigned long int __ss_align; char __ss_padding[128 - sizeof(__ss_align)]; };这个结构体不仅提供了足够大的空间128字节用于存放地址值而且是内存对齐的这是__ss_align成员的作用。三、专用Socket地址结构通用socket地址结构体在设置与获取IP地址和端口号时需要执行繁琐的位置操作因此Linux为各个协议族提供了专门的socket地址结构体。3.1 IPv4专用地址结构sockaddr_in这是最常用的地址结构用于IPv4网络通信struct sockaddr_in { sa_family_t sin_family; // 地址族AF_INET u_int16_t sin_port; // 端口号要用网络字节序表示 struct in_addr sin_addr; // IPv4地址结构体 }; struct in_addr { u_int32_t s_addr; // IPv4地址要用网络字节序表示 };各成员的作用sin_family必须设置为AF_INET表示IPv4协议族sin_port16位端口号需要使用htons()函数转换为网络字节序sin_addr.s_addr32位IP地址需要使用htonl()或inet_addr()等函数转换为网络字节序3.2 IPv6专用地址结构sockaddr_in6用于IPv6网络通信struct sockaddr_in6 { sa_family_t sin6_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; // scope ID尚处于实验阶段 }; struct in6_addr { unsigned char sa_addr[16]; // IPv6地址要用网络字节序表示 };3.3 UNIX本地域协议地址结构sockaddr_un用于本地进程间通信UNIX域套接字#include sys/un.h struct sockaddr_un { sa_family_t sin_family; // 地址族AF_UNIX char sun_path[108]; // 文件路径名 };3.4 地址结构的实际使用所有专用socket地址类型的变量在实际使用中都需要转化为通用socket地址类型sockaddr强制转换即可因为所有的socket编程接口使用的地址参数类型都是sockaddr。例如struct sockaddr_in server_addr; // 初始化server_addr... bind(sockfd, (struct sockaddr *)server_addr, sizeof(server_addr));四、主机字节序与网络字节序4.1 字节序的概念现代CPU的累加器一次都能装载至少4字节32位机这4个字节在内存中的排列顺序会影响它们被累加器装载成的整数的值这就是字节序问题。字节序分为两种大端字节序Big Endian整数的高位字节存储在内存的低地址处低位字节存储在内存的高地址处小端字节序Little Endian整数的高位字节存储在内存的高地址处低位字节存储在内存的低地址处现代PC大多采用小端字节序因此小端字节序又被称为主机字节序。4.2 网络字节序的由来当格式化的数据在两台使用不同字节序的主机之间直接传递时接收端必然错误地解释数据。解决问题的办法是发送端总是把要发送的数据转化成大端字节序后再发送而接收端知道对方传送过来的数据总是采用大端字节序所以接收端可以根据自身采用的字节序决定是否对接收到的数据进行转换小端机转换大端机不转换。因此大端字节序也称为网络字节序。4.3 字节序转换函数Linux提供了4个函数来完成主机字节序和网络字节序之间的转换#include arpa/inet.h uint32_t htonl(uint32_t hostlong); // 主机字节→网络字节32位长整型 uint16_t htons(uint16_t hostshort); // 主机字节→网络字节16位短整型 uint32_t ntohl(uint32_t netlong); // 网络字节→主机字节32位长整型 uint16_t ntohs(uint16_t netshort); // 网络字节→主机字节16位短整型函数名含义h表示host主机n表示network网络l表示32位长整数longs表示16位短整数short例如htonl表示将32位的长整数从主机字节序转换为网络字节序实际应用中长整型函数htonl/ntohl通常用来转换IP地址短整型函数htons/ntohs用来转换端口号如果主机是小端字节序这些函数将参数做相应的大小端转换然后返回如果主机是大端字节序这些函数不做转换将参数原封不动地返回。五、IP地址转换函数5.1 传统IPv4地址转换函数以下函数用于将点分十进制字符串表示的IPv4地址和网络字节序整数表示的IPv4地址之间进行转换#include arpa/inet.h in_addr_t inet_addr(const char *strptr); // 字符串→32位整数 int inet_aton(const char *cp, struct in_addr *inp); // 字符串→结构体 char *inet_ntoa(struct in_addr in); // 结构体→字符串inet_addr()将点分十进制字符串IP转换为32位网络字节序整数失败返回INADDR_NONEinet_aton()功能同inet_addr()但将结果存储于inp指向的地址结构中成功返回1失败返回0inet_ntoa()将32位整数IP转换为点分十进制字符串注意该函数内部用一个静态变量存储转化结果函数返回值指向该静态内存因此inet_ntoa是不可重入的线程不安全5.2 通用地址转换函数推荐使用以下函数同时适用于IPv4和IPv6地址且是可重入的线程安全#include arpa/inet.h int inet_pton(int af, const char *src, void *dst); const char *inet_ntop(int af, const void *src, char *dst, socklen_t size);inet_pton()将字符串IP地址转换为二进制格式af指定协议族AF_INET或AF_INET6成功返回1无效地址返回0错误返回-1inet_ntop()将二进制地址转换为字符串格式需要指定输出缓冲区的大小size返回指向dst的指针推荐使用inet_pton和inet_ntop替代传统函数因为它们更安全、更通用。六、地址结构的使用示例以下是一个典型的TCP服务器端初始化地址结构的示例struct sockaddr_in server_addr; // 1. 清零结构体 memset(server_addr, 0, sizeof(server_addr)); // 2. 设置地址族 server_addr.sin_family AF_INET; // 3. 设置端口号转换为网络字节序 server_addr.sin_port htons(8080); // 4. 设置IP地址绑定所有本地网卡 server_addr.sin_addr.s_addr htonl(INADDR_ANY); // 或绑定指定IPinet_pton(AF_INET, 192.168.1.100, server_addr.sin_addr); // 5. 绑定地址到套接字 bind(sockfd, (struct sockaddr *)server_addr, sizeof(server_addr));七、总结Linux网络编程中的地址结构体系可以分为三个层次层次结构体用途通用地址sockaddr作为所有socket API的参数类型扩展通用地址sockaddr_storage提供足够的空间容纳所有协议族的地址专用地址sockaddr_in/sockaddr_in6/sockaddr_un分别用于IPv4、IPv6、UNIX域通信在编程实践中程序员通常使用专用地址结构如sockaddr_in来设置地址信息然后通过强制类型转换将其传递给接受通用地址结构参数的API函数。同时务必注意使用字节序转换函数htons、htonl等和IP地址转换函数推荐inet_pton/inet_ntop来确保数据的正确解释。
Linux网络编程基础(地址结构)
一、Socket地址的本质Socket最初的含义是一个IP地址和端口对ip, port它唯一地表示了使用TCP通信的一端这就是Socket地址。在网络编程中需要一种标准化的数据结构来描述这个地址对这就引出了各种地址结构体。二、通用Socket地址结构2.1 sockaddr结构体Socket网络编程接口中表示socket地址的是结构体sockaddr其定义如下#include bits/socket.h struct sockaddr { sa_family_t sa_family; // 地址族类型 char sa_data[14]; // 存放socket地址值14字节 };sa_family地址族类型通常与协议族类型对应如AF_INET表示IPv4协议sa_data用于存放具体的socket地址值但只有14字节2.2 为什么需要通用地址结构socket网络编程接口中使用的是sockaddr作为地址参数类型但不同的协议族如IPv4、IPv6、UNIX域有不同的地址格式和长度。14字节的sa_data根本无法完全容纳多数协议族的地址值例如IPv6地址就需要26字节。因此Linux定义了新的通用socket地址结构体sockaddr_storage#include bits/socket.h struct sockaddr_storage { sa_family_t sa_family; unsigned long int __ss_align; char __ss_padding[128 - sizeof(__ss_align)]; };这个结构体不仅提供了足够大的空间128字节用于存放地址值而且是内存对齐的这是__ss_align成员的作用。三、专用Socket地址结构通用socket地址结构体在设置与获取IP地址和端口号时需要执行繁琐的位置操作因此Linux为各个协议族提供了专门的socket地址结构体。3.1 IPv4专用地址结构sockaddr_in这是最常用的地址结构用于IPv4网络通信struct sockaddr_in { sa_family_t sin_family; // 地址族AF_INET u_int16_t sin_port; // 端口号要用网络字节序表示 struct in_addr sin_addr; // IPv4地址结构体 }; struct in_addr { u_int32_t s_addr; // IPv4地址要用网络字节序表示 };各成员的作用sin_family必须设置为AF_INET表示IPv4协议族sin_port16位端口号需要使用htons()函数转换为网络字节序sin_addr.s_addr32位IP地址需要使用htonl()或inet_addr()等函数转换为网络字节序3.2 IPv6专用地址结构sockaddr_in6用于IPv6网络通信struct sockaddr_in6 { sa_family_t sin6_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; // scope ID尚处于实验阶段 }; struct in6_addr { unsigned char sa_addr[16]; // IPv6地址要用网络字节序表示 };3.3 UNIX本地域协议地址结构sockaddr_un用于本地进程间通信UNIX域套接字#include sys/un.h struct sockaddr_un { sa_family_t sin_family; // 地址族AF_UNIX char sun_path[108]; // 文件路径名 };3.4 地址结构的实际使用所有专用socket地址类型的变量在实际使用中都需要转化为通用socket地址类型sockaddr强制转换即可因为所有的socket编程接口使用的地址参数类型都是sockaddr。例如struct sockaddr_in server_addr; // 初始化server_addr... bind(sockfd, (struct sockaddr *)server_addr, sizeof(server_addr));四、主机字节序与网络字节序4.1 字节序的概念现代CPU的累加器一次都能装载至少4字节32位机这4个字节在内存中的排列顺序会影响它们被累加器装载成的整数的值这就是字节序问题。字节序分为两种大端字节序Big Endian整数的高位字节存储在内存的低地址处低位字节存储在内存的高地址处小端字节序Little Endian整数的高位字节存储在内存的高地址处低位字节存储在内存的低地址处现代PC大多采用小端字节序因此小端字节序又被称为主机字节序。4.2 网络字节序的由来当格式化的数据在两台使用不同字节序的主机之间直接传递时接收端必然错误地解释数据。解决问题的办法是发送端总是把要发送的数据转化成大端字节序后再发送而接收端知道对方传送过来的数据总是采用大端字节序所以接收端可以根据自身采用的字节序决定是否对接收到的数据进行转换小端机转换大端机不转换。因此大端字节序也称为网络字节序。4.3 字节序转换函数Linux提供了4个函数来完成主机字节序和网络字节序之间的转换#include arpa/inet.h uint32_t htonl(uint32_t hostlong); // 主机字节→网络字节32位长整型 uint16_t htons(uint16_t hostshort); // 主机字节→网络字节16位短整型 uint32_t ntohl(uint32_t netlong); // 网络字节→主机字节32位长整型 uint16_t ntohs(uint16_t netshort); // 网络字节→主机字节16位短整型函数名含义h表示host主机n表示network网络l表示32位长整数longs表示16位短整数short例如htonl表示将32位的长整数从主机字节序转换为网络字节序实际应用中长整型函数htonl/ntohl通常用来转换IP地址短整型函数htons/ntohs用来转换端口号如果主机是小端字节序这些函数将参数做相应的大小端转换然后返回如果主机是大端字节序这些函数不做转换将参数原封不动地返回。五、IP地址转换函数5.1 传统IPv4地址转换函数以下函数用于将点分十进制字符串表示的IPv4地址和网络字节序整数表示的IPv4地址之间进行转换#include arpa/inet.h in_addr_t inet_addr(const char *strptr); // 字符串→32位整数 int inet_aton(const char *cp, struct in_addr *inp); // 字符串→结构体 char *inet_ntoa(struct in_addr in); // 结构体→字符串inet_addr()将点分十进制字符串IP转换为32位网络字节序整数失败返回INADDR_NONEinet_aton()功能同inet_addr()但将结果存储于inp指向的地址结构中成功返回1失败返回0inet_ntoa()将32位整数IP转换为点分十进制字符串注意该函数内部用一个静态变量存储转化结果函数返回值指向该静态内存因此inet_ntoa是不可重入的线程不安全5.2 通用地址转换函数推荐使用以下函数同时适用于IPv4和IPv6地址且是可重入的线程安全#include arpa/inet.h int inet_pton(int af, const char *src, void *dst); const char *inet_ntop(int af, const void *src, char *dst, socklen_t size);inet_pton()将字符串IP地址转换为二进制格式af指定协议族AF_INET或AF_INET6成功返回1无效地址返回0错误返回-1inet_ntop()将二进制地址转换为字符串格式需要指定输出缓冲区的大小size返回指向dst的指针推荐使用inet_pton和inet_ntop替代传统函数因为它们更安全、更通用。六、地址结构的使用示例以下是一个典型的TCP服务器端初始化地址结构的示例struct sockaddr_in server_addr; // 1. 清零结构体 memset(server_addr, 0, sizeof(server_addr)); // 2. 设置地址族 server_addr.sin_family AF_INET; // 3. 设置端口号转换为网络字节序 server_addr.sin_port htons(8080); // 4. 设置IP地址绑定所有本地网卡 server_addr.sin_addr.s_addr htonl(INADDR_ANY); // 或绑定指定IPinet_pton(AF_INET, 192.168.1.100, server_addr.sin_addr); // 5. 绑定地址到套接字 bind(sockfd, (struct sockaddr *)server_addr, sizeof(server_addr));七、总结Linux网络编程中的地址结构体系可以分为三个层次层次结构体用途通用地址sockaddr作为所有socket API的参数类型扩展通用地址sockaddr_storage提供足够的空间容纳所有协议族的地址专用地址sockaddr_in/sockaddr_in6/sockaddr_un分别用于IPv4、IPv6、UNIX域通信在编程实践中程序员通常使用专用地址结构如sockaddr_in来设置地址信息然后通过强制类型转换将其传递给接受通用地址结构参数的API函数。同时务必注意使用字节序转换函数htons、htonl等和IP地址转换函数推荐inet_pton/inet_ntop来确保数据的正确解释。