1. 引言
在 Linux 系统编程的学习中,今天我们进入两个非常实用的主题:I/O 多路复用 (select 与 epoll)和 SQLite 数据库。前者是构建高并发网络服务器的基石,后者则是轻量级嵌入式数据库的典范。本文将系统梳理 select 与 epoll 的核心区别,并完整介绍 SQLite 从安装、SQL 语法到 C 语言 API 调用的全流程。
2. select 和 epoll 的区别
2.1 select 特点
- select 监听的文件描述符集合本质是一个数组,有上限限制(通常为 1024)。
- 监听集合存放在应用层;有事件发生时,内核要把数据拷贝到用户空间,资源开销大。
- 只支持水平触发(LT,低速模式)。
- 用户程序必须手动遍历查找哪些 fd 产生事件。
2.2 epoll 特点
- epoll 事件表没有文件描述符数量上限。
- 事件表放在内核中,内核直接检测事件,减少用户态-内核态拷贝,开销小。
- 支持水平触发 LT(默认) + 边沿触发 ET(高速模式)。
- 内核直接返回就绪的 fd,不需要全部遍历。
白话总结:select 适合少量 fd;epoll 适合高并发大量文件描述符,网络服务器用 epoll。
3. 数据库基础
数据库:专门用来对大批量数据做增删改查管理的软件。
3.1 数据库分类
- 关系型数据库 :数据存磁盘文件
- Oracle、MySQL、SQLite
- 非关系型数据库 :大多放内存
- Redis、NoSQL
3.2 安装 SQLite3
bash
sudo apt-get install sqlite3
3.3 SQLite3 点命令(终端专用,不是 SQL 语句)
| 命令 | 作用 |
|---|---|
.tables |
查看表名 |
.databases |
列出数据库文件 |
.headers on/off |
打开 / 关闭列名显示 |
.mode |
设置输出格式 |
.schema ?TABLE? |
查看建表语句 |
.width |
设置显示宽度 |
3.4 SQLite 支持的数据类型
INTEGER整数REAL浮点数TEXT字符串文本BLOB二进制原始字节
4. SQL 基础语句
4.1 create table 创建表
sql
create table 表名(列1 类型1, 列2 类型2,...);
示例:
sql
create table student (id integer, name text, sex text, age integer, score integer);
4.2 insert into 插入数据
sql
insert into 表名 values(值1,值2,...);
-- 指定列插入
insert into 表名(列1,列2) values(值1,值2);
sql
insert into student values (1,"张三","男",18,90);
insert into student values (3,"王二",NULL,NULL,NULL);
insert into student (id,name,sex) values (4,"赵五","男");
4.3 select 查询
sql
select 列名1,列名2... from 表名;
-- *代表全部列
select * from student;
4.4 where 条件过滤
比较符号 := 等于、<> 不等于、> 大于、< 小于、>= 大于等于、<= 小于等于;BETWEEN 在区间内;LIKE 模糊匹配,% 代表任意字符。
示例:
sql
select name,score from student where name like "赵%";
select name from student where score >= 90;
select name,score from student where name like "赵%" and score>95;
4.5 update 更新
sql
UPDATE 表名 SET 列名=新值 WHERE 列名=某值;
sql
update student set score=91 where name="赵五";
4.6 delete 删除行
sql
delete from 表名 where 列名=值;
sql
delete from student where name = "王二";
4.7 order by 排序
asc 升序,desc 降序。
sql
select name,score from student where sex="男" order by score desc;
4.8 drop table 删除整张表
sql
drop table student;
4.9 多表联合查询 join
cross join交叉连接:笛卡尔积,全部两两组合inner join内连接:只取出两边匹配成功的数据outer join外连接:保留一侧全部数据,匹配不到填 NULL
sql
-- 内连接示例
select * from 学生信息 inner join 成绩信息 on 学生信息.ID = 成绩信息.学生ID;
5. C 语言操作 SQLite3 库函数
5.1 sqlite3_open|打开数据库
c
int sqlite3_open(
const char *filename, /*数据库文件名*/
sqlite3 **ppDb /*输出:数据库句柄指针*/
);
- 参数:
filename:数据库文件名,不存在则自动创建ppDb:数据库句柄,输出参数
- 返回值:成功返回
SQLITE_OK,错误返回错误码
5.2 sqlite3_exec|执行 SQL 语句
c
int sqlite3_exec(
sqlite3* pDb, /*数据库句柄*/
const char *sql, /*SQL字符串*/
int (*callback)(void*,int,char**,char**), /*查询回调函数,非查询填NULL*/
void *arg, /*传给回调的第一个参数*/
char **errmsg /*错误信息输出*/
);
参数说明:
pDb:sqlite3_open 拿到的句柄sql:要执行的 SQL 字符串callback:select 查询时必须写回调,每读到一行数据就调用一次;insert/update/delete 填 NULLarg:用户自定义参数,传递给回调函数第一个形参errmsg:出错的时候保存错误字符串,用完记得释放
返回值 :成功 SQLITE_OK
回调函数原型:
c
int callback(void *arg, int ncol, char **value, char **name);
// arg:exec传进来的用户参数
// ncol:这一行有多少列
// value[]:每一列数据字符串
// name[]:每一列的表头名字
5.3 sqlite3_close|关闭数据库
c
int sqlite3_close(sqlite3* pDb);
- 功能:关闭数据库连接,用完必须调用。
6. 综合示例:C 语言操作 SQLite 完整流程
下面是一个完整的示例,演示了打开数据库、建表、插入、查询(带回调)和关闭的完整流程:
c
#include <stdio.h>
#include <sqlite3.h>
// 查询回调函数
int callback(void *arg, int ncol, char **value, char **name) {
for (int i = 0; i < ncol; i++) {
printf("%s = %s\n", name[i], value[i] ? value[i] : "NULL");
}
printf("---\n");
return 0;
}
int main() {
sqlite3 *db = NULL;
char *errmsg = NULL;
// 1. 打开数据库
if (sqlite3_open("test.db", &db) != SQLITE_OK) {
fprintf(stderr, "打开数据库失败: %s\n", sqlite3_errmsg(db));
return -1;
}
// 2. 建表
const char *sql_create = "create table if not exists student (id integer, name text, score integer);";
if (sqlite3_exec(db, sql_create, NULL, NULL, &errmsg) != SQLITE_OK) {
fprintf(stderr, "建表失败: %s\n", errmsg);
sqlite3_free(errmsg);
sqlite3_close(db);
return -1;
}
// 3. 插入数据
const char *sql_insert = "insert into student values (1, '张三', 90);";
if (sqlite3_exec(db, sql_insert, NULL, NULL, &errmsg) != SQLITE_OK) {
fprintf(stderr, "插入失败: %s\n", errmsg);
sqlite3_free(errmsg);
}
// 4. 查询数据(带回调)
const char *sql_query = "select * from student;";
if (sqlite3_exec(db, sql_query, callback, NULL, &errmsg) != SQLITE_OK) {
fprintf(stderr, "查询失败: %s\n", errmsg);
sqlite3_free(errmsg);
}
// 5. 关闭数据库
sqlite3_close(db);
return 0;
}
编译时需链接 sqlite3 库:
bash
gcc demo.c -o demo -lsqlite3
7. 总结
- select vs epoll:select 实现简单但有 fd 数量上限、需用户态遍历;epoll 由内核维护事件表、支持 ET 模式,是高并发场景的首选。
- SQLite :轻量级关系型数据库,支持标准 SQL 语法,通过
sqlite3_open/sqlite3_exec/sqlite3_close三个核心 API 即可在 C 语言中完成大部分数据库操作。 - 回调机制 :
sqlite3_exec的查询回调是理解 SQLite C 接口的关键,每行数据触发一次回调,通过value[]和name[]获取列值与列名。
掌握这两块内容,你就具备了编写高并发网络服务和本地数据持久化程序的基础能力,继续加油!