今天来介绍集群聊天器项目中网络模块代码的核心模块——muduo网络库,一起来看看吧~
环境搭建C++项目——集群聊天服务器项目(一)项目介绍、环境搭建、Boost库安装、Muduo库安装、Linux与vscode配置-CSDN博客
Json第三方库C++项目——集群聊天服务器项目(二)Json第三方库-CSDN博客
一、muduo网络库介绍
muduo由陈硕大佬开发,是一个基于非阻塞IO和事件驱动的C++高并发TCP网络库
网络设计:reactors in threads - one loop per thread
one loop per thread指的是:
(1)一个线程只能有一个事件循环(EventLoop)
(2)一个文件描述符只能由一个线程进行读写,即一个TCP连接必须归属于某个EventLoop管理。
方案的特点是one loop per thread,有一个main reactor负载accept连接,然后把连接分发到某个sub reactor,该连接的所用操作都在那个sub reactor所处的线程中完成。
多个连接可能被分派到多个线程中,以充分利用CPU。
Reactor poll的大小是固定的,根据CPU的数目确定。
原理:一个Base IO thread负责accept新的连接,接收到新的连接以后,使用轮询的方式在reactor pool中找到合适的sub reactor将这个连接挂载上去,这个连接上的所有任务都在这个sub reactor上完成。
如果有过多的耗费CPU I/O的计算任务,可以提交到创建的ThreadPool线程池中专门处理耗时的计算任务。
将epoll和线程池封装起来,好处是能够把网络的I/O代码与业务代码区分开
二、muduo网络库主要的类
TcpServer:用于编写服务器程序的类
TcpClient:用于编写客户端程序的类
接下来使用muduo网络库开发一个基本的服务器程序
三、基于muduo网络库开发服务器程序
3.1 基本步骤
1.组合TcpServer对象
2.创建EventLoop事件循环对象的指针
3.明确TcpServer构造函数需要什么参数,输出ChatServer的构造函数
4.在当前服务器类的构造函数中,注册处理连接和读写事件的回调函数
5.设置合适的服务端线程数量,muduo会自动划分I/O线程和worker线程
3.2 代码
头文件
#include <muduo/net/TcpServer.h> #include <muduo/net/EventLoop.h> #include <iostream> #include <functional> #include <string> using namespace std; using namespace muduo; using namespace muduo::net; using namespace placeholders;
组合TcpServer对象
创建EventLoop事件循环对象的指针
明确TcpServer构造函数需要什么参数
class ChatServer { public: ChatServer(EventLoop* loop, //事件循环——Reactor反应堆 const InetAddress& listenAddr, //IP和端口 const string& nameArg) //服务器的名字 : _server(loop, listenAddr, nameArg), _loop(loop){} private: TcpServer _server; EventLoop *_loop; //epoll };
输出ChatServer的构造函数
构造函数负责给服务器注册用户连接与断开回调函数,注册读写事件回调函数,并设置线程数量,muduo网络库会自动分配线程用于主reactor和子reactor
ChatServer(EventLoop* loop, //事件循环——Reactor反应堆 const InetAddress& listenAddr, //IP和端口 const string& nameArg) //服务器的名字 : _server(loop, listenAddr, nameArg), _loop(loop) { //给服务器注册用户连接的创建和断开回调 _server.setConnectionCallback(std::bind(&ChatServer::OnConnection,this,_1)); //给服务器注册用户读写事件回调 _server.setMessageCallback(std::bind(&ChatServer::OnMessage,this,_1,_2,_3)); //设置服务器的线程数量 1 I/O线程,3个worker线程 _server.setThreadNum(4); }
开启时间循环函数
//开启事件循环 void start(){ _server.start(); }
连接与断开回调函数:显示上线和下线
//专门处理用户连接创建和断开 epoll void OnConnection(const TcpConnectionPtr&conn){ if(conn->connected()){ cout << conn->peerAddress().toIpPort() << "->" << conn->localAddress().toIpPort() << " state:online" << endl; } else{ cout << conn->peerAddress().toIpPort() << "->" << conn->localAddress().toIpPort() << " state:offline" << endl; conn->shutdown(); } }
读写事件回调函数:这里的功能是将客户端发来的信息发回去
//专门处理用户读写事件 void OnMessage(const TcpConnectionPtr&conn , //连接 Buffer*buffer, //缓冲区 Timestamp time) //接受到数据的时间信息 { string buf = buffer->retrieveAllAsString(); cout << "recv data : " << buf << " time:" << time.toString() << endl; conn->send(buf); }
main函数
int main(){ EventLoop loop; //epoll InetAddress addr("127.0.0.1",6000); ChatServer server(&loop,addr,"ChatServer"); server.start(); loop.loop(); //epoll_wait以阻塞方式等待新用户连接或读写事件等 return 0; }
全部代码:
/*muduo网络库给用户提供了两个主要的类 TcpServer:用于编写服务器程序的类 TcpClient:用于编写客户端程序的类 将epoll和线程池封装起来,好处是能够把网络的I/O代码与业务代码区分开 */ #include <muduo/net/TcpServer.h> #include <muduo/net/EventLoop.h> #include <iostream> #include <functional> #include <string> using namespace std; using namespace muduo; using namespace muduo::net; using namespace placeholders; /*基于muduo网络库开发服务器程序 1.组合TcpServer对象 2.创建EventLoop事件循环对象的指针 3.明确TcpServer构造函数需要什么参数,输出ChatServer的构造函数 4.在当前服务器类的构造函数中,注册处理连接和读写事件的回调函数 5.设置合适的服务端线程数量,muduo会自动划分I/O线程和worker线程 */ class ChatServer { public: ChatServer(EventLoop* loop, //事件循环——Reactor反应堆 const InetAddress& listenAddr, //IP和端口 const string& nameArg) //服务器的名字 : _server(loop, listenAddr, nameArg), _loop(loop) { //给服务器注册用户连接的创建和断开回调 _server.setConnectionCallback(std::bind(&ChatServer::OnConnection,this,_1)); //给服务器注册用户读写事件回调 _server.setMessageCallback(std::bind(&ChatServer::OnMessage,this,_1,_2,_3)); //设置服务器的线程数量 1 I/O线程,3个worker线程 _server.setThreadNum(4); } //开启事件循环 void start(){ _server.start(); } private: //专门处理用户连接创建和断开 epoll void OnConnection(const TcpConnectionPtr&conn){ if(conn->connected()){ cout << conn->peerAddress().toIpPort() << "->" << conn->localAddress().toIpPort() << " state:online" << endl; } else{ cout << conn->peerAddress().toIpPort() << "->" << conn->localAddress().toIpPort() << " state:offline" << endl; conn->shutdown(); } } //专门处理用户读写事件 void OnMessage(const TcpConnectionPtr&conn , //连接 Buffer*buffer, //缓冲区 Timestamp time) //接受到数据的时间信息 { string buf = buffer->retrieveAllAsString(); cout << "recv data : " << buf << " time:" << time.toString() << endl; conn->send(buf); } TcpServer _server; EventLoop *_loop; //epoll }; int main(){ EventLoop loop; //epoll InetAddress addr("127.0.0.1",6000); ChatServer server(&loop,addr,"ChatServer"); server.start(); loop.loop(); //epoll_wait以阻塞方式等待新用户连接或读写事件等 return 0; }
3.3 服务器程序执行
3.3.1 g++编译
g++ -o server muduo_server.cpp -lmuduo_net -lmuduo_base -lpthread
终端输入上述语句,其中,g++ -I头文件搜索路径 -L库文件搜素路径 -l库名称,执行server文件,结果如下:
可以看到客户端登录成功后,信息成功回显!
telnet 127.0.0.1 6000
3.3.2 CMake编译
可以查看Linux环境下是否有CMake,有muduo库其实就已经有CMake了,通过下面的命令查看版本号
cmake -version
在3.2文件的同级目录下,创建CMakeLists.txt文件,分别写出编译选项、需要编译的源文件列表、可执行文件存储的路径、生成可执行文件、以及链接的库文件
cmake_minimum_required(VERSION 3.0) project(main) # 配置编译选项 set(CMAKE_CXX_FLAGS ${CMAKE_CXX_FLAGS} -g) # 可调试可执行文件 #配置头文件搜索路径 # include_directories() #配置库文件搜索路径 # link_directories() # 设置需要编译的源文件列表 set(SRC_LIST ./muduo_server.cpp) # 设置可执行文件存储的路径 set(EXECUTABLE_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/bin) # 把.指定路径下的所有源文件放入SRC_LIST变量名中 # aux_source_directory(.SRC_LIST) # 表示生成可执行文件server,由SRC_LIST变量定义的源文件而来 add_executable(server ${SRC_LIST}) # 生成可执行文件 #表示server这个目标程序,需要链接muduo_net muduo_base pthread库文件 target_link_libraries(server muduo_net muduo_base pthread)
可以看到,这里将可执行文件放在了项目文件的bin文件夹下,执行server文件同样回显相同的结果
至此,muduo网络库的示例代码与实验完毕,期待后续项目的更新把~