【C++第三方库】Muduo库结合ProtoBuf库搭建服务端和客户端的过程和源码
每日激励:“不设限和自我肯定的心态:I can do all things。 — Stephen Curry”
绪论:
本章我将结合之前的这俩个第三方库快速上手protobuf序列化和反序列化框架和muduo网络,来去实现muduo库在protocol协议搭建服务端和客户端。
————————
早关注不迷路,话不多说安全带系好,发车啦(建议电脑观看)。
第三方框架muduo库protobuf通信
在muduo库中的数据格式:
- len:代表后面的长度
protobuf根据我们的proto⽂件⽣成的代码中,会⽣成对应类型的类,⽐如TranslateRequest 对应了⼀个TranslateRequest 类,⽽不仅仅如此,protobuf⽐我们想象中做的事情更多,每个对应的类中,
都包含有⼀个描述结构的指针:
static const ::google::protobuf::Descriptor* descriptor();
这个描述结构⾮常重要,其内部可以获取到当前对应类类型名称,以及各项成员的名称,因此通过这些名称,加上协议中的typename字段,就可以实现完美的对应关系了
基于muduo库对于protobuf结构的数据处理进行的网络通信逻辑图:
上图我们能理解成:
将TcpClient部分看成 : 客户端
将ProtobufDispatch部分看出 : 服务端
客户端和服务端都要进行的流程:
将数据就绪绑定codec协议处理器,而在协议处理器中又绑定上事件分发器,对不同事件就绪处理进行分发处理
具体如下图:
了解了上述关系,接下来就可以通过muduo库中陈硕⼤佬提供的接⼝来编写我们的客⼾端/服务器端通信了,其最为简便之处就在于我们可以把更多的精⼒放到业务处理函数的实现上,⽽不是服务器的搭
建或者协议的解析处理上了
具体流程从代码 中边看边敲边学。
实操(编写服务端和客户端流程)
目的:基于muduo库中,对于Protobuf协议的处理代码,实现翻译 + 加法服务端与客户端
- 编写proto文件,生成相关的结构
创建一个protobuf目录(假如你写了muduo的字典实操:创建一个dic目录将dic*文件都移道内部)
在内部创建所需的文件:
- request.proto
- protobuf_client.cpp
- protobuf_server.cpp
- makefile
编写proto文件
- 协议版本
- 创建包
- 创建message结构
- TranslateRequest
- msg
- TranslateResponse
- msg
- AddRequest
- num1
- num2
- AddResponse
- result
- TranslateRequest
- protoc生成文件
编写protobuf服务端
使用muduo库中的样例代码
第三方库的muduo库关于Protobuf相关的代码拷贝进来,在third/muduo-master/examples/protobf,将protobuf目录拷贝一份到系统默认寻找头文件目录下(/user/include),这样就能使用里面code.cc、code.h、dispatcher.h 文件
头文件包含使code.h/dispatcher.h/request.pb.h
编写方法:学习server.cc的内容
创建Server类
针对不同的请求定义不同的智能指针类
- 类型重命名:定义一智能指针方便后面的使用
如:TranslateRequestPtr:指针:TranslateRequest(该指针在刚刚生成的request.pb.h)
AddRequestPtr、MessagePtr(Message指针在google::protobuf::下) - 构造
- 参数传入端口:
- 初始化:
- _server(loop、InetAddress、name)
- _dispatch(在ProtobufCode类内,并初始化绑定自定义onUnknowMessage函数)
- _codec(在ProtobufCode类内,并绑定ProtobufDispatchmeow命名空间内的onProtobuffmessage函数,并将 分发器 绑定给该函数)
- 定义onUnknowMessage,收到未知消息事件就绪后的回调方法
- 注册业务请求处理函数
- 使用registerMessageCallback<填写类型>,类型有你protobuf结构中定义的两种请求报文 TranslateRequest、AddRequest
- 给分发器绑定onTranslate、onAdd
- 定义回调函数:
- onTranslate(TcpConnnectionPtr,TranslateRequestPtr,TimeStamp)
- onAdd (同上,只不过注意 AddRequestPtr)
- 同样给消息事件就绪回调 函数
- _server绑定ProctobufCodec中的onMessageh函数(为了处理得到数据后的序列化工作!),并且把 协议处理器 绑定
- _server绑定自定义的onConnection函数
对于上面的onTranslate、onAdd、onMessageh、onConnec
- onUnknowMessage:
查看server.cc内的写法- onConnection (conn)
判断连接是否存在
成功 LOG_INFO << 新连接建立成功!
连接即将关闭- onTranslate
从TranslateRequestPtr类的message对象中的有效信息,也就是需要翻译的内容(使用message对象的msg函数)
进行翻译得到结果(translte将从muduo中拷贝过来使用 )
组织protobuf的响应(定义TranslateResponse resp对象调用set_msg函数组织响应)
发送响应(使用codec对象send()codec会在内部进行序列化然后发送)- onAdd
通过message对象的num1…函数获取数据
计算结果result
创建Add的应答类response,设置结果使用set_result函数、最后再次使用codec发送
- start
- _server 开启
- _baselopp 开启监听
成员变量:、
服务器对象:_server
请求分发器:_dispatcher
protobuf协议处理器:_codec
循环监视器:_baseloop
主函数
创建server对象
启动
编写protobuf客户端
打开client.cc,头文件拷贝过来,带上request.pb.h
类Client
成员变量:
- _client
- _dispatcher(ProtobufCodec类)
- codec
- 在从muduo中拷贝:
1. 实现同步:latch
2. 异步循环处理线程loopthread(头文件 muoduo/eventloopthread.h)
3. 客户端对应的链接conn(CountDownlatch.h)
请求的智能指针:
- MessagePtr(对该对象创建google::protobuf::Message)
- AddResponsePtr
- TranslateResponsePtr
构造:
- 参数(ip、port)(设置所要请求的主机的socket{ip,port})
- 初始化:
- latch(1)
- _client(loop,inetaddress(ip,port),name)
- dispatcher(雷同server略,同样是给个默认处理函数,onknowMessage,this,_1,_2,_3)
- codec(雷同server略,同样是把dispatch给到codec,&ProtobufDispatch::onProtobufMessage,&_dispatch,_1,_2,_3)
- 注册回调函数( 雷同拷贝server的过来)
- 注意:对于dispatch注册的处理函数的类型为Response,并且知道其所需要的函数有几个参数(看源码的构造,得知该适配器中的函数需要有几个参数)
- 设置回调函数(onTranslate、onAdd)
- connect链接的接口
- client调用connect函数
- latch 等待 连接到来
- Tranlate(string)(这是用户用来发送请求的)
- 定义请求 req(使用request::TranslateRequeset,因为适配send的Message父类)
- 设置信息数据
- 调用send
- Add(n1.n2)
- AddRequest req;(使用request::AddRequest ,因为适配send的Message父类)
- 设置num1、num2
- 再send
private内容:
send发送的接口(Message父类指针对象 * message)
- 判断连接是否存在
- 存在:修改send:使用codec调用send
- 不存在返回false
响应处理函数:
- onTranslate(这是接受到返回结果的)(参数:TcpConnectionPtr、TranslateResponse、Timestamp)
- cout 翻译结果 message->msg() endl
- onAdd
- cout 加法结果 message->result() endl
主函数:
- client对象
- 链接服务器
- 调用翻译
- 调用加法
- 休眠1s
makefile
改成文件名protobuf_client / …_server
生成的可执行程序为 client / server
注意联合编译:codec.cc
链接库protobuf
需要注意的点(注意其中一些地方会用到文件权限的修改,以及sudo提升权限,这些就不诉了,可以评论提问)
- /third/build/release-install-cpp11/include/muduo头文件放到/usr/include下
- 将库文件放到库文件内,在编译时-L寻找库
将该库文件third/build/release-install-cpp11/lib重命名为muduo,放到/lib下
- 需要添加-lz的库文件,否则回报下图的错误
- 主要要加上request.pb.cc联合编译,否则回报下图问题
一些头文件问题:
- 在头文件处加上:#include “protobuf/codec/codec.cc”,一起编译
- 并且修改/include/protobuf/codec/codec.cc内部头文件路劲问题,以及将google-inl.h文件从/third/muduo-master/muduo/net/protorpc拷贝到当前目录下
.PHONY:all
all: client server
client:protobuf_client.cpp request.pb.cc
g++ -o $@ $^ -std=c++14 -lmuduo_net -lmuduo_base -pthread -lprotobuf -L/lib/muduo -lz
server:protobuf_server.cpp request.pb.cc
g++ -o $@ $^ -std=c++14 -lmuduo_net -lmuduo_base -pthread -lprotobuf -L/lib/muduo -lz
.PHONY:clean
clean:
rm -f server client
服务端所有源码:
#include "protobuf/codec/codec.h"
#include "protobuf/codec/dispatcher.h"
#include "muduo/base/Logging.h"
#include "muduo/base/Mutex.h"
#include "muduo/net/EventLoop.h"
#include "muduo/net/TcpServer.h"
#include "protobuf/codec/codec.cc"
#include "request.pb.h"
#include <iostream>
#include <functional>
class Server{
public:
//创建指针指针方便后面使用
typedef std::shared_ptr<request::TranslateRequest> TranslateRequestPtr;
typedef std::shared_ptr<request::AddRequest> AddRequestPtr;
typedef std::shared_ptr<google::protobuf::Message> MessagePtr;
Server(uint16_t port):_server(&_baseloop,muduo::net::InetAddress("0.0.0.0",port),"TcpServer"),
_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))
{
//给dispatcher内部注册请求处理函数:
//1. 如当监听到Translate请求后就会执行该函数
_dispatcher.registerMessageCallback<request::TranslateRequest>(std::bind(&Server::onTranslate,this,std::placeholders::_1,std::placeholders::_2,std::placeholders::_3));
//2. ....
_dispatcher.registerMessageCallback<request::AddRequest>(std::bind(&Server::onAdd,this,std::placeholders::_1,std::placeholders::_2,std::placeholders::_3));
//再给server 注册处理函数 ; 连接事件就绪 / 正常事件就绪
//连接就绪:
_server.setConnectionCallback(std::bind(&Server::onConnection,this,std::placeholders::_1));
//事件就绪:
//对于绑定ProtobufCodec里面的onMessage函数,是因为需要进行数据的序列化操作,所以使用Protobuf
_server.setMessageCallback(std::bind(&ProtobufCodec::onMessage,&_codec,std::placeholders::_1,std::placeholders::_2,std::placeholders::_3));
}
void start()
{
//启动服务器
_server.start();
//开启循环监听
_baseloop.loop();
}
private:
//未知事件就绪:
void onUnknownMessage(const muduo::net::TcpConnectionPtr& conn,
const MessagePtr& message,
muduo::Timestamp)
{
LOG_INFO << "onUnknownMessage: " << message->GetTypeName();
conn->shutdown();
}
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 onTranslate(const muduo::net::TcpConnectionPtr& conn,const TranslateRequestPtr& message,muduo::Timestamp){
std::string req = message->msg();
std::string translate_res = Translate(req);
request::TranslateResponse resp;
resp.set_msg(translate_res);
_codec.send(conn,resp);
}
void onAdd(const muduo::net::TcpConnectionPtr& conn,const AddRequestPtr& message,muduo::Timestamp){
uint32_t num1 = message->num1();
uint32_t num2 = message->num2();
uint32_t result = num1 + num2;
request::AddResponse resp;
resp.set_result((uint32_t)result);
_codec.send(conn,resp);
}
void onConnection(const muduo::net::TcpConnectionPtr &conn){
if(!conn->connected())
{
std::cout << "新连接关闭" << std::endl;
}
else
{
std::cout << "新连接成功" << std::endl;
}
}
private:
ProtobufDispatcher _dispatcher;
ProtobufCodec _codec;
muduo::net::EventLoop _baseloop;
muduo::net::TcpServer _server;
};
int main()
{
Server server(8080);
server.start();
return 0;
}
客户端源码:
#include "protobuf/codec/codec.h"
#include "protobuf/codec/dispatcher.h"
#include "muduo/base/Logging.h"
#include "muduo/base/Mutex.h"
#include "muduo/net/EventLoop.h"
#include "muduo/net/TcpServer.h"
#include "muduo/net/TcpClient.h"
#include "muduo/base/CountDownLatch.h"
#include "muduo/net/EventLoopThread.h"
#include "protobuf/codec/codec.cc"
#include "request.pb.h"
#include <iostream>
#include <functional>
#include <string>
class Client{
public:
typedef std::shared_ptr<google::protobuf::Message> MessagePtr;
typedef std::shared_ptr<request::AddResponse> AddResponsePtr;
typedef std::shared_ptr<request::TranslateResponse> TranslateResponsePtr;
//使用 _loopthread.startLoop() 获取loop,因为在客户端不能发生阻塞没导致无法send
Client(std::string sip,int16_t sport):
_client(_loopthread.startLoop(),muduo::net::InetAddress(sip,sport),"TcpClient"),
_dispatcher(std::bind(&Client::onknowMessage,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)),
_latch(1)
{
//注册消息处理函数
_dispatcher.registerMessageCallback<request::TranslateResponse>(std::bind(&Client::onTranslate,this,std::placeholders::_1,std::placeholders::_2,std::placeholders::_3));
//2. ....
_dispatcher.registerMessageCallback<request::AddResponse>(std::bind(&Client::onAdd,this,std::placeholders::_1,std::placeholders::_2,std::placeholders::_3));
//再给server 注册处理函数 ; 连接事件就绪 / 正常事件就绪
//连接就绪:
_client.setConnectionCallback(std::bind(&Client::onConnection,this,std::placeholders::_1));
//事件就绪:
//对于绑定ProtobufCodec里面的onMessage函数,是因为需要进行数据的序列化操作,所以使用Protobuf
_client.setMessageCallback(std::bind(&ProtobufCodec::onMessage,&_codec,std::placeholders::_1,std::placeholders::_2,std::placeholders::_3));
}
void connect()
{
_client.connect();//建立连接
_latch.wait();//等待连接
}
void Translate(const std::string& message)
{
request::TranslateRequest resp;
resp.set_msg(message);
send(&resp);
}
void Add(int num1,int num2){
request::AddRequest resp;
resp.set_num1(num1);
resp.set_num2(num2);
send(&resp);
}
private:
bool send(const google::protobuf::Message* msg)
{
if(_conn->connected())
{
_codec.send(_conn,*msg);
return true;
}
return false;
}
void onConnection(const muduo::net::TcpConnectionPtr &conn){
if(!conn->connected())
{
std::cout << "新连接关闭" << std::endl;
}
else
{
std::cout << "新连接成功" << std::endl;
_latch.countDown();//当连接成功后,唤醒
_conn = conn;//注意别忘了!
}
}
void onTranslate(const muduo::net::TcpConnectionPtr&,
const TranslateResponsePtr& message,
muduo::Timestamp)
{
std::cout << "翻译结果" << message->msg() << std::endl;
}
void onAdd(const muduo::net::TcpConnectionPtr&,
const AddResponsePtr& message,
muduo::Timestamp){
std::cout << "加法结果" << message->result() << std::endl;
}
void onknowMessage(const muduo::net::TcpConnectionPtr& conn,
const MessagePtr& message,
muduo::Timestamp)
{
LOG_INFO << "onUnknownMessage: " << message->GetTypeName();
conn->shutdown();
}
private:
ProtobufDispatcher _dispatcher;
ProtobufCodec _codec;
muduo::net::EventLoopThread _loopthread;//不在像中服务器使用eventloop,因为他是阻塞式的循环,这样会导致无法send
muduo::net::TcpClient _client;
muduo::CountDownLatch _latch;//进行防止等待连接导致的阻塞
muduo::net::TcpConnectionPtr _conn;
};
int main()
{
Client client("127.0.0.1",8080);
client.connect();
client.Translate("apple");
client.Add(1,2);
sleep(1);
return 0;
}
运行结果:
本章完。预知后事如何,暂听下回分解。
如果有任何问题欢迎讨论哈!
如果觉得这篇文章对你有所帮助的话点点赞吧!
持续更新大量C++细致内容,早关注不迷路。