这些个结构体哟!!!


一、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_insockaddr 之间的关系

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 一致,确保结构体在内存中的布局正确。