2021SC@SDUSC BRPC代码分析(十三) —— Server主要流程代码详解

2021SC@SDUSC


一、简介

        分配给我的内容bvar和bthread经过我的多篇技术博客的介绍,已经基本完成。到这里也达到了篇数的要求。所以我考虑从宏观流程的角度对代码进行分析。
        了解过web或者网络技术的应该都对RPC框架的理论并不陌生,它和我们在常规开发时使用的Http请求都是一样的。简单借用这张图片来说明,本质上是一个request请求对应一个response。这也就是brpc在做的工作,他开发的是效率高于我们平时所用的诸如Http之类的RPC框架。
在这里插入图片描述
既然是通信,就一定会有Server服务器端和Client客户端,这篇文章将结合代码,宏观的看一下,我们建立一个服务器端,到底发生了什么。(本次介绍所有内容基本位于server.cpp)

二、代码分析

首先我们可以看一份官方给出的echo服务器(又叫回显服务器,它是网络中最简单的服务,就像编程的HelloWorld一样)。

#include <gflags/gflags.h>
#include <butil/logging.h>
#include <brpc/server.h>
#include "echo.pb.h"

DEFINE_bool(echo_attachment, true, "Echo attachment as well");
DEFINE_int32(port, 8000, "TCP Port of this server");
DEFINE_int32(idle_timeout_s, -1, "Connection will be closed if there is no "
             "read/write operations during the last `idle_timeout_s'");
DEFINE_int32(logoff_ms, 2000, "Maximum duration of server's LOGOFF state "
             "(waiting for client to close connection before server stops)");

// Your implementation of example::EchoService
// Notice that implementing brpc::Describable grants the ability to put
// additional information in /status.
namespace example {
class EchoServiceImpl : public EchoService {
public:
    EchoServiceImpl() {};
    virtual ~EchoServiceImpl() {};
    virtual void Echo(google::protobuf::RpcController* cntl_base,
                      const EchoRequest* request,
                      EchoResponse* response,
                      google::protobuf::Closure* done) {
        // This object helps you to call done->Run() in RAII style. If you need
        // to process the request asynchronously, pass done_guard.release().
        brpc::ClosureGuard done_guard(done);

        brpc::Controller* cntl =
            static_cast<brpc::Controller*>(cntl_base);

        // The purpose of following logs is to help you to understand
        // how clients interact with servers more intuitively. You should 
        // remove these logs in performance-sensitive servers.
        LOG(INFO) << "Received request[log_id=" << cntl->log_id() 
                  << "] from " << cntl->remote_side() 
                  << " to " << cntl->local_side()
                  << ": " << request->message()
                  << " (attached=" << cntl->request_attachment() << ")";

        // Fill response.
        response->set_message(request->message());

        // You can compress the response by setting Controller, but be aware
        // that compression may be costly, evaluate before turning on.
        // cntl->set_response_compress_type(brpc::COMPRESS_TYPE_GZIP);

        if (FLAGS_echo_attachment) {
            // Set attachment which is wired to network directly instead of
            // being serialized into protobuf messages.
            cntl->response_attachment().append(cntl->request_attachment());
        }
    }
};
}  // namespace example

int main(int argc, char* argv[]) {
    // Parse gflags. We recommend you to use gflags as well.
    GFLAGS_NS::ParseCommandLineFlags(&argc, &argv, true);

    // Generally you only need one Server.
    brpc::Server server;

    // Instance of your service.
    example::EchoServiceImpl echo_service_impl;

    // Add the service into server. Notice the second parameter, because the
    // service is put on stack, we don't want server to delete it, otherwise
    // use brpc::SERVER_OWNS_SERVICE.
    if (server.AddService(&echo_service_impl, 
                          brpc::SERVER_DOESNT_OWN_SERVICE) != 0) {
        LOG(ERROR) << "Fail to add service";
        return -1;
    }

    // Start the server.
    brpc::ServerOptions options;
    options.idle_timeout_sec = FLAGS_idle_timeout_s;
    if (server.Start(FLAGS_port, &options) != 0) {
        LOG(ERROR) << "Fail to start EchoServer";
        return -1;
    }

    // Wait until Ctrl-C is pressed, then Stop() and Join() the server.
    server.RunUntilAskedToQuit();
    return 0;
}

我们分着看这段demo,看看他做了什么,内部对应了上面操作。
首先是一个服务,我们建立服务器肯定是要向里面添加服务的,这段demo写了一段简单的发送接收message。
在这里插入图片描述
然后它在主函数中添加了这个服务。
在这里插入图片描述
我们去看看内部干了什么。内部依旧对于这个函数有三个重载,都是去调用了AddServiceInternal(service, false, options)。
在这里插入图片描述
我们找到这个函数。
首先提一下,大家可以看到这些地方的service类型都是google::protobuf::Service*,这是个什么类型?
protbuf是Google出品的一种与语言无关、与平台无关,是一种可扩展的用于序列化和结构化数据的方法,常用于用于通信协议,数据存储等。
他是一种灵活,高效,自动化的机制,用于序列化结构化数据,对比于 XML,他更小,更快,更简单。
简单来理解,大家应该都使用过json,protbuf是一种优于json的数据结构。pprotbuf作为一种结构,它内部专门定义了Service这种概念。如果要将消息类型用于RPC (远程过程调用)系统,可以在.proto文件中定义RPC服务接口和协议缓冲编译器将使用选择的语言生成服务接口代码和存根。因此,如果想用一种接受自定的搜索请求并返回搜索响应的方法来定义RPC服务,可以在.proto文件中定义它。
brpc正是基于这种格式完成的。
然后看回这个函数,参数有三个,服务,是否是内部服务,以及添加的service的选项。它会首先初始化服务器。在初始化中比较重要的就是为服务注册了多种支持的协议。在这里插入图片描述
server端会自动尝试其支持的协议,无需用户指定。cntl->protocol()可获得当前协议。server能从一个listen端口建立不同协议的连接,不需要为不同的协议使用不同的listen端口,一个连接上也可以传输多种协议的数据包, 但一般不会这么做(也不建议),支持的协议有:

  • 百度标准协议,显示为"baidu_std",默认启用。
  • 流式RPC协议,显示为"streaming_rpc", 默认启用。
  • http/1.0和http/1.1协议,显示为”http“,默认启用。
  • http/2和gRPC协议,显示为"h2c"(未加密)或"h2"(加密),默认启用。

brpc中专门有Protocol类,用来封装解析各种协议。上面的初始化时就进行了这里的注册。在这里插入图片描述
注册之后可以添加多种选项,比如开启对其他协议的支持(public_pbrpc协议、nshead+mcpack协议等),关闭闲置连接,开启ssl等。这些功能都在官方文档中被详细解释,有兴趣或者实际应用时可以去官方查看教程。
在这里插入图片描述
然后我们就可以去启动这个服务器。
在这里插入图片描述
和上面的addService一样,都重载了多个形式。最终指向一个StartInternal函数。
在这里插入图片描述
函数传入了IP地址,监听的端口号以及一些可选设置项。开始进行所有给出可选项的设置。
在这里插入图片描述
然后就开始监听,根据给出的IP和端口号的范围不停尝试,接收到就停止尝试。一个server只能监听一个端口(不考虑ServerOptions.internal_port),需要监听N个端口就起N个Server。
在这里插入图片描述
然后去建立起一个Acceptor接收器。
在这里插入图片描述
在BuildAcceptor中,可以看到它采用所有支持的协议addHandler添加进去,相当于一个服务器在一个端口支持多种协议。
在这里插入图片描述
然后将socket套接字的使用权给到这里,进行读写即可。
在这里插入图片描述
这是StartAccept内操作,Socket::Create函数是根据options新建socket套接字并把id存入第二个参数中,其中最重要的操作就是用options.on_edge_triggered_event所指代的函数进行epoll add,在当前服务端start的场景下,也就是在监听fd上用OnNewConnections注册epoll事件处理新过来的连接,至此启动完成,后续等待epoll事件进行相应处理。
在这里插入图片描述
最后还有一个RunUntilAskedToQuit函数
在这里插入图片描述
在这里插入图片描述
它里面里主要是用signal函数注册了退出信号,一旦有退出信号就会返回true,从而结束死循环并停止server。

总结

以上就是今天介绍的全部内容,本文主要结合echo_c++的demo进行了Server的启动部分详细源码分析,之后的博客会继续进行其他代码的分析。

猜你喜欢

转载自blog.csdn.net/m0_46306466/article/details/122173930