Muduo网络库:底层实质上为Linux的epoll + pthread线程池,且依赖boost库。 muduo的网络设计核心为一个线程一个事件循环,有一个main Reactor负载accept连接,然后把连接分发到某个sub Reactor(采用轮询的方式来选择sub Reactor),该连接的所用操作都在那个sub Reactor所处的线程中完成。多个连接可能被分派到多个线程中,以充分利用CPU,Reactor poll的大小是固定的,根据CPU的数目确定。如果有过多的耗费CPU I/O的计算任务,可以提交到创建的ThreadPool线程池中专门处理耗时的计算任务。
关于Reactor模型详解可参考此篇博客:高性能网络服务器基础
一般我们见到的高并发网络模型如下,有一个I/O线程(epoll),专门处理新用户链接;新用户链接完成后通过特定算法分发给不同的工作线程,线程数一般与CPU核数对等,工作线程专门处理已链接用户的读写事件。
muduo网络库实质为: epoll + 线程池,优点是能够将网络I/O的代码和业务代码分开。 而业务代码主要分为:用户的连接和断开、用户的可读写事件两类。至于什么时候发生这些事件,由网络库进行上报,如何监听这些事件,都是网络库所封装好的,我们就可以快速进行项目开发。
muduo给用户提供了两个主要的类:
1、TcpServer:用于编写服务器程序。
2、TcpClient:用于编写客户端程序。
我们是vscode远程在Linux上进行开发的,需要提前安装好muduo网络库,搭建vscode远程开发环境,如下:
1、moduo库安装:Linux平台下muduo网络库源码编译安装
2、vscode远程环境配置:windows+vscode搭建远程linux开发环境
以上环境安装好后,因为我们使用的为第三方库,代码编译完成后需要链接相应的.so库,可以通过以下两种不同的方式进行链接:
1、我们可以在终端上通过命令方式手动进行链接相应库文件,如下:
2、按F1调出vscode编译配置文件c_cpp_properties.json,依据自己需要修改配置。
也可以按ctrl + shift + b,点击齿轮。
进入task.json依据自己需要配置链接库。
muduo库服务器编程流程:
1、组合TcpServer对象;
2、创建EventLoop事件循环对象的指针,可以向loop上注册感兴趣的事件,相应事件发生loop会上报给我们;
3、明确TcpServer构造函数需要的参数,输出服务器对应类的构造函数;
TcpServer(EventLoop* loop, //事件循环
const InetAddress& listenAddr, //绑定IP地址 + 端口号
const string& nameArg, //TcpServer服务器名字
Option option = kNoReusePort); //tcp协议选项
4、在当前服务器类的构造函数中,注册处理连接断开的回调函数和处理读写事件的回调函数主要通过下面两个函数回调实现;
void setConnectionCallback(const ConnectionCallback& cb) //链接的创建与断开
{ connectionCallback_ = cb; }
void setMessageCallback(const MessageCallback& cb) //消息读写事件
{ messageCallback_ = cb; }
5、设置合适的服务器端线程数量,muduo会自动分配I/O线程与工作线程;
6、开启事件循环start();
muduo服务器端编程代码如下:
#include
#include
#include
#include
#include
using namespace std;
using namespace muduo;
using namespace muduo::net;
using namespace placeholders;
//基于muduo网络库开发服务器程序
class ChatServer
{
public:
//3、明确TcpServer构造函数需要的参数,输出服务器对应类的构造函数
ChatServer(EventLoop *loop, const InetAddress &listenAddr, const string &nameArg) //事件循环、IP+port、服务器名字
: _server(loop, listenAddr, nameArg), _loop(loop)
{
//4.1、注册用户连接的创建和断开事件的回调
_server.setConnectionCallback(std::bind(&ChatServer::onConnection, this, _1)); //利用绑定器绑定成员方法onConnection,保持参数与muduo库函数参数一致
//4.2、注册用户读写事件的回调
_server.setMessageCallback(std::bind(&ChatServer::onMessage, this, _1, _2, _3)); //利用绑定器绑定成员方法onMessage,保持参数与muduo库函数参数一致
//5、设置服务器端的线程数量
_server.setThreadNum(4);
}
//6.开启事件循环
void start()
{
_server.start();
}
private:
//4.1 专门处理用户的连接和断开
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(); //连接断开将socket资源释放
//或者调用_loop->quit()退出epoll;
}
}
//4.2 专门处理用户读写事件
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; //1、组合TcpServer对象
EventLoop *_loop; //2、创建EventLoop事件循环对象的指针
};
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以阻塞的方式等待新用户连接或处理已连接用户的读写事件
return 0;
}
我们调用了muduo第三方库,因此代码编译完成后还需要链接相应库文件,muduo_net必须写在muduo_base前面(muduo_base依赖了muduo_net库),命令如下:
执行程序./server,我们新打开一个终端充当客户端,发送相应数据可以看到服务器可以正常回显。
此时我们客户端进行退出(telnet退出为ctrl + ]),连接断开,可以看到服务器也能够正常退出。