本文介绍下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的协议开发,基本步骤是:
编写proto文件
基于proto生成对应的桩子文件.cc.hh
修改服务端文件,实现方法
修改客户端文件,实现方法
修改客户端文件,增加调用代码
// 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());
}