#Linux#IO多路复用#epoll#TCP并发服务器#SQLite3#网络编程
前言
在 Linux 网络开发中,单循环服务器一次只能服务一个客户端,无法应对多客户端同时接入的场景。为了实现并发服务器,我们有多进程、多线程、IO 多路复用三类方案。其中 IO 多路复用是高性能网络开发的核心技术,Nginx、Redis 等经典开源项目底层都基于 epoll 实现。
网络数据完成交互之后,往往需要做数据持久化,嵌入式领域最常用就是轻量级文件数据库 SQLite3。本文就从 IO 多路复用开始,梳理并发服务器模型以及 SQLite 数据库全套知识点。
一、IO 多路复用
IO 多路复用核心思想:单个进程 / 线程,同时监控多个文件描述符,当某个 fd 可读 / 可写事件触发,再去执行对应的 IO 操作,不需要创建大量进程线程,减少资源开销。
1.select
-
原理:使用位图数组维护需要监听的文件描述符集合,最大监听数量受内核默认
1024限制。 -
缺点
- 最大只能监控 1024 个文件描述符;
- 用户态和内核态之间频繁拷贝 fd 集合;
- 事件到来返回完整集合,应用程序需要循环遍历全部 fd 判断哪个就绪;
- 仅支持水平触发。
-
核心 API
int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);
//配套宏
FD_ZERO(&fds); //清空集合
FD_SET(fd, &fds); //添加fd到集合
FD_CLR(fd, &fds); //从集合删除fd
FD_ISSET(fd, &fds); //判断fd是否就绪
nfds:监控最大文件描述符 + 1;- readfds/writefds/exceptfds:读、写、异常事件集合;
- timeout:超时时间,填 NULL 代表永久阻塞等待事件。
- 返回值:就绪事件个数;0 代表超时;-1 代表出错。
2.poll
- 原理:使用链表保存待监听文件描述符集合,解除 1024 上限。
- 缺点
- 仍然需要用户层和内核层来回拷贝数据;
- 事件返回后,应用程序依旧需要遍历所有描述符找就绪 fd;
- 只支持水平触发。
3.epoll(Linux 独有,高性能)
epoll 是 Linux 下 IO 多路复用最高效实现,Nginx、Redis 底层采用。
-
特点
- 使用红黑树在内核维护 fd 集合,没有 1024 数量上限;
- 内核直接返回已经就绪的事件,不需要全部遍历;
- 支持水平触发、边沿触发两种模式;
-
三套核心 API
//1.创建epoll实例,返回epoll文件描述符
int epoll_create(int size);//2.对监控集合做增删改操作
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
//op: EPOLL_CTL_ADD 添加;EPOLL_CTL_MOD 修改;EPOLL_CTL_DEL 删除//3.阻塞等待事件到来
int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);
epoll_event结构体保存 fd 和关注事件(EPOLLIN 读事件、EPOLLOUT 写事件)。- 返回就绪事件的数量。
触发模式:
- 水平触发 LT:只要缓冲区有数据就一直通知;select/poll 默认模式。
- 边沿触发 ET:只有状态发生变化才通知一次,读写必须一次性读完缓冲区所有数据,性能更高。
二、TCP 并发服务器
单循环 TCP 服务器:同一时间只能处理一个客户端连接,客户端处理完成之后才能接待下一个。 并发服务器:同时服务多个客户端,有三种主流实现方式。
方式 1:多进程并发服务器
- 流程:
socket→bind→listen,循环调用accept获取客户端连接,fork()创建子进程专门和该客户端通信;父进程继续等待新连接。 - 优缺点 ✅ 进程之间数据隔离,稳定性高; ❌ 进程资源开销大,大量客户端时系统压力高,需要处理僵尸进程回收。
方式 2:多线程并发服务器
- 流程:主线程循环
accept得到客户端 connfd,调用pthread_create创建子线程处理客户端 IO;设置线程分离属性pthread_detach,线程结束操作系统自动回收资源。 - 优缺点 ✅ 线程开销远小于进程,创建速度快; ❌ 线程共享进程资源,一个线程崩溃会造成整个进程退出;多线程需要处理互斥锁同步。
方式 3:IO 多路复用并发服务器(epoll)
- 流程: 1)创建 socket、bind、listen; 2)创建 epoll,把监听套接字
sockfd加入 epoll 监听集合; 3)循环执行epoll_wait等待事件; 4)如果是监听套接字就绪,调用 accept 获取新客户端 fd,加入 epoll; 5)如果是客户端 connfd 就绪,执行 recv/send 完成数据收发。 - 优缺点 ✅ 单进程即可实现高并发,资源消耗极低;工业高性能服务器主流; ❌ 编码逻辑相对复杂,只支持 Linux 平台。
UDP 本身天然并发,不需要上述模型。
三、数据库 --- SQLite3
3.1 数据库基础概念
- 普通文件:直接读写文件,增删改查逻辑全部由程序员自己实现,大数据量效率低下。
- 数据库:专门用于数据管理,高效完成增删改查。
- 关系型数据库:Oracle、MySQL、SQLite(二维表格)
- 非关系型数据库:Redis、MongoDB(键值存储)
- SQLite3 特点(嵌入式首选)
- 开源免费纯 C 实现;
- 文件型数据库,整个数据库保存在一个
.db文件,可以直接拷贝迁移; - 资源占用很小,最大支持 2TB 数据。
3.2 Linux 环境安装
sudo apt install sqlite3
sudo apt install libsqlite3-dev
#可视化工具
sudo apt install sqlitebrowser
3.3 sqlite3 命令行常用指令
#打开数据库文件
sqlite3 test.db
.tables #查看全部表
.headers on #打开表头
.mode column #对齐输出
.quit #退出
SQL 语句(大小写不敏感)
-
创建表
CREATE TABLE class1(id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT, score REAL);
-
插入数据
insert into class1(name,score) values("zhangsan",88);
-
查询
select * from class1;
select * from class1 where score>60 order by score desc; -
修改
update class1 set score=90 where name="zhangsan";
-
删除
delete from class1 where id=2;
drop table class1;
数据类型:
INTEGER整型、REAL浮点、TEXT字符串、BLOB二进制。
3.4 SQLite3 C 语言 API
sqlite3_open():打开数据库文件,获取数据库句柄sqlite3_errmsg():获取错误提示字符串sqlite3_exec():执行 SQL 语句。- 参数包含回调函数:执行 select 查询的时候,每读到一行数据就调用一次回调函数;
- 回调函数参数:列数量、每列值、表头名称;回调必须返回 0。
- 预处理接口
sqlite3_prepare_v2/sqlite3_bind_text/sqlite3_step/sqlite3_finalize:适合带变量插入查询,防止 SQL 注入,词典项目就是使用这套 API。 sqlite3_close():关闭数据库句柄。
编译要点:gcc 编译时必须链接库
-lsqlite3。
小结
- IO 多路复用解决单进程处理多路 IO 的问题,
select受限于 1024,poll解除数量上限,epoll是 Linux 高性能首选,支持水平触发、边沿触发。 - TCP 并发服务器实现方案对比:
- 多进程:稳定性高,资源开销大;
- 多线程:开销中等,共享内存有线程安全风险;
- epoll IO 多路复用:单进程高并发,Nginx/Redis 采用。
- SQLite3 是嵌入式轻量文件数据库,掌握命令行 SQL 语句和 C 语言两套 API,可实现数据持久化存储;编译链接
‑lsqlite3。 - 工程中词典导入数据库作业,就是使用
sqlite3_prepare_v2预处理接口读取文本文件批量插入,再接收终端输入做数据库查询。