IO模型与并发服务器设计

一、阻塞IO与非阻塞IO

1.1 阻塞IO

层面 步骤 说明
用户层 调用recv 执行 recv(fd, buf, sizeof(buf), 0),程序卡在这里等待数据
内核第1步 检查缓冲区 检查socket接收缓冲区是否有数据
内核第2步 无数据时睡眠 将进程状态设为 TASK_INTERRUPTIBLE(可中断睡眠),移出CPU运行队列,调度其他进程运行
内核第3步 数据到达并唤醒 网卡触发硬件中断 → 内核将数据拷贝到接收缓冲区 → 唤醒进程(状态改为 TASK_RUNNING),重新加入运行队列

1.2 非阻塞IO

层面 步骤 说明
用户层 设置非阻塞 fcntl(fd, F_SETFL, O_NONBLOCK) 设置非阻塞标志
用户层 循环轮询 while循环中调用 recv(),若返回值 ≥0 则数据就绪退出循环
用户层 错误处理 若返回 -1 且 errno != EAGAIN,则退出程序
用户层 避免占满CPU usleep(1000) 短暂休眠,防止忙等待占用CPU
内核第1步 检查缓冲区 检查接收缓冲区,无论是否有数据都立即返回结果
内核第2步 无数据时 返回 EAGAIN 错误码,进程继续执行(不进入睡眠)

1.3 fcntl函数

项目 内容
头文件 #include <fcntl.h> #include <unistd.h>
函数原型 int fcntl(int fd, int cmd, ... /* arg */);
作用 对文件描述符进行各种控制操作
fd 要操作的文件描述符
cmd 控制命令(如 F_SETFL 设置状态标志、F_GETFL 获取状态标志)
arg 可选参数,取决于 cmd 的具体命令
成功返回值 取决于 cmd 命令(如 F_GETFL 返回文件状态标志)
失败返回值 返回 -1(并重置错误码)

二、IO多路复用

2.1 IO多路复用

项目 内容
定义 单线程或单进程管理多个文件描述符(如套接字)的技术
核心机制 通过系统调用(如select、poll、epoll)监视多个IO操作的状态
通知方式 当某个IO操作就绪(可读、可写或发生异常)时,通知应用程序进行处理
提高并发性能 单线程可处理大量连接,无需为每个连接创建线程/进程
高效管理大量连接 避免资源浪费,降低系统开销
与非阻塞IO协作 结合使用可实现更高效的异步处理模式

2.2 select函数

项目 内容
头文件 #include <sys/socket.h> #include <sys/types.h> #include <sys/select.h>
函数原型 int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);
nfds 要监视的最大文件描述符 + 1
readfds 要监视的读文件描述符集合,不关心则传 NULL
writefds 要监视的写文件描述符集合,不关心则传 NULL
exceptfds 要监视的异常文件描述符集合,不关心则传 NULL
timeout 超时时间,NULL 表示永久阻塞
成功返回值 返回就绪的文件描述符个数
失败返回值 返回 -1(并重置错误码)

2.3 poll函数

项目 内容
头文件 #include <poll.h>
函数原型 int poll(struct pollfd *fds, nfds_t nfds, int timeout);
fds 指向 pollfd 结构体数组的指针,描述要监视的文件描述符及其事件
nfds fds 数组中有效描述符元素的数量
timeout 等待时间,单位为毫秒
成功返回值 返回结构体中 revents 域不为 0 的文件描述符个数
超时返回值 超时前没有任何事件发生,返回 0
失败返回值 返回 -1(并重置错误码)

三、并发服务器

3.1 服务器模型

项目 循环服务器 并发服务器
特点 同一时刻只能响应一个客户端的请求 同一时刻可以响应多个客户端的请求
TCP服务器 默认是循环服务器(因为 accept 和 recv 两个阻塞函数相互影响) 可通过多线程/多进程实现
UDP服务器 - 默认是并发服务器(只有一个阻塞函数 recvfrom)

3.2 多进程并发服务器

3.2.1 多线程并发服务器实现原理

角色 步骤 说明
主线程 创建监听Socket socket() → bind() → listen()
主线程 循环接收连接 while(1) { accept() 接收新连接 }
主线程 创建子线程 接收到新连接后,创建子线程处理该连接
子线程 处理请求 recv()/send() 处理客户端请求
子线程 关闭连接 close() 关闭连接

3.2.2 fork多进程并发服务器实现原理

模式 角色 步骤 说明
fork模型 主进程 创建监听Socket → bind() → listen() 主进程负责监听连接
fork模型 主进程 while(1) { accept() → fork() } 接收新连接后创建子进程处理
fork模型 子进程 close(监听Socket) 关闭继承自父进程的监听Socket
fork模型 子进程 recv()/send() → exit() 处理客户端请求后退出
fork模型 特点 父子进程完全独立,崩溃互不影响 -
预fork优化 启动时 预先创建多个子进程 类似Apache架构
预fork优化 负载均衡 通过共享监听socket(SO_REUSEPORT)实现 -

3.3 IO多路复用并发服务器

角色 步骤 说明
主线程 第1步 创建监听Socket → bind() → listen()
主线程 第2步 初始化 fd_set 集合,将监听Socket加入集合
主线程 第3步 while(1) 循环调用 select() 监听所有fd
主线程 第4步 select() 返回就绪的fd数量
主线程 第5步 遍历每个就绪的fd
主线程 判断分支 如果是监听Socket → accept() 新连接并加入 fd_set
主线程 判断分支 如果是普通Socket → recv()/send() 处理数据

3.4 pollfd结构体

cs 复制代码
struct pollfd{
    int fd;//文件描述符
    short events;//等待的事件
    short revents;//实际发生的事件
};
事件类别 常值 说明
读事件 POLLIN 普通或优先带数据可读
读事件 POLLPRI 高优先级数据可读
写事件 POLLOUT 普通或优先带数据可写
写事件 POLLWRNORM 普通数据可写
错误事件 POLLERR 发生错误
错误事件 POLLHUP 发生挂起
错误事件 POLLNVAL 描述符不是打开的文件
相关推荐
和裕14 分钟前
定制纸箱刀模费全解析:费用定义与可减免合作场景
大数据·运维·网络·人工智能·算法
剑锋所指,所向披靡!20 分钟前
HTTP服务器的基本相关概念
服务器·网络协议·http
troy12822 分钟前
智能体沙箱:如何为 Agent 模型搭建安全试验场,降低幻觉带来的损失
网络·安全
LHR_friendship40 分钟前
HCIP课程全部内容
网络·笔记·学习·hcip·华为ensp
吴声子夜歌41 分钟前
Nginx应用与运维——Nginx集群负载与配置管理(二)
运维·nginx
硅基手札1 小时前
【Linux内核专栏 11】驱动框架:platform / char / misc / 设备树
linux·运维·服务器
Chengbei111 小时前
拒绝无效洞!蚂蚁Src榜一Clown 2.0 SRC SKill 第二代带状态闭环的白盒 & 黑盒审计 Skill
网络·人工智能·安全·web安全·网络安全·自动化·安全架构
2301_777998341 小时前
TCP协议:滑动窗口与流量控制
服务器·网络·网络协议·tcp/ip·php
DongQiShanRen1 小时前
裁决台账双向互校(上):名册与实物的第一道对账
java·linux·运维·数据库·人工智能·自然语言处理·数据挖掘
156082072191 小时前
使用JFMRFVU3P多通道同步相位测试
网络·人工智能