概念
- ⼀个线程只能有⼀个事件循环(EventLoop), ⽤于响应计时器和IO事件
- ⼀个⽂件描述符只能由⼀个线程进⾏读写,换句话说就是⼀个TCP连接必须归属于某个EventLoop 管理
常见接口介绍
TcpServer类基础介绍
TcpServer 和 TcpClient:分别用于创建TCP服务器和客户端。TcpServer负责监听客户端连接并接受新连接,而TcpClient用于连接到服务器。
typedef std::shared_ptr<TcpConnection> TcpConnectionPtr;
typedef std::function<void(const TcpConnectionPtr&)> ConnectionCallback;
typedef std::function<void(const TcpConnectionPtr&,Buffer*,Timestamp)> MessageCallback;
class InetAddress : public muduo::copyable
{
public:InetAddress(StringArg ip, uint16_t port, bool ipv6 = false);
};
class TcpServer : noncopyable
{
public:enum Option{kNoReusePort, kReusePort, //重复使用端口,可以通过 setsockopt 实现};TcpServer(EventLoop* loop,const InetAddress& listenAddr,const string& nameArg,Option option = kNoReusePort);void setThreadNum(int numThreads); //设置线程数量,即子Reactor的数量void start();/// 当⼀个新连接建⽴成功的时候被调⽤void setConnectionCallback(const ConnectionCallback& cb){connectionCallback_ = cb;}/// 消息的业务处理回调函数---这是收到新连接消息的时候被调⽤的函数void setMessageCallback(const MessageCallback& cb){messageCallback_ = cb;}
};
TcpClient类基础介绍
class TcpClient : noncopyable
{
public:// TcpClient(EventLoop* loop);// TcpClient(EventLoop* loop, const string& host, uint16_t port);TcpClient(EventLoop* loop,const InetAddress& serverAddr,const string& nameArg);~TcpClient(); // force out-line dtor, for std::unique_ptr members.void connect();//连接服务器void disconnect();//关闭连接void stop();//获取客⼾端对应的通信连接Connection对象的接⼝,发起connect后,有可能还没有连接建⽴成功TcpConnectionPtr connection() const{MutexLockGuard lock(mutex_);return connection_;}/// 连接服务器成功时的回调函数void setConnectionCallback(ConnectionCallback cb){connectionCallback_ = std::move(cb);}/// 收到服务器发送的消息时的回调函数void setMessageCallback(MessageCallback cb){messageCallback_ = std::move(cb);}
private:EventLoop* loop_;ConnectionCallback connectionCallback_;MessageCallback messageCallback_;WriteCompleteCallback writeCompleteCallback_;TcpConnectionPtr connection_ GUARDED_BY(mutex_);
};
/*
需要注意的是,因为muduo库不管是服务端还是客⼾端都是异步操作,
对于客⼾端来说如果我们在连接还没有完全建⽴成功的时候发送数据,这是不被允许的。
因此我们可以使⽤内置的CountDownLatch类进⾏同步控制
*/
class CountDownLatch : noncopyable
{
public:explicit CountDownLatch(int count);void wait() {MutexLockGuard lock(mutex_);while (count_ > 0){condition_.wait();}}void countDown() {MutexLockGuard lock(mutex_);--count_;if (count_ == 0){condition_.notifyAll();}}int getCount() const;
private:mutable MutexLock mutex_;Condition condition_ GUARDED_BY(mutex_);int count_ GUARDED_BY(mutex_);
};
EventLoop类基础介绍
EventLoop:是整个Muduo库的核心,负责事件的监听、分发和处理。每个线程都有一个独立的EventLoop,遵循“one loop per thread”的原则,即一个事件循环对应一个线程。它通过轮询的方式监听事件,并在事件发生时调用相应的处理函数。
class EventLoop : noncopyable
{
public:/// Loops forever./// Must be called in the same thread as creation of the object.void loop();/// Quits loop./// This is not 100% thread safe, if you call through a raw pointer,/// better to call through shared_ptr<EventLoop> for 100% safety.void quit();TimerId runAt(Timestamp time, TimerCallback cb);/// Runs callback after @c delay seconds./// Safe to call from other threads.TimerId runAfter(double delay, TimerCallback cb);/// Runs callback every @c interval seconds./// Safe to call from other threads.TimerId runEvery(double interval, TimerCallback cb);/// Cancels the timer./// Safe to call from other threads.void cancel(TimerId timerId);
private:std::atomic<bool> quit_;/// Poller是EventLoop的底层实现,负责具体的I/O多路复用操作,如epoll_wait等,用于检测哪些Channel上有事件发生。std::unique_ptr<Poller> poller_;mutable MutexLock mutex_;std::vector<Functor> pendingFunctors_ GUARDED_BY(mutex_);
};
TcpConnection类基础介绍
TcpConnection:表示一个TCP连接,用于在客户端和服务器之间传输数据。它提供了发送数据、关闭连接等方法。
class TcpConnection : noncopyable,public std::enable_shared_from_this<TcpConnection>
{
public:/// Constructs a TcpConnection with a connected sockfd/// User should not create this object.TcpConnection(EventLoop* loop,const string& name,int sockfd,const InetAddress& localAddr,const InetAddress& peerAddr);bool connected() const { return state_ == kConnected; }bool disconnected() const { return state_ == kDisconnected; }void send(string&& message); // C++11void send(const void* message, int len);void send(const StringPiece& message);// void send(Buffer&& message); // C++11void send(Buffer* message); // this one will swap datavoid shutdown(); // NOT thread safe, no simultaneous callingvoid setContext(const boost::any& context){context_ = context;}const boost::any& getContext() const{return context_;}boost::any* getMutableContext(){return &context_;}void setConnectionCallback(const ConnectionCallback& cb){connectionCallback_ = cb;}void setMessageCallback(const MessageCallback& cb){messageCallback_ = cb;}
private:enum StateE { kDisconnected, kConnecting, kConnected, kDisconnecting };EventLoop* loop_;ConnectionCallback connectionCallback_;MessageCallback messageCallback_;WriteCompleteCallback writeCompleteCallback_;boost::any context_;
};
Buffer类基础介绍
Buffer:用于数据的缓冲处理,提高数据读写的效率。
class Buffer : public muduo::copyable
{
public:static const size_t kCheapPrepend = 8;static const size_t kInitialSize = 1024;explicit Buffer(size_t initialSize = kInitialSize): buffer_(kCheapPrepend + initialSize),readerIndex_(kCheapPrepend),writerIndex_(kCheapPrepend);void swap(Buffer& rhs)size_t readableBytes() constsize_t writableBytes() constconst char* peek() constconst char* findEOL() constconst char* findEOL(const char* start) constvoid retrieve(size_t len)void retrieveInt64()void retrieveInt32()void retrieveInt16()void retrieveInt8()string retrieveAllAsString()string retrieveAsString(size_t len)void append(const StringPiece& str)void append(const char* /*restrict*/ data, size_t len)void append(const void* /*restrict*/ data, size_t len)char* beginWrite()const char* beginWrite() constvoid hasWritten(size_t len)void appendInt64(int64_t x)void appendInt32(int32_t x)void appendInt16(int16_t x)void appendInt8(int8_t x)int64_t readInt64()int32_t readInt32()int16_t readInt16()int8_t readInt8()int64_t peekInt64() constint32_t peekInt32() constint16_t peekInt16() constint8_t peekInt8() constvoid prependInt64(int64_t x)void prependInt32(int32_t x)void prependInt16(int16_t x)void prependInt8(int8_t x)void prepend(const void* /*restrict*/ data, size_t len)
private:std::vector<char> buffer_;size_t readerIndex_;size_t writerIndex_;static const char kCRLF[];
};
工作流程
初始化
在使用Muduo库构建网络应用程序时,首先需要创建一个EventLoop对象,它作为程序的核心,负责管理事件循环和处理各种事件。接着,根据是创建服务器还是客户端,分别初始化TcpServer或TcpClient对象,并设置相应的回调函数,如连接回调和消息回调,以便在连接建立、断开或收发数据时执行特定的业务逻辑。
事件循环
EventLoop的 loop() 方法启动事件循环,这是程序的核心执行部分。在事件循环中,程序通过I/O多路复用技术(如epoll_wait)阻塞等待I/O事件的发生。当有事件就绪时,Poller对象检测到这些事件,并将它们返回给EventLoop。EventLoop随后根据事件类型,分发给相应的Channel对象进行处理。
网络通信处理
-
服务器端:TcpServer对象在启动后开始监听指定端口的客户端连接。当有客户端连接请求到达时,程序接受该连接并创建一个TcpConnection对象来管理这个连接。TcpConnection对象会触发连接回调,通知用户有新的客户端连接建立。之后,当客户端发送数据时,程序会触发消息回调,用户可以在回调函数中实现具体的业务逻辑来处理收到的数据,并通过TcpConnection的 send() 方法向客户端发送响应数据。
-
客户端:TcpClient对象连接到指定的服务器地址后,同样会创建一个TcpConnection对象来管理与服务器的连接,并触发连接回调。之后,客户端可以通过TcpConnection的 send() 方法向服务器发送数据,同时也能接收服务器发送的数据,并在消息回调中进行处理。
多线程支持
Muduo库支持多线程并发处理,采用“one loop per thread”的线程模型。每个线程都有自己的EventLoop,负责处理该线程上的所有事件。在多线程环境下,主线程(main reactor)负责监听新的连接,当有新的客户端连接时,会根据一定的策略(如轮询)选择一个子线程(sub reactor)来处理该连接的后续读写事件。这样,多个线程可以同时处理不同的连接,充分利用多核处理器的优势,提高程序的并发处理能力和性能。
工作流程总结
-
初始化:创建EventLoop,根据需要创建TcpServer或TcpClient,设置回调函数。
-
事件循环:调用EventLoop的 loop() 方法,进入事件循环,等待事件发生。
-
事件检测与分发:通过I/O多路复用检测事件,将事件分发给对应的Channel。
-
网络事件处理:
-
连接建立/断开:触发连接回调,处理连接相关逻辑。
-
数据收发:触发消息回调,处理数据接收和发送逻辑。
-
-
多线程协作(如适用):在多线程环境下,主线程和子线程协同处理不同的连接事件。
-
程序退出:当需要停止服务器或客户端时,调用相应的停止方法,退出事件循环,清理资源。
使用案例
server.hpp
#include <muduo/net/TcpServer.h>
#include <muduo/net/EventLoop.h>
#include <muduo/net/TcpConnection.h>
#include <muduo/net/Buffer.h>
#include <iostream>
#include <string>
#include <unordered_map>class DictServer
{
public:DictServer(int port): _server(&_baseloop, muduo::net::InetAddress("0.0.0.0", port),"DictServer", muduo::net::TcpServer::kReusePort){// 设置连接事件回调函数_server.setConnectionCallback(std::bind(&DictServer::OnConnection,this,std::placeholders::_1));_server.setMessageCallback(std::bind(&DictServer::OnMessage,this, std::placeholders::_1,std::placeholders::_2,std::placeholders::_3));}void Start(){_server.start(); // 开始监听_baseloop.loop(); // 开始循环事件监控}private:void OnConnection(const muduo::net::TcpConnectionPtr &conn){if (conn->connected())std::cout << "连接建立!" << std::endl;elsestd::cout << "连接断开!" << std::endl;}void OnMessage(const muduo::net::TcpConnectionPtr &conn, muduo::net::Buffer *buf, muduo::Timestamp){static std::unordered_map<std::string, std::string> dict_map = {{"apple", "苹果"},{"pear", "梨"},{"hello", "你好"}};std::string msg = buf->retrieveAllAsString();std::string res;auto it = dict_map.find(msg);if (it == dict_map.end())res = "未知单词!";elseres = it->second;conn->send(res);}private:muduo::net::EventLoop _baseloop;muduo::net::TcpServer _server;
};
smain.cc
#include "server.hpp"
#include <memory>
#include <cstring>
int main(int argc, char *argv[])
{if (argc != 2){std::cout << "usage error,please use ./server port" << std::endl;return 0;}int port = std::stoi(argv[1]);std::unique_ptr<DictServer> server = std::make_unique<DictServer>(port);server->Start();return 0;
}
client.hpp
#include <muduo/net/TcpClient.h>
#include <muduo/net/EventLoop.h>
#include <muduo/net/EventLoopThread.h>
#include <muduo/net/TcpConnection.h>
#include <muduo/net/Buffer.h>
#include <muduo/base/CountDownLatch.h>
#include <iostream>
#include <string>class DictClient
{public:DictClient(const std::string& sip,int sport):_baseloop(_loopthread.startLoop()),//one loop per thread_downlatch(1),//初始化计数器为1,为0时会唤醒_client(_baseloop,muduo::net::InetAddress(sip,sport),"DictClient"){//设置连接事件的回调_client.setConnectionCallback(std::bind(&DictClient::OnConnection,this,std::placeholders::_1));//设置连接消息的回调_client.setMessageCallback(std::bind(&DictClient::OnMessage,this,std::placeholders::_1,std::placeholders::_2,std::placeholders::_3));//连接服务器_client.connect();//保证连接成功后才能发送消息_downlatch.wait();}bool send(const std::string& msg){if(_conn->connected()==false){std::cout<<"连接已断开,发送数据失败!"<<std::endl;return false;}_conn->send(msg);return true;}private:void OnConnection(const muduo::net::TcpConnectionPtr& conn){if(conn->connected()){std::cout<<"连接建立!"<<std::endl;_downlatch.countDown();//计数减一,为0唤醒阻塞_conn=conn;}else{std::cout<<"连接断开!"<<std::endl;_conn.reset();}}void OnMessage(const muduo::net::TcpConnectionPtr& conn,muduo::net::Buffer* buf,muduo::Timestamp){std::string res=buf->retrieveAllAsString();std::cout<<res<<std::endl;}private:muduo::net::TcpConnectionPtr _conn;muduo::CountDownLatch _downlatch;muduo::net::EventLoopThread _loopthread;muduo::net::EventLoop* _baseloop;muduo::net::TcpClient _client;
};
cmain.cc
#include "client.hpp"
#include <memory>
int main(int argc, char *argv[])
{if (argc != 3){std::cout << "usage error" << std::endl;return 0;}std::string sip = argv[1];int sport = std::stoi(argv[2]);std::unique_ptr<DictClient> client = std::make_unique<DictClient>(sip.c_str(), sport);while (1){std::string msg;std::cin >> msg;client->send(msg);}return 0;
}
makefile
CFLAG= -I ../../build/release-install-cpp11/include/
LFLAG= -L ../../build/release-install-cpp11/lib -l muduo_net -l muduo_base -pthread
.PHONY:all
all:server client
server:smain.ccg++ -o $@ $^ -std=c++17 $(CFLAG) $(LFLAG)
client:cmain.ccg++ -o $@ $^ -std=c++17 $(CFLAG) $(LFLAG)
.PHONY:clean
clean:rm -rf server client