C++Linux项目推荐-Web多人聊天+MySQL+Redis+Websocket+Json,可以写简历的C++项目

news2024/12/29 8:38:30

1 项目地址

项目配套视频简介:程序员老廖的个人空间-程序员老廖个人主页-哔哩哔哩视频 (bilibili.com)

1.1 项目原有功能

https://github.com/anarthal/servertech-chat.git

功能:

  1. 支持HTTP请求,掌握HTTP API + json的请求相应
  2. 支持Websocket,掌握json做序列化和反序列化
  3. 支持多房间聊天
  4. 支持多人聊天
  5. 支持MySQL存储用户信息
  6. 支持Redis缓存token,存储聊天消息
  7. json序列化
  8. 静态网页支持
  9. 支持单元测试
  10. 支持python脚本性能测试

1.2 建议扩展功能

  1. 基于Reactor网络模型构建HTTP服务和Websocket服务,替换现有的协程框架;
  2. 使用rapidjson做序列化和反序列化;
  3. 仿写MySQL/Redis连接池;
  4. 增加房间创建/修改/删除接口,并将房间成员存储到MySQL;
  5. 单元测试替换为gtest;
  6. ........可以不断扩展,总而言之,就是比做单纯的webserver项目强

2 开发环境

对gcc/g++编译版本要求比较高,建议升级到10.0以后的编译器版本。

  • Ubuntu 20.04 ,如果Linux没有基础可以参考:Linux C/C++开发环境搭建(系列视频)教程,vscode远程ubuntu调试多个c++文件,让你少走弯路_哔哩哔哩_bilibili

  • MySQL 8.0

  • Redis 6.0

  • gcc/g++ 10.5.0,如果你的编译器版本较低则可以参考【小记】Ubuntu 工具链升级 gcc 流程 - 芯片烤电池 - 博客园 (cnblogs.com) 进行升级。

  • boost库 1.86版本

3 部署服务端

3.1 安装boost库

该项目依赖boost库,需要先安装boost库,我们从官网下载(也可以从我提供的百度云链接下载)

# 下载
wget https://archives.boost.io/release/1.86.0/source/boost_1_86_0_rc1.zip --no-check-certificate

#解压
unzip -x boost_1_86_0_rc1.zip

#进入boost
cd boost_1_86_0

#配置boost库
./bootstrap.sh

#编译Boost库
./b2


#安装Boost库
sudo ./b2 install
#这将Boost库安装到系统默认的位置(一般是/usr/local)。

3.2 编译聊天室服务

  1. 下载源码
git clone https://github.com/anarthal/servertech-chat.git

PS:下载时最新的commit 0008f72e9bf7d

  1. 编译源码
cd servertech-chat/
cd server/
mkdir build
cmake .. -DCMAKE_CXX_STANDARD=17
make

在make时可能会报错,我编译时遇到的报错情况以及修改方法,可以参考以下方法把 **三处报错 **修改后再一起重新编译:

(1)CMake Error at /usr/lib/x86_64-linux-gnu/cmake/Boost-1.71.0/BoostConfig.cmake:117 (find_package): Could not find a configuration file for package "boost_json" that exactly

解决方法:修改servertech-chat/server/CMakeLists.txt,手动指定boost的路径: PATHS /usr/local/lib

大约在14行修改:

find_package(Boost REQUIRED COMPONENTS headers context json regex url PATHS /usr/local/lib)

(2) undefined reference to symbol 'pthread_condattr_setclock@@GLIBC_2.3.3'

undefined reference to `boost::charconv::to_chars(char, char, double, boost::charconv::chars_format)'

解决方法:修改servertech-chat/server/CMakeLists.txt,增加pthread,boost_charconv两个库

大约在67行的target_link_libraries()里添加,如下所示:

target_link_libraries(
    servertech_chat
    PUBLIC
    Boost::headers
    Boost::context
    Boost::json
    Boost::regex
    Boost::url
    OpenSSL::Crypto
    OpenSSL::SSL
    ICU::data
    ICU::i18n
    ICU::uc
    boost_charconv
    pthread
)

(3)boost库的头文件报错

/usr/local/include/boost/redis/adapter/detail/adapters.hpp 报错

添加 #define _LIBCPP_VERSION

然后重新编译

#确保此时是在servertech-chat/server/build目录
# 删除之前cmake产生的文件,但要注意你一定是在servertech-chat/server/build目录
rm -rf *
#重新cmake配置
cmake .. -DCMAKE_CXX_STANDARD=17
# 重新编译
make

编译成功后产生一个 main的执行文件,就是我们聊天室的服务程序。

现在我们还不能直接运行,还要配置MySQL和Redis。

3.3 配置MySQL和Redis

3.3.1 配置MySQL

  1. 启动MySQL

如果MySQL没有启动则需要启动

  1. 修改程序访问MySQL的用户名和密码

/home/lqf/long/servertech-chat/server/src/services/mysql_client.cpp

修改用户和密码,我这里用户名是root,密码123456,所以改成如下所示

  1. 修改程序访问MySQL的地址

host我们用默认的就行,因为当前部署是在MySQL所在机器部署的

3.3.2 配置Redis

以不需要密码的方式启动redis即可。

3.4 重新编译和启动服务程序

  1. 重新编译程序

因为我们重新修改了源码文件,所以需要使用make命令重新编译

#确保此时是在servertech-chat/server/build目录
# 重新编译
make
  1. 启动服务程序

启动服务程序,这里要注意命令格式:

Usage: ./main <address> <port> <doc_root>
Example:
    ./main 0.0.0.0 8080 .

doc_root的路径一定要设置对,比如./main 0.0.0.0 8080 ../../doc ,即是要正确给出这个项目自带的doc的目录

我目前是在build目录下启动的,因为doc是在servertech-chat目录下,我的启动格式如下所示(8080端口是web客户端调用http api时访问的端口,这里不要改其他的端口)

lqf@ubuntu:~/long/servertech-chat/server/build$ ./main 0.0.0.0 8080 ../../doc

正常启动后(没有信息输出是正常的):

我们光有服务程序还不行,需要在 《4 部署客户端》 继续部署Web客户端,这样才能访问服务程序。

  1. 查看数据库情况

(这里只是告诉大家这个服务程序对应的数据库名字,以及有哪些表,表结构是怎么样的)

服务程序启动后,数据库servertech_chat不存在则自动创建,我们使用mysql命令进入MySQL命令行控制台,可以查看到数据库servertech_chat被创建了。

数据库只有一个表,用来存储用户信息。

4 部署客户端

需要安装node 16.14以上的版本

4.1 安装node

  1. 下载node
wget https://cdn.npmmirror.com/binaries/node/v21.7.3/node-v21.7.3-linux-x64.tar.gz
  1. 解压
tar zxf node-v21.7.3-linux-x64.tar.gz
  1. 使用node /npm命令生效

创建软链接,注意自己的路径,比如我的node路径是/home/lqf/long/node-v21.7.3-linux-x64

sudo ln -s /home/lqf/long/node-v21.7.3-linux-x64/bin/node /usr/local/bin/node
sudo ln -s /home/lqf/long/node-v21.7.3-linux-x64/bin/npm /usr/local/bin/npm
  1. 配置国内的源

国内源速度快一些。

# 设置国内源
npm config set registry https://registry.npmmirror.com
# 查看国内源
npm get registry
  1. 验证安装的版本是否正确
node -v
显示
v21.7.3

npm -v
显示
10.5.0

4.2 部署Web客户端

  1. 使用npm安装web客户端需要的组件

Web客户端程序目录:servertech-chat/client

安装客户端需要的node组件

# 进入Web客户端代码目录
cd client
# 安装web客户端需要的组件
npm install
  1. 启动客户端
npm run dev

服务器会将任何匹配 URL http://localhost:3000/api/(.*) 的传入 HTTP 流量路由到位于 http://localhost:8080/api/ 的 C++ 服务器。如果你的 C++ 服务器在不同的端口上运行,请相应地编辑 client/.env.development 文件修改端口。

访问web客户端

在浏览器访问 http://localhost:3000, 如果是在服务器外部访问,则把localhost改成 服务器的ip地址,比如:

http://192.168.1.27:3000

进入界面:

创建账号

登录聊天室

在聊天窗口根据提示发送消息就可以了。

5 项目架构分析

我们主要关注服务端的代码。我们的重点不是学习boost,而是理清楚框架,然后可以改造成自己的聊天室。

get_hello_data获取房间的历史消息

request_room_history_event

5.1 数据存储

MySQL:存储用户信息,在servertech_chat数据库对应的users表。

Redis:存储房间消息和用户cookie

  • 房间消息:使用redis的stream结构,key为房间id,value为房间的聊天消息,更多详情参考:Redis Stream | 菜鸟教程 (runoob.com)
  • 用户cookie,使用redis的string结构,key为cookie,value为用户id,cookie默认有效期是7天,超过七天redis就将他删除,就需要用户重新登录。

5.2 消息格式

5.2.1 HTTP请求消息格式

create_account创建账号消息

API URL:http:xxx.xxx.xxx.xxx:3000/api/create-account

{
    "username": "darren",
    "email": "326873713@qq.com",
    "password": "xxxxxxx"
}

测试范例:

login登录消息

API URL:http:xxx.xxx.xxx.xxx:3000/api/login

{
    "email": "326873713@qq.com",
    "password": "xxxxxxx"
}

测试范例:

5.2.2 Websocket交互消息格式

刚websocket连接的消息

服务器回应客户端的数据

{
    "type": "hello",
    "payload": {
        "me": {
            "id": 5,
            "username": "小鸭子米奇"
        },
        "rooms": [
            {
                "id": "beast",
                "name": "程序员老廖",
                "hasMoreMessages": false,
                "messages": [
                    {
                        "id": "1726840364728-0",
                        "content": "222222",
                        "user": {
                            "id": 5,
                            "username": "小鸭子米奇"
                        },
                        "timestamp": 1726840364726
                    },
                    {
                        "id": "1726840317055-0",
                        "content": "222",
                        "user": {
                            "id": 5,
                            "username": "小鸭子米奇"
                        },
                        "timestamp": 1726840317055
                    } 
                  .......
                ]
            },
            {
                "id": "async",
                "name": "Boost.Async",
                "hasMoreMessages": false,
                "messages": [
                    {
                        "id": "1726839255147-0",
                        "content": "2",
                        "user": {
                            "id": 5,
                            "username": "小鸭子米奇"
                        },
                        "timestamp": 1726839255146
                    },
                    {
                        "id": "1726836482227-0",
                        "content": "22222222",
                        "user": {
                            "id": 5,
                            "username": "小鸭子米奇"
                        },
                        "timestamp": 1726836482218
                    }
                ]
            },
            {
                "id": "db",
                "name": "Database connectors",
                "hasMoreMessages": false,
                "messages": []
            },
            {
                "id": "wasm",
                "name": "Web assembly",
                "hasMoreMessages": false,
                "messages": []
            }
        ]
    }
}
聊天消息格式

发送端:比如用户名:小鸭子米奇,用户id:5发送的消息,此时会携带cookie

{
  "type": "clientMessages",
  "payload": {
    "roomId": "beast",
    "messages": [
      {
        "content": "这是小鸭子发送的消息"
      }
    ]
  }
}

经过服务端处理后转发给其他接收者的消息,此时消息类型type 变为“serverMessages”,message字段增加了消息id,并增加了用户信息 "user": { "id": 5, "username": "小鸭子米奇"},,以及时间戳timestamp。

{
    "type": "serverMessages",
    "payload": {
        "roomId": "beast",
        "messages": [
            {
                "id": "1726839290525-0",
                "content": "这是小鸭子发送的消息",
                "user": {
                    "id": 5,
                    "username": "小鸭子米奇"
                },
                "timestamp": 1726839290524
            }
        ]
    }
}

发送端的json数据只所以不带用户信息,是因为其可以通过cookie从redis读取user_id,再根据 user_id去MySQL查询到username,这里这个设计可以了解,但这种做法虽然减少了客户端发送的数据量,但每条消息都访问MySQL对性能有影响的。

5.3 HTTP或者Websocket数据处理

服务端程序入口servertech-chat/server/src/main.cpp的main函数,重点在于launch_http_listener函数。

int main(int argc, char* argv[])
{
........
    // 对外提供服务的入口
    auto ec = launch_http_listener(ioc.get_executor(), listening_endpoint, st);
........
}

接下来分析launch_http_listener函数的重点内容,这里就是一套tcp server的操作,我们重点是看accept_loop函数。

error_code chat::launch_http_listener(
    boost::asio::any_io_executor ex,
    boost::asio::ip::tcp::endpoint listening_endpoint,
    std::shared_ptr<shared_state> state
)
{
    .........
    boost::asio::spawn(
        std::move(ex),
        [acceptor = std::move(acceptor), st = std::move(state)](boost::asio::yield_context yield) mutable {
            accept_loop(std::move(acceptor), std::move(st), yield);
        },
        rethrow_handler  // Propagate exceptions to the io_context
    );
    ............
}

继续分析accept_loop(), 我们有tcp server端的基础,应该能理解每个新连接过来,需要通过accept获取新连接,这里我们只关注拿到新连接后怎么处理,即是run_http_session是我们关注的重点

static void accept_loop(
    boost::asio::ip::tcp::acceptor acceptor,
    std::shared_ptr<chat::shared_state> st,
    boost::asio::yield_context yield
)
{
    ........
    while (true)
    {
        // Accept a new connection
        auto sock = acceptor.async_accept(yield[ec]);
        if (ec)
            return chat::log_error(ec, "accept");

        // Launch a new session for this connection. Each session gets its
        // own stackful coroutine, so we can get back to listening for new connections.
        boost::asio::spawn(
            sock.get_executor(),
            [state = st, socket = std::move(sock)](boost::asio::yield_context yield) mutable {
                //重点在于run_http_session
                run_http_session(std::move(socket), std::move(state), yield);
            },
            rethrow_handler  // Propagate exceptions to the io_context
        );
    }
    .......
}

继续分析chat::run_http_session()函数,该函数读取socket数据,然后分析是否是websocket或者http协议,不同的协议调用不同函数处理:

  • handle_chat_websocket 聊天的时候是websockt协议
    • chat_websocket_session::run() 这里负责读取聊天消息,并转发给房间里的其他人
      • 本质是调用event_handler_visitor的error_with_message operator()(client_messages_event& evt)
  • handle_http_request 注册和登录是http协议
    • handle_http_request_impl 根据url解析api请求,以http://xxx/api 开头的是http api请求,其他的认为是静态文件请求

5.3.1 HTTP请求处理流程

handle_http_request_impl函数

  • api/create-account 创建账号,调用chat::handle_create_account
    • 将用户信息写入MySQL
    • 生成cookie返回给客户端,并且服务端将该cookie存储在redis,以string类型存储,cookie作为key,用户id作为value。
  • api/login 登录账号,调用chat::handle_login:
    • 解析json获取邮箱和密码
    • 根据邮箱获取用户id,然后校验密码
    • 校验成功则生成cookie返回给客户端并存储在服务端。

5.3.2 Websocket处理流程

servertech-chat/server/src/api/chat_websocket.cpp

分析websocket的处理函数event_handler_visitor 的 error_with_message operator()(client_messages_event& evt),这里主要的流程:

  1. 先把消息存储到std::vector msgs;
  2. 将消息存储到redis ,调用 result_with_message<std::vectorstd::string> store_messages函数
    1. 使用XADD把消息加载到redis,其实是stream模式,使用room_id作为key。参考:Redis Stream | 菜鸟教程 (runoob.com)
    2. redis-cli里,可以使用 XREAD COUNT 3 STREAMS beast 0 来读取beast房间的消息。
  3. 将redis返回的消息id赋值给msgs,并重新封装成消息
  4. 将重新封装后带消息id的消息 发给所有的客户端 st.pubsub().publish(evt.roomId, server_evt.to_json());
    1. chat_websocket_session::on_message
      1.  websocket::write 发送消息给接收端

6 项目建议

如果不打算深入理解,只需要把这个项目的流程梳理清楚,然后基于自己的webserver扩展这些逻辑。

扩展建议在《1.2 建议扩展功能》。

通过扩展增加代码量,这样在面试的时候更游刃有余。

本文由博客一文多发平台 OpenWrite 发布!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2205587.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

如何使用ssm实现疫情居家办公OA系统

TOC 10902ssm疫情居家办公OA系统 系统概述 进过系统的分析后&#xff0c;就开始记性系统的设计&#xff0c;系统设计包含总体设计和详细设计。总体设计只是一个大体的设计&#xff0c;经过了总体设计&#xff0c;我们能够划分出系统的一些东西&#xff0c;例如文件、文档、数…

手机怎么玩荒野大镖客2?GameViewer远程助你手机随时随地畅玩大表哥2

手机免费玩电脑游戏&#xff0c;原来手机也能随时随地玩荒野大镖客2&#xff1f;如果你想你手机随时随地畅玩大表哥2&#xff0c;可以使用网易GameViewer远程来实现。 GameViewer远程4K蓝光144帧的高画质&#xff0c;可以让你在玩荒野大镖客2时&#xff0c;体验到开放大世界的所…

2024年11月软考准考证什么时候打印?

打印时间一览表&#xff1a; 北京&#xff1a;2024年11月5日至11月8日 上海&#xff1a;2024年11月6日10:00至11月8日16:00 天津&#xff1a;2024年11月5日9:00后 重庆&#xff1a;2024年11月4日9:00至10日9:05 广东&#xff1a;2024年11月5日9:00至8日17:00 深圳&#xf…

靠“代工+营销”支撑,又突击分红10亿元,毛戈平冲刺上市为哪般

撰稿|行星 来源|贝多财经 10月9日&#xff0c;毛戈平化妆品股份有限公司&#xff08;下称“毛戈平”或“毛戈平公司”&#xff09;在港交所更新招股书&#xff0c;继续推进港股上市进程。此前&#xff0c;毛戈平曾于4月8日向港交所递表&#xff0c;但因财务资料已过有效期而“…

selenium:Select类操作复选框和下拉框(7)

复选框/下拉框操作的Select类 主要使用selinium中的类Select来模拟选择网页上的下拉框或者复选框中的内容&#xff0c;使用前先导入 from selenium.webdriver.support.ui import Select 主要方法如下&#xff1a; 函数 功能 select_by_value 根据复选框/下拉框的值选择 se…

openfeign解释及其应用

文章目录 前言一、FeignClient详解注解使用范围注解属性说明value()name()contextId()qualifiers()configurationfallbackfallbackFactorypath 二、openfeign走网关gateway 前言 本文讨论的是springcloud分布式微服务架构下&#xff0c;如何让openfeign请求也走gateway网关 本…

浏览器服务端文件下载控制(安全阻止、文件浏览器打开还是下载行为控制)

文章目录 简介Chrome已阻止不安全内容下载PDF直接打开txt、xml、js文件被自动打开了而不是下载阿里OSS设置response header阿里OSS修改metadata 简介 随着浏览器的发展&#xff0c;有很多安全方面的限制&#xff0c;对我们的文件下载行为产生了很大的影响。 在JavaScript下载…

鸿蒙OS投票机制

(基于openharmony5.0) 投票机制 param get | grep ohos.boot.time 图 投票机制参数图 只有当所有的投票完成&#xff0c;开机动画才会退出&#xff0c;整理需要投票的系统应用&#xff08;三方应用不参与投票&#xff09;如下图所示&#xff1a; 以进程foundation为例&…

pytorh学习笔记——波士顿房价预测

机器学习的“hello world”&#xff1a;波士顿房价预测 波士顿房价预测的背景不用提了&#xff0c;简单了解一下数据集的结构。 波士顿房价的数据集&#xff0c;共有506组数据&#xff0c;每组数据共14项&#xff0c;前13项是影响房价的各种因素&#xff0c;比如&…

深入探究d3d9.dll文件:从d3d9.dll文件丢失的原因到解决方案

在使用电脑的过程中&#xff0c;你是否突然遇到过这样令人头疼的情况&#xff1a;当你试图打开某个游戏或者特定的图形软件时&#xff0c;屏幕上弹出一个恼人的错误提示框&#xff0c;上面赫然写着“d3d9.dll文件丢失”。这个看似小小的文件缺失&#xff0c;却可能像一道无法逾…

如何把视频变成自己的原创?提升视频原创度的7个技巧

在短视频平台发布作品时&#xff0c;时常因为原创问题&#xff0c;而被限流。如何在海量视频中脱颖而出&#xff0c;让自己的作品具有独特性和原创性&#xff0c;是每位创作者都需要思考的问题。本文将详细介绍如何通过一系列前期准备和后期处理技巧&#xff0c;将视频素材转化…

Windows11 24H2 纯净专业工作站版:无捆绑,安心使用!

Windows11系统工作站版集成Windows11系统专业版的所有安全优势和其他丰富特性&#xff0c;全面满足用户的使用需求。今天&#xff0c;系统之家小编给大家带来2024最新的Windows11 24H2纯净专业工作站版系统下载&#xff0c;这个系统版本不包含捆绑流氓软件&#xff0c;系统环境…

洛谷 P7076 [CSP-S2020] 动物园(位运算)

题目传送门 解题思路 可以先开一个 把所有动物的编号或起来。 然后对于每一个 和 &#xff0c;我们可以判断一下这个饲料是否需要购买&#xff08;对于原来有的动物&#xff09;。 如果它原本是不需要购买的&#xff0c;我们可以开一个 来标记这一位。 最后&#xff0c;…

内网渗透-隧道代理转发

文章目录 前言环境搭建工具清单工具使用Frp命令执行实验 Lcx命令执行实验 reGeorg命令执行实验Proxifier ew(EarthWorm)正向代理命令执行实验 反向代理命令执行实验SocksCap netsh命令执行 pingtunnel命令执行实验 ngrok命令执行&&实验 cs命令执行实验 前言 本文章介绍…

如何安装NOI(全国青少年信息学奥林匹克竞赛) Linux环境:详细安装指南

在全国青少年信息学奥林匹克竞赛&#xff08;NOI&#xff09;中&#xff0c;NOI Linux环境是比赛指定的操作系统。该环境基于Ubuntu&#xff0c;专门为编程竞赛设计和优化&#xff0c;包含了C编程和算法竞赛所需的工具和编译器。为了让学生在比赛中熟悉这一环境&#xff0c;了解…

深圳有哪些神仙公司?

前文写了一篇杭州有哪些神仙公司&#xff1f;有读者看完之后&#xff0c;建议出一篇深圳的神仙公司名单&#xff0c;这就安排了。 之前在深圳待过一段时间&#xff0c;整体印象很好&#xff0c;气候宜人&#xff0c;是一座充满活力、创新和机遇的城市。 坐标深圳的小伙伴&…

【C++】线程库常用接口

1.创建线程&#xff0c;等待线程&#xff0c;获取线程id 2.全局变量&#xff0c;局部变量&#xff0c;互斥锁 要让不同的线程访问同一个变量和同一把锁&#xff0c;有两种方法&#xff1a; 2.1方法一 定义全局的变量和全局的锁&#xff0c;这样自然就能访问到。 但全局变量在…

物联网护士站!RFID与传感技术如何提升病患管理智能化?

随着物联网技术的迅速发展&#xff0c;智慧医疗的理念逐渐被广泛应用于医院管理和医疗服务中&#xff0c;成为现代医疗的重要组成部分。通过物联网技术的引入&#xff0c;医院不仅能够实现对患者的智能化医疗&#xff0c;还能高效管理设备和物资&#xff0c;从而推动智慧医疗的…

[C++]使用onnxruntime部署yolov8-cls图像分类onnx模型

如果只需要opencv去部署yolov8分类模型可以参考博文&#xff1a;https://blog.csdn.net/FL1623863129/article/details/142734780 本文和 opencv去部署yolov8分类模型区别是&#xff1a;opencv部署推理核心使用opencv自带api&#xff0c;而本文推理核心用的onnxruntime&#x…

ros2:从github上下载源码进行编译

首先&#xff0c;创建工作空间 # 1. 递归创建工作空间目录 mkdir -p catkin_ws/src # 2. 进入src目录 cd catkin_ws/src然后如果你没有安装git&#xff0c;需要 sudo apt install git然后输入。 git clone https://github.com/6-robot/wpr_simulation.git这时候&#xff0c;…