目录
内容简介:
详细内容:
1:数据库的引入
2: MySQL基础及在聊天室中的应用
2.1 MySQL基础
1 建库,建表,增删查改
2 数据表的约束
3 数据表数据的插入,更新,删除
4 数据表查询
1 select基础查询
2 WHERE条件查询
3 ORDER BY排序
4 LIMIT
2.2 聊天室中数据表的设计
3: Redis基础及在聊天室中的应用
1 Redis基本概念
2 聊天室中Redis常用数据结构:
1 String
2 Hash
3 List
4 Set
4: C++程序和数据库之间的通信
5: 总结
内容简介:
1. 聊天室为什么需要数据库
2. MySQL基础和聊天室中的使用
3. Redis基础和聊天室中的使用
4. C++程序和数据库之间的通信
5. 总结
详细内容:
1:数据库的引入
能不能不用任何数据库?
把用户信息、在线状态存在服务器内存里,就像:
打开手机备忘录,只打字不保存,一旦闪退、关机,所有内容全部消失.
即:断电即清空、重启数据全丢
如果聊天室不用数据库,会出现:
服务器一旦重启,所有注册的用户账号、好友关系、聊天记录全部清零,用户每次打开软件都要重新注册,无法正常使用。
因此,需要数据库.
进一步的,我们会发现,聊天室的数据有完全不同的特点,可分成两类:
第一类:需要永久保存、很少变动的数据。比如账号密码、个人资料、好友关系、群组信息、历史聊天记录。这些数据哪怕服务器重启、电脑关机,也必须完好保留。
第二类:实时变动、不用永久保存、访问极频繁的数据。比如用户在线状态、临时验证码、用户离线消息。有人上线、下线,在线状态一秒变动好几次;用户登录后验证码就失效,这些数据不需要长期存,但是需要秒级快速查询。
这就引出:单一数据库虽然可以实现所有功能,但是不同数据对存储性能和访问速度的要求不同,因此聊天室通常采用MySQL和Redis结合的方案。
永久稳定的数据交给MySQL,高速实时的数据交给Redis,两者搭配,撑起一个完整、流畅的聊天室系统。
我给大家整理了一张场景对照表,方便大家直观理解:
数据类型 | 数据特点 | 适配数据库 |
|---|---|---|
用户账号、密码、资料 | 长期保存、稳定不变 | MySQL |
好友、群组关系、历史消息 | 结构化、需留存、关系复杂 | MySQL |
用户在线状态 | 高频变动、实时查询 | Redis |
离线消息、临时验证码 | 短期有效、快速读写 | Redis |
2: MySQL基础及在聊天室中的应用
在聊天室中,我们要学,如何设计表、如何存取数据、如何保证数据关系合理
安装教程:MYSQL安装教程
2.1 MySQL基础
1 建库,建表,增删查改
mysql -u root -p (登录)show databases;
create databaseStu;默认的编码方式 latin1 (单字节编码)(建库)
->
create databaseStucharacter set utf8;设置成 utf-8中文能正常显示
/
alterdatabase Stucharsetutf8;修改
use Stu;
select database();
show tables;
create table `信息`( (建表)
`姓名` varchar(20),
`年龄` int
);
show create table `信息`;
alter table `信息` add(/drop/modify) `性别` char(10);
2 数据表的约束
引入:
为防止 重复用户名、用户没有密码、好友表里出现一个不存在的用户 ID、年龄填负数这类错误数据 被插入到数据表,MySQL中定义了一些维护数据库完整性的规则;这些规则常称为表的约束。
聊天室常用约束如下:
| 约束条件 | 说明 |
|---|---|
| PRIMARY KEY | 主键约束用于唯一标识对应的记录 |
| FOREIGN KEY | 外键约束 |
| NOT NULL | 非空约束 |
| UNIQUE | 唯一性约束 |
在聊天室的应用:
比如,用户表
CREATE TABLE user(
id INTPRIMARY KEYAUTO_INCREMENT,
username VARCHAR(50)UNIQUENOT NULL,
password VARCHAR(100)NOT NULL
);
PRIMARY KEY:保证id不会重复
UNIQUE:保证同一个人不可以重复注册
3 数据表数据的插入,更新,删除
以聊天室user表为例:
1准备一张表
CREATE TABLE user( id INT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(50) UNIQUE NOT NULL, password VARCHAR(100) NOT NULL, nickname VARCHAR(50), create_time DATETIME );| 字段 | 作用 | 聊天室用途 |
|---|---|---|
| id | 用户唯一编号 | 好友关系、消息记录中引用 |
| username | 账号名 | 登录、注册 |
| password | 密码 | 登录验证 |
| nickname | 昵称 | 显示给好友 |
| create_time | 注册时间 | 记录用户信息 |
2插入数据
INSERT INTO user(username,password,nickname,create_time) VALUES( 'jack', '123456', 'Jack', NOW() );执行后:
| id | username | password | nickname | create_time |
|---|---|---|---|---|
| 1 | jack | 123456 | Jack | 2026-07-21 20:00:00 |
注意:
这里没有插入id。
因为:
id INT PRIMARY KEY AUTO_INCREMENT表示:
id由MySQL自动生成
3更新数据
UPDATE user SET password='888888' WHERE username='jack'; //jack修改密码修改密码:123456->888888执行后:
| id | username | password |
|---|---|---|
| 1 | jack | 888888 |
4删除数据
DELETE FROM user WHERE username='jack'; //注销账号执行前:
| id | username | password | nickname |
|---|---|---|---|
| 1 | jack | 123456 | Jack |
| 2 | tom | abcdef | Tom |
执行后:
| id | username | password | nickname |
|---|---|---|---|
| 2 | tom | abcdef | Tom |
但是,在实际聊天室中,用户注销通常采用逻辑删除,即修改状态字段从1到0,而不是直接删除数据,避免影响好友关系和历史消息
补充:
TRUNCATE和DETELE的区别
TRUNCATE和DETELE都能实现删除表中的所有数据的功能,但两者也是有区别的:
1、DELETE语句后可跟WHERE子句,可通过指定WHERE子句中的条件表达式只删除满足条件的部分记录;但是,TRUNCATE语句只能用于删除表中的所有记录。
2、使用TRUNCATE语句删除表中的数据后,再次向表中添加记录时自动增加字段的默认初始值重新由1开始;使用DELETE语句删除表中所有记录后,再次向表中添加记录时自动增加字段的值为删除时该字段的最大值加1
3、DELETE语句是DML语句,TRUNCATE语句通常被认为是DDL语句
4 数据表查询
目标:服务器收到客户端请求后,通过SQL查询数据库,找到需要的数据,然后返回给客户端
1 select基础查询
1> *查询所有字段
SELECT *
FROM 表名;
2> 查询指定字段
SELECT 字段1,字段2
FROM 表名;
->eg:
SELECT username,password
FROM user;
2 WHERE条件查询
SELECT 字段
FROM 表
WHERE 条件;->
用户Jack登录时
客户端:username=jack
服务器查询:
SELECT password
FROM user
WHEREusername='jack';数据库返回:
123456
服务器比较密码
还可以查询好友(WHERE user_id=1)和聊天记录(WHERE sender=1ANDreceiver=2)
聊天室常用多条件查询,例如:
查询好友:
SELECT * FROM friend WHERE user_id=1 AND friend_id=2;聊天室中的查询大部分都是带条件查询,而不是简单查询全部数据。
3 ORDER BY排序
可用于查询历史消息
查询:
SELECT *
FROM message
ORDER BYtime;结果:
10:01
10:02
10:03
4 LIMIT
SELECT *
FROM message
ORDER BYtime DESC(DESC最新消息放最上面,ASC最早消息放最上面)
LIMIT20;
假设:
聊天记录100万条,用户打开聊天窗口,不可能一次加载100万条.只取最近20条消息
2.2 聊天室中数据表的设计
目的:以用户表,好友关系表,消息表,群成员表为例,熟悉MYSQL在聊天室中的用法和约束
| 约束条件 | 说明 |
|---|---|
| PRIMARY KEY | 主键约束用于唯一标识对应的记录 |
| FOREIGN KEY | 外键约束 |
| NOT NULL | 非空约束 |
| UNIQUE | 唯一性约束 |
故可设计为:
1. 用户表 user
作用:
保存用户基本信息,用于:
- 注册
- 登录
- 查询用户资料
表结构:
| 字段 | 类型 | 约束 | 说明 |
|---|---|---|---|
| id | INT | PRIMARY KEY | 用户唯一编号 |
| username | VARCHAR(50) | UNIQUE、NOT NULL | 用户名 |
| password | VARCHAR(100) | NOT NULL | 密码 |
| nickname | VARCHAR(50) | 昵称 | |
| create_time | DATETIME | 注册时间 |
CREATE TABLE user( id INT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(50) UNIQUE NOT NULL, password VARCHAR(100) NOT NULL, nickname VARCHAR(50), create_time DATETIME );2. 好友关系表 friend
作用:
保存用户之间的好友关系
表结构:
| 字段 | 类型 | 说明 |
|---|---|---|
| id | INT | 关系编号 |
| user_id | INT | 用户id |
| friend_id | INT | 好友id |
| create_time | DATETIME | 建立好友时间 |
CREATE TABLE friend( id INT PRIMARY KEY AUTO_INCREMENT, user_id INT NOT NULL, friend_id INT NOT NULL, create_time DATETIME );3. 消息表 message
作用:
保存聊天历史记录
用于:
- 查看聊天记录
- 离线消息补偿
- 消息查询
表结构:
| 字段 | 类型 | 说明 |
|---|---|---|
| id | INT | 消息编号 |
| sender_id | INT | 发送者id |
| receiver_id | INT | 接收者id |
| content | TEXT | 消息内容 |
| send_time | DATETIME | 发送时间 |
CREATE TABLE message( id INT PRIMARY KEY AUTO_INCREMENT, sender_id INT NOT NULL, receiver_id INT NOT NULL, content TEXT, send_time DATETIME );查询Jack和Tom聊天记录:
SELECT * FROM message WHERE (sender_id=1 AND receiver_id=2) OR (sender_id=2 AND receiver_id=1) ORDER BY send_time;4. 群成员表 group_member
作用:
保存群组和用户之间的关系。
例如:
群1001 成员: jack tom lucy表结构:
| 字段 | 类型 | 说明 |
|---|---|---|
| id | INT | 编号 |
| group_id | INT | 群id |
| user_id | INT | 用户id |
| role | INT | 成员身份 |
CREATE TABLE group_member( id INT PRIMARY KEY AUTO_INCREMENT, group_id INT NOT NULL, user_id INT NOT NULL, role INT DEFAULT 0 );3: Redis基础及在聊天室中的应用
1 Redis基本概念
Redis采用:
key-value键值对方式存储数据
类似C++:
unordered_map<string,string> -> map["username"]="jack";2 聊天室中Redis常用数据结构:
| 类型 | 特点 | 聊天室应用 |
|---|---|---|
| String | 字符串 | 验证码、在线状态 |
| Hash | 对象存储 | 用户信息 |
| List | 列表 | 离线消息 |
| Set | 集合 | 好友列表 |
1 String
1>基础
创建数据:SETusername jack
查询:GETusername jack
删除:DELusername jack
2>聊天室中应用
保存验证码
SETEX verify:jack 300 123456 //jack的验证码是123456,300s后过期,自动删除2 Hash
1>基础
HSETuser:jack username jack
HGETuser:jack password
HGETALLuser:jack
HEXISTSuser:tom
2>聊天室中应用
保存用户基本信息
// user:10001 // username jack // password 123456 // email xxx HSET user:10001 username jack HSET user:10001 password 123456 HSET user:10001 email xxx3 List
类似:C++队列 queue<string>
List类似双端队列,可以实现消息队列功能
1>基础
添加:
LPUSHoffline:jack hello (从左加)-永远先看到最新消息-朋友圈
RPUSHoffline:jack hi (从右加)-新的消息在后-聊天室离线消息
删除:
LPOPoffline:jack (从
offline:jack的List里面,删除并取出最左边的一条消息)注:聊天室常用RPUSH+LPOP->先入先出
查看:
LRANGEoffline:jack 0 -1 (list range,查看list中的一段数据)
这里表示获取jack离线信息中,从第一个元素到最后一个元素
2>聊天室中应用
保存离线消息
//B离线 //A给B发送 hi~ 服务器: RPUSH offline:B "A:hi~" //接着,C给B发送 你在干嘛? 服务器: RPUSH offline:B "C:你在干嘛?" //B上线后,服务器: LRANGE offline:B 0 -1 //发送给客户端,B看到: A:hi~ C:你在干嘛? 4 Set
相当于C++中unordered_set
特点:无序,不重复
在聊天室中,根据好友列表,判断私聊是否合法
(好友列表不是直接存储的,而是由好友关系表动态查询得到。MySQL保存用户之间的关系,服务器根据用户id查询friend表,生成好友列表返回给客户端)
添加:SADDfriend:jack tom
查看:
SISMEMBERSfriend:jack tom
-set is member,查看是不是成员,即判断是否存在.返回1是好友
SMEMBERSfriend:jack
-set members,获取整个集合
删除:
SREM(set remove,删除集合中的元素)
eg:tom和jack删除好友时,需要:
SREM friends:tom jack
SREM friends:jack tom
4: C++程序和数据库之间的通信
现在,从数据库知识进入项目实现
聊天室的C++服务器代码,怎么把一个C++变量传递给MySQL/Redis,让数据库真正保存数据?
核心逻辑:
C++聊天室程序 → 发送SQL → MySQL仓库
装上驱动库,C++ 就拥有了发送 SQL 的能力
伪代码模拟流程:(示例为了展示流程使用字符串拼接,实际项目使用预处理语句)
// 1.程序启动,和MySQL建立连接 连接MySQL("127.0.0.1","root","123456","chatroom"); // 2.收到客户端注册信息 string name = "jack"; string pwd = "123456"; // 3.拼凑SQL指令纸条 string sql = "INSERT INTO user(username,password) VALUES('"+name+"','"+pwd+"');"; // 4.发送纸条给MySQL 发送SQL(sql); // 5.接收MySQL的回信 if(执行成功){ 通知客户端:注册成功 }else{ 通知客户端:注册失败 }即:
MySQL 是独立运行的程序,相当于一个仓库。C++ 程序本身不能直接操作数据库。
C++ 需要借助驱动库,把业务数据拼装成我们熟悉的 SQL 字符串,把这条 SQL 发送给 MySQL 服务。
MySQL 识别 SQL 语句,完成增删改查,同时利用主键、唯一约束等规则校验数据是否合法,最后把执行结果回传给 C++ 服务端
与之相似,C++通过Redis客户端库(redis-plus-plus/hiredis)发送Redis命令
以<MySQL为例,体会:
代码:C++程序接收用户名和密码,然后保存到MySQL的user表中
1>mysql准备:
mysql -u root -p
CREATE DATABASE test_chat;
USE test_chat;
create table user( id INT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(50) NOT NULL, password VARCHAR(50) NOT NULL );
2>c++程序:
文件:
mysql_test.cpp
代码:
#include <bits/stdc++.h> #include <string> #include <mysql/mysql.h> using namespace std; int main(){ // 1.初始化mysql句柄 MYSQL* mysql = mysql_init(nullptr); if (mysql == nullptr){ cout << "mysql init error" << endl; return -1; } // 2.建立连接 地址,用户名,密码,数据库名,端口 if (!mysql_real_connect(mysql, "127.0.0.1", "root", "0527985", "test_chat", 3306, nullptr, 0)){ cout << "connect mysql error:" << mysql_error(mysql) << endl; mysql_close(mysql); return -1; } cout << "mysql connect success" << endl; // 3.接收用户输入(模拟聊天室客户端注册) string username; string password; cout << "请输入用户名:"; cin >> username; cout << "请输入密码:"; cin >> password; // 4.拼接插入SQL(简易演示,正式项目要用预处理防止注入) string sql = "INSERT INTO user(username,password) VALUES('" + username + "','" + password + "');"; // 5.执行SQL int ret = mysql_query(mysql, sql.c_str()); if (ret != 0) cout << "执行SQL失败:" << mysql_error(mysql) << endl; else cout << "注册数据写入数据库成功!" << endl; // 关闭连接 mysql_close(mysql); return 0; }3>编译运行:
g++ mysql_test.cpp \
-o mysql_test \
-lmysqlclient./mysql_test
4>结果:
输入:
mysql connect success
请输入用户名:
jack请输入密码:
123456
查看数据库:
SELECT * FROM user;
5: 总结
ChatServer
|
------------------
| |
MySQL Redis
长期数据 实时数据
用户信息 在线状态
好友关系 离线消息
历史记录 验证码
MySQL负责聊天室中需要长期保存的数据,Redis负责聊天室中高频访问、实时变化的数据,两者结合实现高性能即时通讯系统。