修炼者
修炼者
发布于 2025-10-14 / 28 阅读
0
0

gRPC

本文介绍下ubuntu下,基于C++或Java从源码构建gRPC,并编写典型的服务器和客户端。

系统环境

$ lsb_release -a
No LSB modules are available.
Distributor ID:	Ubuntu
Description:	Ubuntu 24.04.3 LTS
Release:	24.04
Codename:	noble

基于C++的操作步骤

设置环境变量

export MY_INSTALL_DIR=$HOME/.local
mkdir -p $MY_INSTALL_DIR
export PATH="$MY_INSTALL_DIR/bin:$PATH"

安装cmake等工具

sudo apt install -y cmake
cmake --version # 确认版本在3.16以上
sudo apt install -y build-essential autoconf libtool pkg-config

下载gRPC源码

git clone --recurse-submodules -b v1.74.0 --depth 1 --shallow-submodules https://github.com/grpc/grpc

构建并安装grpc

这里会将grpc和protobuf都安装好。整个构建时间较长,make这里花费了差不多30分钟。

这里官方建议通过指定CMAKE_INSTALL_PREFIX实现本地安装,不然移除操作比较繁琐。

# 进入grpc源码根目录
cd grpc
mkdir -p cmake/build
pushd cmake/build
cmake -DgRPC_INSTALL=ON \
      -DgRPC_BUILD_TESTS=OFF \
      -DCMAKE_CXX_STANDARD=17 \
      -DCMAKE_INSTALL_PREFIX=$MY_INSTALL_DIR \
      ../..
make -j 4
make install
popd

构建实例并体验

# grpc根目录 :安装上节构建成功后,将回到这里
cd examples/cpp/helloworld

pushd cmake/build
cmake -DCMAKE_PREFIX_PATH=$MY_INSTALL_DIR ../..
make -j 4

# 体验下echo服务与客户端
./greeter_server &
./greeter_client

修改实例并构建

grpc基于protobuf的协议开发,基本步骤是:

  1. 编写proto文件

  2. 基于proto生成对应的桩子文件.cc.hh

  3. 修改服务端文件,实现方法

  4. 修改客户端文件,实现方法

  5. 修改客户端文件,增加调用代码

// helloworld.proto
syntax = "proto3";

option java_multiple_files = true;
option java_package = "io.grpc.examples.helloworld";
option java_outer_classname = "HelloWorldProto";
option objc_class_prefix = "HLW";

package helloworld;

// The greeting service definition.
service Greeter {
  // Sends a greeting
  rpc SayHello (HelloRequest) returns (HelloReply) {}

  rpc SayHelloStreamReply (HelloRequest) returns (stream HelloReply) {}

  rpc SayHelloBidiStream (stream HelloRequest) returns (stream HelloReply) {}

  // Sends another greeting
  rpc SayHelloAgain (HelloRequest) returns (HelloReply) {}
}

// The request message containing the user's name.
message HelloRequest {
  string name = 1;
}

// The response message containing the greetings
message HelloReply {
  string message = 1;
}

构建(make -j 4)将基于proto生成对应的服务端和客户端桩子代码。然后可以在服务端代码实现:

//  greeter_server.cc
class GreeterServiceImpl final : public Greeter::Service {
  Status SayHello(ServerContext* context, const HelloRequest* request,
                  HelloReply* reply) override {
    std::string prefix("Hello ");
    reply->set_message(prefix + request->name());
    return Status::OK;
  }
  Status SayHelloAgain(ServerContext* context, const HelloRequest* request,
                       HelloReply* reply) override {
    std::string prefix("Hello again ");
    reply->set_message(prefix + request->name());
    return Status::OK;
  }
};
// greeter_client.cc
class GreeterClient {
 public:
  GreeterClient(std::shared_ptr<Channel> channel)
      : stub_(Greeter::NewStub(channel)) {}

  // Assembles the client's payload, sends it and presents the response back
  // from the server.
  std::string SayHello(const std::string& user) {
    // Data we are sending to the server.
    HelloRequest request;
    request.set_name(user);

    // Container for the data we expect from the server.
    HelloReply reply;

    // Context for the client. It could be used to convey extra information to
    // the server and/or tweak certain RPC behaviors.
    ClientContext context;

    // The actual RPC.
    Status status = stub_->SayHello(&context, request, &reply);

    // Act upon its status.
    if (status.ok()) {
      return reply.message();
    } else {
      std::cout << status.error_code() << ": " << status.error_message()
                << std::endl;
      return "RPC failed";
    }
  }
  std::string SayHelloAgain(const std::string& user) {
    // Follows the same pattern as SayHello.
    HelloRequest request;
    request.set_name(user);
    HelloReply reply;
    ClientContext context;

    // Here we can use the stub's newly available method we just added.
    Status status = stub_->SayHelloAgain(&context, request, &reply);
    if (status.ok()) {
      return reply.message();
    } else {
      std::cout << status.error_code() << ": " << status.error_message()
                << std::endl;
      return "RPC failed";
    }
  }

 private:
  std::unique_ptr<Greeter::Stub> stub_;
};
// greeter_client.cc
int main(int argc, char** argv) {
  absl::ParseCommandLine(argc, argv);
  // Instantiate the client. It requires a channel, out of which the actual RPCs
  // are created. This channel models a connection to an endpoint specified by
  // the argument "--target=" which is the only expected argument.
  std::string target_str = absl::GetFlag(FLAGS_target);
  // We indicate that the channel isn't authenticated (use of
  // InsecureChannelCredentials()).
  GreeterClient greeter(
      grpc::CreateChannel(target_str, grpc::InsecureChannelCredentials()));
  std::string user("world");
  std::string reply = greeter.SayHello(user);
  std::cout << "Greeter received: " << reply << std::endl;

  reply = greeter.SayHelloAgain(user);
  std::cout << "Greeter received: " << reply << std::endl;

  return 0;
}

基于Java的操作步骤

JDK环境

$ javac -version
javac 1.8.0_462
$ java -version
openjdk version "1.8.0_462"
OpenJDK Runtime Environment (build 1.8.0_462-8u462-ga~us1-0ubuntu2~24.04.2-b08)
OpenJDK 64-Bit Server VM (build 25.462-b08, mixed mode)

下载gRPC源码

git clone -b v1.75.0 --depth 1 https://github.com/grpc/grpc-java

构建实例并体验

cd grpc-java/examples


#如果无法访问外网,需要手动下载提示的url,放到~/.gradle/...../ 对应版本的目录下
# 我这里下载后是放到这里  ~/.gradle/wrapper/dists/gradle-8.10.2-bin/a04bxjujx95o3nb99gddekhwo
./gradlew installDist  

# 体验实例
./build/install/examples/bin/hello-world-server &
./build/install/examples/bin/hello-world-client

修改实例并构建

Java相对C更简单,修改proto、实现server、client即可。

#  HelloWorldServer.java
static class GreeterImpl extends GreeterGrpc.GreeterImplBase {

    @Override
    public void sayHello(HelloRequest req, StreamObserver<HelloReply> responseObserver) {
      HelloReply reply = HelloReply.newBuilder().setMessage("Hello " + req.getName()).build();
      responseObserver.onNext(reply);
      responseObserver.onCompleted();
    }

    @Override
    public void sayHelloAgain(HelloRequest req, StreamObserver<HelloReply> responseObserver) {
      // Generate another greeting message for the new method.
      HelloReply reply = HelloReply.newBuilder().setMessage("Hello again " + req.getName()).build();

      // Send the reply back to the client.
      responseObserver.onNext(reply);

      // Indicate that no further messages will be sent to the client.
      responseObserver.onCompleted();
    }
  }
# HelloWorldClientjava
 public void greet(String name) {
    logger.info("Will try to greet " + name + " ...");
    HelloRequest request = HelloRequest.newBuilder().setName(name).build();
    HelloReply response;
    try {
      response = blockingStub.sayHello(request);
    } catch (StatusRuntimeException e) {
      logger.log(Level.WARNING, "RPC failed: {0}", e.getStatus());
      return;
    }
    logger.info("Greeting: " + response.getMessage());

    try {
      // Call the new method on the server.
      response = blockingStub.sayHelloAgain(request);
    } catch (StatusRuntimeException e) {
      // Log a warning if the RPC fails.
      logger.log(Level.WARNING, "RPC failed: {0}", e.getStatus());
      return;
    }

    // Log the response from the new method.
    logger.info("Greeting: " + response.getMessage());
  }

参考资料

  1. grpc官方文档 https://grpc.io/docs/languages/cpp/quickstart/

  2. grpc源码 https://github.com/grpc/grpc/

  3. Java文档 https://grpc.io/docs/languages/java/quickstart/


评论