# srpc **Repository Path**: pczhang/srpc ## Basic Information - **Project Name**: srpc - **Description**: 使用libevent 和 protobuf实现的rpc框架 - **Primary Language**: Unknown - **License**: Apache-2.0 - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 1 - **Created**: 2023-11-08 - **Last Updated**: 2023-11-08 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # srpc 解决远程调用问题 1. 序列化反序列化,protobuf 2. 网络传输:socket # 使用 最后更新时间:2021年7月12日08:18:35 1. 环境准备 - cmake >= 3.12 1. yum remove -y cmake 2. https://cmake.org/files/v3.2/cmake-3.2.0.tar.gz ; ./configure; make && make install - 安装libevent libevent-devel、libevent - 安装protobuf 3.18 find_package 的原理 https://zhuanlan.zhihu.com/p/97369704?utm_source=wechat_session 2. 编译 ``` cd build cmake .. make ``` 3. 运行 ``` cd build ./srpc_service ``` ``` cd build ./srpc_client ``` # TODO List - [x] 打通RPC框架基本 - [ ] 编译为lib - [ ] 解决细节问题 - [ ] 线程安全 - [ ] 内存泄露 - [ ] cout、print替换为日志支持 - [ ] 并发支持 - [ ] k8s集群部署服务 - [ ] 测试 - [ ] 单元测试 - [ ] 压力测试 - [ ] E2E(端到端)测试 # 1. 工作流程 ## 客户端 1. 填充request 2. 申请RPCClient 3. 申请RPCChannel 4. rpcChannelClient open rpcChannel、 6. 客户端调用stub.Echo(request, responses) 7. RPCchannel将数据打包成元信息(包好请求方法名) 8. 读事件须是异步的,才能让响应及时返回(后边加关键字设置成同步) ## 服务端 1. 启动RpcChannelService 2. RpcChannelServer.RegisterService(service) 3. 根据Response,获取服务,在服务池中实例化服务对象(反射) - 目前使用对象池 4. 响应服务 ## libevent 1. event_base()初始化event_base 2. event_set()初始化event 3. event_base_set()将event绑定到指定的event_base上 4. event_add()将event添加到事件链表上,注册事件 5. event_base_dispatch()循环、检测、分发事件 # 2. 步骤 1. 定义.proto文件 - 定义message - 定义service - 使用标志符号生成抽象类 ``` option cc_generic_services = true ``` 2. 使用protoc工具生成接口类 ``` protoc echo_service.proto -I./ --cpp_out=./ ``` 3. 实现抽象类 4. 编译文件 ``` CMakeLists.txt ``` 5. 实现通信方式RPC channel # 3. UML图 ```mermaid classDiagram class EchoService_Stub{ } class EchoService{ } ``` # 4. 参考 1. [如何使用protobuf实现RPC](https://blog.csdn.net/csdnnews/article/details/116214114) 2. 流程框架图 ![nimg.ws.126.net](README.assets/nimg.ws.126.net.jpeg) [图源和图文解释](https://blog.51cto.com/u_15127585/2782884) 3. libevent的使用 https://blog.csdn.net/ti_an_1989/article/details/20710725 4. libevent详解 https://www.cnblogs.com/secondtonone1/p/5535722.html 5. buffer_event读取数据 https://blog.csdn.net/weixin_36750623/article/details/83855731 6. buffer_event 客户端实例,以及相关讲解 https://blog.csdn.net/u010710458/article/details/80067885 7. 【最详细】libevent使用实例 https://www.cnblogs.com/kingstarer/p/6629562.html ``` 1. buffer_event 是有读、写缓冲区的 2. 读的回调函数中向写缓冲区写数据,会在读的回调函数结束后写 ``` 8. buffer_event是个c库,不能处理对象的成员函数,只有传递静态的成员函数 9. buffer_event 多线程支持 ``` Nerver mind. it's my fault. It need to include event2/thread.h and links event_pthreads to make it run ``` # 5. 错误解决 1. 编译连接问题,cmake问题 ```shell /Users/zhuyue/Desktop/Project/simpleRPC/protos/echo_service.pb.h:10:10: fatal error: 'google/protobuf/port_def.inc' file not found #include ``` 解决方法: > cmake link_directories(/usr/local/lib/)无效,使用find_library 2. 在从命令行读取字符串时,没有设置末尾终止符`\0`, 导致序列化时出问题 ```cmd String field 'simplerpc.EchoRequestPB.message' contains invalid UTF-8 data when serializing a protocol buffer. Use the 'bytes' type if you intend to send raw bytes. ``` 3. 在服务器端无法解析出数据,原因是没有使用`c_str()` ```c++ void CallMethod(const MethodDescriptor *method, RpcController *controller, const Message *request, Message *response, Closure *done) { // 序列化请求变数,填充 request 栏位 // (这里的 request 变数,是客户端程式传进来的) string request_str; if(request->SerializeToString(&request_str) != true){ cout<< "Serialize Error " << endl; exit(-1); } /** * Step3: 通过 libevent 接口函数发送 TCP 数据 * **/ bufferevent_write(mBuffterEvent, request_str.c_str(), request_str.size()); } ``` # 6. 依赖 1. brew install libevent # 7. 其它说明 libeventPractice/ 主要用于练习libevent ## 1. 引文 类比网卡,把数据存入缓冲,写入缓冲再发送 ## 2. 结构 #### 缓冲区:输入缓冲,输出缓冲,evbuffer #### 回调和水位:读取回调,写入回调,水位 对于读取回调,低水位, 低于某个值不处理;高水位,高于某个值不再往buffer存,阻塞再此处 对于写入回调,低水位,写入操作低于某个量时,写入回调将 #### 事件回调:BEV_EVENT_READING,BEV_EVENT_WRITING,BEV_EVENT_EOF ## 3. stub RPC中出现的stub解释:服务调用过程中,真正的方法逻辑存在于服务端中,那么客户端 保存就是服务端真实方法的一个存根(也可以认为是服务端的代理,存放服务端的地址等信息);即当客户端需要远程访问服务端方法的时候, 就可以凭借 服务端在客户端中的存根来组装发起远程调用所需要的信息;类似于我在银行存了一笔钱,下回来存钱的时候,就可以凭借存根,知道我上回存款信息。 # 8. 总结 1、首先创建一个事件event监听端口上的连接请求,当成功建立连接后,为每个连接创建一个bufferevent,监听该连接上数据的发送和接收 2、当读事件ev_read被触发时(客户端发送数据),调用回调函数bufferevent_readcb()完成数据的接收,并调用用户的读回调函数,构造待发送数据并写入发送缓冲区 3、当写事件ev_write被触发时(客户端等待接收数据),调用回调函数bufferevent_writecb()完成数据的发送 ``` struct bufferevent *bev; evutil_make_socket_nonblocking(fd); //使用bufferevent_socket_new创建一个struct bufferevent *bev,关联该sockfd,托管给event_base BEV_OPT_CLOSE_ON_FREE表示释放bufferevent时关闭底层传输端口。这将关闭底层套接字,释放底层bufferevent等。 bev = bufferevent_socket_new(base, fd, BEV_OPT_CLOSE_ON_FREE); //设置读写对应的回调函数 bufferevent_setcb(bev, readcb, NULL, errorcb, NULL); // bufferevent_setwatermark(bev, EV_READ, 0, MAX_LINE); //启用读写事件,其实是调用了event_add将相应读写事件加入事件监听队列poll。正如文档所说,如果相应事件不置为true,buf ferevent是不会读写数据的 bufferevent_enable(bev, EV_READ|EV_WRITE); ```