Java 从零开始手写 RPC—如何实现客户端调用服务端?

开发 后端
写完了客户端和服务端,那么如何实现客户端和服务端的调用呢?下面就让我们一起来看一下。希望本文对你有所帮助!

[[429717]]

写完了客户端和服务端,那么如何实现客户端和服务端的调用呢?

下面就让我们一起来看一下。

接口定义

计算方法

  1. package com.github.houbb.rpc.common.service; 
  2.  
  3.  
  4. import com.github.houbb.rpc.common.model.CalculateRequest; 
  5. import com.github.houbb.rpc.common.model.CalculateResponse; 
  6.  
  7.  
  8. /** 
  9.  * <p> 计算服务接口 </p> 
  10.  * 
  11.  * <pre> Created: 2018/8/24 下午4:47  </pre> 
  12.  * <pre> Project: fake  </pre> 
  13.  * 
  14.  * @author houbinbin 
  15.  * @since 0.0.1 
  16.  */ 
  17. public interface Calculator { 
  18.  
  19.  
  20.     /** 
  21.      * 计算加法 
  22.      * @param request 请求入参 
  23.      * @return 返回结果 
  24.      */ 
  25.     CalculateResponse sum(final CalculateRequest request); 
  26.  
  27.  

pojo

对应的参数对象:

  • CalculateRequest
  1. package com.github.houbb.rpc.common.model; 
  2.  
  3.  
  4. import java.io.Serializable
  5.  
  6.  
  7. /** 
  8.  * <p> 请求入参 </p> 
  9.  * 
  10.  * <pre> Created: 2018/8/24 下午5:05  </pre> 
  11.  * <pre> Project: fake  </pre> 
  12.  * 
  13.  * @author houbinbin 
  14.  * @since 0.0.3 
  15.  */ 
  16. public class CalculateRequest implements Serializable { 
  17.  
  18.  
  19.     private static final long serialVersionUID = 6420751004355300996L; 
  20.  
  21.  
  22.     /** 
  23.      * 参数一 
  24.      */ 
  25.     private int one; 
  26.  
  27.  
  28.     /** 
  29.      * 参数二 
  30.      */ 
  31.     private int two; 
  32.  
  33.  
  34.     public CalculateRequest() { 
  35.     } 
  36.  
  37.  
  38.     public CalculateRequest(int one, int two) { 
  39.         this.one = one; 
  40.         this.two = two; 
  41.     } 
  42.  
  43.  
  44.     //getter setter toString 
  45.  
  46.  
  • CalculateResponse
  1. package com.github.houbb.rpc.common.model; 
  2.  
  3.  
  4. import java.io.Serializable
  5.  
  6.  
  7. /** 
  8.  * <p> 请求入参 </p> 
  9.  * 
  10.  * <pre> Created: 2018/8/24 下午5:05  </pre> 
  11.  * <pre> Project: fake  </pre> 
  12.  * 
  13.  * @author houbinbin 
  14.  * @since 0.0.3 
  15.  */ 
  16. public class CalculateResponse implements Serializable { 
  17.  
  18.  
  19.     private static final long serialVersionUID = -1972014736222511341L; 
  20.  
  21.  
  22.     /** 
  23.      * 是否成功 
  24.      */ 
  25.    private boolean success; 
  26.  
  27.  
  28.     /** 
  29.      * 二者的和 
  30.      */ 
  31.    private int sum
  32.  
  33.  
  34.     public CalculateResponse() { 
  35.     } 
  36.  
  37.  
  38.     public CalculateResponse(boolean success, int sum) { 
  39.         this.success = success; 
  40.         this.sum = sum
  41.     } 
  42.  
  43.  
  44.     //getter setter toString 

客户端

核心部分

RpcClient 需要添加对应的 Handler,调整如下:

  1. Bootstrap bootstrap = new Bootstrap(); 
  2. ChannelFuture channelFuture = bootstrap.group(workerGroup) 
  3.         .channel(NioSocketChannel.class) 
  4.         .option(ChannelOption.SO_KEEPALIVE, true
  5.         .handler(new ChannelInitializer<Channel>(){ 
  6.             @Override 
  7.             protected void initChannel(Channel ch) throws Exception { 
  8.                 ch.pipeline() 
  9.                         .addLast(new LoggingHandler(LogLevel.INFO)) 
  10.                         .addLast(new CalculateRequestEncoder()) 
  11.                         .addLast(new CalculateResponseDecoder()) 
  12.                         .addLast(new RpcClientHandler()); 
  13.             } 
  14.         }) 
  15.         .connect(RpcConstant.ADDRESS, port) 
  16.         .syncUninterruptibly(); 

netty 中的 handler 泳道设计的非常优雅,让我们的代码可以非常灵活地进行拓展。

接下来我们看一下对应的实现。

RpcClientHandler

  1. package com.github.houbb.rpc.client.handler; 
  2.  
  3.  
  4. import com.github.houbb.log.integration.core.Log; 
  5. import com.github.houbb.log.integration.core.LogFactory; 
  6. import com.github.houbb.rpc.client.core.RpcClient; 
  7. import com.github.houbb.rpc.common.model.CalculateRequest; 
  8. import com.github.houbb.rpc.common.model.CalculateResponse; 
  9. import io.netty.channel.ChannelHandlerContext; 
  10. import io.netty.channel.SimpleChannelInboundHandler; 
  11.  
  12.  
  13. /** 
  14.  * <p> 客户端处理类 </p> 
  15.  * 
  16.  * <pre> Created: 2019/10/16 11:30 下午  </pre> 
  17.  * <pre> Project: rpc  </pre> 
  18.  * 
  19.  * @author houbinbin 
  20.  * @since 0.0.2 
  21.  */ 
  22. public class RpcClientHandler extends SimpleChannelInboundHandler { 
  23.  
  24.  
  25.     private static final Log log = LogFactory.getLog(RpcClient.class); 
  26.  
  27.  
  28.     @Override 
  29.     public void channelActive(ChannelHandlerContext ctx) throws Exception { 
  30.         CalculateRequest request = new CalculateRequest(1, 2); 
  31.  
  32.  
  33.         ctx.writeAndFlush(request); 
  34.         log.info("[Client] request is :{}", request); 
  35.     } 
  36.  
  37.  
  38.     @Override 
  39.     protected void channelRead0(ChannelHandlerContext ctx, Object msg) throws Exception { 
  40.         CalculateResponse response = (CalculateResponse)msg; 
  41.         log.info("[Client] response is :{}", response); 
  42.     } 
  43.  
  44.  

这里比较简单,channelActive 中我们直接发起调用,入参的对象为了简单,此处固定写死。

channelRead0 中监听服务端的相应结果,并做日志输出。

CalculateRequestEncoder

请求参数是一个对象,netty 是无法直接传输的,我们将其转换为基本对象:

  1. package com.github.houbb.rpc.client.encoder; 
  2.  
  3.  
  4. import com.github.houbb.rpc.common.model.CalculateRequest; 
  5. import io.netty.buffer.ByteBuf; 
  6. import io.netty.channel.ChannelHandlerContext; 
  7. import io.netty.handler.codec.MessageToByteEncoder; 
  8.  
  9.  
  10. /** 
  11.  * @author binbin.hou 
  12.  * @since 0.0.3 
  13.  */ 
  14. public class CalculateRequestEncoder extends MessageToByteEncoder<CalculateRequest> { 
  15.  
  16.  
  17.     @Override 
  18.     protected void encode(ChannelHandlerContext ctx, CalculateRequest msg, ByteBuf out) throws Exception { 
  19.         int one = msg.getOne(); 
  20.         int two = msg.getTwo(); 
  21.  
  22.  
  23.         out.writeInt(one); 
  24.         out.writeInt(two); 
  25.     } 
  26.  
  27.  

CalculateResponseDecoder

针对服务端的响应,也是同理。

我们需要把基本的类型,封装转换为我们需要的对象。

  1. package com.github.houbb.rpc.client.decoder; 
  2.  
  3.  
  4. import com.github.houbb.rpc.common.model.CalculateResponse; 
  5. import io.netty.buffer.ByteBuf; 
  6. import io.netty.channel.ChannelHandlerContext; 
  7. import io.netty.handler.codec.ByteToMessageDecoder; 
  8.  
  9.  
  10. import java.util.List; 
  11.  
  12.  
  13. /** 
  14.  * 响应参数解码 
  15.  * @author binbin.hou 
  16.  * @since 0.0.3 
  17.  */ 
  18. public class CalculateResponseDecoder extends ByteToMessageDecoder { 
  19.  
  20.  
  21.     @Override 
  22.     protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception { 
  23.         boolean success = in.readBoolean(); 
  24.         int sum = in.readInt(); 
  25.  
  26.  
  27.         CalculateResponse response = new CalculateResponse(success, sum); 
  28.         out.add(response); 
  29.     } 
  30.  
  31.  

服务端

设置处理类

RpcServer 中的处理类要稍微调整一下,其他的保持不变。

  1. ServerBootstrap serverBootstrap = new ServerBootstrap(); 
  2. serverBootstrap.group(workerGroup, bossGroup) 
  3.         .channel(NioServerSocketChannel.class) 
  4.         // 打印日志 
  5.         .handler(new LoggingHandler(LogLevel.INFO)) 
  6.         .childHandler(new ChannelInitializer<Channel>() { 
  7.             @Override 
  8.             protected void initChannel(Channel ch) throws Exception { 
  9.                 ch.pipeline() 
  10.                         .addLast(new CalculateRequestDecoder()) 
  11.                         .addLast(new CalculateResponseEncoder()) 
  12.                         .addLast(new RpcServerHandler()); 
  13.             } 
  14.         }) 
  15.         // 这个参数影响的是还没有被accept 取出的连接 
  16.         .option(ChannelOption.SO_BACKLOG, 128) 
  17.         // 这个参数只是过一段时间内客户端没有响应,服务端会发送一个 ack 包,以判断客户端是否还活着。 
  18.         .childOption(ChannelOption.SO_KEEPALIVE, true); 

RpcServerHandler

一开始这里是空实现,我们来添加一下对应的实现。

  1. package com.github.houbb.rpc.server.handler; 
  2.  
  3.  
  4. import com.github.houbb.log.integration.core.Log; 
  5. import com.github.houbb.log.integration.core.LogFactory; 
  6. import com.github.houbb.rpc.common.model.CalculateRequest; 
  7. import com.github.houbb.rpc.common.model.CalculateResponse; 
  8. import com.github.houbb.rpc.common.service.Calculator; 
  9. import com.github.houbb.rpc.server.service.CalculatorService; 
  10. import io.netty.channel.ChannelHandlerContext; 
  11. import io.netty.channel.SimpleChannelInboundHandler; 
  12.  
  13.  
  14. /** 
  15.  * @author binbin.hou 
  16.  * @since 0.0.1 
  17.  */ 
  18. public class RpcServerHandler extends SimpleChannelInboundHandler { 
  19.  
  20.  
  21.     private static final Log log = LogFactory.getLog(RpcServerHandler.class); 
  22.  
  23.  
  24.     @Override 
  25.     public void channelActive(ChannelHandlerContext ctx) throws Exception { 
  26.         final String id = ctx.channel().id().asLongText(); 
  27.         log.info("[Server] channel {} connected " + id); 
  28.     } 
  29.  
  30.  
  31.     @Override 
  32.     protected void channelRead0(ChannelHandlerContext ctx, Object msg) throws Exception { 
  33.         final String id = ctx.channel().id().asLongText(); 
  34.  
  35.  
  36.         CalculateRequest request = (CalculateRequest)msg; 
  37.         log.info("[Server] receive channel {} request: {} from ", id, request); 
  38.  
  39.  
  40.         Calculator calculator = new CalculatorService(); 
  41.         CalculateResponse response = calculator.sum(request); 
  42.  
  43.  
  44.         // 回写到 client 端 
  45.         ctx.writeAndFlush(response); 
  46.         log.info("[Server] channel {} response {}", id, response); 
  47.     } 
  48.  
  49.  

读取到客户端的访问之后,我们获取到计算的入参 CalculateRequest,然后调用 sum 方法,获取到对应的 CalculateResponse,将结果通知客户端。

CalculateRequestDecoder

这里和客户端是一一对应的,我们首先把 netty 传递的基本类型转换为 CalculateRequest 对象。

  1. package com.github.houbb.rpc.server.decoder; 
  2.  
  3.  
  4. import com.github.houbb.rpc.common.model.CalculateRequest; 
  5. import io.netty.buffer.ByteBuf; 
  6. import io.netty.channel.ChannelHandlerContext; 
  7. import io.netty.handler.codec.ByteToMessageDecoder; 
  8.  
  9.  
  10. import java.util.List; 
  11.  
  12.  
  13. /** 
  14.  * 请求参数解码 
  15.  * @author binbin.hou 
  16.  * @since 0.0.3 
  17.  */ 
  18. public class CalculateRequestDecoder extends ByteToMessageDecoder { 
  19.  
  20.  
  21.     @Override 
  22.     protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception { 
  23.         int one = in.readInt(); 
  24.         int two = in.readInt(); 
  25.  
  26.  
  27.         CalculateRequest request = new CalculateRequest(one, two); 
  28.         out.add(request); 
  29.     } 
  30.  
  31.  

CalculateResponseEncoder

这里和客户端类似,我们需要把 response 转换为基本类型进行网络传输。

  1. package com.github.houbb.rpc.server.encoder; 
  2.  
  3.  
  4. import com.github.houbb.rpc.common.model.CalculateResponse; 
  5. import io.netty.buffer.ByteBuf; 
  6. import io.netty.channel.ChannelHandlerContext; 
  7. import io.netty.handler.codec.MessageToByteEncoder; 
  8.  
  9.  
  10. /** 
  11.  * @author binbin.hou 
  12.  * @since 0.0.3 
  13.  */ 
  14. public class CalculateResponseEncoder extends MessageToByteEncoder<CalculateResponse> { 
  15.  
  16.  
  17.     @Override 
  18.     protected void encode(ChannelHandlerContext ctx, CalculateResponse msg, ByteBuf out) throws Exception { 
  19.         boolean success = msg.isSuccess(); 
  20.         int result = msg.getSum(); 
  21.         out.writeBoolean(success); 
  22.         out.writeInt(result); 
  23.     } 
  24.  
  25.  

CalculatorService

服务端对应的实现类。

  1. public class CalculatorService implements Calculator { 
  2.  
  3.  
  4.     @Override 
  5.     public CalculateResponse sum(CalculateRequest request) { 
  6.         int sum = request.getOne()+request.getTwo(); 
  7.  
  8.  
  9.         return new CalculateResponse(truesum); 
  10.     } 
  11.  
  12.  

测试

服务端

启动服务端:

  1. new RpcServer().start(); 

服务端启动日志:

  1. [DEBUG] [2021-10-05 11:53:11.795] [main] [c.g.h.l.i.c.LogFactory.setImplementation] - Logging initialized using 'class com.github.houbb.log.integration.adaptors.stdout.StdOutExImpl' adapter. 
  2. [INFO] [2021-10-05 11:53:11.807] [Thread-0] [c.g.h.r.s.c.RpcServer.run] - RPC 服务开始启动服务端 
  3. 十月 05, 2021 11:53:13 上午 io.netty.handler.logging.LoggingHandler channelRegistered 
  4. 信息: [id: 0xd399474f] REGISTERED 
  5. 十月 05, 2021 11:53:13 上午 io.netty.handler.logging.LoggingHandler bind 
  6. 信息: [id: 0xd399474f] BIND: 0.0.0.0/0.0.0.0:9527 
  7. 十月 05, 2021 11:53:13 上午 io.netty.handler.logging.LoggingHandler channelActive 
  8. 信息: [id: 0xd399474f, L:/0:0:0:0:0:0:0:0:9527] ACTIVE 
  9. [INFO] [2021-10-05 11:53:13.101] [Thread-0] [c.g.h.r.s.c.RpcServer.run] - RPC 服务端启动完成,监听【9527】端口 

客户端

启动客户端:

  1. new RpcClient().start(); 

日志如下:

  1. [DEBUG] [2021-10-05 11:54:12.158] [main] [c.g.h.l.i.c.LogFactory.setImplementation] - Logging initialized using 'class com.github.houbb.log.integration.adaptors.stdout.StdOutExImpl' adapter. 
  2. [INFO] [2021-10-05 11:54:12.164] [Thread-0] [c.g.h.r.c.c.RpcClient.run] - RPC 服务开始启动客户端 
  3. 十月 05, 2021 11:54:13 上午 io.netty.handler.logging.LoggingHandler channelRegistered 
  4. 信息: [id: 0x4d75c580] REGISTERED 
  5. 十月 05, 2021 11:54:13 上午 io.netty.handler.logging.LoggingHandler connect 
  6. 信息: [id: 0x4d75c580] CONNECT: /127.0.0.1:9527 
  7. 十月 05, 2021 11:54:13 上午 io.netty.handler.logging.LoggingHandler channelActive 
  8. 信息: [id: 0x4d75c580, L:/127.0.0.1:54030 - R:/127.0.0.1:9527] ACTIVE 
  9. [INFO] [2021-10-05 11:54:13.403] [Thread-0] [c.g.h.r.c.c.RpcClient.run] - RPC 服务启动客户端完成,监听端口:9527 
  10. 十月 05, 2021 11:54:13 上午 io.netty.handler.logging.LoggingHandler write 
  11. 信息: [id: 0x4d75c580, L:/127.0.0.1:54030 - R:/127.0.0.1:9527] WRITE: 8B 
  12.          +-------------------------------------------------+ 
  13.          |  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f | 
  14. +--------+-------------------------------------------------+----------------+ 
  15. |00000000| 00 00 00 01 00 00 00 02                         |........        | 
  16. +--------+-------------------------------------------------+----------------+ 
  17. 十月 05, 2021 11:54:13 上午 io.netty.handler.logging.LoggingHandler flush 
  18. 信息: [id: 0x4d75c580, L:/127.0.0.1:54030 - R:/127.0.0.1:9527] FLUSH 
  19. [INFO] [2021-10-05 11:54:13.450] [nioEventLoopGroup-2-1] [c.g.h.r.c.c.RpcClient.channelActive] - [Client] request is :CalculateRequest{one=1, two=2} 
  20. 十月 05, 2021 11:54:13 上午 io.netty.handler.logging.LoggingHandler channelRead 
  21. 信息: [id: 0x4d75c580, L:/127.0.0.1:54030 - R:/127.0.0.1:9527] READ: 5B 
  22.          +-------------------------------------------------+ 
  23.          |  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f | 
  24. +--------+-------------------------------------------------+----------------+ 
  25. |00000000| 01 00 00 00 03                                  |.....           | 
  26. +--------+-------------------------------------------------+----------------+ 
  27. 十月 05, 2021 11:54:13 上午 io.netty.handler.logging.LoggingHandler channelReadComplete 
  28. 信息: [id: 0x4d75c580, L:/127.0.0.1:54030 - R:/127.0.0.1:9527] READ COMPLETE 
  29. [INFO] [2021-10-05 11:54:13.508] [nioEventLoopGroup-2-1] [c.g.h.r.c.c.RpcClient.channelRead0] - [Client] response is :CalculateResponse{success=truesum=3} 

可以看到,输出了对应的请求参数和响应结果。

当然,此时服务端也有对应的新增日志:

  1. 十月 05, 2021 11:54:13 上午 io.netty.handler.logging.LoggingHandler channelRead 
  2. 信息: [id: 0xd399474f, L:/0:0:0:0:0:0:0:0:9527] READ: [id: 0xbc9f5927, L:/127.0.0.1:9527 - R:/127.0.0.1:54030] 
  3. 十月 05, 2021 11:54:13 上午 io.netty.handler.logging.LoggingHandler channelReadComplete 
  4. 信息: [id: 0xd399474f, L:/0:0:0:0:0:0:0:0:9527] READ COMPLETE 
  5. [INFO] [2021-10-05 11:54:13.432] [nioEventLoopGroup-2-1] [c.g.h.r.s.h.RpcServerHandler.channelActive] - [Server] channel {} connected 00e04cfffe360988-00001d34-00000001-2a80d950d8166c0c-bc9f5927 
  6. [INFO] [2021-10-05 11:54:13.495] [nioEventLoopGroup-2-1] [c.g.h.r.s.h.RpcServerHandler.channelRead0] - [Server] receive channel 00e04cfffe360988-00001d34-00000001-2a80d950d8166c0c-bc9f5927 request: CalculateRequest{one=1, two=2} from  
  7. [INFO] [2021-10-05 11:54:13.505] [nioEventLoopGroup-2-1] [c.g.h.r.s.h.RpcServerHandler.channelRead0] - [Server] channel 00e04cfffe360988-00001d34-00000001-2a80d950d8166c0c-bc9f5927 response CalculateResponse{success=truesum=3} 

 

责任编辑:姜华 来源: 今日头条
相关推荐

2021-10-14 08:39:17

Java Netty Java 基础

2021-10-21 08:21:10

Java Reflect Java 基础

2021-10-27 08:10:15

Java 客户端 Java 基础

2021-10-13 08:21:52

Java websocket Java 基础

2021-10-20 08:05:18

Java 序列化 Java 基础

2009-08-21 15:36:41

服务端与客户端

2009-08-21 15:54:40

服务端与客户端

2010-03-18 17:47:07

Java 多客户端通信

2021-10-29 08:07:30

Java timeout Java 基础

2024-03-06 14:58:52

客户端微服务架构

2023-11-17 09:13:36

2011-09-09 09:44:23

WCF

2009-08-21 16:14:52

服务端与客户端通信

2009-08-21 15:59:22

服务端与客户端通信

2023-03-06 08:01:56

MySQLCtrl + C

2010-11-19 14:22:04

oracle服务端

2023-04-03 08:13:05

MySQLCtrl + C

2014-06-01 11:03:13

VDI零客户端

2010-05-31 10:11:32

瘦客户端

2023-01-29 23:51:07

微服务框架Go
点赞
收藏

51CTO技术栈公众号