【c++】sockaddr,sockaddr_in

本文介绍了C++中用于处理网络通信地址的struct sockaddr和struct sockaddr_in结构体。它们用于存储地址类型、IP地址和端口信息。sockaddr_in结构体包含更具体的字段,如sin_family、sin_port和sin_addr,而sockaddr则更为通用。在编程时,通常将sockaddr_in填充后转换为sockaddr,用于bind、connect等函数。网络编程中主要操作sockaddr_in结构体。

struct sockaddr和struct sockaddr_in这两个结构体用来处理网络通信的地址。在各种系统调用或者函数中,只要和网络地址打交道,就得用到这两个结构体。网络中的地址包含3个方面的属性:
1 地址类型; 2 ip地址; 3 端口

上图看结构:

#include <sys/in.h>

这里写图片描述

#include <sys/socket.h>

这里写图片描述

#include <sockaddr.h>

这里写图片描述

我们从这个宏定义开始,可以看到宏__SOCKADDR_COMMON事实上是声明一个变量,类型是unsigned short int,sa_prefix是变量名的前缀,宏的##表示连接的意思,即family作为变量名的后半部分,所以我们在socket编程的时候是为变量sin_family赋值。
另外宏里面还可以使用#,表示把宏参数变为一个字符串。

讲解了宏__SOCKADDR_COMMON的含义,sockaddr和sockaddr_in的结构内容相信大家都能看懂,我们注意一下,sockaddr和sockaddr_in第一个变量都是用2字节存放地址类型,区别在于sockaddr_in第二个和第三个变量端口号和地址,第四个变量是字节填充,而sockaddr只剩下第二个变量放置data,可以看到这两个结构体的长度是一样的。

在平常编程中把类型、ip地址、端口填充sockaddr_in结构体,然后强制转换成sockaddr,作为参数传递给系统调用函数。
这里写图片描述

sockaddr常用于bind、connect、recvfrom、sendto等函数的参数,指明地址信息。是一种通用的套接字地址。而sockaddr_in是internet环境下套接字的地址形式。所以在网络编程中我们会对sockaddr_in结构体进行操作。使用sockaddr_in来建立所需的信息,最后使用类型转化就可以了

<think> 我们正在讨论Linux环境下C++的struct sockaddr_in。用户要求了解其使用方法、定义、与sockaddr的区别等。 根据之前的历史,我们已经讨论过sockaddr,现在重点在sockaddr_in。 步骤: 1. 介绍sockaddr_in的定义和结构成员 2. 说明其与sockaddr的关系(继承/转换) 3. 展示典型的使用方法(初始化、赋值、转换等) 4. 注意事项(如字节序、清零等) 5. 完整示例代码(TCP客户端/服务端片段) 6. 与sockaddr的区别总结表格 7. 相关问题 注意:用户引用了一个getsockname的代码片段,说明获取本地地址的方法,我们可以在注意事项或示例中提及。 我们将按照以下结构组织: 一、struct sockaddr_in 定义与作用 二、结构成员详解 三、与struct sockaddr的关系 四、典型使用流程(IPv4) 五、完整示例(TCP服务端绑定地址) 六、注意事项(包括用户引用的getsockname使用) 七、与sockaddr的区别总结 八、相关问题 注意:使用中文,数学公式用LaTeX,代码块用C++,表格对比。 </think>### Linux环境下C++中`struct sockaddr_in`详解 #### 一、核心定义与作用 `struct sockaddr_in`是Linux网络编程中**IPv4专用地址结构**,定义在`<netinet/in.h>`头文件中: ```c++ struct sockaddr_in { sa_family_t sin_family; // 地址族 (AF_INET) in_port_t sin_port; // 16位端口号 struct in_addr sin_addr; // 32位IPv4地址 unsigned char sin_zero[8];// 填充字节(保证与sockaddr大小一致) }; struct in_addr { in_addr_t s_addr; // 32位IPv4地址(网络字节序) }; ``` - **核心作用**:存储IPv4套接字地址信息(IP+端口),用于`bind()`, `connect()`, `accept()`等系统调用 - **内存布局**:与通用`sockaddr`保持**16字节相同大小**,通过强制类型转换互操作 #### 二、关键操作流程 ##### 1. 结构初始化 ```c++ #include <netinet/in.h> #include <arpa/inet.h> sockaddr_in server_addr{}; server_addr.sin_family = AF_INET; // 必须设为AF_INET server_addr.sin_port = htons(8080); // 端口转换网络字节序 inet_pton(AF_INET, "192.168.1.100", &server_addr.sin_addr); // IP字符串转二进制 memset(server_addr.sin_zero, 0, sizeof(server_addr.sin_zero)); // 填充清零[^1] ``` ##### 2. 地址信息获取(参考用户引用) ```c++ sockaddr_in localaddr{}; socklen_t len = sizeof(localaddr); // 必须正确设置结构体大小 if (getsockname(fd, reinterpret_cast<sockaddr*>(&localaddr), &len) == 0) { std::cout << "IP: " << inet_ntoa(localaddr.sin_addr) << " Port: " << ntohs(localaddr.sin_port) << std::endl; } else { perror("getsockname error"); } ``` #### 三、核心成员详解 | 成员 | 类型 | 作用描述 | 字节数 | 示例值 | |---------------|---------------|------------------------------------|--------|---------------------| | `sin_family` | `sa_family_t` | 地址族标识(固定为`AF_INET`) | 2 | `AF_INET` | | `sin_port` | `in_port_t` | 端口号(网络字节序) | 2 | `htons(8080)` | | `sin_addr` | `struct in_addr` | IPv4地址(网络字节序) | 4 | `inet_addr("127.0.0.1")` | | `sin_zero` | `uint8_t[8]` | 填充字段(必须置零) | 8 | 全0 | > **字节序转换公式**: > 设主机端口为$P_h$,网络端口为$P_n$,则: > $$ > P_n = \text{htons}(P_h) \quad P_h = \text{ntohs}(P_n) > $$ #### 四、与`sockaddr`的关系 ```c++ +------------------+ reinterpret_cast | sockaddr_in | <=====================> | sockaddr | +------------------+ +-----------------+ | sin_family (2) |------------------------>| sa_family (2) | | sin_port (2) |--+ | | | sin_addr (4) | | | sa_data[14] | | sin_zero[8] (8) | | | | +------------------+ | +-----------------+ | 内存映射关系 +--> sin_port → sa_data[0:1] +--> sin_addr → sa_data[2:5] +--> sin_zero → sa_data[6:13] ``` #### 五、完整TCP服务端示例 ```c++ #include <sys/socket.h> #include <netinet/in.h> #include <cstring> #include <iostream> int main() { // 创建TCP套接字 int sockfd = socket(AF_INET, SOCK_STREAM, 0); // 配置服务器地址 sockaddr_in server_addr{}; server_addr.sin_family = AF_INET; server_addr.sin_addr.s_addr = INADDR_ANY; // 监听所有接口 server_addr.sin_port = htons(8080); // 端口转换 // 绑定地址 if (bind(sockfd, reinterpret_cast<sockaddr*>(&server_addr), sizeof(server_addr)) < 0) { std::cerr << "绑定失败: " << strerror(errno); return 1; } // 监听并接受连接 listen(sockfd, 5); sockaddr_in client_addr{}; socklen_t client_len = sizeof(client_addr); int client_fd = accept(sockfd, reinterpret_cast<sockaddr*>(&client_addr), &client_len); // 获取客户端信息 char client_ip[INET_ADDRSTRLEN]; inet_ntop(AF_INET, &client_addr.sin_addr, client_ip, sizeof(client_ip)); std::cout << "客户端连接: " << client_ip << ":" << ntohs(client_addr.sin_port) << std::endl; close(client_fd); close(sockfd); return 0; } ``` #### 六、关键注意事项 1. **强制类型转换** 使用时必须转换为`sockaddr*`: ```c++ // 正确 connect(sockfd, reinterpret_cast<sockaddr*>(&addr), sizeof(addr)); // 错误(类型不匹配) connect(sockfd, &addr, sizeof(addr)); ``` 2. **结构体大小传递** 系统调用需要精确的结构大小: ```c++ socklen_t len = sizeof(sockaddr_in); // 正确获取大小 getsockname(fd, (sockaddr*)&addr, &len); ``` 3. **地址转换函数对比** | 函数 | 输入 | 输出 | 安全性 | |-------------------------|------------------|------------------|--------| | `inet_addr("192.168.1.1")` | IPv4字符串 | `in_addr_t` | 不安全(不支持"255.255.255.255") | | `inet_aton("192.168.1.1", &addr)` | IPv4字符串 | `struct in_addr` | 较安全 | | `inet_pton(AF_INET, "192.168.1.1", &addr)` | IPv4/IPv6字符串 | 二进制地址 | 最安全 | 4. **特殊地址值** ```c++ server_addr.sin_addr.s_addr = htonl(INADDR_ANY); // 监听所有接口 server_addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);// 环回地址(127.0.0.1) ``` #### 七、最佳实践总结 | 场景 | 推荐做法 | 风险点 | |--------------------|-----------------------------------------------|-------------------------| | **初始化** | C++11统一初始化`{}` + `memset` | 未初始化导致随机值 | | **IP地址转换** | `inet_pton()` + `inet_ntop()` | 使用弃用的`inet_addr()` | | **端口赋值** | `htons()`显式转换 | 忘记转换导致端口错误 | | **结构体传递** | `reinterpret_cast` + `sizeof()`动态获取大小 | 硬编码大小或忘记类型转换| | **多协议支持** | 优先使用`sockaddr_storage` | IPv4/IPv6兼容性问题 | > **网络字节序验证**: > 小端系统(Little-Endian)中端口`8080`的转换: > $$ > \text{htons}(8080) = \text{0x901F} \quad (8080_{10} = 1F90_{16}) > $$
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值