本文介绍: 但这种方式是直接调用服务层的方法,就把网络模块的代码和业务模块的代码给强耦合一起了,这不是好的方法..由于客户端和服务器通信收发消息,需要判断这个消息是属于哪种业务的,就需要一个业务的标识,所以就用。当有登录业务需求就调用相应的服务登录方法,当有注册业务需求就调用相应的服务注册方法,这样就用到。回调思想,要想解耦模块之间的关系,一般有两种方法,一种就是使用基于面向接口的编程,在。对应一个操作.不管具体做什么业务,并不会直接调用业务模块的相关的方法.存储消息id和其对应的业务处理方法.注册消息以及对应的。

跟着施磊老师做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}

原文地址:https://blog.csdn.net/weixin_41987016/article/details/135991635

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。

如若转载,请注明出处:http://www.7code.cn/show_65369.html

如若内容造成侵权/违法违规/事实不符,请联系代码007邮箱:suwngjj01@126.com进行投诉反馈,一经查实,立即删除!

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注