遇到了一点问题都一一解决了
1.vscode远程连接死活连不上,最后我把他给卸了,但是注意这里一定要把他卸干净,相应的方法网上一大堆,卸干净以后才连上linux
2.运行程序的过程中我发现数据库有点问题,但连接数据库时也是连接不上(密码是对的)后来看了这个博客才解决,博客找不到了,用的是这个命令***mysqladmin -uroot -p旧密码 password 新密码***
3.数据库修改表名;alter table old_name rename to new_name;
项目大概的流程
1.将每一个文件放在指定的目录下,这里要建立各个用途不一的目录
1.bin:放编译完成后的可执行文件
2.build:放CMake编译产生的中间文件
3.include:项目中的头文件
4.src:放源文件
5.thirdparty:放第三方库文件(这里是json.hpp)
2.CMake文件的生成
使用CMake前要在linux上安装CMake
CMake是一个跨平台的安装(编译)工具,可以用简单的语句来描述所有平台的安装(编译过程)
项目根目录下的CMakeLists.txt
cmake_minimum_required(VERSION 3.0)
project(chat)
# 配置编译选项
set(CMAKE_CXX_FLAGS ${CMAKE_CXX_FLAGS} -g)
# 配置最终的可执行文件输出的路径
set(EXECUTABLE_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/bin)
# 配置头文件的搜索路径
include_directories(${PROJECT_SOURCE_DIR}/include)
include_directories(${PROJECT_SOURCE_DIR}/include/server)
include_directories(${PROJECT_SOURCE_DIR}/include/server/db)
include_directories(${PROJECT_SOURCE_DIR}/include/server/model)
include_directories(${PROJECT_SOURCE_DIR}/include/server/redis)
include_directories(${PROJECT_SOURCE_DIR}/thirdparty)
# 加载子目录
add_subdirectory(src)
src目录下的CMakeLists.txt文件
add_subdirectory(server)
add_subdirectory(client)
src/server下的CMakeLists.txt文件
# 定义了一个SRC_LIST变量,包含了该目录下所有的源文件
aux_source_directory(. SRC_LIST)
aux_source_directory(./db DB_LIST)
aux_source_directory(./model MODEL_LIST)
aux_source_directory(./redis REDIS_LIST)
# 指定生成可执行文件
add_executable(ChatServer ${SRC_LIST} ${DB_LIST} ${MODEL_LIST} ${REDIS_LIST})
# 指定可执行文件链接时需要依赖的库文件
target_link_libraries(ChatServer muduo_net muduo_base mysqlclient hiredis pthread)
/src/client下的CMakeLists.txt文件
# 定义了一个SRC_LIST变量,包含了该目录下所有的源文件
aux_source_directory(. SRC_LIST)
# 指定生成可执行文件
add_executable(ChatClient ${SRC_LIST})
# 指定可执行文件链接时需要依赖的库文件
target_link_libraries(ChatClient pthread)
3.客户端与服务器建立连接
此项目是基于muduo网络库开发的,muduo库的介绍就是:一个基于reactor反应堆模型的多线程C++网络库,网络模块几乎都交给了muduo来实现,这里讲一讲muduo的安装。
安装muduo的地址,muduo是基于boost开发的,所以还要安装boost
关于muduo网络库的使用可以看这里
我项目里网络模块完全交给了muduo,然后就只关注业务模块的代码。
4.数据库各个表的建立
-
用户表user:保存用户的id,name,password,state
id:主键,用来识别用户身份
name:非空且唯一
password:密码,非空
state:ENUM(‘online’,‘offline’)状态默认offline -
friend表:通过id来识别朋友属性,
userid:用户id,与好友id组成联合主键
friendid:好友id
5.利用json来序列化和反序列化
Json是一种轻量级的数据交换格式(也叫数据序列化方式)。Json采用完全独立于编程语言的文本格式
来存储和表示数据。简洁和清晰的层次结构使得 Json 成为理想的数据交换语言。 易于人阅读和编
写,同时也易于机器解析和生成,并有效地提升网络传输效率。
关于什么是序列化和为什么要序列化看这里
5.客户端通过服务器在数据库上注册业务
- 如果注册成功会在用户表上添加一个用户信息
客户端连接成功后,向服务器端发送一个序列化json对象,json里包含了msgid,name和password,服务器接收到以后进行反序列化,因为在业务类的私有成员里有一个存储函数的map表,然后根据msgid处理相应的业务(这里是注册业务),在服务器类通过函数对象接收msgid对应的函数,没找到的话会返回一个自定义函数(提示错误信息),然后将参数传入函数完成注册业务。 - 注册业务会和数据库进行交互,在/include/server/model下有着负责和数据库交互的各种头文件,每个头文件里是各个表相关的类。
这里有一个user.hpp
user类里存储一个用户的基本信息,以及修改和获取信息的方法。 - UserModel.hpp是有一个UserModel类,这是一个User表的数据操作类(可以直接修改数据库中信息)
// User表的增加方法
bool insert(User &user);
// 根据用户号码查询用户信息
User query(int id);
// 更新用户的状态信息
bool updateState(User user);
// 重置用户的状态信息
void resetState();
用MySQL可以生产一个mysql对象,通过mysql对象可以和MySQL进行交互。
- 在/include/server/dp.hpp中有一个MySQL类,这是一个和数据库进行信息交互的类。
#ifndef DB_H
#define DB_H
#include <mysql/mysql.h>
#include <string>
using namespace std;
// 数据库操作类
class MySQL
{
public:
// 初始化数据库连接
MySQL();
// 释放数据库连接资源
~MySQL();
// 连接数据库
bool connect();
// 更新操作
bool update(string sql);
// 查询操作
MYSQL_RES *query(string sql);
// 获取连接
MYSQL* getConnection();
private:
MYSQL *_conn;
};
#endif
在#include <mysql/mysql.h>这个头文件里提供了操作数据库的方法。
1.连接数据库的方法
// 连接数据库
bool MySQL::connect()
{
MYSQL *p = mysql_real_connect(_conn, server.c_str(), user.c_str(),
password.c_str(), dbname.c_str(), 3306, nullptr, 0);
if (p != nullptr)
{
// C和C++代码默认的编码字符是ASCII,如果不设置,从MySQL上拉下来的中文显示?
mysql_query(_conn, "set names gbk");
LOG_INFO << "connect mysql success!";
}
else
{
LOG_INFO << "connect mysql fail!";
}
return p;
}
2.初始化数据库和断开连接
// 初始化数据库连接
MySQL::MySQL()
{
_conn = mysql_init(nullptr);
}
// 释放数据库连接资源
MySQL::~MySQL()
{
if (_conn != nullptr)
mysql_close(_conn);
}
3.更新操作
// 更新操作
bool MySQL::update(string sql)
{
if (mysql_query(_conn, sql.c_str()))
{
LOG_INFO << __FILE__ << ":" << __LINE__ << ":"
<< sql << "更新失败!";
return false;
}
return true;
}
4.查询操作
// 查询操作
MYSQL_RES *MySQL::query(string sql)
{
if (mysql_query(_conn, sql.c_str()))
{
LOG_INFO << __FILE__ << ":" << __LINE__ << ":"
<< sql << "查询失败!";
return nullptr;
}
return mysql_use_result(_conn);
}
5.获取连接
// 获取连接
MYSQL* MySQL::getConnection()
{
return _conn;
}
有了这些方法,就可以在UserModel中封装对user的insert函数了
// User表的增加方法
bool UserModel::insert(User &user)
{
// 1.组装sql语句
char sql[1024] = {0};
sprintf(sql, "insert into user(name, password, state) values('%s', '%s', '%s')",
user.getName().c_str(), user.getPwd().c_str(), user.getState().c_str());
MySQL mysql;
if (mysql.connect())
{
if (mysql.update(sql))
{
// 获取插入成功的用户数据生成的主键id
user.setId(mysql_insert_id(mysql.getConnection()));
return true;
}
}
return false;
}