这些个结构体哟!!!
一、sockaddr_in
结构体概述#
1.1 定义与作用#
sockaddr_in
是用于 IPv4 网络编程的地址结构体,包含了 IP 地址和端口号等信息。在使用套接字进行网络通信时,需要使用该结构体来指定通信双方的地址信息。
1.2 结构体定义#
sockaddr_in
定义在头文件 <netinet/in.h>
中,具体定义如下:
1
2
3
4
5
6
|
struct sockaddr_in {
sa_family_t sin_family; /* 地址族 (Address Family) */
in_port_t sin_port; /* 16 位端口号 (Port Number) */
struct in_addr sin_addr; /* 32 位 IP 地址 (IPv4 Address) */
unsigned char sin_zero[8];/* 填充字节 (Padding) */
};
|
sa_family_t
:通常是一个 unsigned short
,指定地址族。
in_port_t
:通常是一个 unsigned short
,用于存储端口号。
struct in_addr
:用于存储 IPv4 地址。
sin_zero
:填充使得结构体大小与 struct sockaddr
保持一致。
二、sockaddr_in
结构体成员详解#
2.1 sin_family
:地址族#
- 类型:
sa_family_t
- 含义:指定地址族,必须设置为
AF_INET
,表示使用 IPv4 地址。
- 注意:不要与
PF_INET
混淆,AF_INET
用于套接字地址,PF_INET
用于套接字通信域。
2.2 sin_port
:端口号#
- 类型:
in_port_t
(通常为 uint16_t
)
- 含义:16 位的端口号,用于标识进程间通信的端点。
- 注意:端口号需要使用 网络字节序(大端序)。应使用
htons()
函数进行转换。
2.3 sin_addr
:IP 地址#
- 类型:
struct in_addr
- 含义:存储 32 位 IPv4 地址。
- 成员:
in_addr_t s_addr
,实际的 IP 地址值。
- 注意:IP 地址需要使用 网络字节序。应使用
inet_pton()
或 inet_addr()
等函数进行设置。
2.4 sin_zero
:填充字节#
- 类型:
unsigned char[8]
- 含义:填充字段,使
sockaddr_in
的大小与 sockaddr
结构体一致。
- 作用:一般不使用,应将其置零。
三、sockaddr_in
与 sockaddr
之间的关系#
3.1 sockaddr
结构体#
sockaddr
是通用的套接字地址结构体,定义如下:
1
2
3
4
|
struct sockaddr {
sa_family_t sa_family; /* 地址族 */
char sa_data[14]; /* 地址数据 */
};
|
3.2 类型转换#
在使用套接字函数(如 bind()
、connect()
)时,通常需要将 sockaddr_in
转换为 sockaddr
指针。这是因为这些函数使用通用的 sockaddr
结构体来支持不同的地址族。
1
2
|
struct sockaddr_in addr_in;
struct sockaddr *addr = (struct sockaddr *)&addr_in;
|
3.3 为什么需要转换#
- 兼容性:套接字函数设计为支持多种协议族,使用通用的
sockaddr
结构体,可以通过 sa_family
来区分具体的地址类型。
- 灵活性:通过类型转换,可以在需要时传递特定的地址结构体,同时保持函数接口的一致性。
四、sockaddr_in
的使用示例#
4.1 初始化 sockaddr_in
#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
#include <stdio.h>
#include <string.h>
#include <arpa/inet.h>
#include <netinet/in.h>
void init_sockaddr_in(struct sockaddr_in *addr, const char *ip_str, uint16_t port) {
memset(addr, 0, sizeof(struct sockaddr_in)); // 清零
addr->sin_family = AF_INET; // 设置地址族
// 设置端口号,使用网络字节序
addr->sin_port = htons(port);
// 设置 IP 地址
if (inet_pton(AF_INET, ip_str, &(addr->sin_addr)) <= 0) {
perror("inet_pton failed");
}
}
|
4.2 在服务器端使用 sockaddr_in
#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
|
int server_socket_setup(uint16_t port) {
int sockfd;
struct sockaddr_in server_addr;
// 创建套接字
sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd < 0) {
perror("socket creation failed");
return -1;
}
// 初始化地址结构体
init_sockaddr_in(&server_addr, "0.0.0.0", port);
// 绑定地址
if (bind(sockfd, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0) {
perror("bind failed");
close(sockfd);
return -1;
}
// 开始监听
if (listen(sockfd, SOMAXCONN) < 0) {
perror("listen failed");
close(sockfd);
return -1;
}
return sockfd;
}
|
4.3 在客户端使用 sockaddr_in
#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
int client_socket_setup(const char *server_ip, uint16_t port) {
int sockfd;
struct sockaddr_in server_addr;
// 创建套接字
sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd < 0) {
perror("socket creation failed");
return -1;
}
// 初始化服务器地址
init_sockaddr_in(&server_addr, server_ip, port);
// 连接服务器
if (connect(sockfd, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0) {
perror("connect failed");
close(sockfd);
return -1;
}
return sockfd;
}
|
五、使用注意事项#
5.1 网络字节序与主机字节序#
- 问题:不同机器可能使用不同的字节序(大端或小端)。
- 解决方案:使用
htons()
、htonl()
将主机字节序转换为网络字节序;使用 ntohs()
、ntohl()
进行逆转换。
1
|
addr->sin_port = htons(port); // 端口号转换
|
5.2 IP 地址的设置#
- 方法:
- 使用
inet_pton()
:推荐,支持 IPv4 和 IPv6,线程安全。
- 使用
inet_addr()
:仅支持 IPv4,返回网络字节序的整数表示。
- 使用
INADDR_ANY
:绑定到所有可用接口,通常用于服务器端。
- 使用
INADDR_LOOPBACK
:绑定到本地主机(127.0.0.1)。
1
|
addr->sin_addr.s_addr = htonl(INADDR_ANY);
|
5.3 初始化 sin_zero
#
- 注意:
sin_zero
一般不使用,但为了兼容性,通常将其置零。
- 方法:使用
memset()
或显式赋值。
1
|
memset(&(addr->sin_zero), 0, sizeof(addr->sin_zero));
|
5.4 套接字地址长度#
- 获取长度:使用
sizeof(struct sockaddr_in)
,或者使用 socklen_t
类型。
- 原因:不同的地址结构体可能长度不同,使用
sizeof
可以确保传递正确的长度。
1
|
socklen_t addr_len = sizeof(struct sockaddr_in);
|
六、常见陷阱#
6.1 忘记字节序转换#
- 现象:端口号或 IP 地址不正确,无法连接。
- 解决方案:确保使用
htons()
、htonl()
进行必要的转换。
6.2 地址族不匹配#
- 问题:
sin_family
设置错误,导致函数调用失败。
- 解决方案:始终将
sin_family
设置为 AF_INET
(IPv4)或 AF_INET6
(IPv6)。
6.3 使用未初始化的结构体#
- 问题:未对
sockaddr_in
结构体进行初始化,包含垃圾值。
- 解决方案:使用
memset()
清零,或逐个字段进行赋值。
6.4 错误的类型转换#
- 问题:在调用套接字函数时,未正确转换为
struct sockaddr *
类型。
- 解决方案:使用显式类型转换。
1
|
(struct sockaddr *)&server_addr
|
七、拓展资料:关键概念解释#
7.1 网络字节序与主机字节序#
- 字节序:
- 大端序(Big Endian):高位字节存储在低地址。
- 小端序(Little Endian):低位字节存储在低地址。
- 网络字节序:TCP/IP 协议规定使用大端序,称为网络字节序。
- 转换函数:
htons()
:主机字节序到网络字节序(16 位)。
htonl()
:主机字节序到网络字节序(32 位)。
ntohs()
、ntohl()
:网络字节序到主机字节序。
7.2 IP 地址与端口#
- IP 地址:用于标识网络中的主机或设备,IPv4 地址为 32 位,通常表示为点分十进制格式(如
192.168.1.1
)。
- 端口号:用于标识主机上的具体应用或服务,取值范围为 0 到 65535,其中 0 到 1023 为系统保留端口。
7.3 结构体填充与对齐#
- 填充(Padding):为了满足内存对齐要求,编译器可能在结构体中插入填充字节。
- 对齐(Alignment):数据在内存中按一定的字节数对齐,以提高访问效率。
sin_zero
的作用:填充使得 sockaddr_in
的大小与 sockaddr
一致,确保结构体在内存中的布局正确。