当前位置: 首页 > article >正文

C++集群聊天服务器 网络模块+业务模块+CMake构建项目 笔记 (上)

跟着施磊老师做C++项目,施磊老师_腾讯课堂 (qq.com)

一、网络模块ChatServer

  • chatserver.hpp
#ifndef CHATSERVER_H
#define CHATSERVER_H

#include <muduo/net/TcpServer.h>
#include <muduo/net/EventLoop.h>
using namespace muduo;
using namespace muduo::net;

// 聊天服务器的主类
class ChatServer {
public:
    // 初始化聊天服务器对象
    ChatServer(EventLoop* loop,const InetAddress& listenAddr,const string& nameArg);
    // 启动服务
    void start(); 
private:
    // 上报链接相关信息的回调函数
    void onConnection(const TcpConnectionPtr& conn);
    // 上报读写事件相关信息的回调函数
    void onMessage(const TcpConnectionPtr& conn,Buffer* buffer,Timestamp time);
    TcpServer m_server; // 组合的muduo库,实现服务器功能的类对象
    EventLoop *m_loop;  // 指向事件循环的指针
};

#endif
  • chatserver.cpp
#include "chatserver.hpp"
#include "chatservice.hpp"
#include "json.hpp"
#include <functional>
#include <string>
#include <iostream>
using namespace std;
using namespace placeholders;
using json = nlohmann::json;

ChatServer::ChatServer(EventLoop *loop, const InetAddress &listenAddr, const string &nameArg)
    : m_server(loop, listenAddr, nameArg), m_loop(loop) {
    // 注册链接回调
    m_server.setConnectionCallback(std::bind(&ChatServer::onConnection, this, _1));
    // 注册消息回调
    m_server.setMessageCallback(std::bind(&ChatServer::onMessage, this, _1, _2, _3));
    // 设置线程数量
    m_server.setThreadNum(4);
}

// 启动服务
void ChatServer::start() {
    m_server.start();
}

// 上报链接相关信息的回调函数
void ChatServer::onConnection(const TcpConnectionPtr &conn) {
    // 客户端断开连接
    if(!conn->connected()) {
        conn->shutdown();// 释放socket fd资源
    }
}

// 上报读写事件相关信息的回调函数
void ChatServer::onMessage(const TcpConnectionPtr &conn, Buffer *buffer, Timestamp time) {
    string buf = buffer->retrieveAllAsString();
    
    std::cout<<"buf: "<<buf.c_str()<<std::endl;
    // 数据的反序列化
    json js = json::parse(buf);
    // 达到的目的:完全解耦网络模块的代码和业务模块的代码
    // 通过js["msgid"] 获取 => 业务handler => conn js time
    auto msghandler = ChatService::getInstance()->getHandler(js["msgid"].get<int>());
    // 回调消息绑定好的事件处理器,来执行相应的业务处理
    msghandler(conn,js,time);
}

json里边会包含一个msgid.由于客户端和服务器通信收发消息,需要判断这个消息是属于哪种业务的,就需要一个业务的标识,所以就用msgid来表示业务的标识.在onMessage函数中,并不想出现。

当有登录业务需求就调用相应的服务登录方法,当有注册业务需求就调用相应的服务注册方法,这样就用到if...else,或者switch case,但这种方式是直接调用服务层的方法,就把网络模块的代码和业务模块的代码给强耦合一起了,这不是好的方法.

方法二:每一个消息都有一个msgid(一个消息id映射一个事件处理),事先给它绑定一个回调操作,让一个id对应一个操作.不管具体做什么业务,并不会直接调用业务模块的相关的方法.

利用OOP回调思想,要想解耦模块之间的关系,一般有两种方法,一种就是使用基于面向接口的编程,在C++里边的"接口"可以理解为抽象基类.那也就是面向抽象基类的编程.另一种就是基于回调函数

这里使用基于回调函数来实现,m_msgHandlerMap存储消息id和其对应的业务处理方法.注册消息以及对应的Handler回调操作,就是把消息id对应的事件处理器给绑定了,LOGIN_MSG绑定的是login处理登录业务,REG_MSG绑定的是reg处理注册业务.

ChatService单例对象通过js["msgid"] 获取消息对应的处理器(业务handler)msghandler,由于回调消息绑定了事件处理器,可用它来执行相应的业务处理-->msghandler(conn,js,time);

二、业务模块ChatService

  • public.hpp
#ifndef PUBLIC_H
#define PUBLIC_H
/*
    server和client的公共文件
*/
enum EnMsgType {
    LOGIN_MSG = 1, // 登录消息
    REG_MSG // 注册消息
};
#endif // PUBLIC_H
  • chatservice.hpp
#ifndef CHATSERVICE_H
#define CHATSERVICE_H

#include <muduo/net/TcpConnection.h>
#include <unordered_map>
#include <functional>
using namespace std;
using namespace muduo;
using namespace muduo::net;

#include "json.hpp"
using json = nlohmann::json;

// 表示处理消息的事件回调方法类型
using MsgHandler = std::function<void(const TcpConnectionPtr& conn,json& js,Timestamp)>;

// 聊天服务器业务类
class ChatService {
public:
    // 获取单例对象的接口函数
    static ChatService* getInstance();
    // 处理登录业务
    void login(const TcpConnectionPtr& conn,json& js,Timestamp time); 
    // 处理注册业务(register)
    void reg(const TcpConnectionPtr& conn,json& js,Timestamp time); 
    // 获取消息对应的处理器
    MsgHandler getHandler(int msgid);

    ChatService(const ChatService&) = delete;
    ChatService& operator=(const ChatService&) = delete;
private:
    // 注册消息以及对应的Handler回调操作
    ChatService();
    // 存储消息id和其对应的业务处理方法
    unordered_map<int,MsgHandler> m_msgHandlerMap;
};

#endif // CHATSERVICE_H
  • chatservice.cpp
#include "chatservice.hpp"
#include "public.hpp"
#include <muduo/base/Logging.h>
using namespace muduo;

// 获取单例对象的接口函数 线程安全的单例对象
ChatService* ChatService::getInstance() {
    static ChatService service;
    return &service;
}

// 注册消息以及对应的Handler回调操作
ChatService::ChatService() {
    m_msgHandlerMap.insert({LOGIN_MSG,std::bind(&ChatService::login, this, _1, _2, _3)});  
    m_msgHandlerMap.insert({REG_MSG,std::bind(&ChatService::reg, this, _1, _2, _3)});  
}

// 处理登录业务
void ChatService::login(const TcpConnectionPtr &conn, json &js, Timestamp time) {
    LOG_INFO << "do login service!!!";
}

// 处理注册业务
void ChatService::reg(const TcpConnectionPtr &conn, json &js, Timestamp time) {
    LOG_INFO << "do reg service!!!";
}

// 获取消息对应的处理器
MsgHandler ChatService::getHandler(int msgid) {
    // 记录错误日志,msgid没有对应的事件处理回调
    auto it = m_msgHandlerMap.find(msgid);
    if(it == m_msgHandlerMap.end()) {
        // 返回一个默认的处理器,空操作
        return [=](const TcpConnectionPtr &conn, json &js, Timestamp) {
            LOG_ERROR << "msgid:" << msgid << " can not find handler!";
        };
    }
    else {
        return m_msgHandlerMap[msgid];
    }
}

三、src/server目录下的main.cpp

#include "chatserver.hpp"
#include <iostream>
using namespace std;
int main() {
    EventLoop loop;
    InetAddress addr("127.0.0.1", 6000);
    ChatServer server(&loop, addr, "ChatServer");
    server.start();
    loop.loop(); // 启动事件循环
    return 0;
}

四、thirdparty目录下是json.hpp

下载:GitHub - nlohmann/json: JSON for Modern C++

在single_include/nlohmann里头有一个json.hpp,把它放到我们的项目中就可以了

五、CMake构建项目

(1)在src/server目录中的CMakeLists.txt

# 定义了一个SRC_LIST变量 包含了该目录下所有的源文件
aux_source_directory(. SRC_LIST)
# 指定生成可执行文件
add_executable(ChatServer ${SRC_LIST})
# 指定可执行文件链接时需要依赖的库文件
target_link_libraries(ChatServer muduo_net muduo_base pthread)

(2)在src目录下的CMakeLists.txt

add_subdirectory(server)

(3)与include和src,以及thirdparty同级目录的CMakeLists.txt

cmake_minimum_required(VERSION 3.28.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}/thirdparty)

# 加载子目录
add_subdirectory(src)

在此目录下打开终端,执行命令:

cmake -B build
cmake --build build

接着执行

./bin/ChatServer

打开另一个终端,执行

telnet 127.0.0.1 6000
输入以下内容进行测试:
{"msgid":1}
{"msgid":2}

七、点对点聊天业务代码和测试

在public.hpp中添加ONE_CHAT_MSG, // 聊天消息

#ifndef PUBLIC_H
#define PUBLIC_H
/*
    server和client的公共文件
*/
enum EnMsgType {
    LOGIN_MSG = 1, // 登录消息
    LOGIN_MSG_ACK, // 登录响应消息
    REG_MSG, // 注册消息
    REG_MSG_ACK, // 注册响应消息
    ONE_CHAT_MSG, // 聊天消息
};
#endif // PUBLIC_H

在ChatService.hpp中添加一对一聊天业务函数声明

public:
    // 一对一聊天业务
    void oneChat(const TcpConnectionPtr& conn,json& js,Timestamp time);

 在ChatService.cpp中实现一对一聊天业务函数

// 一对一聊天业务
void ChatService::oneChat(const TcpConnectionPtr &conn, json &js, Timestamp time) {
    int toid = js["to"].get<int>();
    {
        lock_guard<mutex> lock(m_connMutex);
        auto it = m_userConnMap.find(toid);
        if(it != m_userConnMap.end()) {
            // toid在线,转发消息  服务器主动推送消息给toid用户
            it->second->send(js.dump());
            return;
        }
    }
    // toid不在线,存储离线消息
    // ...(待续写)
}

登录heheda和Tom账号 

 

在heheda账号中发送:
{"msgid":5,"id":1,"from":"heheda","to":2,"msg":"hello,I am Heheda!"}
于是,Tom账号收到
{"from":"heheda","id":1,"msg":"hello,I am Heheda!","msgid":5,"to":2}


在Tom账号中发送:
{"msgid":5,"id":2,"from":"Tom","to":1,"msg":"hello,I am Tom!"}
于是,heheda账号收到
{"from":"Tom","id":2,"msg":"hello,I am Tom!","msgid":5,"to":1}

 


http://www.kler.cn/a/229117.html

相关文章:

  • ubuntu开机自启某个应用
  • 【NextJS】PostgreSQL 遇上 Prisma ORM
  • 深入理解 Entity、VO、QO、DTO 的区别及其在 MVC 架构中的应用
  • 简历_使用优化的Redis自增ID策略生成分布式环境下全局唯一ID,用于用户上传数据的命名以及多种ID的生成
  • 关于vite+vue3+ts项目中env.d.ts 文件详解
  • docker 部署 MantisBT
  • Spring Boot(六十五):使用 ant.jar 执行 SQL 脚本文件
  • Windows下Node.js下载安装及环境变量配置教程
  • 陪诊小程序开发:重塑医疗服务体验,让关爱触手可及
  • 学习数据结构和算法的第3天
  • MySQL:关于存储过程
  • Packet Tracer - Configure IOS Intrusion Prevention System (IPS) Using the CLI
  • 第1章 简单使用 Linux
  • 空气质量预测 | Matlab实现基于SVR支持向量机回归的空气质量预测模型
  • 【RK3399 Android10, 支持温控风扇】
  • mysql关于left join关联查询时on和where条件区别
  • 数学建模:数据相关性分析(Pearson和 Spearman相关系数)含python实现
  • Kafka SASL_SSL双重认证
  • wins 安装 tensorflow keras
  • 一个冷门的js加密逆向分析
  • LeetCode:9.回文数,对整数的反转操作
  • 紫光展锐M6780丨一语即达,“声”临其境
  • Django的配置文件setting.py
  • SENet在双塔中的应用
  • Oracle12c之Sqlplus命令行窗口基本使用
  • SpringBoot实战第三天