一、项目介绍
1. 对视频点播系统的认识
搭建视频共享点播服务器,可以让所有人通过浏览器访问服务器,实现视频的上传查看,以及管理并播放的功能。主要是完成服务器端的程序业务功能的实现以及前端访问界面 html 的编写,能够支持客户端浏览器针对服务器上的所有视频进行操作。
2. 服务端功能模块划分
该视频点播系统基本上包含四个模块:数据管理、网络通信、业务处理、前端界面,其功能如下:
- 数据管理模块:负责针对客户端上传的视频信息进行管理。
- 网络通信模块:搭建网络通信服务器,实现与客户端通信。
- 业务处理模块:针对客户端的各个请求进行对应业务处理并响应结果。
- 前端界面模块:完成前端浏览器上视频共享点播的各个 html 页面,在页面中支持增删改查以及观看功能。
二、环境搭建
2.1 升级GCC
由于在该项目中会引入许多第三方库,比如httplib
库,该库就会要求gcc编译器必须是较新的版本。如果使用老版本的编译器要么编译不通过,要么就会运行报错。因此我们需要对gcc进行升级,以下是升级至 gcc 7.3 的方法:
- 查看当前gcc版本
gcc --version
- 安装centos-release-scl
sudo yum install centos-release-scl-rh centos-release-scl
- 安装devtoolset
sudo yum install devtoolset-7-gcc devtoolset-7-gcc-c++
这里需要注意一下,如果想安装7.版本的,就改成devtoolset-7-gcc,以此类推。
- 激活对应的devtoolset
source /opt/rh/devtoolset-7/enable
此时GCC就成功升级到了 7.3 版本。
需要注意的是scl命令启用只是临时的,退出 shell 或重启就会恢复原系统gcc版本。如果想要一启动shell就立即生效可以进行以下配置:
echo "source /opt/rh/devtoolset-7/enable" >> ~/.bashrc
即把启动scl的命令添加到文件.bashrc
中,每次启动shell就会执行该语句。
2.2 安装JsonCpp库
JSON 是一种轻量级的数据交换格式。它可以代表数字、字符串、值的有序序列和名称/值的集合对。
JsonCpp 是一个C++库,允许操作 JSON 值,包括字符串的序列化和反序列化。它还可以保存反序列化/序列化步骤中的现有注释,方便
用于存储用户输入文件的格式。
以下是安装JsonCpp的命令:
sudo yum install epel-release sudo yum install jsoncpp-devel
安装好的JsonCpp存放在/usr/include/jsoncpp/json
目录下:
2.3 引入httplib库
cpp-httplib
是个开源的库,是一个c++封装的http库,使用这个库可以在linux、windows平台下完成http客户端、http服务端的搭建,这是一个多线程“阻塞”HTTP 库。使用起来非常方便,只需要包含头文件httplib.h
即可。
获取httplib
库:
git clone https://github.com/yhirose/cpp-httplib.git
2.4 MySQL数据库及开发包安装
此次项目的开发我们使用的数据库是MariaDB,MariaDB数据库管理系统是MySQL的一个分支,主要由开源社区在维护,采用GPL授权许可 MariaDB的目的是完全兼容MySQL,包括API和命令行,使之能轻松成为MySQL的代替品。
以下是安装MariaDB数据库的步骤:
- 安装 MariaDB 服务
sudo yum install -y mariadb-server
- 安装 MariaDB 命令行客户端
sudo yum install -y mariadb
- 安装 MariaDB C library
sudo yum install -y mariadb-libs
其实 MariaDB 的服务中包含了客户端和相关的C语言接口,因此只需要安装 mariadb-server
即可。
- 安装 MariaDB 开发包
sudo yum install -y mariadb-devel
安装完成后,以下是启动数据库的命令:
- 启动服务
systemctl start mariadb
执行该命令启动服务之后,如果重启或者关机了,下次还需要重新启动,可以执行下列指令设置开启自启。
- 设置服务开机自启
systemctl enable mariadb
- 查看服务状态
systemctl status mariadb
启动服务成功后,我们可以看到MariaDB 的状态是 active(running):
● mariadb.service - MariaDB database server
Loaded: loaded (/usr/lib/systemd/system/mariadb.service; enabled; vendor preset: disabled)
Active: active (running) since Wed 2023-03-01 20:25:02 CST; 1min 32s ago
Main PID: 7678 (mysqld_safe)
CGroup: /system.slice/mariadb.service
├─7678 /bin/sh /usr/bin/mysqld_safe --basedir=/usr
└─7844 /usr/libexec/mysqld --basedir=/usr --datadir=/var/lib/mysql --plugin-dir=/usr/lib64/mysql/plugin --log-error=/var/log/mariadb/mariadb.log --pid-file=/var/run/mariadb/mariadb.pid --socket=/var/lib/mysql/mysql…
然后可以测试连接数据库:
- 使用命令行客户端尝试连接
mysql -uroot
出现以下结果就代表连接成功了:
Welcome to the MariaDB monitor. Commands end with ; or \g.
Your MariaDB connection id is 2
Server version: 5.5.68-MariaDB MariaDB Server
Copyright © 2000, 2018, Oracle, MariaDB Corporation Ab and others.
Type ‘help;’ or ‘\h’ for help. Type ‘\c’ to clear the current input statement.
MariaDB [(none)]>
- 查看 mariadb 版本号
MariaDB [(none)]> select version(); +----------------+ | version() | +----------------+ | 5.5.68-MariaDB | +----------------+ 1 row in set (0.00 sec)
更改配置:
为了在使用数据库时支持中文,还需要进行以下配置。
- 在
/etc/my.cnf.d/client.cnf
文件的[client]
下添加default-character-set = utf8
。
- 在
/etc/my.cnf.d/mysql-client.cnf
文件下的[mysql]
下添加default-character-set = utf8
。 - 在
/etc/my.cnf.d/server.cnf
下的[mysqld]
添加以下语句
collation-server = utf8_general_ci init-connect = 'SET NAMES utf8' character-set-server = utf8 sql-mode = TRADITIONAL
三、第三方库的认识
3.1 认识JsonCpp
首先认识Json:
Json 是一种数据交换格式,采用完全独立于编程语言的文本格式来存储和表示数据。
例如:使用Json来表示张三同学的学生信息。
const char* name1 = "张三"; int age1 = 18; float scores1[3] = {60.0, 59.5, 61.0}; const char* name2 = "李四"; int age2 = 19; float scores2[3] = {69.0, 58.5, 64.0}; // Json这种数据交换格式就是将这样的多种数据对象封装成一个字符串: [ { "姓名" : "张三", "年龄" : 18, "成绩" : [860.0, 59.5, 61.0] }, { "姓名" : "李四", "年龄" : 19, "成绩" : [69.0, 58.5, 64.0] }, ]
Json可以封装的数据对象可以是:对象,数组,字符串,数字等等。
认识JsonCpp:
JsonCpp 库用于实现 Json 格式的序列化和反序列化,完成将多个数据对象组织成为 Json 格式字符串,以及将 Json
格式字符串解析得到多个数据对象的功能。
其中主要借助三个类以及对于的几个成员函数完成的。
Json 数据对象:
class Json::Value{ Value &operator=(const Value &other); // Value重载了[]和=,因此所有的赋值和获取数据都可以通过 Value& operator[](const std::string& key);// 简单的方式完成 val["姓名"] = "小明"; Value& operator[](const char* key); Value removeMember(const char* key);// 移除元素 const Value& operator[](ArrayIndex index) const; // val["成绩"][0] Value& append(const Value& value);// 添加数组元素val["成绩"].append(88); ArrayIndex size() const;// 获取数组元素个数 val["成绩"].size(); std::string asString() const;// 转string string name = val["name"].asString(); const char* asCString() const;// 转char* char *name = val["name"].asCString(); Int asInt() const;// 转int int age = val["age"].asInt(); float asFloat() const;// 转float bool asBool() const;// 转 bool }
Json序列化类:
// 建议低版本使用 class JSON_API Writer { virtual std::string write(const Value& root) = 0; } class JSON_API FastWriter : public Writer { virtual std::string write(const Value& root); } class JSON_API StyledWriter : public Writer { virtual std::string write(const Value& root); } // 建议较高版本使用,如果用低版本接口可能报错 class JSON_API StreamWriter { virtual int write(Value const& root, std::ostream* sout) = 0; } class JSON_API StreamWriterBuilder : public StreamWriter::Factory { virtual StreamWriter* newStreamWriter() const; }
Json反序列化类:
// 低版本用起来更简单 class JSON_API Reader { bool parse(const std::string& document, Value& root, bool collectComments = true); } // 高版本更推荐 class JSON_API CharReader { virtual bool parse(char const* beginDoc, char const* endDoc, Value* root, std::string* errs) = 0; } class JSON_API CharReaderBuilder : public CharReader::Factory { virtual CharReader* newCharReader() const; }
3.2 JsonCpp实现序列化
#include <iostream> #include <memory> #include <sstream> #include <string> #include <jsoncpp/json/json.h> int main() { const char* name = "张三"; int age = 18; float scores[3] = {60.0, 59.5, 61.0}; Json::Value value; value["姓名"] = name; value["年龄"] = age; value["成绩"].append(scores[0]); value["成绩"].append(scores[1]); value["成绩"].append(scores[2]); Json::StreamWriterBuilder swb; std::unique_ptr<Json::StreamWriter> sw(swb.newStreamWriter()); std::stringstream ss; int ret = sw->write(value, &ss); if(ret != 0) { std::cout << "write falied!" << std::endl; return -1; } std::cout << ss.str() << std::endl; return 0; }
运行结果:
3.3 JsonCpp实现反序列化
#include <iostream> #include <memory> #include <sstream> #include <string> #include <jsoncpp/json/json.h> int main() { std::string str = R"({"姓名":"李四", "年龄":19, "成绩":[69.0, 58.5, 64.0]})"; Json::Value root; Json::CharReaderBuilder crb; std::unique_ptr<Json::CharReader> cr(crb.newCharReader()); std::string err; cr->parse(str.c_str(), str.c_str() + str.size(), &root, &err); std::cout << root["姓名"].asString() << std::endl; std::cout << root["年龄"].asInt() << std::endl; int sz = root["成绩"].size(); for (int i = 0; i < sz; ++i) std::cout << root["成绩"][i].asFloat() << std::endl; for(auto it = root["成绩"].begin(); it != root["成绩"].end(); ++it) std::cout << it->asString() << std::endl; return 0; }
运行结果:
3.4 认识MySQL数据库的API
MySQL C语言API就是用C语言编写的MySQL编程接口,使用这些接口函数可以实现对MySQL数据库的查询等操作。以下是MySQL的C语言API接口。
- MySQL句柄初始化
MYSQL* mysql_init(MYSQL* mysql);
【说明】
- 参数为空则动态申请句柄空间进行初始化,如果调用成功则返回MySQL句柄,失败则返回
NULL
。
句柄是什么?
句柄(handle)是C++程序设计中经常提及的一个术语。它并不是一种具体的、固定不变的数据类型或实体,而是代表了程序设计中的一个广义的概念。句柄一般是指获取另一个对象的方法——一个广义的指针,它的具体形式可能是一个整数、一个对象或就是一个真实的指针,而它的目的就是建立起与被访问对象之间的唯一的联系。
在C++中,要访问一个对象,通常可以建立一个指向对象的指针。但是在很多具体的应用中,直接用指针代表对象并不是一个好的解决方案。因此引入了句柄的概念。
- 连接MySQL服务器
MYSQL* mysql_real_connect(MYSQL* mysql, const char* host, const char* user, const char* passwd, const char* db, unsigned int port, const char* unix_socket, unsigned long client_flag);
【参数说明】
- mysql:初始化完成的句柄
- host:连接的MySQL服务器的地址
- user:连接的数据库的用户名
- passwd:该用户连接数据库的密码
- db:默认选择的数据库名称
- port:连接的MySQL服务器的端口,默认0是3306端口
- unix_socket:通信管道文件或者socket文件,通常置为NULL
- client_flag:客户端的标志位,通常置为0【返回值】
- 连接成功返回MySQL句柄,失败则返回
NULL
- 设置当前客户端的字符集
int mysql_set_character_set(MYSQL* mysql, const char* csname);
【参数说明】
- mysql:初始化完成的句柄
- csname:字符集名称,通常为
utf8
【返回值】
- 调用成功返回0,失败则返回非0
- 选择操作的数据库
int mysql_select_db(MYSQL* mysql, const char* db);
【参数说明】
- mysql:初始化完成的句柄
- db:要进行操作的数据库名称
【返回值】
- 调用成功返回0,失败则返回非0
- 执行SQL语句
int mysql_query(MYSQL* mysql, const char* stmt_str);
【参数说明】
- mysql:初始化完成的句柄
- stmt_str:要执行的sql语句
【返回值】
- 调用成功返回0,失败则返回非0
- 保存查询结果到本地
MYSQL_RES* mysql_store_result(MYSQL* mysql);
【参数说明】
- mysql:初始化完成的句柄
【返回值】
- 调用成功返回结果集的首地址,失败则返回
NULL
- 获取结果集中的行数
uint64_t mysql_num_rows(MYSQL_RES* result);
【参数说明】
- result:保存到本地的结果集
【返回值】
- 返回结果集中数据的条数,也是是行数。
- 获取每一条结果集的列数
unsigned int mysql_num_fields(MYSQL_RES* result);
【参数说明】
- result:保存到本地的结果集
【返回值】
- 返回结果每一条集中数据的列数
- 遍历结果集
MYSQL_ROW mysql_fecth_row(MYSQL_RES* result);
【参数说明】
- result:保存到本地的结果集
【返回值】
MYSQL_ROW
实际上是一个char**
类型的二级指针将每一条数据表示成了字符串指针数组。比如row[0]
表示第0行,row[1]
表示第一行。- 并且这个接口会保存当前读取的结果集位置,每次获取的都是下一条数据。
- 释放结果集
void mysql_free_result(MYSQL_RES* result);
【说明】
- result是保存到本地的结果集,无返回值。
注意一定要释放结果集,否则会造成内存泄漏。
- 关闭数据库客户端的连接,销毁MySQL句柄
void mysql_close(MYSQL* mysql);
- 获取MySQL接口中执行错误的原因
const char* mysql(MYSQL* mysql);
【说明】
- 该函数会返回执行sql语句失败的原因。
3.5 使用MySQL的API实现对数据的增删改查
- 创建一个测试所用数据库和表
create database if not exists test_db; use test_db; create table if not exists test_tb( id int primary key auto_increment, age int, name varchar(32), score decimal(4, 2) );
创建成功使用desc test_tb;
语句查看对test_tb
的描述:
对数据库进行增删改查的代码样例:
- 向数据库里面增加数据
#include <stdio.h> #include <unistd.h> #include <string.h> #include <mysql/mysql.h> //添加数据 int add(MYSQL* mysql) { const char* sql = "insert into test_tb values (null, 18, '张三', 61.5)"; int ret = mysql_query(mysql, sql); if(ret != 0) { printf("query %s failed, error massage: %s\n", sql, mysql_error(mysql)); return -1; } return 0; } int main() { //初始化操作句柄 MYSQL* mysql = mysql_init(NULL); if(NULL == mysql) { printf("init mysql handle failed!\n"); return -1; } //连接mysql服务器 if (mysql_real_connect(mysql, "127.0.0.1", "root", "", "test_db", 0, NULL, 0) == NULL) { printf("mysql connect failed!\n"); return -1; } //设置客户端字符集 mysql_set_character_set(mysql, "utf8"); add(mysql); //关闭句柄 mysql_close(mysql); return 0; }
编译指令:
gcc -o mysql_test mysql_test.c -L/usr/lib64/mysql -lmysqlclient
【项目】视频点播系统2:https://developer.aliyun.com/article/1384001