Muduo 库快速上手
- [一.Muduo 库简介](#一.Muduo 库简介)
- 二.五个常用类介绍
- [三.结合 Protobuf 定制的应用层协议](#三.结合 Protobuf 定制的应用层协议)
一.Muduo 库简介
一句话概括,Muduo 库是基于非阻塞 IO 和事件驱动的 C++ 高并发 TCP 网络编程库。使用主从 Reactor 模型,使用的线程模型是是 one thread one loop
- Reactor:基于事件触发的模型(基于 epoll 进行 IO 事件监控)
- 主从 Reactor:将 IO 事件监控进一步划分,主 Reactor 线程只监控新建连接的事件,一旦连接事件触发,就会读取连接,然后执行回调函数;从 Reactor 线程针对新建连接进行 IO 事件监控,一旦 IO 事件触发,就会执行 IO 操作以及业务处理(我们设置的回调函数)。
- one thread one loop:一个线程只能有一个事件循环(EventLoop 对象),用于响应计时器和和 IO 事件
- 一个文件描述符只能由一个线程进行读写,换句话说一个 TCP 连接必须由某个 EventLoop 对象管理
二.五个常用类介绍
muduo::net::TcpServer:通常由服务端主线程调用其 start 接口,监听端口。通常需要配合 EventLoop 对象使用,循环地监控新建连接事件
cpp
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,
};
TcpServer(EventLoop *loop,
const InetAddress &listenAddr,
const string &nameArg,
Option option = kNoReusePort);
//设置从线程个数
void setThreadNum(int numThreads);
void start();
/// 当⼀个新连接建⽴成功的时候被调⽤
void setConnectionCallback(const ConnectionCallback &cb)
{
connectionCallback_ = cb;
}
/// 消息的业务处理回调函数---这是收到新连接消息的时候被调⽤的函数
void setMessageCallback(const MessageCallback &cb)
{
messageCallback_ = cb;
}
};
muduo::net::EventLoop:用于循环地监控事件,需要调用 loop 接口开始监控,注意这是一个阻塞式接口
cpp
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_;
std::unique_ptr<Poller> poller_;
mutable MutexLock mutex_;
std::vector<Functor> pendingFunctors_ GUARDED_BY(mutex_);
};
muduo::net::TcpConnection:代表一个连接,当连接事件触发时,会创建 TcpConnection 对象。我们在从 Reactor 线程的回调函数中通常会用到它的 send 方法
cpp
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++11
void send(const void *message, int len);
void send(const StringPiece &message);
// void send(Buffer&& message); // C++11
void send(Buffer *message); // this one will swap data
void shutdown(); // NOT thread safe, no simultaneous calling
void 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_;
};
muduo::net::Buffer:用户层缓冲区,当普通 IO 事件触发时,会将接受缓冲区数据读取到 Buffer 对象中,我们在从 Reactor 线程的回调函数中通常会使用 retrieve 接口提取数据
cpp
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() const
size_t writableBytes() const
const char *peek() const
const char *findEOL() const
const char *findEOL(const char *start) const
void 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() const
void 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() const
int32_t peekInt32() const
int16_t peekInt16() const
int8_t peekInt8() const
void 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::net::Client:调用其内部的 connect 与服务端建立连接,但 connect 是非阻塞接口,我们能 send 数据的前提是获取到一个有效的 TcpConnection,所以通常需要配合 CountDownLatch 来同步
cpp
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_);
};
三.结合 Protobuf 定制的应用层协议
Client.hpp:向外提供翻译和加法服务,向服务端发送这两种请求
cpp
#pragma once
#include "muduo/protobuf/codec.h"
#include "muduo/protobuf/dispatcher.h"
#include "muduo/base/Logging.h"
#include "muduo/base/Mutex.h"
#include "muduo/net/EventLoop.h"
#include "muduo/net/TcpClient.h"
#include "muduo/net/EventLoopThread.h"
#include "muduo/base/CountDownLatch.h"
#include "request.pb.h"
#include <functional>
#include <iostream>
using std::cin;
using std::cout;
using std::endl;
class Client
{
public:
typedef std::shared_ptr<ns_proto::TranslateResponse> translateResponsePtr;
typedef std::shared_ptr<ns_proto::AddResponse> AddResponsePtr;
typedef std::shared_ptr<google::protobuf::Message> MessagePtr;
private:
muduo::net::EventLoopThread _loopThread;
muduo::CountDownLatch _latch;
muduo::net::TcpClient _client;
muduo::net::TcpConnectionPtr _connPtr;
ProtobufDispatcher _distpatcher;
ProtobufCodec _codec;
public:
Client(const std::string &serverIp, int serverPort)
: _loopThread(),
_latch(1),
_client(_loopThread.startLoop(), muduo::net::InetAddress(serverIp, serverPort), "client"),
_connPtr(),
_distpatcher(std::bind(&Client::onUnknownMessage, this, std::placeholders::_1,
std::placeholders::_2, std::placeholders::_3)),
_codec(std::bind(&ProtobufDispatcher::onProtobufMessage, &_distpatcher, std::placeholders::_1,
std::placeholders::_2, std::placeholders::_3))
{
_client.setConnectionCallback(std::bind(&Client::onConnection, this, std::placeholders::_1));
_client.setMessageCallback(std::bind(&ProtobufCodec::onMessage, &_codec, std::placeholders::_1,
std::placeholders::_2, std::placeholders::_3));
// 给响应分发器注册业务处理函数
_distpatcher.registerMessageCallback<ns_proto::TranslateResponse>(std::bind(&Client::onTranslate,
this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3));
_distpatcher.registerMessageCallback<ns_proto::AddResponse>(std::bind(&Client::onAdd,
this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3));
}
void connect()
{
_client.connect(); //非阻塞
_latch.wait();
}
void translate(const std::string& word)
{
ns_proto::TranslateRequest req;
req.set_msg(word);
send(_connPtr, req); //多态
}
void add(int num1, int num2)
{
ns_proto::AddRequest req;
req.set_num1(num1);
req.set_num2(num2);
send(_connPtr, req); //多态
}
private:
// 给_client设置的回调
void onConnection(muduo::net::TcpConnectionPtr connPtr)
{
if (connPtr->connected())
{
cout << "连接成功" << endl;
_connPtr = connPtr;
_latch.countDown();
}
else
{
cout << "连接关闭" << endl;
_connPtr.reset();
}
}
// 业务处理函数
//分发器在回调函数表中找不到该请求时的处理方法
void onUnknownMessage(muduo::net::TcpConnectionPtr connPtr, MessagePtr msgPtr, muduo::Timestamp time)
{
cout << "未知消息:" << msgPtr->GetTypeName() << endl;
connPtr->shutdown();
}
void onTranslate(muduo::net::TcpConnectionPtr connPtr, translateResponsePtr respPtr, muduo::Timestamp time)
{
cout << respPtr->msg() << endl;
}
void onAdd(muduo::net::TcpConnectionPtr connPtr, AddResponsePtr respPtr, muduo::Timestamp time)
{
cout << respPtr->result() << endl;
}
//成员方法的子方法
void send(muduo::net::TcpConnectionPtr connPtr, const google::protobuf::Message& message)
{
_codec.send(connPtr, message);
}
};
Server.hpp:处理和响应客户端的翻译和加法请求
cpp
#pragma once
#include "muduo/protobuf/codec.h"
#include "muduo/protobuf/dispatcher.h"
#include "muduo/base/Logging.h"
#include "muduo/base/Mutex.h"
#include "muduo/net/EventLoop.h"
#include "muduo/net/TcpServer.h"
#include "request.pb.h"
#include <functional>
#include <stdio.h>
#include <unistd.h>
using std::cin;
using std::cout;
using std::endl;
class Server
{
public:
typedef std::shared_ptr<ns_proto::TranslateRequest> TranslateRequestPtr;
typedef std::shared_ptr<ns_proto::TranslateResponse> TranslateResponsePtr;
typedef std::shared_ptr<ns_proto::AddRequest> AddRequestPtr;
typedef std::shared_ptr<ns_proto::AddResponse> AddResponsePtr;
typedef std::shared_ptr<google::protobuf::Message> MessagePtr;
private:
muduo::net::EventLoop _baseLoop;
muduo::net::TcpServer _server;
ProtobufDispatcher _dispatcher;
ProtobufCodec _codec;
public:
Server(int serverPort)
: _baseLoop(),
_server(&_baseLoop, muduo::net::InetAddress("0.0.0.0", serverPort), "TcpServer", muduo::net::TcpServer::kReusePort),
_dispatcher(std::bind(&Server::onUnknownMessage, this, std::placeholders::_1,
std::placeholders::_2, std::placeholders::_3)),
_codec(std::bind(&ProtobufDispatcher::onProtobufMessage, &_dispatcher, std::placeholders::_1,
std::placeholders::_2, std::placeholders::_3))
{
//给_server注册两个回调函数
_server.setConnectionCallback(std::bind(&Server::onConnection, this, std::placeholders::_1));
_server.setMessageCallback(std::bind(&ProtobufCodec::onMessage, &_codec, std::placeholders::_1,
std::placeholders::_2, std::placeholders::_3));
//给分发器注册业务处理函数
_dispatcher.registerMessageCallback<ns_proto::TranslateRequest>(std::bind(&Server::onTranslate, this, std::placeholders::_1,
std::placeholders::_2, std::placeholders::_3));
_dispatcher.registerMessageCallback<ns_proto::AddRequest>(std::bind(&Server::onAdd, this, std::placeholders::_1,
std::placeholders::_2, std::placeholders::_3));
}
void start()
{
//开启监听状态
_server.start();
//开始循环监控事件
_baseLoop.loop();
}
private:
// 给TcpServer设置的回调函数
void onConnection(muduo::net::TcpConnectionPtr connPtr)
{
if (connPtr->connected())
{
cout << "连接成功" << endl;
}
else
{
cout << "连接关闭" << endl;
connPtr.reset();
}
}
// 业务处理函数
void onUnknownMessage(muduo::net::TcpConnectionPtr connPtr, MessagePtr msgPtr, muduo::Timestamp time)
{
cout << "未知消息" << endl;
connPtr->shutdown();
}
void onTranslate(muduo::net::TcpConnectionPtr connPtr, TranslateRequestPtr reqPtr, muduo::Timestamp time)
{
std::string word = reqPtr->msg();
std::string desc;
translate(word, &desc);
//构建响应
ns_proto::TranslateResponse resp;
resp.set_msg(desc);
//让协议处理器帮我们打包发送
_codec.send(connPtr, resp);
}
void onAdd(muduo::net::TcpConnectionPtr connPtr, AddRequestPtr reqPtr, muduo::Timestamp time)
{
int num1 = reqPtr->num1();
int num2 = reqPtr->num2();
ns_proto::AddResponse resp;
resp.set_result(num1 + num2);
//让协议处理器帮我们发送
_codec.send(connPtr, resp);
}
bool translate(const std::string &word, std::string *descPtr)
{
static std::unordered_map<std::string, std::string> dict = {{"hello", "你好"}, {"你好", "hello"}};
if (dict.count(word) == 0)
{
*descPtr = "该单词不存在";
return false;
}
else
{
*descPtr = dict[word];
return true;
}
}
};