使用 C++ 和 muduo 網絡庫來實現一個簡單的聊天服務器和客戶端。
服務器端:
class chatServer
{
public:// 初始化TcpServerchatServer(muduo::net::EventLoop *loop,const muduo::net::InetAddress &listenAddr): _server(loop, listenAddr, "chatServer"){// 通過綁定器設置回調函數_server.setConnectionCallback(bind(&chatServer::onConnection, this, _1));_server.setMessageCallback(bind(&chatServer::onMessage, this, _1, _2, _3));// 設置EventLoop的線程個數_server.setThreadNum(10);}// 啟動ChatServer服務void start(){_server.start();}private:// TcpServer綁定的回調函數,當有新連接或連接中斷時調用void onConnection(const muduo::net::TcpConnectionPtr &con);// TcpServer綁定的回調函數,當有新數據時調用void onMessage(const muduo::net::TcpConnectionPtr &con,muduo::net::Buffer *buf,muduo::Timestamp time);private:muduo::net::TcpServer _server;
};
首先:構造函數中
? ? ? ? 初始化TcpServer對象_server,傳入EventLoop,監聽地址和服務器名稱
? ? ? ? 設置連接和消息的回調函數,這些函數將在連接建立或者斷開時調用,以及接受到新消息時調用
? ? ? ? 設置Eventloop的線程數為10 使用10個線程來處理網絡事件
而start()方法 是為了啟用TcpServer,開始監聽和接受連接
其中私有成員:
? ? ? ? 兩個函數 分別是為了處理新連接或者連接斷開的回調函數。一個是為了處理接收到的新消息的回調函數。
? ? ? ? 私有成員的變量:_server:是TcpServer對象,是用來管理網絡連接和事件。
客戶端:
class chatClient
{
public:chatClient(muduo::net::EventLoop *loop,const muduo::net::InetAddress &addr): _client(loop, addr, "chatClient"){// 設置客戶端TCP連接回調接口_client.setConnectionCallback(bind(&chatClient::onConnection, this, _1));// 設置客戶端接收數據回調接口_client.setMessageCallback(bind(&chatClient::onMessage, this, _1, _2, _3));}// 連接服務器void connect(){_client.connect();}private:// TcpClient綁定回調函數,當連接或者斷開服務器時調用void onConnection(const muduo::net::TcpConnectionPtr &con);// TcpClient綁定回調函數,當有數據接收時調用void onMessage(const muduo::net::TcpConnectionPtr &con,muduo::net::Buffer *buf,muduo::Timestamp time);muduo::net::TcpClient _client;
};
構造函數:
????????初始化 TcpClient
對象 _client
,傳入 EventLoop
、服務器地址和客戶端名稱。
????????設置連接和消息的回調函數,這些函數將在連接建立或斷開時調用,以及接收到新消息時調用。
? ? ? ? connect()的作用是啟動客戶端連接到服務器。
私有成員:
? ?onConnection()
:處理連接或斷開服務器的回調函數。
? ?onMessage()
:處理接收到的新消息的回調函數。
_client
:TcpClient
對象,用于管理與服務器的連接和數據傳輸。
?
網絡服務器編程常用模型
-
方案1:accept + read/write
這不是并發服務器。每個連接都需要一個獨立的線程或進程來處理,不適合高并發場景。 -
方案2:accept + fork - process-pre-connection
適合并發連接數不大,計算任務工作量大于fork的開銷的場景。服務器接受連接后,通過fork
創建子進程來處理每個連接。 -
方案3:accept + thread - thread-pre-connection
比方案2的開銷小了一點,但是并發造成線程堆積過多。服務器接受連接后,為每個連接創建一個線程來處理。 -
方案4:muduo的網絡設計:reactors in threads - one loop per thread
一個main reactor
負責accept
連接,然后把連接分發到某個sub reactor
(采用round-robin的方式來選擇sub reactor),該連接的所有操作都在那個sub reactor所在的線程中完成。多個連接可能被分派到多個線程中,以充分利用CPU。Reactor poll
的大小是固定的,根據CPU的數目確定。 -
方案5:reactors in process - one loop pre process
類似于Nginx服務器的網絡模塊設計,基于進程設計,采用多個Reactors
充當I/O進程和工作進程,通過一把accept
鎖,完美解決多個Reactors
的“驚群現象”。
?
muduo中的reactor模型
? ? ? ? Reactor模型是一種事件處理模式,用于處理并發的服務請求。它可以處理一個或多個輸入源(one or more inputs),并通過服務處理器(Service Handler)將輸入事件(Event)同步分發給相應的請求處理器(Request Handler)進行處理。
-
muduo網絡庫的模型
-
main Reactor
負責監聽新的連接請求,并將新用戶連接分配給工作線程(工作線程中包含epoll
)。 -
工作線程使用
epoll
來處理已連接用戶的讀寫事件。 -
對于耗時的I/O操作(如傳送文件、音視頻),可以單獨開辟線程來處理,以避免阻塞主線程。
-
-
使用多個Reactors
-
mainReactor
負責接受新的客戶端連接,并將這些連接分發給subReactor
。 -
每個
subReactor
處理一部分客戶端的讀寫、解碼、計算、編碼和發送操作。
-
void setConnectionCallback(const ConnectionCallback& cb)
{connectionCallback_ = cb;
}/// Set message callback.
/// Not thread safe.
void setMessageCallback(const MessageCallback& cb)
{messageCallback_ = cb;
}
?
class ChatServer
{
public:ChatServer(EventLoop *loop, // 事件循環const InetAddress &listenAddr, // IP+Portconst string &nameArg) // 服務器的名字: _server(loop, listenAddr, nameArg), _loop(loop){// 給服務器注冊用戶連接的創建和斷開回調_server.setConnectionCallback(std::bind(&ChatServer::onConnection, this, _1));// 給服務器注冊用戶讀寫事件回調}private:// 專門處理用戶的連接創建和斷開 epoll listenfd acceptvoid onConnection(const TcpConnectionPtr&){}TcpServer _server; // #1EventLoop *_loop; // #2 epoll
};
?
-
構造函數:
-
ChatServer
類接受一個EventLoop
指針、一個InetAddress
對象(包含IP地址和端口號)和一個服務器名稱。 -
使用這些參數初始化
TcpServer
對象_server
和一個EventLoop
指針_loop
。并注冊連接回調函數onConnection
。 -
注冊連接回調函數
onConnection
,當有新連接或連接斷開時調用。
-
-
私有成員變量:
-
_server
:TcpServer
對象,用于管理網絡連接和事件。 -
_loop
:EventLoop
指針,用于處理事件循環。
-
?