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 描述符不是打开的文件
相关推荐
2401_873479402 小时前
运营活动被薅羊毛怎么防?用IP查询+设备指纹联动封堵漏洞
java·网络·tcp/ip·github
uiop_uiop_uiop2 小时前
fnOS LUKS on RAID Storage Pool
服务器
应用市场2 小时前
Android A/B 无缝更新机制深度剖析
android·网络
IT大白鼠2 小时前
Linux进程与计划任务管理:技术详解与实战指南
linux·运维·服务器
临街的小孩2 小时前
Docker 容器内运行 ROS Noetic 图形界面(rqt_image_view)极简教程总结
运维·docker·容器
rosemary5122 小时前
SOME/IP初试
网络·网络协议·tcp/ip·someip
不知名的老吴2 小时前
认识Python网络套接字编程
网络
hweiyu003 小时前
Linux命令:arptables
linux·运维
Yang96113 小时前
鼎讯 SZT-2000A:铁路高速万兆网络一站式测试方案
网络