在上一讲中介绍了Json库的相关知识,本次接着介绍muduo库的相关内容,这些知识在本项目中都会使用到。
1.muduo库简介
muduo库顶层就是epoll(IO复用技术) + Linux的pthread多线程,所以只能安装在Linux系统中。此外它依赖Boost库,相关的安装已经在我的博客中写出环境配置。
muduo 的线程模型为「one loop per thread + threadPool」模型。一个线程对应一个事件循环(EventLoop),也对应着一个 Reactor 模型。EventLoop 负责 IO 和定时器事件的分派。
其中有 mainReactor 和 subReactor。mainReactor通过Acceptor接收新连接,然后将新连接派发到subReactor上进行连接的维护。这样mainReactor可以只专注于监听新连接的到来,而从维护旧连接的业务中得到解放。同时多个Reactor可以并行运行在多核 CPU 中,增加服务效率。因此我们可以通过 muduo 快速完成网络模块。
如果不使用muduo库,我们想设计一个高并发的服务器,就需要使用epoll+线程池技术,手动创建epoll对象,然后在主线程注册监听连接的fd读事件,用来管理新客户的连接,如果有新连接进来就在线程池中选一个线程处理该客户的相关操作。因此主线程去管理新客户的连接,子线程去处理客户的读写事件和业务操作。
因此使用muduo库,可以让我们只关注业务模块(处理客户的读写相关事件),而不去关心网络IO模块,从而提高效率。
2.muduo库的使用
muduo库提供了两个主要的类:
- TcpServer:用于编写服务器程序
- TcpClient:用于编写客户端程序
基于muduo网络库开发服务器程序:
- 组合TcpServer对象
- 创建EventLoop事件循环对象的指针
- 明确TcpServer构造函数需要什么参数,输出ChatServer的构造函数
- 在当前服务器类的构造函数当中,注册处理连接的回调函数和处理读写时间的回调函数
- 设置合适的服务端线程数量,muduo库会自己分配I/O线程和worker线程
/*
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;
class ChatServer
{
public:
ChatServer(EventLoop *loop, // 事件循环
const InetAddress &listenAddr, // IP+Port
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 listenfd accept
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(); // close(fd)
// _loop->quit();
}
}
// 专门处理用户的读写事件
void onMessage(const TcpConnectionPtr &conn, // 连接
Buffer *buffer, // 缓冲区
Timestamp time) // 接收到数据的时间信息
{
string buf = buffer->retrieveAllAsString(); //转成string
cout << "recv data:" << buf << " time:" << time.toFormattedString() << endl;
conn->send(buf); //
}
TcpServer _server; // #1
EventLoop *_loop; // #2 epoll
};
int main()
{
EventLoop loop; // epoll
InetAddress addr("127.0.0.1", 6000);
ChatServer server(&loop, addr, "ChatServer");
server.start(); // listenfd epoll_ctl添加到epoll上
loop.loop(); // epoll_wait以阻塞方式等待新用户连接,已连接用户的读写事件等
}
3.编译和运行
muduo库依赖muduo_net.so、muduo_base.so、pthread.so三个动态链接库
3.1g++编译运行
g++ -o server muduo_server.cpp -lmuduo_net -lmuduo_base -lpthread
注意:-lmuduo_net写在前面,因为muduo_base依赖muduo_net
3.2vscode编译运行
在vscode里修改编译配置信息:ctrl + shift + b选择g++的哪个,在"args"中增加三个动态库链接:
在muduo_server.cpp里ctrl + shift + b点击编译代码,结果如下:
可以看到args都现显示在/usr/bin/g++ 后面,由vscode自动编译。
总结
我们使用muduo库,因为它封装了epoll和多线程,因此可以用它来处理网络IO模块,我们只需要关心业务模块,大大提高了工作效率。
业务模块只需要我们注册两个回调函数:setConnectionCallback和setMessageCallback,这样我们就能将网络IO模块和业务模块解耦。
最后编译链接后运行代码,代码正常无误。