跟著施磊老師做C++項(xiàng)目,施磊老師_騰訊課堂 (qq.com)
一、網(wǎng)絡(luò)模塊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;
// 聊天服務(wù)器的主類
class ChatServer {
public:
// 初始化聊天服務(wù)器對(duì)象
ChatServer(EventLoop* loop,const InetAddress& listenAddr,const string& nameArg);
// 啟動(dòng)服務(wù)
void start();
private:
// 上報(bào)鏈接相關(guān)信息的回調(diào)函數(shù)
void onConnection(const TcpConnectionPtr& conn);
// 上報(bào)讀寫(xiě)事件相關(guān)信息的回調(diào)函數(shù)
void onMessage(const TcpConnectionPtr& conn,Buffer* buffer,Timestamp time);
TcpServer m_server; // 組合的muduo庫(kù),實(shí)現(xiàn)服務(wù)器功能的類對(duì)象
EventLoop *m_loop; // 指向事件循環(huán)的指針
};
#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) {
// 注冊(cè)鏈接回調(diào)
m_server.setConnectionCallback(std::bind(&ChatServer::onConnection, this, _1));
// 注冊(cè)消息回調(diào)
m_server.setMessageCallback(std::bind(&ChatServer::onMessage, this, _1, _2, _3));
// 設(shè)置線程數(shù)量
m_server.setThreadNum(4);
}
// 啟動(dòng)服務(wù)
void ChatServer::start() {
m_server.start();
}
// 上報(bào)鏈接相關(guān)信息的回調(diào)函數(shù)
void ChatServer::onConnection(const TcpConnectionPtr &conn) {
// 客戶端斷開(kāi)連接
if(!conn->connected()) {
conn->shutdown();// 釋放socket fd資源
}
}
// 上報(bào)讀寫(xiě)事件相關(guān)信息的回調(diào)函數(shù)
void ChatServer::onMessage(const TcpConnectionPtr &conn, Buffer *buffer, Timestamp time) {
string buf = buffer->retrieveAllAsString();
std::cout<<"buf: "<<buf.c_str()<<std::endl;
// 數(shù)據(jù)的反序列化
json js = json::parse(buf);
// 達(dá)到的目的:完全解耦網(wǎng)絡(luò)模塊的代碼和業(yè)務(wù)模塊的代碼
// 通過(guò)js["msgid"] 獲取 => 業(yè)務(wù)handler => conn js time
auto msghandler = ChatService::getInstance()->getHandler(js["msgid"].get<int>());
// 回調(diào)消息綁定好的事件處理器,來(lái)執(zhí)行相應(yīng)的業(yè)務(wù)處理
msghandler(conn,js,time);
}
json里邊會(huì)包含一個(gè)msgid.由于客戶端和服務(wù)器通信收發(fā)消息,需要判斷這個(gè)消息是屬于哪種業(yè)務(wù)的,就需要一個(gè)業(yè)務(wù)的標(biāo)識(shí),所以就用msgid來(lái)表示業(yè)務(wù)的標(biāo)識(shí).在onMessage函數(shù)中,并不想出現(xiàn)。
當(dāng)有登錄業(yè)務(wù)需求就調(diào)用相應(yīng)的服務(wù)登錄方法,當(dāng)有注冊(cè)業(yè)務(wù)需求就調(diào)用相應(yīng)的服務(wù)注冊(cè)方法,這樣就用到if...else,或者switch case,但這種方式是直接調(diào)用服務(wù)層的方法,就把網(wǎng)絡(luò)模塊的代碼和業(yè)務(wù)模塊的代碼給強(qiáng)耦合一起了,這不是好的方法.
方法二:每一個(gè)消息都有一個(gè)msgid(一個(gè)消息id映射一個(gè)事件處理),事先給它綁定一個(gè)回調(diào)操作,讓一個(gè)id對(duì)應(yīng)一個(gè)操作.不管具體做什么業(yè)務(wù),并不會(huì)直接調(diào)用業(yè)務(wù)模塊的相關(guān)的方法.
利用OOP回調(diào)思想,要想解耦模塊之間的關(guān)系,一般有兩種方法,一種就是使用基于面向接口的編程,在C++里邊的"接口"可以理解為抽象基類.那也就是面向抽象基類的編程.另一種就是基于回調(diào)函數(shù)
這里使用基于回調(diào)函數(shù)來(lái)實(shí)現(xiàn),m_msgHandlerMap存儲(chǔ)消息id和其對(duì)應(yīng)的業(yè)務(wù)處理方法.注冊(cè)消息以及對(duì)應(yīng)的Handler回調(diào)操作,就是把消息id對(duì)應(yīng)的事件處理器給綁定了,LOGIN_MSG綁定的是login處理登錄業(yè)務(wù),REG_MSG綁定的是reg處理注冊(cè)業(yè)務(wù).
ChatService單例對(duì)象通過(guò)js["msgid"] 獲取消息對(duì)應(yīng)的處理器(業(yè)務(wù)handler)msghandler,由于回調(diào)消息綁定了事件處理器,可用它來(lái)執(zhí)行相應(yīng)的業(yè)務(wù)處理-->msghandler(conn,js,time);
二、業(yè)務(wù)模塊ChatService
- public.hpp
#ifndef PUBLIC_H
#define PUBLIC_H
/*
server和client的公共文件
*/
enum EnMsgType {
LOGIN_MSG = 1, // 登錄消息
REG_MSG // 注冊(cè)消息
};
#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;
// 表示處理消息的事件回調(diào)方法類型
using MsgHandler = std::function<void(const TcpConnectionPtr& conn,json& js,Timestamp)>;
// 聊天服務(wù)器業(yè)務(wù)類
class ChatService {
public:
// 獲取單例對(duì)象的接口函數(shù)
static ChatService* getInstance();
// 處理登錄業(yè)務(wù)
void login(const TcpConnectionPtr& conn,json& js,Timestamp time);
// 處理注冊(cè)業(yè)務(wù)(register)
void reg(const TcpConnectionPtr& conn,json& js,Timestamp time);
// 獲取消息對(duì)應(yīng)的處理器
MsgHandler getHandler(int msgid);
ChatService(const ChatService&) = delete;
ChatService& operator=(const ChatService&) = delete;
private:
// 注冊(cè)消息以及對(duì)應(yīng)的Handler回調(diào)操作
ChatService();
// 存儲(chǔ)消息id和其對(duì)應(yīng)的業(yè)務(wù)處理方法
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;
// 獲取單例對(duì)象的接口函數(shù) 線程安全的單例對(duì)象
ChatService* ChatService::getInstance() {
static ChatService service;
return &service;
}
// 注冊(cè)消息以及對(duì)應(yīng)的Handler回調(diào)操作
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)});
}
// 處理登錄業(yè)務(wù)
void ChatService::login(const TcpConnectionPtr &conn, json &js, Timestamp time) {
LOG_INFO << "do login service!!!";
}
// 處理注冊(cè)業(yè)務(wù)
void ChatService::reg(const TcpConnectionPtr &conn, json &js, Timestamp time) {
LOG_INFO << "do reg service!!!";
}
// 獲取消息對(duì)應(yīng)的處理器
MsgHandler ChatService::getHandler(int msgid) {
// 記錄錯(cuò)誤日志,msgid沒(méi)有對(duì)應(yīng)的事件處理回調(diào)
auto it = m_msgHandlerMap.find(msgid);
if(it == m_msgHandlerMap.end()) {
// 返回一個(gè)默認(rèn)的處理器,空操作
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(); // 啟動(dòng)事件循環(huán)
return 0;
}
四、thirdparty目錄下是json.hpp
下載:GitHub - nlohmann/json: JSON for Modern C++
在single_include/nlohmann里頭有一個(gè)json.hpp,把它放到我們的項(xiàng)目中就可以了
五、CMake構(gòu)建項(xiàng)目
(1)在src/server目錄中的CMakeLists.txt
# 定義了一個(gè)SRC_LIST變量 包含了該目錄下所有的源文件
aux_source_directory(. SRC_LIST)
# 指定生成可執(zhí)行文件
add_executable(ChatServer ${SRC_LIST})
# 指定可執(zhí)行文件鏈接時(shí)需要依賴的庫(kù)文件
target_link_libraries(ChatServer muduo_net muduo_base pthread)
(2)在src目錄下的CMakeLists.txt
add_subdirectory(server)
(3)與include和src,以及thirdparty同級(jí)目錄的CMakeLists.txt
cmake_minimum_required(VERSION 3.28.0)
project(chat)
# 配置編譯選項(xiàng)
set(CMAKE_CXX_FLAGS ${CMAKE_CXX_FLAGS} -g)
# 配置可執(zhí)行文件生成路徑
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)
在此目錄下打開(kāi)終端,執(zhí)行命令:
cmake -B build
cmake --build build
接著執(zhí)行
./bin/ChatServer
打開(kāi)另一個(gè)終端,執(zhí)行
telnet 127.0.0.1 6000
輸入以下內(nèi)容進(jìn)行測(cè)試:
{"msgid":1}
{"msgid":2}
七、點(diǎn)對(duì)點(diǎn)聊天業(yè)務(wù)代碼和測(cè)試
在public.hpp中添加ONE_CHAT_MSG, // 聊天消息
#ifndef PUBLIC_H
#define PUBLIC_H
/*
server和client的公共文件
*/
enum EnMsgType {
LOGIN_MSG = 1, // 登錄消息
LOGIN_MSG_ACK, // 登錄響應(yīng)消息
REG_MSG, // 注冊(cè)消息
REG_MSG_ACK, // 注冊(cè)響應(yīng)消息
ONE_CHAT_MSG, // 聊天消息
};
#endif // PUBLIC_H
在ChatService.hpp中添加一對(duì)一聊天業(yè)務(wù)函數(shù)聲明
public:
// 一對(duì)一聊天業(yè)務(wù)
void oneChat(const TcpConnectionPtr& conn,json& js,Timestamp time);
?在ChatService.cpp中實(shí)現(xiàn)一對(duì)一聊天業(yè)務(wù)函數(shù)
// 一對(duì)一聊天業(yè)務(wù)
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在線,轉(zhuǎn)發(fā)消息 服務(wù)器主動(dòng)推送消息給toid用戶
it->second->send(js.dump());
return;
}
}
// toid不在線,存儲(chǔ)離線消息
// ...(待續(xù)寫(xiě))
}
登錄heheda和Tom賬號(hào)?
?
在heheda賬號(hào)中發(fā)送:
{"msgid":5,"id":1,"from":"heheda","to":2,"msg":"hello,I am Heheda!"}
于是,Tom賬號(hào)收到
{"from":"heheda","id":1,"msg":"hello,I am Heheda!","msgid":5,"to":2}
在Tom賬號(hào)中發(fā)送:
{"msgid":5,"id":2,"from":"Tom","to":1,"msg":"hello,I am Tom!"}
于是,heheda賬號(hào)收到
{"from":"Tom","id":2,"msg":"hello,I am Tom!","msgid":5,"to":1}
文章來(lái)源:http://www.zghlxwxcb.cn/news/detail-828943.html
糾正:?文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-828943.html
把這句 int toid = js["to"].get<int>();
改成 int toid = js["toid"].get<int>();
到了這里,關(guān)于C++集群聊天服務(wù)器 網(wǎng)絡(luò)模塊+業(yè)務(wù)模塊+CMake構(gòu)建項(xiàng)目 筆記 (上)的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!