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