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[] 获取列值与列名。

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

相关推荐
Rabitebla9 小时前
【Linux 底层原理】进程(十):进程等待详解---从僵尸进程到 wait、waitpid 与 status
linux·c++·笔记·学习·visual studio
Decay__9 小时前
18_MySQL 智能巡检方案 —— 巡检项清单 / 一键巡检脚本 / 健康评分
运维·数据库·sql·mysql·架构
java_logo9 小时前
Docker 部署 Liquibase:轻松搭建数据库变更管理
数据库·docker·容器·数据库管理·liquibase·轩辕镜像·数据库变更
凤凰院凶涛QAQ11 小时前
《MySQL内核拆解:从SQL到InnoDB》(四) : 联合查询 & 视图
数据库·sql·mysql
码农小河6611 小时前
基于SSM+JSP的高校专业信息管理系统【附源码+数据库】
java·开发语言·数据库
张小姐的猫11 小时前
【MySQL】访问 & 数据流动原理
android·数据库·mysql·adb·1024程序员节
Android系统攻城狮14 小时前
Linux FFmpeg深度解析之avcodec_default_get_encode_buffer调用流程与实战(二十八)
linux·运维·服务器·ffmpeg·车载音视频·ffmpeg音视频
程序员初一14 小时前
8080 打不开的四层定位:监听地址、ufw、DOCKER 链、安全组
linux·ssh
程序开发阿元14 小时前
swappiness 60 和 10 差在哪:一台 1C1G 的三轮对比
linux·ssh