|
|
|
|
公众号矩阵

Java 从零开始手写 RPC-Netty4 实现客户端和服务端

Netty是由JBOSS提供的一个java开源框架,现为 Github上的独立项目。Netty提供异步的、事件驱动的网络应用程序框架和工具,用以快速开发高性能、高可靠性的网络服务器和客户端程序。

作者:老马啸西风来源:今日头条|2021-10-14 08:39

说明

上一篇代码基于 socket 的实现非常简单,但是对于实际生产,一般使用 netty。

至于 netty 的优点可以参考:

为什么选择 netty?[1]

http://houbb.github.io/2019/05/10/netty-definitive-gudie-04-why-netty

java 从零开始手写 RPC (02)-netty4 实现客户端和服务端

代码实现

maven 引入

  1. <dependency> 
  2.     <groupId>io.netty</groupId> 
  3.     <artifactId>netty-all</artifactId> 
  4.     <version>${netty.version}</version> 
  5. </dependency> 

引入 netty 对应的 maven 包,此处为 4.1.17.Final。

服务端代码实现

netty 的服务端启动代码是比较固定的。

  1. package com.github.houbb.rpc.server.core; 
  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.server.constant.RpcServerConst; 
  7. import com.github.houbb.rpc.server.handler.RpcServerHandler; 
  8. import io.netty.bootstrap.ServerBootstrap; 
  9. import io.netty.channel.*; 
  10. import io.netty.channel.nio.NioEventLoopGroup; 
  11. import io.netty.channel.socket.nio.NioServerSocketChannel; 
  12.  
  13.  
  14. /** 
  15.  * rpc 服务端 
  16.  * @author binbin.hou 
  17.  * @since 0.0.1 
  18.  */ 
  19. public class RpcServer extends Thread { 
  20.  
  21.  
  22.     private static final Log log = LogFactory.getLog(RpcServer.class); 
  23.  
  24.  
  25.     /** 
  26.      * 端口号 
  27.      */ 
  28.     private final int port; 
  29.  
  30.  
  31.     public RpcServer() { 
  32.         this.port = RpcServerConst.DEFAULT_PORT; 
  33.     } 
  34.  
  35.  
  36.     public RpcServer(int port) { 
  37.         this.port = port; 
  38.     } 
  39.  
  40.  
  41.     @Override 
  42.     public void run() { 
  43.         // 启动服务端 
  44.         log.info("RPC 服务开始启动服务端"); 
  45.  
  46.  
  47.         EventLoopGroup bossGroup = new NioEventLoopGroup(); 
  48.         EventLoopGroup workerGroup = new NioEventLoopGroup(); 
  49.  
  50.  
  51.         try { 
  52.             ServerBootstrap serverBootstrap = new ServerBootstrap(); 
  53.             serverBootstrap.group(workerGroup, bossGroup) 
  54.                     .channel(NioServerSocketChannel.class) 
  55.                     .childHandler(new ChannelInitializer<Channel>() { 
  56.                         @Override 
  57.                         protected void initChannel(Channel ch) throws Exception { 
  58.                             ch.pipeline().addLast(new RpcServerHandler()); 
  59.                         } 
  60.                     }) 
  61.                     // 这个参数影响的是还没有被accept 取出的连接 
  62.                     .option(ChannelOption.SO_BACKLOG, 128) 
  63.                     // 这个参数只是过一段时间内客户端没有响应,服务端会发送一个 ack 包,以判断客户端是否还活着。 
  64.                     .childOption(ChannelOption.SO_KEEPALIVE, true); 
  65.  
  66.  
  67.             // 绑定端口,开始接收进来的链接 
  68.             ChannelFuture channelFuture = serverBootstrap.bind(port).syncUninterruptibly(); 
  69.             log.info("RPC 服务端启动完成,监听【" + port + "】端口"); 
  70.  
  71.  
  72.             channelFuture.channel().closeFuture().syncUninterruptibly(); 
  73.             log.info("RPC 服务端关闭完成"); 
  74.         } catch (Exception e) { 
  75.             log.error("RPC 服务异常", e); 
  76.         } finally { 
  77.             workerGroup.shutdownGracefully(); 
  78.             bossGroup.shutdownGracefully(); 
  79.         } 
  80.     } 
  81.  
  82.  

为了简单,服务端启动端口号固定,RpcServerConst 常量类内容如下:

  1. public final class RpcServerConst { 
  2.  
  3.  
  4.     private RpcServerConst(){} 
  5.  
  6.  
  7.     /** 
  8.      * 默认端口 
  9.      * @since 0.0.1 
  10.      */ 
  11.     public static final int DEFAULT_PORT = 9627; 
  12.  
  13.  

RpcServerHandler

当然,还有一个比较核心的类就是 RpcServerHandler

  1. public class RpcServerHandler extends SimpleChannelInboundHandler { 
  2.     @Override 
  3.     protected void channelRead0(ChannelHandlerContext ctx, Object msg) throws Exception { 
  4.         // do nothing now 
  5.     } 

目前是空实现,后续可以添加对应的日志输出及逻辑处理。

测试

启动测试的代码非常简单:

  1. /** 
  2.  * 服务启动代码测试 
  3.  * @param args 参数 
  4.  */ 
  5. public static void main(String[] args) { 
  6.     new RpcServer().start(); 

说明

上面我们实现了服务端的实现,这一节来一起看一下 client 客户端代码实现。

代码实现

RpcClient

  1. /* 
  2.  * Copyright (c)  2019. houbinbin Inc. 
  3.  * rpc All rights reserved. 
  4.  */ 
  5.  
  6.  
  7. package com.github.houbb.rpc.client.core; 
  8.  
  9.  
  10. import com.github.houbb.log.integration.core.Log; 
  11. import com.github.houbb.log.integration.core.LogFactory; 
  12. import com.github.houbb.rpc.client.handler.RpcClientHandler; 
  13.  
  14.  
  15. import io.netty.bootstrap.Bootstrap; 
  16. import io.netty.channel.Channel; 
  17. import io.netty.channel.ChannelFuture; 
  18. import io.netty.channel.ChannelInitializer; 
  19. import io.netty.channel.ChannelOption; 
  20. import io.netty.channel.EventLoopGroup; 
  21. import io.netty.channel.nio.NioEventLoopGroup; 
  22. import io.netty.channel.socket.nio.NioSocketChannel; 
  23. import io.netty.handler.logging.LogLevel; 
  24. import io.netty.handler.logging.LoggingHandler; 
  25.  
  26.  
  27. /** 
  28.  * <p> rpc 客户端 </p> 
  29.  * 
  30.  * <pre> Created: 2019/10/16 11:21 下午  </pre> 
  31.  * <pre> Project: rpc  </pre> 
  32.  * 
  33.  * @author houbinbin 
  34.  * @since 0.0.2 
  35.  */ 
  36. public class RpcClient extends Thread { 
  37.  
  38.  
  39.     private static final Log log = LogFactory.getLog(RpcClient.class); 
  40.  
  41.  
  42.     /** 
  43.      * 监听端口号 
  44.      */ 
  45.     private final int port; 
  46.  
  47.  
  48.     public RpcClient(int port) { 
  49.         this.port = port; 
  50.     } 
  51.  
  52.  
  53.     public RpcClient() { 
  54.         this(9527); 
  55.     } 
  56.  
  57.  
  58.     @Override 
  59.     public void run() { 
  60.         // 启动服务端 
  61.         log.info("RPC 服务开始启动客户端"); 
  62.  
  63.  
  64.         EventLoopGroup workerGroup = new NioEventLoopGroup(); 
  65.  
  66.  
  67.         try { 
  68.             Bootstrap bootstrap = new Bootstrap(); 
  69.             ChannelFuture channelFuture = bootstrap.group(workerGroup) 
  70.                     .channel(NioSocketChannel.class) 
  71.                     .option(ChannelOption.SO_KEEPALIVE, true
  72.                     .handler(new ChannelInitializer<Channel>(){ 
  73.                         @Override 
  74.                         protected void initChannel(Channel ch) throws Exception { 
  75.                             ch.pipeline() 
  76.                                     .addLast(new LoggingHandler(LogLevel.INFO)) 
  77.                                     .addLast(new RpcClientHandler()); 
  78.                         } 
  79.                     }) 
  80.                     .connect("localhost", port) 
  81.                     .syncUninterruptibly(); 
  82.  
  83.  
  84.             log.info("RPC 服务启动客户端完成,监听端口:" + port); 
  85.             channelFuture.channel().closeFuture().syncUninterruptibly(); 
  86.             log.info("RPC 服务开始客户端已关闭"); 
  87.         } catch (Exception e) { 
  88.             log.error("RPC 客户端遇到异常", e); 
  89.         } finally { 
  90.             workerGroup.shutdownGracefully(); 
  91.         } 
  92.     } 
  93.  
  94.  

.connect("localhost", port) 声明了客户端需要连接的服务端,此处和服务端的端口保持一致。

RpcClientHandler

客户端处理类也比较简单,暂时留空。

  1. /* 
  2.  * Copyright (c)  2019. houbinbin Inc. 
  3.  * rpc All rights reserved. 
  4.  */ 
  5.  
  6.  
  7. package com.github.houbb.rpc.client.handler; 
  8.  
  9.  
  10. import io.netty.channel.ChannelHandlerContext; 
  11. import io.netty.channel.SimpleChannelInboundHandler; 
  12.  
  13.  
  14. /** 
  15.  * <p> 客户端处理类 </p> 
  16.  * 
  17.  * <pre> Created: 2019/10/16 11:30 下午  </pre> 
  18.  * <pre> Project: rpc  </pre> 
  19.  * 
  20.  * @author houbinbin 
  21.  * @since 0.0.2 
  22.  */ 
  23. public class RpcClientHandler extends SimpleChannelInboundHandler { 
  24.  
  25.  
  26.     @Override 
  27.     protected void channelRead0(ChannelHandlerContext ctx, Object msg) throws Exception { 
  28.         // do nothing. 
  29.     } 
  30.  
  31.  

启动测试

服务端

首先启动服务端。

客户端

然后启动客户端连接服务端,实现如下:

  1. /** 
  2.  * 服务启动代码测试 
  3.  * @param args 参数 
  4.  */ 
  5. public static void main(String[] args) { 
  6.     new RpcClient().start(); 

小结

为了便于大家学习,以上源码已经开源:

https://github.com/houbb/rpc

我是老马,期待与你的下次重逢。

References

[1] 为什么选择 netty?: http://houbb.github.io/2019/05/10/netty-definitive-gudie-04-why-netty

【编辑推荐】

  1. 鸿蒙官方战略合作共建——HarmonyOS技术社区
  2. 如何从Windows 10免费升级到Windows 11?
  3. Windows 11正式推送!安装方法、ISO镜像下载都在这里
  4. 突发!苹果正式关闭iOS 14.8验证系统
  5. 你升级了吗?Windows 11正式版发布 体积缩小40%
  6. 更新Windows 11了吗 为啥我到现在还没收到推送
【责任编辑:姜华 TEL:(010)68476606】

点赞 0
分享:
大家都在看
猜你喜欢

订阅专栏+更多

订阅51CTO邮刊

点击这里查看样刊

订阅51CTO邮刊

51CTO服务号

51CTO官微