一、IP地址详解
(一)核心概念
概念 | 说明 |
---|
IP地址 | 网络设备的唯一逻辑标识符,相当于网络世界的"门牌号" |
主机 | 任何接入网络的终端设备(计算机/手机/服务器等) |
核心作用 | ① 设备标识 ② 路由寻址 ③ 数据传输 |
(二)技术特性
1. 基础结构
32位IPv4地址结构
+----------------+----------------+
| 网络号 | 主机号 |
+----------------+----------------+
2. 关键组成
组成部分 | 作用 |
---|
网络号 | 标识所属网络(类似城市+街道) |
主机号 | 标识具体设备(类似门牌号) |
3. 存储方式
(三)地址分类(经典分类)
1. 类型划分
类别 | 首位格式 | 网络号范围 | 主机号范围 | 典型示例 |
---|
A类 | 0XXXXXXX | 前8位 | 后24位 | 1.0.0.1 ~ 126.255.255.254 |
B类 | 10XXXXXX | 前16位 | 后16位 | 128.0.0.1 ~ 191.255.255.254 |
C类 | 110XXXXX | 前24位 | 后8位 | 192.0.0.1 ~ 223.255.255.254 |
D类 | 1110XXXX | 组播地址 | | 224.0.0.0 ~ 239.255.255.255 |
E类 | 1111XXXX | 保留地址 | | 240.0.0.0 ~ 255.255.255.254 |
2. 特殊地址
地址类型 | 范围 | 用途 |
---|
私有地址 | 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16 | 局域网内部使用 |
环回地址 | 127.0.0.0/8 | 本机测试 |
自动配置地址 | 169.254.0.0/16 | DHCP失败时临时使用 |
(四)现代划分方式(CIDR)
1. 表示方法
格式:IP地址/前缀长度
示例:192.168.1.0/24
2. 地址计算示例
给定地址:192.168.1.100/24
网络地址:192.168.1.0 (IP & 子网掩码)
广播地址:192.168.1.255 (网络地址 | 主机位全1)
可用主机范围:192.168.1.1 ~ 192.168.1.254
最大主机数:2^8 - 2 = 254
(五)协议版本对比
特性 | IPv4 | IPv6 |
---|
地址长度 | 32位 | 128位 |
地址表示 | 点分十进制(192.168.1.1) | 冒号分隔十六进制(2001:db8::1) |
地址空间 | 约42.9亿 | 3.4×10³⁸ |
包头复杂度 | 简单 | 扩展头部机制 |
二、OSI七层模型(理论框架)
OSI层级 | 名称 | 核心功能 | 典型协议/技术 | 数据单元 |
---|
7 | 应用层 | 用户接口与应用服务 | HTTP、FTP、SMTP、DNS | 消息(Message) |
6 | 表示层 | 数据格式转换、加密/解密、压缩 | SSL/TLS、JPEG、ASCII、GZIP | |
5 | 会话层 | 建立/管理/终止会话连接 | RPC、PPTP、NetBIOS | |
4 | 传输层 | 端到端连接、流量控制 | TCP、UDP、SCTP | 段(Segment) |
3 | 网络层 | 寻址与路由选择 | IP、ICMP、NAT、OSPF | 包(Packet) |
2 | 数据链路层 | 帧封装、MAC地址访问、差错校验 | Ethernet、PPP、VLAN、ARP | 帧(Frame) |
1 | 物理层 | 比特流传输、物理介质规范 | RS-232、1000BASE-T、Wi-Fi | 比特(Bit) |



关键技术示例:
应用层:浏览器访问网页时使用的HTTP协议
表示层:HTTPS通信中的TLS加密过程
会话层:VPN连接使用的PPTP协议
传输层:TCP的三次握手建立连接
网络层:路由器使用OSPF协议计算最优路径
数据链路层:交换机的MAC地址表学习
物理层:光纤传输的激光信号调制
(二)TCP/IP四层模型(实际标准)
TCP/IP层级 | 对应OSI层 | 核心功能 | 典型协议 | 设备示例 |
---|
应用层 | 5-7 | 整合应用服务与网络接口 | HTTP、DNS、SMTP、MQTT | 网关设备 |
传输层 | 4 | 端到端数据传输管理 | TCP、UDP、QUIC | 负载均衡器 |
网络层 | 3 | 数据包路由与地址管理 | IP、ICMP、BGP、IPSec | 路由器 |
网络接口层 | 1-2 | 物理连接与本地网络传输 | Ethernet、Wi-Fi、PPP | 交换机/网卡 |
协议栈工作流程:
sequenceDiagram
应用层->>传输层: 封装应用数据(如HTTP请求)
传输层->>网络层: 添加TCP头+端口号
网络层->>网络接口层: 添加IP头+IP地址
网络接口层->>物理介质: 封装帧头+MAC地址
(三)关键对比分析
对比维度 | OSI模型 | TCP/IP模型 |
---|
设计定位 | 理论参考模型 | 实际应用标准 |
层间关系 | 严格分层 | 松散分层 |
协议支持 | 未绑定具体协议 | 绑定TCP/IP协议族 |
普及程度 | 主要用于教学 | 互联网实际标准 |
地址管理 | 网络层独立寻址 | IP地址统一寻址 |
校验机制 | 每层独立校验 | 端到端校验为主 |
(四)核心协议详解
1. 帧结构示例(Ethernet II)
+---------------+---------------+-----------------+---------------+
| 目标MAC(6B) | 源MAC(6B) | 类型(2B) | 数据(46-1500B)| FCS(4B) |
+---------------+---------------+-----------------+---------------+
2. IP数据包分片
原始包:总长度5000B(超过MTU 1500B)
分片1:1480B数据 + 20B头(偏移0,MF=1)
分片2:1480B数据 + 20B头(偏移185,MF=1)
分片3:40B数据 + 20B头(偏移370,MF=0)
3. TCP连接管理
三次握手:
Client → SYN → Server
Client ← SYN+ACK ← Server
Client → ACK → Server
四次挥手:
Client → FIN → Server
Client ← ACK ← Server
Client ← FIN ← Server
Client → ACK → Server
三、客户端/服务器(C/S)模型与传输协议详解
(一)C/S架构核心特性
特性 | 说明 |
---|
角色划分 | 客户端(请求方)与服务端(响应方) |
通信模式 | 1:N 服务模式(单个服务端服务多个客户端) |
网络依赖 | 基于网络协议栈实现远程通信 |
协议支持 | 可基于TCP/UDP等不同传输层协议实现 |
(二)TCP协议深度解析
1. 核心特性
graph TD
A[面向连接] --> B[三次握手建立连接]
A --> C[四次挥手断开连接]
D[可靠传输] --> E[序列号机制]
D --> F[确认应答机制]
D --> G[超时重传机制]
H[字节流模式] --> I[无边界数据流]
H --> J[数据顺序保证]
2. 协议头结构(20字节基础头)
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-------------------------------+---------------+-----------------+
| 源端口 | 目标端口 |
+-------------------------------+---------------+-----------------+
| 序列号(SEQ) |
+---------------------------------------------------------------+
| 确认号(ACK) |
+-----+---------+-----+-----------------------------------------+
|首部长度| 保留 |控制位| 窗口大小 |
+-----+---------+-----+-----------------------------------------+
| 校验和 | 紧急指针(URG) |
+---------------------+-----------------------------------------+
3. 典型应用场景
1. Web服务(HTTP/HTTPS)
2. 文件传输(FTP/SFTP)
3. 电子邮件(SMTP/POP3)
4. 数据库连接(MySQL/Oracle)
5. 远程登录(SSH/Telnet)
(三)UDP协议深度解析
1. 核心特性
graph TD
A[无连接] --> B[无需建立连接]
C[不可靠传输] --> D[无确认机制]
C --> E[可能丢包]
F[数据报模式] --> G[独立数据包]
F --> H[保留边界]
2. 协议头结构(8字节固定头)
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-------------------------------+---------------+-----------------+
| 源端口 | 目标端口 |
+-------------------------------+---------------+-----------------+
| 数据长度 | 校验和 |
+-------------------------------+---------------+-----------------+
3. 典型应用场景
1. 实时视频/音频传输(Zoom/WebRTC)
2. DNS域名解析
3. 网络监控(SNMP)
4. 物联网通信(MQTT-SN)
5. 在线游戏(部分实时对战)
(四)协议对比分析
对比维度 | TCP | UDP |
---|
连接方式 | 面向连接(三次握手) | 无连接 |
可靠性 | 可靠传输(确认+重传) | 尽最大努力交付 |
数据边界 | 字节流(无边界) | 数据报(保留边界) |
传输效率 | 较低(有连接开销) | 较高(无控制开销) |
头部开销 | 20-60字节 | 固定8字节 |
拥塞控制 | 完善机制(滑动窗口等) | 无控制 |
适用场景 | 需可靠传输的应用 | 实时性要求高的应用 |

四、socket() 函数详解
(一)函数原型
#include <sys/socket.h>
int socket(int domain, int type, int protocol);
(二)核心功能
- 创建通信端点:在内核中创建套接字数据结构
- 分配资源:建立协议控制块(PCB)
- 返回描述符:类似文件描述符的操作接口
(三)参数详解
1. domain(地址族)
常用值 | 说明 | 典型应用 |
---|
AF_INET | IPv4网络协议 | 互联网通信(TCP/IP) |
AF_INET6 | IPv6网络协议 | 下一代互联网通信 |
AF_UNIX | 本地通信(文件系统路径) | 同主机进程间高效通信 |
AF_PACKET | 底层包接口 | 网络嗅探器、协议分析工具 |
注意:AF_*
(地址族)与PF_*
(协议族)在Linux中已统一,但推荐使用AF_
前缀
2. type(套接字类型)
类型标志 | 特性 | 对应协议 |
---|
SOCK_STREAM | 面向连接、可靠传输、字节流 | TCP |
SOCK_DGRAM | 无连接、不可靠、数据报 | UDP |
SOCK_RAW | 原始网络层访问 | IP/ICMP |
SOCK_SEQPACKET | 有序分组服务 | SCTP |
3. protocol(协议选择)
取值 | 说明 |
---|
0 | 自动选择默认协议(推荐) |
IPPROTO_TCP | 显式指定TCP协议(=6) |
IPPROTO_UDP | 显式指定UDP协议(=17) |
IPPROTO_RAW | 原始IP协议(需要权限) |
标准协议组合:
// TCP套接字
socket(AF_INET, SOCK_STREAM, 0);
// UDP套接字
socket(AF_INET, SOCK_DGRAM, 0);
// 原始ICMP套接字
socket(AF_INET, SOCK_RAW, IPPROTO_ICMP);
(四)返回值处理
返回值 | 说明 |
---|
正整数 | 新创建的套接字描述符(>=3,0-2为stdin/stdout/stderr) |
-1 | 创建失败,检查errno:<br>• EACCES(权限不足)<br>• EMFILE(描述符耗尽)<br>• EPROTONOSUPPORT(协议不支持) |
(五)使用示例
1. 创建TCP套接字
int tcp_sock = socket(AF_INET, SOCK_STREAM, 0);
if (tcp_sock == -1) {
perror("TCP socket creation failed");
exit(EXIT_FAILURE);
}
2. 创建UDP套接字
int udp_sock = socket(AF_INET, SOCK_DGRAM, 0);
if (udp_sock == -1) {
perror("UDP socket creation failed");
exit(EXIT_FAILURE);
}
3. 创建原始套接字(需root权限)
int raw_sock = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP);
if (raw_sock == -1) {
perror("Raw socket creation failed");
exit(EXIT_FAILURE);
}
五、connect() 函数详解
(一)函数原型
#include <sys/types.h>
#include <sys/socket.h>
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
(二)核心功能
- 建立连接:在客户端与目标服务器之间建立通信链路(TCP三次握手)
- 地址绑定:对于UDP可预先指定默认接收地址
- 错误检测:验证网络可达性与服务可用性
(三)参数解析
参数 | 类型 | 说明 |
---|
sockfd | int | 由socket() 创建的套接字描述符 |
addr | struct sockaddr* | 目标服务器地址结构(需转换为实际协议地址结构) |
addrlen | socklen_t | 地址结构体的实际长度(字节数) |
(四)地址结构示例(IPv4)
struct sockaddr_in {
sa_family_t sin_family; // 地址族(AF_INET)
in_port_t sin_port; // 端口号(网络字节序)
struct in_addr sin_addr; // IP地址(网络字节序)
unsigned char sin_zero[8]; // 填充字段(全0)
};
struct in_addr {
in_addr_t s_addr; // 32位IPv4地址
};
(五)使用流程
sequenceDiagram
Client->>Socket: 1. socket()
Client->>Socket: 2. connect()
Note right of Socket: 触发三次握手
Socket-->>Client: 连接成功(返回0)
or Socket-->>Client: 连接失败(返回-1)
(六)返回值处理
返回值 | 说明 |
---|
0 | 连接建立成功 |
-1 | 连接失败,检查errno:<br>• ECONNREFUSED(目标拒绝)<br>• ETIMEDOUT(超时)<br>• ENETUNREACH(网络不可达) |
(七)典型应用场景
1. TCP客户端连接
int main() {
// 创建套接字
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd == -1) {
perror("Socket creation failed");
exit(EXIT_FAILURE);
}
// 配置服务器地址
struct sockaddr_in serv_addr = {
.sin_family = AF_INET,
.sin_port = htons(8080),
.sin_addr.s_addr = inet_addr("192.168.1.100")
};
// 建立连接
if (connect(sockfd, (struct sockaddr*)&serv_addr, sizeof(serv_addr)) == -1) {
perror("Connection failed");
close(sockfd);
exit(EXIT_FAILURE);
}
// 数据传输...
close(sockfd);
return 0;
}
2. UDP预连接(非必须)
// UDP套接字创建
int udp_sock = socket(AF_INET, SOCK_DGRAM, 0);
// 指定默认接收地址
struct sockaddr_in serv_addr = {...};
connect(udp_sock, (struct sockaddr*)&serv_addr, sizeof(serv_addr));
// 后续可直接使用send()代替sendto()
send(udp_sock, buffer, strlen(buffer), 0);
六、网络字节序转换与IP地址处理
(一)字节序转换函数
1. 核心函数列表
函数原型 | 功能描述 | 常用场景 |
---|
uint32_t htonl(uint32_t hostlong) | 主机到网络序(32位) | IP地址转换 |
uint16_t htons(uint16_t hostshort) | 主机到网络序(16位) | 端口号转换 |
uint32_t ntohl(uint32_t netlong) | 网络到主机序(32位) | 接收IP地址解析 |
uint16_t ntohs(uint16_t netshort) | 网络到主机序(16位) | 接收端口号解析 |
2. 使用示例
// 转换端口号
uint16_t port = 8080;
uint16_t net_port = htons(port);
// 转换IPv4地址
uint32_t ip = 0xC0A80001; // 192.168.0.1
uint32_t net_ip = htonl(ip);
(二)IP地址转换函数
1. 传统方法(IPv4)
#include <arpa/inet.h>
// 字符串转网络字节序(已过时但常见)
in_addr_t inet_addr(const char *cp);
特性:
- 支持点分十进制格式(如"192.168.0.1")
- 失败返回
INADDR_NONE
(通常是0xFFFFFFFF) - 不支持IPv6地址
示例:
struct sockaddr_in addr;
addr.sin_addr.s_addr = inet_addr("192.168.1.100");
if (addr.sin_addr.s_addr == INADDR_NONE) {
perror("Invalid IP address");
}
2. 现代方法(推荐)
// 通用地址转换(支持IPv4/IPv6)
int inet_pton(int af, const char *src, void *dst);
参数 | 说明 |
---|
af | 地址族(AF_INET/AF_INET6) |
src | 点分地址字符串 |
dst | 存储转换结果的缓冲区 |
返回值:
示例:
struct in_addr ipv4_addr;
if (inet_pton(AF_INET, "10.0.0.1", &ipv4_addr) <= 0) {
perror("Address conversion failed");
}
(三)地址转换对照表
字符串地址 | 十六进制值(网络序) | 十进制值(主机序) |
---|
127.0.0.1 | 0x7F000001 | 2130706433 |
192.168.1.100 | 0xC0A80164 | 3232235876 |
255.255.255.255 | 0xFFFFFFFF | 4294967295 |
(四)错误处理指南
错误现象 | 可能原因 | 解决方案 |
---|
inet_addr返回INADDR_NONE | 地址格式错误 | 使用正则表达式验证输入 |
inet_pton返回0 | 地址与协议族不匹配 | 检查AF_INET/AF_INET6 |
转换后数值异常 | 字节序转换错误 | 确认使用ntohl/ntohs |
七、bind()
函数详解
(一)函数原型
#include <sys/socket.h>
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
(二)核心功能
- 地址绑定:将套接字与指定的网络地址/端口绑定
- 资源分配:声明对特定网络接口的控制权
- 服务标识:为客户端提供可连接的目标端点
(三)参数解析
参数 | 类型 | 说明 |
---|
sockfd | int | 由socket() 创建的套接字描述符 |
addr | struct sockaddr* | 包含绑定地址信息的结构体指针 |
addrlen | socklen_t | 地址结构体的实际长度(字节数) |
(四)使用场景对比
场景 | 服务器端 | 客户端 |
---|
必要性 | 必须调用(需固定服务端口) | 通常省略(系统自动分配临时端口) |
典型地址 | 明确指定IP和端口(如0.0.0.0:80) | 可指定为INADDR_ANY +0(自动选择) |
错误处理 | 严格检查EADDRINUSE (端口占用) | 关注EACCES (特权端口权限) |
(五)服务器端典型用法
// 创建TCP套接字
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
// 配置绑定地址
struct sockaddr_in serv_addr = {
.sin_family = AF_INET,
.sin_port = htons(8080), // 明确指定端口
.sin_addr.s_addr = htonl(INADDR_ANY) // 监听所有接口
};
// 绑定地址
if (bind(sockfd, (struct sockaddr*)&serv_addr, sizeof(serv_addr)) == -1) {
perror("Bind failed");
close(sockfd);
exit(EXIT_FAILURE);
}
(六)客户端特殊用法
// 创建UDP套接字
int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
// 指定源端口(特殊需求)
struct sockaddr_in cli_addr = {
.sin_family = AF_INET,
.sin_port = htons(54321), // 固定客户端端口
.sin_addr.s_addr = inet_addr("192.168.1.100")
};
if (bind(sockfd, (struct sockaddr*)&cli_addr, sizeof(cli_addr)) == -1) {
perror("Client bind failed");
close(sockfd);
exit(EXIT_FAILURE);
}
(七)错误处理指南
错误码 | 原因描述 | 解决方案 |
---|
EADDRINUSE | 地址/端口已被占用 | 更换端口或等待释放 |
EACCES | 无权限绑定特权端口(<1024) | 使用sudo 运行或改用非特权端口 |
EINVAL | 套接字已绑定过地址 | 检查是否重复调用bind() |
ENOTSOCK | 文件描述符不是套接字 | 验证sockfd 来源 |
八、listen()
函数详解
(一)函数原型
#include <sys/socket.h>
int listen(int sockfd, int backlog);
(二)核心功能
- 监听模式:将套接字设置为被动接收连接状态
- 队列管理:建立连接请求缓冲队列
- 服务准备:为后续
accept()
调用提供就绪环境
(三)参数解析
参数 | 类型 | 说明 |
---|
sockfd | int | 已绑定的流式套接字描述符(需先调用bind() ) |
backlog | int | 等待连接队列的最大长度(详见下文说明) |
(四)队列机制详解
graph LR
A[Client SYN] --> B{SYN队列<br>(未完成握手)}
B -->|完成三次握手| C[ACCEPT队列<br>(已建立连接)]
C --> D[accept()取出]
style B fill:#f9d5e5,stroke:#f25287
style C fill:#d5e8d4,stroke:#82b366
- SYN队列(半连接队列):存储未完成三次握手的请求
- ACCEPT队列(全连接队列):存储已完成握手的连接
- backlog参数:通常指ACCEPT队列的最大长度(不同系统实现有差异)
(五)使用规范
1. 前置条件
// 创建套接字
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
// 绑定地址(必须步骤)
struct sockaddr_in addr = {...};
bind(sockfd, (struct sockaddr*)&addr, sizeof(addr));
// 开始监听
listen(sockfd, SOMAXCONN); // SOMAXCONN为系统允许的最大值
2. 推荐配置
操作系统 | 最大backlog值 | 说明 |
---|
Linux | /proc/sys/net/core/somaxconn | 默认值通常为4096 |
FreeBSD | kern.ipc.soacceptqueue | 默认值通常为128 |
通用方案 | 使用SOMAXCONN 宏定义 | 自动适配系统最大值 |
(六)错误处理
错误码 | 原因描述 | 解决方案 |
---|
EADDRINUSE | 地址/端口已被占用 | 检查端口冲突,使用netstat -tulnp 排查 |
EBADF | 无效套接字描述符 | 验证sockfd 是否已正确创建 |
EINVAL | 套接字未绑定或类型错误 | 确保先调用bind() 且为流式套接字 |
ENOTSOCK | 文件描述符不是套接字 | 检查sockfd 来源 |

九、accept()
函数详解
(一)函数原型
#include <sys/socket.h>
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
(二)核心功能
- 连接接入:从已完成连接队列中取出首个连接
- 创建新套接字:生成专用于该连接的通信套接字
- 客户端信息获取(可选):记录客户端地址信息
(三)参数解析
参数 | 类型 | 说明 |
---|
sockfd | int | 处于监听状态的流式套接字描述符 |
addr | struct sockaddr* | 客户端地址信息存储缓冲区(可NULL) |
addrlen | socklen_t* | 输入:缓冲区大小<br>输出:实际地址长度(值-结果参数) |
(四)使用流程
sequenceDiagram
Client->>Server: SYN
Server->>Client: SYN-ACK
Client->>Server: ACK
Note right of Server: 连接进入ACCEPT队列
Server->>Server: accept()
Server-->>Client: 建立数据通道
(五)典型用法
1. 基础用法(不获取客户端信息)
int new_sock = accept(listen_sock, NULL, NULL);
if (new_sock == -1) {
perror("Accept failed");
// 错误处理
}
2. 获取客户端信息
struct sockaddr_in client_addr;
socklen_t addr_len = sizeof(client_addr);
int new_sock = accept(listen_sock,
(struct sockaddr*)&client_addr,
&addr_len);
if (new_sock != -1) {
printf("Client from %s:%d\n",
inet_ntoa(client_addr.sin_addr),
ntohs(client_addr.sin_port));
}
(六)返回值说明
返回值 | 说明 |
---|
正整数 | 新通信套接字描述符(>=3) |
-1 | 失败,检查errno:<br>• EAGAIN/EWOULDBLOCK(非阻塞模式无连接)<br>• ECONNABORTED(连接中止)<br>• EINTR(被信号中断) |
十、send()
与 recv()
函数
(一)函数原型
#include <sys/socket.h>
// 发送数据
ssize_t send(int sockfd, const void *buf, size_t len, int flags);
// 接收数据
ssize_t recv(int sockfd, void *buf, size_t len, int flags);
(二)核心功能对比
特性 | send() | recv() |
---|
传输方向 | 主动发送数据到对端 | 被动接收来自对端的数据 |
缓冲区管理 | 从应用缓冲区复制到内核发送缓冲区 | 从内核接收缓冲区复制到应用缓冲区 |
阻塞行为 | 等待内核缓冲区空间 | 等待网络数据到达 |
连接状态检测 | 通过返回值检测连接异常 | 返回0表示对端已关闭连接 |
(三)参数详解
通用参数:
参数 | 说明 |
---|
sockfd | 已建立连接的流式套接字描述符(TCP) |
buf | 数据缓冲区指针 |
len | 数据长度(send 为待发送长度,recv 为缓冲区容量) |
flags | 控制标志(见下文详解) |
标志位说明:
标志值 | 适用函数 | 说明 |
---|
MSG_OOB | 两者 | 处理带外数据(最高优先级数据) |
MSG_DONTWAIT | 两者 | 非阻塞操作(等价于设置O_NONBLOCK ) |
MSG_PEEK | recv | 查看数据但不移除接收队列 |
MSG_WAITALL | recv | 等待直到填满缓冲区(可能被信号中断) |
MSG_NOSIGNAL | send | 禁止生成SIGPIPE 信号 |
0表示阻塞接受,通常可以设置为 0。
(四)返回值处理指南
send()
返回值:
返回值 | 状态说明 |
---|
>0 | 成功发送的字节数(可能小于len ) |
0 | 对端正常关闭连接(仅非阻塞模式可能) |
-1 | 错误发生,检查errno:<br>• EAGAIN /EWOULDBLOCK (非阻塞未就绪)<br>• ECONNRESET (连接重置)<br>• EPIPE (对端关闭连接) |
recv()
返回值:
返回值 | 状态说明 |
---|
>0 | 实际接收的字节数 |
0 | 对端已正常关闭连接 |
-1 | 错误发生,检查errno:<br>• EAGAIN /EWOULDBLOCK (非阻塞无数据)<br>• ECONNRESET (连接被重置) |
(五)模式对比
阻塞模式 vs 非阻塞模式
特性 | 阻塞模式 | 非阻塞模式 |
---|
send() 行为 | 等待直到所有数据进入内核缓冲区 | 立即返回已发送数据量 |
recv() 行为 | 等待直到有数据到达 | 立即返回当前可用数据量 |
错误处理 | 简化编程逻辑 | 需要处理EAGAIN /EWOULDBLOCK |
适用场景 | 简单客户端/低并发服务器 | 高并发服务器(配合I/O多路复用) |
综合应用:
client //作为主动的角色
#include<stdio.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <strings.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
//./client port ip
int main(int argc, const char *argv[])
{
if(argc != 3)
{
printf("Usage: %s <port> <ip>\n",argv[0]);
return -1;
}
int fd = socket(AF_INET,SOCK_STREAM,0);
if(fd < 0)
{
perror("socket fail\n");
return -1;
}
struct sockaddr_in seraddr;
bzero(&seraddr,sizeof(seraddr));
seraddr.sin_family = AF_INET;
seraddr.sin_port = htons(atoi(argv[1]));
seraddr.sin_addr.s_addr = inet_addr(argv[2]);
if(connect(fd,(struct sockaddr*)&seraddr,sizeof(seraddr)) < 0)
{
perror("connect fail");
return -1;
}
printf("connect success!\n");
char buf[1024];
while(1)
{
printf(">");
fgets(buf,sizeof(buf),stdin);
send(fd,buf,strlen(buf)+1,0);
if(strncmp(buf,"quit",4) == 0)
{
break;
}
recv(fd,buf,sizeof(buf),0);
printf("s: %s",buf);
}
close(fd);
return 0;
}
server //被动角色
#include<stdio.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <strings.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
int main(int argc, const char *argv[])
{
if(argc != 3)
{
printf("Usage: %s <port> <ip>\n",argv[0]);
return -1;
}
//1.socket 创建通信一端
int fd = socket(AF_INET,SOCK_STREAM,0);
if(fd < 0)
{
perror("socket fail\n");
return -1;
}
struct sockaddr_in seraddr;
bzero(&seraddr,sizeof(seraddr));
seraddr.sin_family = AF_INET;
seraddr.sin_port = htons(atoi(argv[1]));
seraddr.sin_addr.s_addr = inet_addr(argv[2]);
//2.bind--绑定服务端的地址信息
if(bind(fd,(const struct sockaddr*)&seraddr,sizeof(seraddr)) < 0)
{
perror("bind fail");
return -1;
}
//3.listen 监听
if(listen(fd,5) < 0)
{
perror("listen fail");
return -1;
}
//4.accept
#if 0
int connfd = accept(fd,NULL,NULL);
if(connfd < 0)
{
perror("accept fail");
return -1;
}
printf("-----client----connectted\n");
#endif
struct sockaddr_in cliaddr;
bzero(&cliaddr,sizeof(cliaddr));
socklen_t len = sizeof(cliaddr);
int connfd = 0;
if ((connfd = accept(fd,(struct sockaddr*)&cliaddr,&len)) < 0)
{
perror("accept fail");
return -1;
}
printf("-------client-----info-----\n");
printf("ip : %s\n",inet_ntoa(cliaddr.sin_addr));
printf("port: %d\n",ntohs(cliaddr.sin_port));
printf("---------------------------\n");
char buf[1024];
char sbuf[1024];
while(1)
{
recv(connfd,buf,sizeof(buf),0);
printf("c: %s\n",buf);
if(strncmp(buf,"quit",4) == 0)
{
break;
}
sprintf(sbuf,"server + %s\n",buf);
send(connfd,sbuf,strlen(sbuf)+1,0);
}
close(fd);
close(connfd);
return 0;
}
十一、TCP粘包问题及解决方案详解
(一)粘包现象本质
1. 产生原因
graph LR
A[发送方] -->|连续小包| B[TCP发送缓冲区]
B -->|合并发送| C[网络传输]
C -->|整块接收| D[接收方缓冲区]
D -->|单次读取| E[接收方应用]
2. 典型场景
- 快速连续发送小数据包(如心跳包)
- 接收缓冲区大于单个数据包
- 网络延迟导致数据堆积
(二)解决方案对比
方案类型 | 实现方式 | 优点 | 缺点 | 典型应用场景 |
---|
分隔符法 | 自定义特殊字符作为消息边界 | 实现简单 | 需处理转义问题 | 文本协议(如HTTP) |
长度前缀法 | 数据前添加长度头 | 处理高效 | 需约定长度格式 | 二进制协议 |
固定长度法 | 所有消息统一长度 | 解析简单 | 空间浪费 | 金融领域 |
复合结构法 | 结构体封装元数据 | 扩展性强 | 需处理对齐问题 | 复杂业务系统 |
(三)核心解决方案实现
1. 分隔符方案(以\r\n
为例)
// 发送端
const char* msg = "Hello World\r\n";
send(sockfd, msg, strlen(msg), 0);
// 接收端处理逻辑
char buffer[4096];
size_t recv_pos = 0;
while(1) {
ssize_t n = recv(sockfd, buffer + recv_pos, sizeof(buffer)-recv_pos, 0);
if(n <= 0) break;
recv_pos += n;
char* end = strstr(buffer, "\r\n");
while(end) {
*end = '\0';
process_message(buffer); // 处理完整消息
size_t remain = recv_pos - (end - buffer + 2);
memmove(buffer, end+2, remain);
recv_pos = remain;
end = strstr(buffer, "\r\n");
}
}
2. 长度前缀法(4字节网络序头)
// 发送端封装
void send_packet(int sockfd, const void* data, size_t len) {
uint32_t net_len = htonl(len);
send(sockfd, &net_len, 4, MSG_MORE); // 发送长度头
send(sockfd, data, len, 0); // 发送数据体
}
// 接收端处理
enum { HEAD_SIZE = 4 };
size_t need_len = HEAD_SIZE;
size_t recv_pos = 0;
uint32_t body_len = 0;
char buffer[4096];
while(1) {
ssize_t n = recv(sockfd, buffer + recv_pos, need_len, 0);
if(n <= 0) break;
recv_pos += n;
if(recv_pos >= need_len) {
if(need_len == HEAD_SIZE) {
body_len = ntohl(*(uint32_t*)buffer);
need_len = body_len;
recv_pos = 0;
} else {
process_message(buffer, body_len); // 处理完整消息
need_len = HEAD_SIZE;
recv_pos = 0;
}
}
}
3. 复合结构法(协议头+数据体)
#pragma pack(push, 1) // 禁用内存对齐
struct PacketHeader {
uint16_t version; // 协议版本
uint32_t cmd; // 操作指令
uint32_t checksum; // 校验和
uint32_t body_len; // 数据体长度
};
#pragma pack(pop)
// 发送封装函数
void send_packet(int sockfd, uint32_t cmd, const void* data, size_t len) {
struct PacketHeader header = {
.version = 0x0101,
.cmd = htonl(cmd),
.body_len = htonl(len)
};
header.checksum = calc_checksum(&header, data);
send(sockfd, &header, sizeof(header), MSG_MORE);
send(sockfd, data, len, 0);
}
练习二、客户端向服务器传输一个文件(粘包)
cilent
#include<stdio.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <strings.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>
#include <sys/wait.h>
#include <sys/stat.h>
#include <fcntl.h>
typedef struct
{
int size;
char data[1024];
}msg_t;
//./client port ip
int main(int argc, const char *argv[])
{
#if 1
if(argc != 4)
{
printf("Usage: %s <port> <ip> <filename>\n",argv[0]);
return -1;
}
#endif
int fd = socket(AF_INET,SOCK_STREAM,0);
if(fd < 0)
{
perror("socket fail\n");
return -1;
}
struct sockaddr_in seraddr;
bzero(&seraddr,sizeof(seraddr));
seraddr.sin_family = AF_INET;
seraddr.sin_port = htons(atoi(argv[1]));
seraddr.sin_addr.s_addr = inet_addr(argv[2]);
if(connect(fd,(struct sockaddr*)&seraddr,sizeof(seraddr)) < 0)
{
perror("connect fail");
return -1;
}
//名字
msg_t msg;
msg.size = -1;
strcpy(msg.data,argv[3]);
send(fd,&msg,sizeof(msg),0);
//文件内容
int fd_s = open(argv[3],O_RDONLY);
if(fd_s < 0)
{
perror("open fail");
return -1;
}
while(1)
{
int ret = read(fd_s,msg.data,sizeof(msg.data));
msg.size = ret;
send(fd,&msg,sizeof(msg),0);
if(ret ==0)
break;
}
close(fd_s);
close(fd);
return 0;
}
server
#include<stdio.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <strings.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>
#include <sys/wait.h>
#include <sys/stat.h>
#include <fcntl.h>
typedef struct
{
int size;
char data[1024];
}msg_t;
int main(int argc, const char *argv[])
{
if(argc != 2)
{
printf("Usage: %s <port>\n",argv[0]);
return -1;
}
//1.socket 创建通信一端
int fd = socket(AF_INET,SOCK_STREAM,0);
if(fd < 0)
{
perror("socket fail\n");
return -1;
}
struct sockaddr_in seraddr;
bzero(&seraddr,sizeof(seraddr));
seraddr.sin_family = AF_INET;
seraddr.sin_port = htons(atoi(argv[1]));
seraddr.sin_addr.s_addr = INADDR_ANY;//绑定所有IP
//2.bind--绑定服务端的地址信息
if(bind(fd,(const struct sockaddr*)&seraddr,sizeof(seraddr)) < 0)
{
perror("bind fail");
return -1;
}
//3.listen 监听
if(listen(fd,5) < 0)
{
perror("listen fail");
return -1;
}
//4.accept
int connfd = accept(fd,NULL,NULL);
if(connfd < 0)
{
perror("accept fail");
return -1;
}
char buf[1024];
msg_t msg;
int ret = recv(connfd,&msg,sizeof(msg),0);
int fd_dest = open(msg.data,O_WRONLY|O_CREAT|O_TRUNC,0666);
if(fd < 0)
{
perror("open fail");
return -1;
}
while(1)
{
ret = recv(connfd,&msg,sizeof(msg),0);
if(msg.size == 0)
break;
write(fd_dest,msg.data,msg.size);
}
return 0;
}
十二、UDP C/S 模型实现详解
(一)协议特性对比
特性 | TCP | UDP |
---|
连接方式 | 面向连接 | 无连接 |
可靠性 | 可靠传输 | 尽最大努力交付 |
数据边界 | 字节流 | 保留数据报边界 |
传输效率 | 较低(有控制开销) | 较高 |
应用场景 | 文件传输、Web访问 | 实时视频、DNS查询 |
(二)核心函数说明
1. sendto()
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,
const struct sockaddr *dest_addr, socklen_t addrlen);
- 功能:发送数据报到指定地址
- 参数:
dest_addr
:目标地址结构指针addrlen
:地址结构长度
2. recvfrom()
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,
struct sockaddr *src_addr, socklen_t *addrlen);
- 功能:接收数据报并获取来源地址
- 参数:
src_addr
:来源地址结构指针(可NULL)addrlen
:输入时为缓冲区大小,输出时为实际地址长度
(三)完整实现示例
一对一通信(进程):
头文件
#ifndef _HEAD_H_
#define _HEAD_H_
#include <stdio.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <strings.h>
#include <string.h>
#include <signal.h>
#include <sys/wait.h>
#endif
UDP 服务器端
#include "head.h"
void handler(int signo)
{
printf("%d exit....\n",getpid());
exit(0);
}
//./cli ip port
int main(int argc, const char *argv[])
{
if (argc != 3)
{
printf("Usage: %s <ip> <port>\n",argv[0]);
return -1;
}
//1.socket
int fd = socket(AF_INET,SOCK_DGRAM,0);
if (fd < 0)
{
perror("socket fail");
return -1;
}
struct sockaddr_in seraddr;
bzero(&seraddr,sizeof(seraddr));
seraddr.sin_family = AF_INET;
seraddr.sin_port = htons(atoi(argv[1]));
seraddr.sin_addr.s_addr = inet_addr(argv[2]);
//2.bind
if (bind(fd,(const struct sockaddr*)&seraddr,sizeof(seraddr)) < 0)
{
perror("bind fail");
return -1;
}
struct sockaddr_in cliaddr;
bzero(&cliaddr,sizeof(cliaddr));
socklen_t len = sizeof(cliaddr);
char buf[1024] = {0};
recvfrom(fd,buf,sizeof(buf),0,(struct sockaddr*)&cliaddr,&len);
printf("cli: %s \n",buf);
signal(SIGUSR1,handler);
pid_t pid = fork();
if (pid < 0)
{
perror("fork fail");
return -1;
}
if (pid > 0)
{
while (1)
{
printf(">");
fgets(buf,sizeof(buf),stdin);
sendto(fd,buf,strlen(buf)+1,0,(const struct sockaddr*)&cliaddr,sizeof(cliaddr));
if (strncmp(buf,"quit",4) == 0)
{
kill(pid,SIGUSR1);
wait(NULL);
exit(0);
}
}
}else if (pid == 0)
{
while (1)
{
recvfrom(fd,buf,sizeof(buf),0,NULL,NULL);
printf("buf = %s\n",buf);
if (strncmp(buf,"quit",4) == 0)
{
kill(getppid(),SIGUSR1);
exit(0);
}
}
}
return 0;
}
UDP 客户端
#include "head.h"
void handler(int signo)
{
exit(0);
}
//./cli ip port
int main(int argc, const char *argv[])
{
if (argc != 3)
{
printf("Usage: %s <ip> <port>\n",argv[0]);
return -1;
}
//1.socket
int fd = socket(AF_INET,SOCK_DGRAM,0);
if (fd < 0)
{
perror("socket fail");
return -1;
}
struct sockaddr_in seraddr;
bzero(&seraddr,sizeof(seraddr));
seraddr.sin_family = AF_INET;
seraddr.sin_port = htons(atoi(argv[1]));
seraddr.sin_addr.s_addr = inet_addr(argv[2]);
signal(SIGUSR1,handler);
pid_t pid = fork();
if (pid < 0)
{
perror("fork fail");
return -1;
}
char buf[1024] = {0};
if (pid > 0)
{
while (1)
{
printf(">");
fgets(buf,sizeof(buf),stdin);
sendto(fd,buf,strlen(buf)+1,0,(const struct sockaddr*)&seraddr,sizeof(seraddr));
if (strncmp(buf,"quit",4) == 0)
{
kill(pid,SIGUSR1);
wait(NULL);
exit(0);
}
}
}else if (pid == 0)
{
while (1)
{
recvfrom(fd,buf,sizeof(buf),0,NULL,NULL);
printf("buf = %s\n",buf);
if (strncmp(buf,"quit",4) == 0)
{
kill(getppid(),SIGUSR1);
exit(0);
}
}
}
return 0;
}
一对一通信(线程):
UDP 客户端
#include "head.h"
void *do_read(void *arg)
{
int fd = *(int *)arg;
char buf[1024] = {0};
while (1)
{
recvfrom(fd,buf,sizeof(buf),0,NULL,NULL);
printf("buf = %s\n",buf);
if (strncmp(buf,"quit",4) == 0)
{
exit(0);
}
}
return NULL;
}
struct sockaddr_in seraddr;
void * do_write(void *arg)
{
int fd = *(int *)arg;
char buf[1024] = {0};
while (1)
{
printf(">");
fgets(buf,sizeof(buf),stdin);
sendto(fd,buf,strlen(buf)+1,0,(const struct sockaddr*)&seraddr,sizeof(seraddr));
if (strncmp(buf,"quit",4) == 0)
{
exit(0);
}
}
}
//./cli ip port
int main(int argc, const char *argv[])
{
if (argc != 3)
{
printf("Usage: %s <ip> <port>\n",argv[0]);
return -1;
}
//1.socket
int fd = socket(AF_INET,SOCK_DGRAM,0);
if (fd < 0)
{
perror("socket fail");
return -1;
}
bzero(&seraddr,sizeof(seraddr));
seraddr.sin_family = AF_INET;
seraddr.sin_port = htons(atoi(argv[1]));
seraddr.sin_addr.s_addr = inet_addr(argv[2]);
pthread_t tid[2];
int ret = pthread_create(&tid[0],NULL,do_read,&fd);
if (ret != 0)
handle_error_en(ret,"pthread_create fail");
ret = pthread_create(&tid[1],NULL,do_write,&fd);
if (ret != 0)
handle_error_en(ret,"pthread_create fail");
printf("-------------main-------\n");
pthread_join(tid[0],NULL);
pthread_join(tid[1],NULL);
return 0;
}
UDP 服务器端
#include "head.h"
void *do_read(void *arg)
{
int fd = *(int *)arg;
char buf[1024] = {0};
while (1)
{
recvfrom(fd,buf,sizeof(buf),0,NULL,NULL);
printf("buf = %s\n",buf);
if (strncmp(buf,"quit",4) == 0)
{
exit(0);
}
}
return NULL;
}
struct sockaddr_in cliaddr;
void * do_write(void *arg)
{
int fd = *(int *)arg;
char buf[1024] = {0};
while (1)
{
printf(">");
fgets(buf,sizeof(buf),stdin);
sendto(fd,buf,strlen(buf)+1,0,(const struct sockaddr*)&cliaddr,sizeof(cliaddr));
if (strncmp(buf,"quit",4) == 0)
{
exit(0);
}
}
}
//./cli ip port
int main(int argc, const char *argv[])
{
if (argc != 3)
{
printf("Usage: %s <ip> <port>\n",argv[0]);
return -1;
}
//1.socket
int fd = socket(AF_INET,SOCK_DGRAM,0);
if (fd < 0)
{
perror("socket fail");
return -1;
}
struct sockaddr_in seraddr;
bzero(&seraddr,sizeof(seraddr));
seraddr.sin_family = AF_INET;
seraddr.sin_port = htons(atoi(argv[1]));
seraddr.sin_addr.s_addr = inet_addr(argv[2]);
//2.bind
if (bind(fd,(const struct sockaddr*)&seraddr,sizeof(seraddr)) < 0)
{
perror("bind fail");
return -1;
}
bzero(&cliaddr,sizeof(cliaddr));
socklen_t len = sizeof(cliaddr);
char buf[1024] = {0};
recvfrom(fd,buf,sizeof(buf),0,(struct sockaddr*)&cliaddr,&len);
printf("cli: %s \n",buf);
pthread_t tid[2];
int ret = pthread_create(&tid[0],NULL,do_read,&fd);
if (ret != 0)
handle_error_en(ret,"pthread_create fail");
ret = pthread_create(&tid[1],NULL,do_write,&fd);
if (ret != 0)
handle_error_en(ret,"pthread_create fail");
printf("-------------main-------\n");
pthread_join(tid[0],NULL);
pthread_join(tid[1],NULL);
return 0;
}
UDP聊天室
头文件
#ifndef _HEAD_H_
#define _HEAD_H_
#include <stdio.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <strings.h>
#include <string.h>
#include <signal.h>
#include <sys/wait.h>
#include <pthread.h>
#include <errno.h>
enum MSG_TYPE
{
LOGIN =1,
CHAT,
QUIT,
};
typedef struct msg
{
enum MSG_TYPE type;
unsigned char name[20];
unsigned char text[256];
}msg_t;
#define handle_error_en(en, msg) \
do { errno = en; perror(msg); exit(EXIT_FAILURE); } while (0)
#define handle_error(msg) \
do { perror(msg); exit(EXIT_FAILURE); } while (0)
#
客户端
#include "head.h"
struct sockaddr_in seraddr;
msg_t msg;
void *do_read(void *arg)
{
int fd = *(int *)arg;
msg_t msg;// 局部变量,用于接收消息(注意这里和全局变量名相同,但作用域不同)
while(1)
{
recvfrom(fd,&msg,sizeof(msg_t),0,NULL,NULL);// 从socket接收客户端消息
printf("[%s]:%s",msg.name,msg.text);
}
return NULL;
}
void *do_write(void *arg)
{
int fd = *(int *)arg;
printf("char>:");
while(1)
{
fgets(msg.text,sizeof(msg.text),stdin);
if(strncmp(msg.text,"quit",4) == 0)
{
msg.type = QUIT;
sendto(fd,&msg,sizeof(msg),0,(const struct sockaddr*)&seraddr,sizeof(seraddr));
exit(0);
}else
{
msg.type = CHAT;
sendto(fd,&msg,sizeof(msg),0,(const struct sockaddr*)&seraddr,sizeof(seraddr));
}
}
return NULL;
}
int main(int argc, const char *argv[])
{
if(argc != 3)
{
printf("Usage: %s <port> <ip>\n",argv[0]);
return -1;
}
int fd = socket(AF_INET,SOCK_DGRAM,0);
if(fd < 0)
{
perror("socket fail");
return -1;
}
bzero(&seraddr,sizeof(seraddr));
seraddr.sin_family = AF_INET;
seraddr.sin_port = htons(atoi(argv[1]));
seraddr.sin_addr.s_addr = inet_addr(argv[2]);
msg.type = LOGIN;
printf("input your name>: ");
fgets(msg.name,sizeof(msg.name),stdin);
msg.name[strlen(msg.name)-1] = '\0';
sprintf(msg.text,"[%s] is login!\n",msg.name);// 构造登录消息
sendto(fd,&msg,sizeof(msg),0,(const struct sockaddr*)&seraddr,sizeof(seraddr));
pthread_t tid[2];
int ret = pthread_create(&tid[0],NULL,do_read,&fd);
if(ret != 0)
handle_error_en(ret,"pthread_create fail");
ret = pthread_create(&tid[1],NULL,do_write,&fd);
if(ret != 0)
handle_error_en(ret,"pthread_create fail");
pthread_join(tid[0],NULL);
pthread_join(tid[1],NULL);
return 0;
}
UDP 服务器端
#include "head.h"
#define N 10
// 定义一个结构体,用于存储客户端地址和状态
typedef struct
{
struct sockaddr_in addr;//客户端地址
enum MSG_TYPE stat;//客户端状态
}cliaddr_t;
cliaddr_t peer_addr[N];// 存储已登录客户端信息的数组
struct sockaddr_in cliaddr;// 用于临时存储接收到的客户端地址
// 广播消息给所有已登录的用户(除了发送者)
void broadcast_msg(int fd,msg_t *msg,struct sockaddr_in *cliaddr)
{
int i = 0;
for(i = 0;i < N;++i)
{
if(peer_addr[i].stat == LOGIN && memcmp(&peer_addr[i].addr,cliaddr,sizeof(struct sockaddr_in)) != 0)
{
sendto(fd,msg,sizeof(msg_t),0,(const struct sockaddr*)&peer_addr[i],sizeof(struct sockaddr_in));
}
}
}
int main(int argc, const char *argv[])
{
if (argc != 3)
{
printf("Usage: %s <port> <ip>\n",argv[0]);
return -1;
}
int fd = socket(AF_INET,SOCK_DGRAM,0);
if(fd < 0)
{
perror("socket fail");
return -1;
}
struct sockaddr_in seraddr;
bzero(&seraddr,sizeof(socket));
seraddr.sin_family = AF_INET;
seraddr.sin_port = htons(atoi(argv[1]));
seraddr.sin_addr.s_addr = inet_addr(argv[2]);
if(bind(fd,(const struct sockaddr*)&seraddr,sizeof(seraddr)) != 0)
{
perror("bind fail");
return -1;
}
bzero(&cliaddr,sizeof(cliaddr));
socklen_t len = sizeof(cliaddr);// 用于recvfrom函数获取客户端地址长度
msg_t msg;// 用于接收消息的结构体
int i = 0;// 用于遍历peer_addr数组的索引
int j = 0;// 在QUIT情况下用于遍历peer_addr数组的索引
while(1)
{
recvfrom(fd,&msg,sizeof(msg),0,(struct sockaddr*)&cliaddr,&len);
printf("cli %s text:%s\n",msg.name,msg.text);
switch(msg.type)
{
case LOGIN:
peer_addr[i].addr = cliaddr;// 存储客户端地址
peer_addr[i].stat = LOGIN;// 设置状态为登录
++i;
broadcast_msg(fd,&msg,&cliaddr);// 广播登录消息
break;
case CHAT:
broadcast_msg(fd,&msg,&cliaddr);// 广播聊天信息
break;
case QUIT:
broadcast_msg(fd,&msg,&cliaddr);// 广播退出消息
for(j = 0;j < N;j++)
{
if(memcmp(&cliaddr,&peer_addr[i].addr,sizeof(struct sockaddr_in)) == 0)// 找到匹配项
{
bzero(&peer_addr[i],sizeof(peer_addr[i]));// 清除用户信息
}
}
break;
}
}
return 0;
}