google多语言通信框架gRPC系列(一)概述
gRPC概述
3/26/2016 9:16:08 AM
目录
一直在寻找多平台多语言的通信框架,微软的WCF框架很强大和灵活,虽然也能通过自定义绑定和其他技术的客户端通信,但是始终没有实现多平台的技术框架的统一。google的gRPC是一个不错的选择,相比于类似框架Thrift等,google的解决方案更成熟和通用。不足的是由于刚刚开源,使用范围有限,国内资料更少。例如仅仅编译C++的gRPC,花了我两天的时间。在这个系列中,我会逐一介绍各个语言使用gRPC的细节。
gRPC是google2015年初开源的通信框架。
使用gRPC可以在客户端调用不同机器上的服务端的方法,而客户端和服务端的开发语言和运行环境可以有很多种,基本涵盖了主流语言和平台。双方交互的协议可以在proto文件中定义,客户端和服务端可以很方便的通过工具生成协议和代理代码。而消息的编码是采用google protocol buffer
,数据量小、速度快。
下图是一个简单的gRPC使用的场景,服务端采用C++开发,客户端可以是anroid,和ruby。
gRPC具有以下特点:
- 基于 HTTP/2, 继而提供了连接多路复用、Body 和 Header 压缩等机制。可以节省带宽、降低TCP链接次数、节省CPU使用和延长电池寿命等。
- 支持主流开发语言(C, C++, Python, PHP, Ruby, NodeJS, C#, Objective-C、Golang、Java)
- IDL (Interface Definition Language) 层使用了 Protocol Buffers, 非常适合团队的接口设计
下面是C#、C++、java的客户端和服务端的例子,这些客户端都向服务端传递一个姓名XXX
,服务端返回 hello XXX
字符串。
作为通信协议的proto文件
无论使用何种语言创建客户端和服务端,都依照相同的proto文件。proto文件定义了协议接口和数据格式。不同的语言都依照proto文件生成服务端的接口和服务类、客户端的服务代理,只是生成工具的命令不同。
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) {}
}
// The request message containing the user's name.
message HelloRequest {
string name = 1;
}
// The response message containing the greetings
message HelloReply {
string message = 1;
}
C#服务端和客户端代码
C#的客户端代理是由工具生成的,所以很简单。
客户端
public static void Main(string[] args)
{
Channel channel = new Channel("127.0.0.1:50051", ChannelCredentials.Insecure);
var client = Greeter.NewClient(channel);
String user = "paul";
var reply = client.SayHello(new HelloRequest { Name = user });
Console.WriteLine("Greeting: " + reply.Message);
channel.ShutdownAsync().Wait();
Console.WriteLine("Press any key to exit...");
Console.ReadKey();
}
服务端
class GreeterImpl : Greeter.IGreeter
{
// Server side handler of the SayHello RPC
public Task<HelloReply> SayHello(HelloRequest request, ServerCallContext context)
{
return Task.FromResult(new HelloReply { Message = "Hello " + request.Name });
}
}
class Program
{
const int Port = 50051;
public static void Main(string[] args)
{
Server server = new Server
{
Services = { Greeter.BindService(new GreeterImpl()) },
Ports = { new ServerPort("localhost", Port, ServerCredentials.Insecure) }
};
server.Start();
Console.WriteLine("Greeter server listening on port " + Port);
Console.WriteLine("Press any key to stop the server...");
Console.ReadKey();
server.ShutdownAsync().Wait();
}
}
C++的服务端和客户端代码
C++的客户端代理无法通过工具生成,需要手动编写。
//C++客户端
class GreeterClient {
public:
GreeterClient(std::shared_ptr<Channel> channel)
: stub_(Greeter::NewStub(channel)) {}
// Assambles 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 {
return "RPC failed";
}
}
private:
std::unique_ptr<Greeter::Stub> stub_;
};
int main(int argc, char** argv) {
// Instantiate the client. It requires a channel, out of which the actual RPCs
// are created. This channel models a connection to an endpoint (in this case,
// localhost at port 50051). We indicate that the channel isn't authenticated
// (use of InsecureChannelCredentials()).
GreeterClient greeter(grpc::CreateChannel(
"localhost:50051", grpc::InsecureChannelCredentials()));
std::string user("world");
std::string reply = greeter.SayHello(user);
std::cout << "Greeter received: " << reply << std::endl;
return 0;
}
//C++服务端
// Logic and data behind the server's behavior.
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;
}
};
void RunServer() {
std::string server_address("0.0.0.0:50051");
GreeterServiceImpl service;
ServerBuilder builder;
// Listen on the given address without any authentication mechanism.
builder.AddListeningPort(server_address, grpc::InsecureServerCredentials());
// Register "service" as the instance through which we'll communicate with
// clients. In this case it corresponds to an *synchronous* service.
builder.RegisterService(&service);
// Finally assemble the server.
std::unique_ptr<Server> server(builder.BuildAndStart());
std::cout << "Server listening on " << server_address << std::endl;
// Wait for the server to shutdown. Note that some other thread must be
// responsible for shutting down the server for this call to ever return.
server->Wait();
}
int main(int argc, char** argv) {
RunServer();
return 0;
}
java的服务端和客户端代码
gRPC本身不支持java语言,但是使用java实现了gRPC,框架是gRPC-java
。使用gRPC-java
一样可以与gRPC的客户端和服务端进行交互。
java客户端代码:
和C++一样,java的客户端仍然不能直接自动生成服务代理类,需要手动创建。
package io.grpc.examples.helloworld;
import io.grpc.ManagedChannel;
import io.grpc.ManagedChannelBuilder;
import io.grpc.StatusRuntimeException;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* A simple client that requests a greeting from the {@link HelloWorldServer}.
*/
public class HelloWorldClient {
private static final Logger logger = Logger.getLogger(HelloWorldClient.class.getName());
private final ManagedChannel channel;
private final GreeterGrpc.GreeterBlockingStub blockingStub;
/** Construct client connecting to HelloWorld server at {@code host:port}. */
public HelloWorldClient(String host, int port) {
channel = ManagedChannelBuilder.forAddress(host, port)
.usePlaintext(true)
.build();
blockingStub = GreeterGrpc.newBlockingStub(channel);
}
public void shutdown() throws InterruptedException {
channel.shutdown().awaitTermination(5, TimeUnit.SECONDS);
}
/** Say hello to server. */
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());
}
/**
* Greet server. If provided, the first element of {@code args} is the name to use in the
* greeting.
*/
public static void main(String[] args) throws Exception {
HelloWorldClient client = new HelloWorldClient("localhost", 50051);
try {
/* Access a service running on the local machine on port 50051 */
String user = "world";
if (args.length > 0) {
user = args[0]; /* Use the arg as the name to greet if provided */
}
client.greet(user);
} finally {
client.shutdown();
}
}
}
java服务端代码:
package io.grpc.examples.helloworld;
import io.grpc.Server;
import io.grpc.ServerBuilder;
import io.grpc.stub.StreamObserver;
import java.io.IOException;
import java.util.logging.Logger;
/**
* Server that manages startup/shutdown of a {@code Greeter} server.
*/
public class HelloWorldServer {
private static final Logger logger = Logger.getLogger(HelloWorldServer.class.getName());
/* The port on which the server should run */
private int port = 50051;
private Server server;
private void start() throws IOException {
server = ServerBuilder.forPort(port)
.addService(GreeterGrpc.bindService(new GreeterImpl()))
.build()
.start();
logger.info("Server started, listening on " + port);
Runtime.getRuntime().addShutdownHook(new Thread() {
@Override
public void run() {
// Use stderr here since the logger may have been reset by its JVM shutdown hook.
System.err.println("*** shutting down gRPC server since JVM is shutting down");
HelloWorldServer.this.stop();
System.err.println("*** server shut down");
}
});
}
private void stop() {
if (server != null) {
server.shutdown();
}
}
/**
* Await termination on the main thread since the grpc library uses daemon threads.
*/
private void blockUntilShutdown() throws InterruptedException {
if (server != null) {
server.awaitTermination();
}
}
/**
* Main launches the server from the command line.
*/
public static void main(String[] args) throws IOException, InterruptedException {
final HelloWorldServer server = new HelloWorldServer();
server.start();
server.blockUntilShutdown();
}
private class GreeterImpl implements GreeterGrpc.Greeter {
@Override
public void sayHello(HelloRequest req, StreamObserver<HelloReply> responseObserver) {
HelloReply reply = HelloReply.newBuilder().setMessage("Hello " + req.getName()).build();
responseObserver.onNext(reply);
responseObserver.onCompleted();
}
}
}