Muduo
muduo库是基于主从reactor模型的高性能服务器(高并发服务器)框架
reactor模型:基于事件触发的模型(基于epoll进行IP事件监控)
主从reactor模型:将IO事件监控有进行进一步的层次划分
主reactor:针对新建链接 事件进行监控
从reactor:针对新建连接后的 IO 事件监控(进行IP操作和业务员处理)
主从reactor必然是一个多执行流的并发模型 one thread one loop(一个事件监控,占据一个线程,进行事件监控)
具体如下:
muduo服务端常用的类:
TcpServer类
typedef std::shared_ptr<TcpConnection> TcpConnectionPtr;连接函数的格式:
typedef std::function<void (const TcpConnectionPtr&)> ConnectionCallback;typedef std::function<void (const TcpConnectionPtr&,Buffer*,Timestamp)> MessageCallback;
//参数:
TcpConnectionPtr:哪一个连接来了-- 这是一个类,再下面会有写 --
Buffer:内部要处理的数据-- 同样是一个类,再下面会有写 --
Timestamp:时间(戳)class InetAddress : public muduo::copyable
{
public:InetAddress(StringArg ip, uint16_t port, bool ipv6 = false);
};class TcpServer : noncopyable
{
public:enum Option{kNoReusePort,kReusePort,};
1. TcpServer构造:TcpServer(EventLoop* loop,const InetAddress& listenAddr,const string& nameArg,Option option = kNoReusePort);
//参数:
loop:事件循环监控类(往下翻紧接着就是这个类)
listenAddr:地址信息
nameArg:Tcp名称
option是否开启端口复用2. 设置从属reactor数量:void setThreadNum(int numThreads);
3. 开启监听:void start();4. 当⼀个新连接建⽴成功的时候被调⽤:
//setConnectionCallback设置成回调函数void setConnectionCallback(const ConnectionCallback& cb){ connectionCallback_ = cb; }5. 消息的业务处理回调函数---这是收到新连接消息的时候被调⽤的函数void setMessageCallback(const MessageCallback& cb){ messageCallback_ = cb; }
}
事件循环muduo::net::EventLoop类:
TcpServer服务器构造时的第一个参数需要传递的参数
主要任务:对TcpServer中的套接字进行事件监控(连接事件、IO事件)
class EventLoop : noncopyable
{
public:/// Loops forever./// Must be called in the same thread as creation of the object.//主要要用的:void loop();//开启事件监控void quit();//...
};
muduo::net::TcpConnection类基础介绍
用于存储已经到来的连接,管理这些连接。
class TcpConnection : noncopyable,
public std::enable_shared_from_this<TcpConnection>
{
public:
//...3. 连接状态:bool connected() const { return state_ == kConnected; }bool disconnected() const { return state_ == kDisconnected; }1. send 发送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 data2. 关闭连接void shutdown(); // NOT thread safe, no simultaneous calling//...private:enum StateE { kDisconnected, kConnecting, kConnected, kDisconnecting };EventLoop* loop_;ConnectionCallback connectionCallback_;MessageCallback messageCallback_;WriteCompleteCallback writeCompleteCallback_;boost::any context_;
};
muduo::net::Buffer类基础介绍
数据
class Buffer : public muduo::copyable
{
public:
//...
1. 可取数据多少:size_t readableBytes() constsize_t writableBytes() const//...
2. 获取缓冲区中的数据:string retrieveAllAsString()string retrieveAsString(size_t len)
//...private:std::vector<char> buffer_;size_t readerIndex_;size_t writerIndex_;static const char kCRLF[];
};
注意上述类中的函数都是进行了删减(不完整的)方便观感
总结上述类之间的相互关系:
我们从下述图来看:
- 首先理解:它是一个主从Reactor模型,它有一个主reactor,它的作用就是用来接收clinet发送来的连接请求,然后再分配给从属reactor进行事件触发监控,监控其设置的IO事件是否就绪。
- 主reactor,本质就是一个TcpServer服务器,也就是先将该Tcp服务器,加入reactor中,通过这台服务器的读事件就绪来处理新链接的到来。
附:
Tcp服务器先放进reactor(epoll)中进行是否有新链接的事件监听
具体原理:
Tcp服务器的读就绪就是可以理解为是否有Client发来SYN连接请求报文,这样就能进行监听clinet的连接请求,并且当有SYN报文(链接请求)来时,就能就行TcpServer的事件触发,处理该事件。
处理方法:自然就是通过accept进行连接(开始三次握手),从而形成新链接并且将新链接给到从reactor进行该连接的IO事件监听
- 从reactor,获取到主reactor给到的新链接文件描述符(socket),然后对该连接的IO监控,当有事件发生就处理需要处理事件及数据(使用设置的回调函数!)。
他们的角色 与 类之间的关系
- Reactor的实现:TcpServer
- 所有链接的管理:TcpConnection
- 对连接的事件监控:EventLoop
- 缓冲区中需要处理的数据:Buffer
服务器实操:英译小项目
流程:
demo/muduo中创建dirct_server.cpp文件
因为要使用muduo库,需要使用muduo库,所以需要把realease-install-cpp11中的include放到系统默认找寻头文件的目录下,并且在编译时指定lib静态库的路径(makefile中 -L)
类:
TranslateServer:
成员:
- muduo:net:EventLoop _baseloop;//事件监控
- muduo:net:TcpServer _server;//Tcp服务器
函数:
- 构造(uint16_t port):_server(…)
- 设置连接后执行的函数setConnectionCallback()
- 将回调函数onConnection设置进去
- 使用函数适配器bind(对指定的函数进行参数绑定,让函数适配特定情况),使用bind是因为,setConnectionCallback参数的类型只有一个参数(而我们写的函数是自带this的,就需要把他移除)
- 将this直接绑定到函数上,就只需要一个参数
- bind(函数,this,std::placeholders::_1);此处的this就是直接绑定到函数,就写一个std::placeholders::_1,就表示只用传递一个参数
- 设置接收到信息函数后的回调函数onMessage,调用setMessageCallback()
- 略,附三个参数就是 _1 、_2 、_3
- 设置连接后执行的函数setConnectionCallback()
- 启动服务器:void start()
- 开始监听:server调用start
- 开始事件监控,baseloop 调用 loop(一个死循环的阻塞接口 loop函数)
将回调函数设为private函数(不暴露给外部):
-
连接成功后的回调函数:void onConnection(const TcpConnectionPtr&)
- 使用connected()函数
- 判断是否连接成功
- 若失败打印新链接关闭!
- 反之打印新链接成功!
-
有事件就绪的回调函数:void onMessage (const muduo::net::TcpConnectionPtr&,Buffer*,muduo::Timestamp)
- 获取字符串数据:使用buf的retrieveAllAsString();
- resp接收: 调用 封装的翻译translate函数接口
- 对客户端进行响应结果
- conn对象进行send发送resp响应
-
翻译函数 string translate(string)
- 简单实现直接定义一个哈希表 dict_map = { {“hello”,“你好”} , {“Apple”,“苹果”}, …}
- 在map中find
- 找到返回、找不到返回没听懂
主函数 :
- 创建TranslateServer对象
- 启动服务
makefile:
-I(i大写):指定头文件所在的路径(进行寻找头文件的路径):
-I+头文件路径(一般根目录即可)
(-I./include)
-L:指定库文件所在的路径(寻找库文件的路径):
-L+库路径(一般根目录即可)
(-L./lib)
-l(小写L):指定引进的库(例:-lpthread 线程库)
此处需要设定头文件所在路径、库文件路径、引入的库
g++ … -L./库的相对路径 -lmuduo_net -lmuduo_base -pthread
muduo 客户端常用的类
muduo::net::TcpClient类基础介绍
class TcpClient : noncopyable
{
public:
//...TcpClient(EventLoop* loop,const InetAddress& serverAddr,const string& nameArg);
//参数:
loop:....
serverAddr:服务器地址信息
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_);
};
因为connection是非阻塞的,就会有问题:可能连接没有成功,就开始发送数据。所以就需要CountDown类进行Wait等待,等待连接到来,在回调函数中进行唤醒,后才能进行send。
客户端实操:英译小项目
创建类:
TcpClient:
成员变量:
- _client TcpClient对象
- _conn TcpConnectionPtr连接对象
- _latch muduo::CountDownLatch对象,进行同步的控制,当有事件发生才唤醒
- _loopthread muduo::net::EventLoopThread对象
头文件…/net/EventLoopThread.h
- 不用eventloop,他是阻塞式的就会导致后面无法send
使用:EventLoopThread(线程和loop合并)- 他里面的线程是直接运行的,不需要我们调用loop启动
成员函数
-
构造:(服务器ip 、 port)
-
void connect() 连接服务器函数,他会阻塞等待,也就需要唤醒,在连接回调函数中唤醒,连接成功之后才返回!
-
void send(msg) 发送数据
私有成员函数:
- 连接建立成功后的回调函数,连接成功后,唤醒上面的连接的阻塞
- void onConnection(TcpConnectionPtr& conn)
- 收到消息后的回调函数:
- void OnMessage(conn,buf,time)//和服务器中的差不多
main函数:
-
创建client对象(sip,sport)
-
clinet进行连接(connect)
-
进行循环
- 写出要用的buf
- 将buf通过client对象send发送给服务器
上方已经将Client完成了框架
继续完成内部内容:
构造:
- 初始化
- _latch 为 1,调用wait才会阻塞
- 设置_client
- eventloop,通过loopptread调用startloop
- 设置服务器的地址,使用InetAddress
- 客户端名称“TranslateClient”
- _client调用设置当链接成功后的回调函数:setConnectionCallabck 绑定回调函数OnConnection
- 设置setMessageCallback ,绑定:OnMessage
连接服务器函数
- 调用connection进行连接服务器会
- 会立即返回,所以需要
- 需要阻塞等待 _latch调用wait,阻塞等待
- 直到连接建立成功
连接成功过的回调函数:
- 判断连接是否成功(调用connected函数)
- 成功,唤醒主线程中的阻塞(_latch.coutnDown)
- 并且把链接指针指向连接成功的conn
- 失败,清空链接(_conn调用reset关机)
发送函数 bool send
- 判断连接是否正常 调用 connected(因为内部是异步进行的,必须保证链接正常)
- 正常:
- _conn调用send函数
- 返回true
- 反之false
收到消息的回调函数
- 将收到的buf进行打印就OK;
- 打印:翻译结果buf 调用 retrieveAllAsstring 函数
英译汉服务端代码
对于内部翻译的数据,此处并没有深入的写成文件或者数据库,而是直接用了简单的map结构。
记住我们的目的:
只是为了去学习muduo服务器的使用,主要目的也是为了快速认识接口并使用。
#include <muduo/net/EventLoop.h>
#include <muduo/net/TcpServer.h>
#include <muduo/net/TcpConnection.h>#include <iostream>
#include <unordered_map>
#include <memory>
#include <functional>class TranslaServer{
private:void ConnectionCallback(const muduo::net::TcpConnectionPtr& conn){if(!conn->connected()){std::cout << "新连接关闭" << std::endl;}else{std::cout << "新连接成功" << std::endl;}}std::string Translate(std::string english){std::unordered_map<std::string,std::string> dict_map = {{"apple","苹果"},{"hello","你好"}}; // for(auto k : dict_map)// std::cout << k.first << std::endl;std::cout <<"eglish:" << english << std::endl; // english.resize(english.size() - strlen("\n"));//注意tcp协议中粘包问题的\r\n,需要把\r\n字符去除,才能算原本的字符串也才能正确的判断auto iter = dict_map.find(english);if(iter == dict_map.end()){return "查不到此单词\n";}return iter->second + "\n";} void MessageCallback(const muduo::net::TcpConnectionPtr& conn,muduo::net::Buffer*buf,muduo::Timestamp time){//将请求的数据从buf中取出来std::string str = buf->retrieveAllAsString();//调用Translate接口进行翻译std::string chinese = Translate(str);//对客户端对象进行send发送响应conn->send(chinese);}public:TranslaServer(int port):_server(&_baseloop,muduo::net::InetAddress("0.0.0.0",port),"TranslateServer",muduo::net::TcpServer::kReusePort)//kReusePort启动端口复用{auto func1 = std::bind(&TranslaServer::ConnectionCallback,this,std::placeholders::_1);//bind的使用://将this指针提前给到函数ConnectionCallback的第一个参数,从后开始就第一个参数实际是原本的第二个参数!!!!_server.setConnectionCallback(func1);//调用有连接后所要执行的函数auto func2 = std::bind(&TranslaServer::MessageCallback,this,std::placeholders::_1,std::placeholders::_2,std::placeholders::_3);//_server.setMessageCallback(func2);}void start(){_server.start();_baseloop.loop();}
private:muduo::net::EventLoop _baseloop;//放到前面,先进行构造,因为在初始化构造_server时要先使用baseloopmuduo::net::TcpServer _server;
};int main()
{TranslaServer svr(8081);svr.start();return 0;
}
英译汉客户端代码:
#include <muduo/net/EventLoop.h>
#include <muduo/net/EventLoopThread.h>
#include <muduo/net/TcpClient.h>
#include <muduo/net/TcpConnection.h>
#include <iostream>
#include <string>
#include <memory>
#include <functional>
#include <muduo/base/CountDownLatch.h>class TranslateClient{
public:TranslateClient(const std::string& serverip,uint16_t serverport):_client(_loopthread.startLoop(),muduo::net::InetAddress(serverip,serverport),"TranslateClient"),_latch(1){auto func1 = std::bind(&TranslateClient::onConnection,this,std::placeholders::_1);//设置连接成功后的回调函数_client.setConnectionCallback(func1);auto func2 = std::bind(&TranslateClient::onMessage,this,std::placeholders::_1,std::placeholders::_2,std::placeholders::_3);//设置连接成功后的回调函数std::cout << 1 << std::endl;_client.setMessageCallback(func2);}void connection()//clinet的连接{_client.connect();//进行连接服务器,会立即返回_latch.wait();//阻塞等待直到连接建立成功}bool send(const std::string& msg){if(_conn->connected()){_conn->send(msg);return true;}return false;}private:void onConnection(const muduo::net::TcpConnectionPtr& conn){if(conn->connected())//因为是异步的,可能断开连接,所以需要:判断该连接是否存在,防止出错{_latch.countDown();//连接成功唤醒主线程中的阻塞_conn = conn;}else{_conn.reset();}}void onMessage(const muduo::net::TcpConnectionPtr& _conn,muduo::net::Buffer* buf,muduo::Timestamp timeout){std::cout << "翻译结果:" << buf->retrieveAllAsString() << std::endl;}private:muduo::net::EventLoopThread _loopthread;//不在像中服务器使用eventloop,因为他是阻塞式的循环,这样会导致无法sendmuduo::CountDownLatch _latch;muduo::net::TcpConnectionPtr _conn;muduo::net::TcpClient _client;
};int main()
{TranslateClient client("127.0.0.1",8081);std::cout << 1 << std::endl;client.connection();while(1){std::string buf;std::cin >> buf;client.send(buf);}return 0;
}