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

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

[[428830]]

说明

上一篇代码基于 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

 

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

2021-10-19 08:58:48

Java 语言 Java 基础

2021-10-21 08:21:10

Java Reflect Java 基础

2021-10-27 08:10:15

Java 客户端 Java 基础

2022-06-14 15:07:04

IPC客户端服务端

2021-10-13 08:21:52

Java websocket Java 基础

2018-12-20 08:50:53

TCPIP服务器

2010-03-01 16:10:32

Linux Samba

2012-05-07 13:55:41

JavaJava Web

2010-03-19 09:26:34

Java Socket

2021-10-20 08:05:18

Java 序列化 Java 基础

2022-01-05 08:03:23

C#通信Rest

2010-01-11 13:05:24

VNC server配

2009-08-21 15:36:41

服务端与客户端

2009-08-21 15:54:40

服务端与客户端

2021-10-29 08:07:30

Java timeout Java 基础

2010-03-18 17:47:07

Java 多客户端通信

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

服务端与客户端通信
点赞
收藏

51CTO技术栈公众号