c++全栈聊天项目
1. 简介
本项目为c++全栈聊天项目实战,包括PC端QT界面编程,asio异步服务器设计,beast网络库搭建http网关,nodejs搭建验证服务,各服务间用grpc通信,server和client用asio通信等,也包括用户信息的录入等,实现跨平台设计,先设计windows的server,之后再考虑移植到linux中,较为全面的展示c++在实际项目中的应用。
2. 创建主界面
2.1 创建方式
新建—>Application—>Qt Widgets Application—>项目名称为lxxlchat—>类名为MainWindow—>基类为QMainWindow
2.2 修改主窗口MainWindow的ui
1.固定大小(宽300,高500)和修改窗口标题
2.修改图标icon
将图标图片放到项目的根目录下
在lxxlchat.pro文件里面添加代码
2.3 主窗口的作用
该项目是让主窗口类MainWindows处于一个顶层,来衔接两个对话框,即登录对话框和注册对话框。主要作用就是让主窗口类里面的类对象之间有交互时,可以在主窗口类中处理,可以减少耦合。
2.4 对话框嵌入到主窗口方式
1.在创建登录对话框
和注册对话框
时,没有指定父对象,直接在主窗口的构造函数中通过setCentralWidget(对话框对象)
即可完成嵌入操作
- 需要手动析构登录对话框和注册对话框,即在主窗口类的析构函数中进行处理
- 当点击关闭主窗口时,可能会出现程序异常退出这种情况
2.在创建登录对话框
和注册对话框
时,指定父对象this。这样就不需要再主窗口类的析构函数中进行手动删除对话框了,但需要完成以下操作,才能实现将对话框嵌入到主窗口。
- 将对话框的固定大小设置为主窗口的固定大小
- 不仅需要添加
setCentralWidget(_login_dlg)
,还需要去除对话框的边框和标题栏_login_dlg->setWindowFlags(Qt::CustomizeWindowHint|Qt::FramelessWindowHint);
3. 创建登录的对话框
3.1 创建方式
新建—>Qt—>Qt设计师界面类—>选择界面模板:Dialog without Buttons—>类名为:LoginDialog
3.2 登录对话框加载到主界面上
将登录对话框的类对象定义在主窗口类的成员里面,在主界面的构造函数中加入如下代码
1 | _login_dlg = new LoginDialog(); //创建登录界面 |
4. 创建资源文件:
4.1 创建方式
新建—>Qt—>Qt Resource File—>名称为rc
4.2 在qt中使用资源文件
1.将资源图片文件res放到根目录下
2.在Qt中引入资源文件res
操作1:在qt的资源文件中添加现有文件
操作2:在弹出的对话框中,将要加入的资源文件全部选上,然后点击打开
操作3:选择Yes to All
4.3 给登录界面添加位图
5. 创建注册的对话框
5.1 创建方式
新建—>Qt—>Qt设计师界面类—>选择界面模板:Dialog without Buttons—>类名为:RegisterDialog
6. Qt中使用qss
1.在根目录下加入style文件夹,在该文件夹下面添加qss文件
2.将qss文件加载到Qt的资源文件中,和之前在Qt资源文件中引入资源图片是一样的操作
3.将qss样式应用到Qt界面上,需要在main()函数下添加如下程序
1 | //加载一个QSS样式表文件,并将其应用到整个Qt应用程序的界面上 |
7. 创建全局的文件
新建—>c++—>c++ Header File—>名称为global.h
新建—>c++—>c++ Source File—>名称为global.cpp
8. 单例类的创建
新建—>c++—>c++ Header File—>名称为singleton.h
9. 创建Qt的Http发送的管理者类
新建—>c++—>c++class—>类名为HttpMgr
注意:该类涉及到网络的使用,所以需要在pro文件里面添加network
模块
CRTP:可以让还未声明的类,继承以自己为模板实例的类(继承以自己为模板的单例类)
10. 定时按钮类
新建—>c++—>c++clase—>类名为TimerBtn,基类为Custom
创建好后,令其继承QPushButton按钮类
将注册界面的获取按钮提升为TimerBtn,当点击该按钮后,就会触发TimerBtn重写的一些功能。
11. 创建可点击的标签类ClickedLabel,用于密码和确认密码的隐藏和显示
新建—>c++,c++clase—>类名为ClickedLabel,基类为Custom(创建好后,继承基类QLabel)
将注册页面的两个label标签提升为ClickedLabel类
将登录界面的忘记密码按钮提升为ClickedLabel类
将ChatPage界面里面的emo_lb和file_lb两个label提升为可点击的ClickedLabel类
12. 创建应该TCP管理者类
新建—>c++,c++clase—>类名为TcpMgr,基类为Custom(先不写,默认的)
13. 创建聊天对话框
新建—>Qt,Qt设计师界面类—>界面模板为Dialog without Buttons—>类名为ChatDialog
14. 创建可点击的按钮类
新建—>c++,c++clase—>类名为ClickedBtn,基类为Custom(创建好后,继承基类QPushButton)
将聊天界面里面的add_btn按钮提升为ClickedBtn类
将ChatPage界面的receive_btn和send_btn提升为ClickedBtn类
15. 创建自定义的输入框类
新建—>c++,c++clase—>类名为CustomizeEdit,基类为Custom(创建好后,继承基类QLineEdit)
将聊天界面里面的search_edit输入框提升为CustomizeEdit类
16. 创建自定义的listwidget类
新建—>c++,c++clase—>类名为ChatUserList,基类为Custom(创建好后,继承基类QListWidget)
将聊天界面里面的char_user_list框提升为ChatUserList类
17. 创建用户窗口
新建—>Qt,Qt设计师界面类—>界面模板为widget—>类名为ChatUserWid
创建好好继承ListItemBase类(自己写的)
18. 创建控制item的基类ListItemBase
新建—>c++,c++clase—>类名为ListItemBase,基类为Custom(创建好后,继承基类QWidget)
19. 创建加载内容的对话框
新建—>Qt,Qt设计师界面类—>界面模板为Dialog without Buttons—>类名为LoadingDlg
20. 创建聊天页类ChatPage
新建—>Qt,Qt设计师界面类—>界面模板为widget—>类名为ChatPage
将聊天界面中QstackedWidget模块里面的chat_page提升为ChatPage类
21. 创建聊天界面(主)
新建—>c++,c++clase—>类名为ChatView,基类为Custom(创建好后,继承基类QWidget)
将聊天界面中的chat_data_list提升为ChatView
22. 创建聊天界面框架(副)
新建—>c++,c++clase—>类名为ChatItemBase,基类为Custom(创建好后,继承基类QWidget)
23. 创建聊天界面的气泡
新建—>c++,c++clase—>类名为BubbleFrame,基类为Custom(创建好后,继承基类QFrame)
24. 创建气泡里面的文本
新建—>c++,c++clase—>类名为TextBubble,基类为Custom(创建好后,继承基类BubbleFrame)
25. 常见气泡里面的图片
新建—>c++,c++clase—>类名为PictureBubble,基类为Custom(创建好后,继承基类BubbleFrame)
26.
新建—>c++,c++clase—>类名为MessageTextEdit,基类为Custom(创建好后,继承基类QTextEdit)
将chatpage.ui里面的chatEdit提升为MessageTextEdit
27. 创建StateWidget类
新建—>c++,c++clase—>类名为StateWidget,基类为Custom
28. 创建搜索列表
新建—>c++,c++clase—>类名为SearchList,基类为Custom(创建好后,继承基类QListWidget)
将ChatDialog对话框的search_list组件升级为SearchList
29.创建用户数据类
新建—>c++,c++clase—>类名为UserData,基类为Custom
30.添加用户item类
新建—>Qt,Qt设计师界面类—>界面模板为widget—>类名为AddUserItem,基类改为ListItemBase
10. vs中配置boost与jsoncpp
2. 网关服务器GateServer
网关服务器主要应答客户端基本的连接请求,包括根据服务器负载情况选择合适服务器给客户端登录、注册、获取验证服务等,接收http请求并应答。
1.绑定和监听连接(服务端)
利用visual studio创建一个空项目,项目名字为GateServer,然后按照之前的方法配置boost库和jsoncpp配置好后,我们添加一个新的类,名字叫CServer。添加成功后生成的CServer.h和CServer.cpp也会自动加入到项目中。
在GateServer
项目中的逻辑:先是CServer启动,其启动后,就会监听连接。当对端有连接请求来之后,就交给HttpConnection
类去管理,在这个类里面,它会先监听读事件,当对端有数据发来之后会触发读回调函数,在这个函数里面会处理读的请求HandleReq()
,并且启动超时检测CheckDeadline()
(检测发送是否超时)。其中在处理读请求HandleReq()
函数中,会调用底层的LogicSystem
逻辑层去处理请求,同时这里会把请求的url
和HttpConnection
对象的智能指针作为参数传过去。而在LogicSystem逻辑层类的HandleGet
函数中,就是在map容器里面去找对应的url,如果之前没有注册过这个url,就会返回false,底层就会返回404错误;如果注册过,就会调用对应的回调(处理器)。
对应写回包就是写好响应头,然后准备对端请求的数据发送过去即可,如果在规定时间内写完发送给对端,在触发的写回调中就会把定时器取消,否则定时器会进行检测,超时的话它就会把socket强制关闭。
2.在Qt中添加客户端的配置文件config.ini
在QT的pro文件中需要添加如下程序:这段程序主要用于在Windows平台的调试模式(debug
配置)下,将配置文件(如config.ini
)从工程目录拷贝到输出目录。可以这样理解,你的Qt程序需要使用config.ini
配置文件来读取网络设置、数据库连接信息等。当你在调试时,程序从debug
目录运行,而这个目录默认情况下并不包含config.ini
。该脚本自动将配置文件从项目的根目录拷贝到debug
目录,确保程序运行时可以正确找到配置文件。
1 | win32:CONFIG(debug, debug | release) |
3.创建一个获取验证码的grpc客户端VerifyGrpcClient:
先定义一个message.proto文件,这是一种用于定义结构化数据的序列化协议,常用于远程过程调用(RPC)系统或者消息格式的定义。代码定义了一个服务和两个消息,用于实现一个获取验证码的接口。
1 | syntax = "proto3"; //定义了Protobuf的语法版本 |
接下来就是在message.proto所在文件夹的powershell上执行如下命令,利用grpc编译后生成的proc.exe来生成proto的grpc的头文件和源文件。即会生成message.grpc.pb.cc和message.grpc.pb.h文件,这两个文件里面保存了grpc通信的接口。
1 | C:\cppsoft\grpc\visualpro\third_party\protobuf\Debug\protoc.exe -I="." --grpc_out="." --plugin=protoc-gen-grpc="C:\cppsoft\grpc\visualpro\Debug\grpc_cpp_plugin.exe" "message.proto" |
对于通信的接口所使用的参数需要通过以下命令来生成,即会得到message.pb.cc和message.pb.h文件,这两个文件保存了通信使用的参数。
1 | C:\cppsoft\grpc\visualpro\third_party\protobuf\Debug\protoc.exe --cpp_out=. "message.proto" |
4.添加一个查询状态的grpc客户端StatusGrpcClient
3. 认证服务
认证服务要给邮箱发送验证码,所以用nodejs较为合适,nodejs是一门IO效率很高而且生态完善的语言,用到发送邮件的库也方便。
1.初始化node.js项目的一个配置文件:在VarifyServer文件夹下打开PowerShell终端执行npm init
,然后一路点击回车即可。
2.安装grpc-js包,也可以安装grpc,grpc是C++版本,grpc-js是js版本,C++版本停止维护了。所以用grpc-js版本。
在VarifyServer文件夹的PowerShell终端下执行npm install @grpc/grpc-js
。
3.安装proto-loader用来动态解析proto文件,在PowerShell终端下继续执行npm install @grpc/proto-loader
。
4.安装email处理的库,在PowerShell终端下继续执行npm install nodemailer
。
5.启动程序:npm run serve
81.68.86.146
流程:
用Qt编写了一个client,它会把请求(获取验证码)给到visual Studio编写的服务端GateServer,而GateServer会调用grpc,把请求投递给验证服务Varify,验证服务就会调用邮箱服务,该邮箱是各个平台提供的邮箱接口API,调用该API,发送到指定的邮箱里。如果发送成功了,邮箱接口API还会把成功的请求告诉验证服务Varify,验证服务也就会通过grpc服务把这个发送成功的请求回复给GateServer。当然不管成功还是失败,GateServer得到结果后都会发送给Qt那边的客户端。
4. 设置验证码过期
验证码是要设置过期的,可以用redis管理过期的验证码自动删除,key为邮箱,value为验证码,过期时间为3min。
4.1 redis服务搭建
1.在Redis-x64-5.0.14.1文件夹下的redis.windows.conf文件中处理如下:
修改端口:
1 | port 6380 |
添加requirepass
1 | # requirepass foobared |
2.通过redis-server.exe
启动redis服务器(在Redis-x64-5.0.14.1目录下):.\redis-server.exe .\redis.windows.conf
3.通过redis-cli.exe
启动客户端并输入密码:
4.widows编译和配置redis(很麻烦,省略)
4.2 VerifyServer增加redis
4.3 mysql
1.在C:\cppsoft\mysql\mysql\bin目录下打开cmd输入以下命令:
1 | //安装mysql,安装完成后Mysql会有一个随机密码 |
得到如下图,随机密码要记住,以后我们改密码会用到
2.在C:\cppsoft\mysql\mysql\bin目录下以管理员身份打开cmd输入以下命令:
1 | //安装mysql服务并启动 |
3.修改mysql密码
首先是在本机启动mysql服务:电脑搜索服务,找到mysql,启动它。
然后在C:\cppsoft\mysql\mysql\bin目录下打开终端,执行命令:.\mysql -uroot -p
,进入后先填写原始密码,上面保留那个
然后执行改命令,进行修改密码:ALTER USER 'root'@'localhost' IDENTIFIED BY '123456';
4.配置环境变量
新建系统变量:
变量名:MYSQL_HOME
变量值:自己的msql目录(C:\cppsoft\mysql)
修改系统的path变量:点击编辑path,进去后添加 %MYSQL_HOME%\bin
5.状态服务器StatusServer
6.ChatServer类
长连接的流程:客户端想要登录,首先需要将登录请求发给GataServer服务器,GataServer就去StatusServer服务器上去查询,如果校验没有任何问题的话,它就会分配一个ip和token,给到GataServer服务器,GataServer就会把这个消息给到客户端。客户端就会利用这个ip和token来登录,ChatServer服务器会验证这个ip和token(去StatusServer服务器上查询),以及该用户的一些信息,如果都没有问题,它就会让其登录,返回一个rsp回包,这样客户端就和ChatServer服务器建立了连接,后续客户端需要发的信息内容,就可以发给ChatServer服务器。
7. usermgr类
8.流程
1.主窗口mainwindow
(创建登录界面对象)展示登录界面logindialog
。 mainwindow —–> logindialog
2.登录界面logindialog
点击注册按钮,发出信号switchRegister,主窗口mainwindow
接收。 loginwindow —–> mainwindow
3.主窗口mainwindow
接收信号switchRegister,执行槽函数SlotSwitchReg(创建注册界面对象),展示注册界面registerdialog
。mainwindow —–> registerdialog
4.注册界面registerdialog点击获取按钮(检查邮箱格式),发送请求获取验证码(调用http管理者httpmgr
的发送请求接口)。
5.注册界面registerdialog
点击确定按钮执行槽函数on_sure_btn_clicked,检查输入框的内容,没有问题就发送请求注册用户(调用http管理者httpmgr
的发送请求接口)。
6.管理者httpmgr
执行完异步发送请求后(向网关服务器GateServer发送请求),等待回复,收到回复后,无论成功与否,都发出信号sig_http_finish(附带请求模块),由自己接收处理。 httpmgr —–> httpmgr
7.管理者httpmgr
收到回复成功信号后,执行槽函数slot_http_finish,根据不同请求,都发出信号sig_login_mod_finish(附带请求模块)
8.注册界面registerdialog
根据接收的信号sig_login_mod_finish,会通过不同的id执行相应的函数对象(注册界面初始化时就注册进去的),比如说有获取验证码的id、注册用户的id。