Comparison of gRPC and Ice remote call protocols

gRPC example

gRPC is a high-performance, open-source RPC framework that supports multiple programming languages ​​and platforms, and uses protobuf as its default serialization protocol.

To use gRPC, you need to define a .proto file that contains service methods, and use the protobuf compiler to generate client and server code. Then use the generated client code to call the methods provided by the server.

Here is a simple example .proto file:

syntax = "proto3";

package myservice;

service MyService {
  rpc SayHello (HelloRequest) returns (HelloResponse) {}
}

message HelloRequest {
  string name = 1;
}

message HelloResponse {
  string message = 1;
}

In this example, we define a service MyService that contains a method that accepts a HelloRequest message and returns a HelloResponse message.

To generate client and server code using the protobuf compiler, use the following commands:

protoc --grpc_out=. --plugin=protoc-gen-grpc=/usr/local/bin/grpc_cpp_plugin myservice.proto

This will generate two header files, myservice.pb.h and myservice.grpc.pb.h, and the corresponding .cc files. These four files provide the mapping of messages and the interface implementation of gRPC services. 4 files need to be placed on the client and server.
Among them: myservice.pb.h and myservice.pb.cc are the message maps of the Protocol Buffers binary protocol.
myservice.grpc.pb.h and myservice.grpc.pb.cc provide grpc network services.

server code

You can write server-side implementations and client-side code to invoke the service. The following is an example of a simple C++ server-side implementation:

#include <iostream>
#include <memory>
#include <string>

#include <grpcpp/grpcpp.h>
#include "myservice.grpc.pb.h"

using grpc::Server;
using grpc::ServerBuilder;
using grpc::ServerContext;
using grpc::Status;
using myservice::HelloRequest;
using myservice::HelloResponse;
using myservice::MyService;

class MyServiceImpl final : public MyService::Service {
    
    
  Status SayHello(ServerContext* context, const HelloRequest* request,
                  HelloResponse* response) override {
    
    
    std::string prefix("Hello, ");
    response->set_message(prefix + request->name());
    return Status::OK;
  }
};

void RunServer() {
    
    
  std::string server_address("0.0.0.0:50051");//服务器的IP地址和端口
  MyServiceImpl service;//示例化一个对象,里面实现了grpc服务的业务逻辑

  ServerBuilder builder;//创建一个对象,用于构建服务器
//指定服务器监听的地址和端口
  builder.AddListeningPort(server_address, grpc::InsecureServerCredentials());
  builder.RegisterService(&service);//将server注册到builder中,表示服务器会响应客户端的请求

  std::unique_ptr<Server> server(builder.BuildAndStart());//创建并使用服务器,同时用智能指针管理这个对象
  std::cout << "Server listening on " << server_address << std::endl;
  server->Wait();//阻塞等待客户端请求到来
}

int main(int argc, char** argv) {
    
    
  RunServer();
  return 0;
}

In this example, the SayHello method of the MyService service is implemented, which combines the name from the request with the "Hello," prefix, and sets it as the message field in the response message.

client code

To write client code to call this service, the following code:

#include <iostream>
#include <memory>
#include <string>

#include <grpcpp/grpcpp.h>
#include "myservice.grpc.pb.h"

using grpc::Channel;
using grpc::ClientContext;
using grpc::Status;
using myservice::HelloRequest;
using myservice::HelloResponse;
using myservice::MyService;

class MyServiceClient {
    
    
 public:
  MyServiceClient(std::shared_ptr<Channel> channel)
      : stub_(MyService::NewStub(channel)) {
    
    }

  std::string SayHello(const std::string& name) {
    
    
    HelloRequest request;//创建一个对象
    request.set_name(name);//将参数设置到请求中

    HelloResponse response;//创建一个对象

    ClientContext context;//创建一个对象

    Status status = stub_->SayHello(&context, request, &response);

    if (status.ok()) {
    
    
      return response.message();
    } else {
    
    
      return "RPC failed";
    }
  }

 private:
  std::unique_ptr<MyService::Stub> stub_;
};

int main(int argc, char** argv) {
    
    
  MyServiceClient client(grpc::CreateChannel(
      "localhost:50051", grpc::InsecureChannelCredentials()));
//创建一个通道对象,用于与grpc服务器建立连接。
  std::string name("World");
  std::string reply = client.SayHello(name);
  std::cout << "Received: " << reply << std::endl;

  return 0;
}

In this example, a MyServiceClient class is created to encapsulate communication with the server. Use the CreateChannel function to create a connection with the server, and use the SayHello method to call the method provided by the server.

Comparison between Ice and grpc

Example comparison https://blog.csdn.net/whzhaochao/article/details/51406539

Ice's concurrency performance is better than grpc

Transport protocol comparison

The transport layer protocol of ICE uses a custom TCP Socket channel (Ice.TCP) for communication. Compared with the traditional TCP protocol, it has the following characteristics:

  1. Support asynchronous communication: Ice.TCP channel provides asynchronous communication mechanism. Ice.TCP channel uses asynchronous I/O model for communication, that is, when sending and receiving data, it does not need to wait for the response of the other party, but directly returns the control right. Wait for data to be ready before processing. This approach can greatly improve communication efficiency and performance.
  2. Support multiplexing: Ice.TCP channel uses multiplexing technologies such as epoll or kqueue, which can simultaneously process multiple requests on a single connection, thereby reducing network overhead.
  3. Support fragmented transmission: Ice.TCP channel divides large data packets into multiple small data packets for transmission, avoiding network congestion and transmission delay. At the same time, it also supports mechanisms such as data compression and encryption, which can further improve transmission efficiency and security.
  4. Serialization and deserialization: The Ice.TCP channel uses a serialization framework such as Protocol Buffers or Slice to serialize and deserialize data for transmission in the network.

However, the HTTP2 used by grpc does not have a custom TCP protocol, which may be more compatible with this mode of browser transmission.

Serialization method comparison

ZeroC Ice uses the Slice language for data serialization, which has the following characteristics:

  1. Strong typing: Slice language is a strongly typed language, which can perform type checking during compilation, avoiding runtime type errors.
  2. Dynamic extension: Slice language supports dynamic extension, which can add new data types or interfaces without modifying the code.
  3. Object-oriented: The Slice language is an object-oriented language that supports features such as classes, inheritance, and polymorphism.
  4. Ease of use: The syntax of the Slice language is simple and easy to understand, making it easy to learn.

Compared with Google's Protocol Buffers, the Slice language also has some advantages:

  1. Support dynamic expansion: Unlike Protocol Buffers, the Slice language supports dynamic expansion, and new data types or interfaces can be added without modifying the code.
  2. Multi-language support: ZeroC Ice supports multiple programming languages, including C++, Java, Python, etc., while Protocol Buffers only supports a few programming languages.
  3. Object-oriented support: The Slice language is an object-oriented language that supports features such as classes, inheritance, polymorphism... [omitted]

Dynamic expansion:

The dynamic extension of the Slice language means that new data types or interfaces can be added without modifying existing code. As an example, suppose we have a Slice interface defined as follows:

interface MyInterface
{
    void MyMethod();
};

Now we want to add a new method to this interface, which can be achieved by inheritance:

interface MyExtendedInterface extends MyInterface
{
    void MyNewMethod();
};

In this way, we extend the original interface and add a new method without modifying the existing code.

In contrast, Google's Protocol Buffers do not support dynamic expansion. If you want to add new fields or message types, you need to modify the existing .proto file and regenerate the code.

// 反例:protobuf 不支持动态扩展
message MyMessage {
  int32 my_field = 1;
}

// 如果要添加新的字段,需要修改已有的 .proto 文件并重新生成代码
message MyExtendedMessage {
  int32 my_field = 1;
  string my_new_field = 2;
}

This will lead to increased code incompatibility and maintenance difficulty.

Guess you like

Origin blog.csdn.net/weixin_44477424/article/details/131565350