这是我参与「第四届青训营 」笔记创作活动的的第5天
上回我们做完了grpc小项目的准备工作,这次我们把这个通信过程完全的实现,首先,grpc协议通信时是要区分客户端与服务端的,因此整个项目大致可以分为客户端代码与服务端代码。
这个项目的文件结构如下
\
服务端
NameNodeRpcServer.java,文件内容如下
package com.li.server;
import java.io.IOException;
import io.grpc.Server;
import io.grpc.ServerBuilder;
public class NameNodeRpcServer {
private Server server = null;
private int port = 8888;
public void start() throws IOException{
server = ServerBuilder
.forPort(this.port)
.addService(new NameNodeRpcServerImpl())
.build()
.start();
System.out.println("服务已启动,端口号为:" + this.port);
Runtime.getRuntime().addShutdownHook(new Thread(){
@Override
public void run() {
// TODO Auto-generated method stub
shutdown();
}
});
}
private void shutdown(){
if(this.server != null)
this.server.shutdown();
}
public void blockUntilShutdown() throws InterruptedException{
if(this.server != null){
this.server.awaitTermination();
}
}
}
在这里主要实现了启动服务,监听端口的,添加服务以及优雅关闭的功能。用Server与ServerBuilder构造服务端实体,forPort()设置端口,addService()添加服务以及addShutdownHook()用于在线程结束时执行关闭的操作,接下来我们实现一下NameNodeRpcServerImpl这个服务类,NameNodeRpcServerImpl.java这个文件的内容如下
package com.li.server;
import com.li.rpc.domain.DataRequest;
import com.li.rpc.domain.DataResponse;
import com.li.rpc.rpcservice.GrpcServiceApisGrpc.GrpcServiceApisImplBase;
import io.grpc.stub.StreamObserver;;
public class NameNodeRpcServerImpl extends GrpcServiceApisImplBase{
@Override
public void getName(DataRequest request, StreamObserver<DataResponse> responseObserver) {
// TODO Auto-generated method stub
String name = request.getName();
String id = request.getInstanceId();
String message = request.getData();
System.out.println("接收到DataRequest请求:");
System.out.println("name = " + name);
System.out.println("InstanceId = " + id);
System.out.println("message = " + message);
DataResponse response = DataResponse.newBuilder()
.setInstanceId("1111122222")
.setMessage("Good morning!" + name)
.build();
responseObserver.onNext(response);
responseObserver.onCompleted();
}
}
可以看到,这里需要继承我们在上次protobuf使用过程中生成的文件,即GrpcServiceApisGrpc.java,我们需要继承这个类里面的Base类,重写getName()方法,然后构造DataResponse并用responseObserver.onNext(response);返回构造的response就成功了,最后再来一个main方法来测试一下NameNodeServer。
即创建Server.java文件,文件内容如下
package com.li.server;
import java.io.IOException;
public class Server {
private NameNodeRpcServer server = null;
public static void main(String[] args) {
Server nameNode = new Server();
nameNode.start();
}
private void start(){
this.server = new NameNodeRpcServer();
try {
this.server.start();
this.server.blockUntilShutdown();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
客户端
客户端的结构与服务端类似,先建立连接,DataClient.java文件内容如下
package com.li.client;
import com.li.rpc.domain.DataRequest;
import com.li.rpc.domain.DataResponse;
import com.li.rpc.rpcservice.GrpcServiceApisGrpc.GrpcServiceApisBlockingStub;
import static com.li.rpc.rpcservice.GrpcServiceApisGrpc.newBlockingStub;
import java.util.concurrent.TimeUnit;
import io.grpc.ManagedChannel;
import io.grpc.ManagedChannelBuilder;
public class DataClient {
private GrpcServiceApisBlockingStub blockingStub = null;
private ManagedChannel channel = null;
public DataClient(String ip, int port){
this.channel = ManagedChannelBuilder.forTarget(ip + ":" + port).usePlaintext().build();
this.blockingStub = newBlockingStub(this.channel);
System.out.println("connected to server at " + ip + ":" + port);
Runtime.getRuntime().addShutdownHook(new Thread(){
@Override
public void run() {
try {
channel.shutdownNow().awaitTermination(5, TimeUnit.SECONDS);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
});
}
public DataResponse getName(DataRequest request){
return this.blockingStub.getName(request);
}
public void shutdown() throws InterruptedException{
this.channel.shutdown().awaitTermination(5L, TimeUnit.SECONDS);
}
}
可以看到我们呢使用ManagedChannelBuilder与套接字构建了一个与Server通信的Channel,然后通过protoc.exe生成的GrpcServiceApisGrpc.java中的newBlockingStub实例化服务实体,通过它就可以调用getName()这个方法,接下来我们构造一个DataRequest来请求服务,DataClientServer.java文件的内容如下
package com.li.client;
import com.li.rpc.domain.DataRequest;
import com.li.rpc.domain.DataResponse;
public class DataClientServer {
private String ip;
private int port;
private DataClient dataClient = null;
public DataClientServer(String ip, int port){
this.ip = ip;
this.port = port;
this.dataClient = new DataClient(this.ip, this.port);
}
public void sendMessage(String id, String name, String data){
DataRequest request = DataRequest.newBuilder()
.setData(data)
.setInstanceId(id)
.setName(name)
.build();
DataResponse response = dataClient.getName(request);
System.out.println("成功取得消息, Name为:" + response.getMessage());
}
public void shutdown() throws InterruptedException{
this.dataClient.shutdown();
}
}
请求的构造方法非常简单,最后我们再测试一下整个项目。创建Client.java,文件内容如下
package com.li.client;
public class Client {
private String ip = "localhost";
private int port = 8888;
DataClientServer clientServer = null;
public void start(){
this.clientServer = new DataClientServer(ip, port);
}
public void shutdown(){
if(this.clientServer != null)
try {
clientServer.shutdown();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
public void sendMessage(String id, String name, String data){
this.clientServer.sendMessage(id, name, data);
}
public static void main(String[] args) {
String id = "helloworld-";
String name = "client-01";
String data = "Good evening";
Client client = new Client();
client.start();
int count = 5;
for(int i = 0; i < count; i++)
client.sendMessage(id + i, name, data);
}
}
我们先启动Server.java,然后启动Client.java就可以看到两者的终端输出如下的内容
Server端的输出
Client端的输出
至此我们就会使用grpc的基本功能了,对它的工作流程有了一个大致的认识