Linux 系统编程 :select/epoll 多路复用与 SQLite 数据库实战

1. 引言

在 Linux 系统编程的学习中,今天我们进入两个非常实用的主题:I/O 多路复用 (select 与 epoll)和 SQLite 数据库。前者是构建高并发网络服务器的基石,后者则是轻量级嵌入式数据库的典范。本文将系统梳理 select 与 epoll 的核心区别,并完整介绍 SQLite 从安装、SQL 语法到 C 语言 API 调用的全流程。


2. select 和 epoll 的区别

2.1 select 特点

  1. select 监听的文件描述符集合本质是一个数组,有上限限制(通常为 1024)。
  2. 监听集合存放在应用层;有事件发生时,内核要把数据拷贝到用户空间,资源开销大。
  3. 只支持水平触发(LT,低速模式)。
  4. 用户程序必须手动遍历查找哪些 fd 产生事件。

2.2 epoll 特点

  1. epoll 事件表没有文件描述符数量上限。
  2. 事件表放在内核中,内核直接检测事件,减少用户态-内核态拷贝,开销小。
  3. 支持水平触发 LT(默认) + 边沿触发 ET(高速模式)。
  4. 内核直接返回就绪的 fd,不需要全部遍历。

白话总结:select 适合少量 fd;epoll 适合高并发大量文件描述符,网络服务器用 epoll。


3. 数据库基础

数据库:专门用来对大批量数据做增删改查管理的软件。

3.1 数据库分类

  1. 关系型数据库 :数据存磁盘文件
    • Oracle、MySQL、SQLite
  2. 非关系型数据库 :大多放内存
    • 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 支持的数据类型

  1. INTEGER 整数
  2. REAL 浮点数
  3. TEXT 字符串文本
  4. 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

  1. cross join 交叉连接:笛卡尔积,全部两两组合
  2. inner join 内连接:只取出两边匹配成功的数据
  3. 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               /*错误信息输出*/
);

参数说明:

  1. pDb:sqlite3_open 拿到的句柄
  2. sql:要执行的 SQL 字符串
  3. callback:select 查询时必须写回调,每读到一行数据就调用一次;insert/update/delete 填 NULL
  4. arg:用户自定义参数,传递给回调函数第一个形参
  5. 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[] 获取列值与列名。

掌握这两块内容,你就具备了编写高并发网络服务和本地数据持久化程序的基础能力,继续加油!

相关推荐
贵州诚鑫致达科技有限公司6 小时前
Linux的yum源怎么重启?源不是服务
linux·运维·服务器
余槐i6 小时前
PostgreSQL 函数索引报错 must be immutable:3种绕法与代价
数据库·后端·sql·postgresql·性能优化
数据库小学妹6 小时前
PostgreSQL表膨胀怎么办?autovacuum清不动的根因与排查命令
数据库·postgresql·表膨胀·mvcc·数据库运维
mister_guo6 小时前
MySQL-1267错误:排序规则冲突的原因与解决方案
数据库·mysql
骇客野人7 小时前
在麒麟操作系统上 分别安装 jdk1.8, 达梦数据库,redis ,memcache 的操作实施方案和步骤
数据库·redis·memcached
美狐美颜SDK开放平台8 小时前
直播APP如何实现美白滤镜功能?视频美颜SDK技术开发方案详解
前端·数据库·人工智能·音视频·美颜sdk·直播美颜sdk·视频美颜sdk
澜山奕秋9 小时前
RHCSA
linux·运维·服务器
花间相见10 小时前
【计算基础|MySQL01】【上手】安装连接、数据类型与建库建表
数据库·后端
Web极客码10 小时前
告别流式截断:我用客户端中间件解决长文本 API 跨国 SSE 断连与数据自愈
linux·agent 图
花间相见10 小时前
【计算基础|MySQL02】【上手】SQL 增删改查
数据库·后端