sockaddr详解
sockaddr
结构体是网络编程中用于表示套接字地址的一种通用数据结构,它主要用于存储参与(IP)Windows/Linux套接字通信的计算机上的一个互联网协议(IP)地址,通过统一地址结构的表示方法,使得不同的地址结构可以被bind()、connect()、recvfrom()、sendto()等函数调用。
sockaddr
结构体定义
sockaddr
结构体在头文件#include <sys/socket.h>中定义,其定义如下:
struct sockaddr { sa_family_t sa_family; // 地址族,AF_xxx char sa_data[14]; // 14字节的协议地址 };
sa_family
: 2字节的地址家族,一般都是“AF_xxx”的形式,它的值包括三种:AF_INET
(IPv4)、AF_INET6
(IPv6)和AF_UNSPEC
,如果指定AF_INET
,那么函数就不能返回任何IPV6相关的地址信息;如果仅指定了AF_INET6
,则就不能返回任何IPv4地址信息。AF_UNSPEC
意味着函数返回的是适用于指定主机名和服务名且适合任何协议族的地址。
sa_data
: 14字节的协议地址,包含套接字中的目标地址和端口信息。
sockaddr_in
结构体定义
为了解决sockaddr
结构体的缺陷,引入了sockaddr_in
结构体,它在头文件#include<netinet/in.h>或#include <arpa/inet.h>中定义,其定义如下:
struct sockaddr_in { short sin_family; // 地址族,AF_INET unsigned short sin_port; // 端口号,网络字节序 struct in_addr sin_addr; // IPv4地址 char sin_zero[8]; // 填充字节,使结构体大小与sockaddr一致 };
sin_family
: 指代协议族,在socket编程中只能是AF_INET
。
sin_port
: 存储端口号(使用网络字节顺序)。
sin_addr
: 存储IP地址,使用in_addr
这个数据结构。
sin_zero
: 是为了让sockaddr
与sockaddr_in
两个数据结构保持大小相同而保留的空字节。
其中in_addr
结构的定义如下:
typedef struct in_addr { union { struct {unsigned char s_b1,s_b2,s_b3,s_b4;} S_un_b; struct {unsigned short s_w1,s_w2;} S_un_w; unsigned long S_addr; // load with zero } S_un; #define s_addr S_un.S_addr // can be used for most tcp/ip implementations #define s_host ((unsigned char*)&S_un) #define s_aliases s_aliases #define s_ni_addr s_addr #define s_nlen 4 #define s_maxlenipp 4 };
网络字节序与主机字节序
在网络通信中,由于不同计算机可能采用不同的字节序(大端或小端),因此需要进行字节序的转换,TCP/IP协议规定:网络数据流应采用大端字节序(低地址高字节序),所以如果当前发送主机方是小端,需要先将数据转成大端,常用的转换函数有:
htonl
: 将32位的长整数从主机字节序转换为网络字节序。
htons
: 将16位的短整数从主机字节序转换为网络字节序。
ntohl
: 将32位的长整数从网络字节序转换为主机字节序。
ntohs
: 将16位的短整数从网络字节序转换为主机字节序。
sockaddr
与sockaddr_in
的区别
sockaddr
是一个通用的套接字地址结构,用于兼容IPv4和IPv6,但在实际编程中并不直接对此数据结构进行操作,而是使用另一个与之等价的数据结构sockaddr_in
。
sockaddr_in
专门用于IPv4网络通信,解决了sockaddr
结构体中目标地址和端口信息混在一起的问题,把端口和地址分开储存在两个变量中。
二者长度一样,都是16个字节,占用的内存大小是一致的,因此可以互相转化,指向sockaddr_in
结构的指针也可以指向sockaddr
。
使用示例
以下是一个简单的使用示例,展示如何使用sockaddr_in
结构体来绑定一个套接字到特定的IP地址和端口上:
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <unistd.h> int main() { int sockfd; struct sockaddr_in mysock; // 创建套接字 sockfd = socket(AF_INET, SOCK_STREAM, 0); if (sockfd < 0) { perror("socket"); exit(EXIT_FAILURE); } // 清零并设置sockaddr_in结构体 memset(&mysock, 0, sizeof(mysock)); mysock.sin_family = AF_INET; mysock.sin_port = htons(8080); // 设置端口号为8080,网络字节序 mysock.sin_addr.s_addr = inet_addr("127.0.0.1"); // 设置IP地址为127.0.0.1(localhost) // 绑定套接字到指定的IP地址和端口上 if (bind(sockfd, (struct sockaddr *)&mysock, sizeof(mysock)) < 0) { perror("bind"); close(sockfd); exit(EXIT_FAILURE); } printf("Socket bound to 127.0.0.1:8080 "); // 关闭套接字 close(sockfd); return 0; }
在这个示例中,首先创建了一个套接字,然后清零并设置了sockaddr_in
结构体的各项参数,最后将该结构体强制转换为sockaddr
类型并传递给bind()
函数以绑定套接字到指定的IP地址和端口上。