2021SC@SDUSC BRPC code analysis (13) —— Detailed explanation of the main process code of Server

2021SC@SDUSC


1. Introduction

        The content bvar and bthread assigned to me have been basically completed after the introduction of my many technical blogs. Here also reached the number of requirements. So I consider analyzing the code from the perspective of the macro process.
        Those who have learned about web or network technology should be familiar with the theory of RPC framework, which is the same as the Http request we use in conventional development. Simply borrow this picture to illustrate, essentially a request corresponds to a response. This is what brpc is doing. He develops an RPC framework that is more efficient than the ones we usually use, such as Http.
insert image description here
Since it is communication, there must be a Server server and a Client client. This article will combine the code to take a macro look at what happens when we build a server. (All the content of this introduction is basically located in server.cpp)

2. Code Analysis

First of all, we can look at an official echo server (also called an echo server, which is the simplest service in the network, just like the programmed 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;
}

Let's look at this demo separately to see what he did, and the interior corresponds to the above operation.
The first is a service. When we build a server, we must add services to it. This demo writes a simple message to send and receive.
insert image description here
Then it adds this service in the main function.
insert image description here
Let's go see what's going on inside. There are still three overloads of this function internally, all of which call AddServiceInternal(service, false, options).
insert image description here
We find this function.
First of all, you can see that the service types in these places are all google::protobuf::Service*. What type is this?
Protbuf is a language-independent and platform-independent method produced by Google. It is an extensible method for serializing and structuring data. It is often used in communication protocols and data storage.
He is a flexible, efficient, and automated mechanism for serializing structured data. Compared with XML, he is smaller, faster, and simpler.
To understand simply, everyone should have used json, and protbuf is a data structure superior to json. As a structure, pprotbuf specifically defines the concept of Service internally. If you want to use the message type for an RPC (Remote Procedure Call) system, you can define the RPC service interface in the .proto file and the protocol buffer compiler will generate the service interface code and stubs in the language of choice. So, if you want to define an RPC service that accepts a custom search request and returns a search response, you can define it in a .proto file.
brpc is based on this format.
Then look back at this function, there are three parameters, service, whether it is an internal service, and the option of the added service. It will first initialize the server. More important in the initialization is to register a variety of supported protocols for the service.insert image description here
The server side will automatically try the protocols it supports without user specification. cntl->protocol() can get the current protocol. The server can establish connections of different protocols from one listen port. It is not necessary to use different listen ports for different protocols. Data packets of multiple protocols can also be transmitted on one connection, but this is generally not done (not recommended). Support The agreements are:

  • Baidu standard protocol, displayed as "baidu_std", enabled by default.
  • Streaming RPC protocol, displayed as "streaming_rpc", enabled by default.
  • The http/1.0 and http/1.1 protocols, displayed as "http", are enabled by default.
  • The http/2 and gRPC protocols, displayed as "h2c" (unencrypted) or "h2" (encrypted), are enabled by default.

There is a Protocol class in brpc, which is used to encapsulate and analyze various protocols. The registration here is done during the above initialization. insert image description here
After registration, you can add a variety of options, such as enabling support for other protocols (public_pbrpc protocol, nshead+mcpack protocol, etc.), closing idle connections, enabling ssl, etc. These functions are explained in detail in the official documentation. If you are interested or in practical application, you can go to the official tutorial to view it.
insert image description here
Then we can start the server.
insert image description here
Like addService above, multiple forms are overloaded. Finally point to a StartInternal function.
insert image description here
The function passes in the IP address, the listening port number, and some optional settings. Starts setting up all options given.
insert image description here
Then start listening, keep trying according to the range of IP and port number given, and stop trying when received. A server can only monitor one port (regardless of ServerOptions.internal_port), and N servers need to monitor N ports.
insert image description here
Then go to build an Acceptor receiver.
insert image description here
In BuildAcceptor, you can see that it uses all supported protocols addHandler to add in, which is equivalent to a server supporting multiple protocols on one port.
insert image description here
Then give the right to use the socket socket here to read and write.
insert image description here
This is the operation in StartAccept. The Socket::Create function creates a new socket socket according to options and stores the id in the second parameter. The most important operation is to use the function referred to by options.on_edge_triggered_event to perform epoll add. In the current server start scenario, that is, use OnNewConnections to register the epoll event on the monitoring fd to process the new connection. So far, the startup is complete, and then wait for the epoll event to be processed accordingly.
insert image description here
Finally, there is a RunUntilAskedToQuit function,
insert image description here
insert image description here
which mainly uses the signal function to register the exit signal. Once there is an exit signal, it will return true, thus ending the infinite loop and stopping the server.

Summarize

The above is all the content introduced today. This article mainly combines the demo of echo_c++ to conduct a detailed source code analysis of the server startup part, and the subsequent blog will continue to analyze other codes.

Guess you like

Origin blog.csdn.net/m0_46306466/article/details/122173930