使用Java搭建一个简单的Netty通信例子

看过dubbo源码的同学应该都清楚,使用dubbo协议的底层通信是使用的netty进行交互,而最近看了dubbo的Netty部分后,自己写了个简单的Netty通信例子。

本文源地址:实现Netty进行通信


准备

工程截图

模块详解

  • rpc-common

rpc-common作为各个模块都需使用的模块,工程中出现的是一些通信时请求的参数以及返回的参数,还有一些序列化的工具。

  • rpc-client

rpc-client中目前只是单单的一个NettyClient启动类。

  • rpc-server

rpc-client中目前也只是单单的一个NettyServer服务启动类。

需要的依赖

目前所有的依赖项都出现在 rpc-common 下的 pom.xml中。

  1. <dependencies>
  2. <!-- Netty -->
  3. <dependency>
  4. <groupId>io.netty</groupId>
  5. <artifactId>netty-all</artifactId>
  6. <version>4.1.10.Final</version>
  7. </dependency>
  8. <dependency>
  9. <groupId>org.slf4j</groupId>
  10. <artifactId>slf4j-log4j12</artifactId>
  11. <version>1.7.25</version>
  12. </dependency>
  13. <!-- Protostuff -->
  14. <dependency>
  15. <groupId>com.dyuproject.protostuff</groupId>
  16. <artifactId>protostuff-core</artifactId>
  17. <version>1.0.9</version>
  18. </dependency>
  19. <dependency>
  20. <groupId>com.dyuproject.protostuff</groupId>
  21. <artifactId>protostuff-runtime</artifactId>
  22. <version>1.0.9</version>
  23. </dependency>
  24. <!-- Objenesis -->
  25. <dependency>
  26. <groupId>org.objenesis</groupId>
  27. <artifactId>objenesis</artifactId>
  28. <version>2.1</version>
  29. </dependency>
  30. <!-- fastjson -->
  31. <dependency>
  32. <groupId>com.alibaba</groupId>
  33. <artifactId>fastjson</artifactId>
  34. <version>1.2.38</version>
  35. </dependency>
  36. </dependencies>

实现

首先我们在common中先定义本次的Request和Response的基类对象。

  1. public class Request {
  2. private String requestId;
  3. private Object parameter;
  4. public String getRequestId() {
  5. return requestId;
  6. }
  7. public void setRequestId(String requestId) {
  8. this.requestId = requestId;
  9. }
  10. public Object getParameter() {
  11. return parameter;
  12. }
  13. public void setParameter(Object parameter) {
  14. this.parameter = parameter;
  15. }
  16. }
  17. public class Response {
  18. private String requestId;
  19. private Object result;
  20. public String getRequestId() {
  21. return requestId;
  22. }
  23. public void setRequestId(String requestId) {
  24. this.requestId = requestId;
  25. }
  26. public Object getResult() {
  27. return result;
  28. }
  29. public void setResult(Object result) {
  30. this.result = result;
  31. }
  32. }

使用fastJson进行本次序列化

Netty对象的序列化转换很好懂, ByteToMessageDecoderMessageToByteEncoder 分别只要继承它们,重写方法后,获取到Object和Byte,各自转换就OK。

不过如果是有要用到生产上的同学,建议不要使用 fastJson,因为它的漏洞补丁真的是太多了,可以使用google的 protostuff

  1. public class RpcDecoder extends ByteToMessageDecoder {
  2. // 目标对象类型进行解码
  3. private Class<?> target;
  4. public RpcDecoder(Class target) {
  5. this.target = target;
  6. }
  7. @Override
  8. protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
  9. if (in.readableBytes() < 4) { // 不够长度丢弃
  10. return;
  11. }
  12. in.markReaderIndex(); // 标记一下当前的readIndex的位置
  13. int dataLength = in.readInt(); // 读取传送过来的消息的长度。ByteBuf 的readInt()方法会让他的readIndex增加4
  14. if (in.readableBytes() < dataLength) { // 读到的消息体长度如果小于我们传送过来的消息长度,则resetReaderIndex. 这个配合markReaderIndex使用的。把readIndex重置到mark的地方
  15. in.resetReaderIndex();
  16. return;
  17. }
  18. byte[] data = new byte[dataLength];
  19. in.readBytes(data);
  20. Object obj = JSON.parseObject(data, target); // 将byte数据转化为我们需要的对象
  21. out.add(obj);
  22. }
  23. }
  24. public class RpcEncoder extends MessageToByteEncoder {
  25. //目标对象类型进行编码
  26. private Class<?> target;
  27. public RpcEncoder(Class target) {
  28. this.target = target;
  29. }
  30. @Override
  31. protected void encode(ChannelHandlerContext ctx, Object msg, ByteBuf out) throws Exception {
  32. if (target.isInstance(msg)) {
  33. byte[] data = JSON.toJSONBytes(msg); // 使用fastJson将对象转换为byte
  34. out.writeInt(data.length); // 先将消息长度写入,也就是消息头
  35. out.writeBytes(data); // 消息体中包含我们要发送的数据
  36. }
  37. }
  38. }

NetyServer

  1. public class NettyServerHandler extends ChannelInboundHandlerAdapter {
  2. @Override
  3. public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
  4. Request request = (Request) msg;
  5. System.out.println("Client Data:" + JSON.toJSONString(request));
  6. Response response = new Response();
  7. response.setRequestId(request.getRequestId());
  8. response.setResult("Hello Client !");
  9. // client接收到信息后主动关闭掉连接
  10. ctx.writeAndFlush(response).addListener(ChannelFutureListener.CLOSE);
  11. }
  12. @Override
  13. public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
  14. ctx.flush();
  15. }
  16. @Override
  17. public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
  18. ctx.close();
  19. }
  20. }
  21. public class NettyServer {
  22. private static final Logger logger = LoggerFactory.getLogger(NettyServer.class);
  23. private String ip;
  24. private int port;
  25. public NettyServer(String ip, int port) {
  26. this.ip = ip;
  27. this.port = port;
  28. }
  29. public void server() throws Exception {
  30. EventLoopGroup bossGroup = new NioEventLoopGroup();
  31. EventLoopGroup workerGroup = new NioEventLoopGroup();
  32. try {
  33. final ServerBootstrap serverBootstrap = new ServerBootstrap();
  34. serverBootstrap.group(bossGroup, workerGroup)
  35. .channel(NioServerSocketChannel.class)
  36. .option(ChannelOption.SO_BACKLOG, 1024)
  37. .option(ChannelOption.SO_SNDBUF, 32 * 1024)
  38. .option(ChannelOption.SO_RCVBUF, 32 * 1024)
  39. .option(ChannelOption.SO_KEEPALIVE, true)
  40. .childHandler(new ChannelInitializer<SocketChannel>() {
  41. protected void initChannel(SocketChannel socketChannel) throws Exception {
  42. socketChannel.pipeline().addLast(new RpcDecoder(Request.class))
  43. .addLast(new RpcEncoder(Response.class))
  44. .addLast(new NettyServerHandler());
  45. }
  46. });
  47. serverBootstrap.childOption(ChannelOption.SO_KEEPALIVE, true); // 开启长连接
  48. ChannelFuture future = serverBootstrap.bind(ip, port).sync();
  49. // if (future.isSuccess()) {
  50. //
  51. // new Register().register("/yanzhenyidai/com.yanzhenyidai.server", ip + ":" + port);
  52. // }
  53. future.channel().closeFuture().sync();
  54. } finally {
  55. bossGroup.shutdownGracefully();
  56. workerGroup.shutdownGracefully();
  57. }
  58. }
  59. public static void main(String[] args) throws Exception {
  60. new NettyServer("127.0.0.1", 20000).server();
  61. }
  62. }

关键名词:

  • EventLoopGroup

    • workerGroup
    • bossGroup

    Server端的EventLoopGroup分为两个,一般workerGroup作为处理请求,bossGroup作为接收请求。

  • ChannelOption

    • SO_BACKLOG
    • SO_SNDBUF
    • SO_RCVBUF
    • SO_KEEPALIVE

    以上四个常量作为TCP连接中的属性。

  • ctx.writeAndFlush(response).addListener(ChannelFutureListener.CLOSE);

    NettyServerHandler中出现的 ChannelFutureListener.CLOSE ,作为Server端主动关闭与Client端的通信,如果没有主动Close,那么NettyClient将会一直处于阻塞状态,得不到NettyServer的返回信息。

NettyClient

  1. public class NettyClient extends SimpleChannelInboundHandler<Response> {
  2. private final String ip;
  3. private final int port;
  4. private Response response;
  5. public NettyClient(String ip, int port) {
  6. this.ip = ip;
  7. this.port = port;
  8. }
  9. @Override
  10. public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
  11. ctx.close();
  12. }
  13. @Override
  14. protected void channelRead0(ChannelHandlerContext channelHandlerContext, Response response) throws Exception {
  15. this.response = response;
  16. }
  17. public Response client(Request request) throws Exception {
  18. EventLoopGroup group = new NioEventLoopGroup();
  19. try {
  20. // 创建并初始化 Netty 客户端 Bootstrap 对象
  21. Bootstrap bootstrap = new Bootstrap();
  22. bootstrap.group(group);
  23. bootstrap.channel(NioSocketChannel.class);
  24. bootstrap.handler(new ChannelInitializer<SocketChannel>() {
  25. @Override
  26. public void initChannel(SocketChannel channel) throws Exception {
  27. ChannelPipeline pipeline = channel.pipeline();
  28. pipeline.addLast(new RpcDecoder(Response.class));
  29. pipeline.addLast(new RpcEncoder(Request.class));
  30. pipeline.addLast(NettyClient.this);
  31. }
  32. });
  33. bootstrap.option(ChannelOption.TCP_NODELAY, true);
  34. // String[] discover = new Discover().discover("/yanzhenyidai/com.yanzhenyidai.server").split(":");
  35. // 连接 RPC 服务器
  36. ChannelFuture future = bootstrap.connect(ip, port).sync();
  37. // 写入 RPC 请求数据并关闭连接
  38. Channel channel = future.channel();
  39. channel.writeAndFlush(request).sync();
  40. channel.closeFuture().sync();
  41. return response;
  42. } finally {
  43. group.shutdownGracefully();
  44. }
  45. }
  46. public static void main(String[] args) throws Exception {
  47. Request request = new Request();
  48. request.setRequestId(UUID.randomUUID().toString());
  49. request.setParameter("Hello Server !");
  50. System.out.println(JSON.toJSONString(new NettyClient("127.0.0.1", 30000).client(request)));
  51. }
  52. }

测试

如果以上所有内容都准备就绪,那么就可以进行调试了。

启动顺序,先启动NettyServer,再启动NettyClient。


总结

记得刚出来工作时,有工作很多年的同事问我了不了解Netty,当时工作太短,直说听过Putty,现在回想起来真的挺丢人的,哈哈。

简单的Java实现Netty进行通信的更多相关文章

  1. Java使用Netty实现简单的RPC

    造一个轮子,实现RPC调用 在写了一个Netty实现通信的简单例子后,萌发了自己实现RPC调用的想法,于是就开始进行了Netty-Rpc的工作,实现了一个简单的RPC调用工程. 如果也有兴趣动手造轮子 ...

  2. java socket实现全双工通信

    java socket实现全双工通信 单工.半双工和全双工的定义 如果在通信过程的任意时刻,信息只能由一方A传到另一方B,则称为单工. 如果在任意时刻,信息既可由A传到B,又能由B传A,但只能由一个方 ...

  3. 从一个简单的Java单例示例谈谈并发

    一个简单的单例示例 单例模式可能是大家经常接触和使用的一个设计模式,你可能会这么写 public class UnsafeLazyInitiallization { private static Un ...

  4. 从一个简单的Java单例示例谈谈并发 JMM JUC

    原文: http://www.open-open.com/lib/view/open1462871898428.html 一个简单的单例示例 单例模式可能是大家经常接触和使用的一个设计模式,你可能会这 ...

  5. Mac OS中Java Servlet与Http通信

    Java中一个Servlet其实就是一个类,用来扩展服务器的性能,服务器上驻留着可以通过“请求-响应”编程模型来访问的应用程序.Servlet可以对任何类型的请求产生响应,但通常只用来扩展Web服务器 ...

  6. java下的串口通信-RXTX

    关于java实现的串口通信,使用的是开源项目RXTX,之前sun公司也有JCL项目,不过已经很久没有更新了,RXTX项目地址:点击打开,但是两个项目的API用法是一样的,只是导入的包不一样而已.简单的 ...

  7. 最简单的 Java内存模型 讲解

    前言 在网上看了很多文章,也看了好几本书中关于JMM的介绍,我发现JMM确实是Java中比较难以理解的概念.网上很多文章中关于JMM的介绍要么是照搬了一些书上的内容,要么就干脆介绍的就是错的.本文试着 ...

  8. java基础学习02(简单的java程序)

    简单的java程序 一.完成的目标 1. 理解java程序的基本组成 2. 如何对程序代码进行注释 3. java标识符的命名规则 4. 了解java中的关键字 5. 使用java定义变量或声明变量 ...

  9. Linux环境下部署完JDK后运行一个简单的Java程序

    前言 前一篇文章详细讲解了如何在Windows环境下安装虚拟机+Linux系统,并且成功部署了JDK. 不过部署完JDK之后,我们判断部署是否成功的依据是看"java -version&qu ...

随机推荐

  1. ubuntu-18.0.4 samba安装

    (1)安装 sudo apt-get -y install samba samba-common (2)创建一个用于分享的samba目录. mkdir /home/myshare (3)给创建的这个目 ...

  2. DataTable 与XML 交互

    一.将DataTable的内容写入到XML文件中 /// <summary> /// 将DataTable的内容写入到XML文件中 /// </summary> /// < ...

  3. 解决IE升级后必须以管理员运行的问题

    很多网友可能都遇到过这样的问题,在ie升级后,无法打开,必须以管理员身份运行.今天我也遇到了这个问题.最终找到了解决办法. 1.Win + R 2.输入 regedit,定位到 HKEY_CURREN ...

  4. keras API的使用,神经网络层,优化器,损失函数,查看模型层数,compile和fit训练

    layers介绍 Flatten和Dense介绍 优化器 损失函数 compile用法 第二个是onehot编码 模型训练 model.fit  两种创建模型的方法 from tensorflow.p ...

  5. js上传文件前判断获取文件大小并且加以判断

    描述:要求浏览器单个上传文件大小不超过10M. 解决方案: var fileSize = $("#fileId")[0].files[0].size/(1024*1024);if( ...

  6. GOLANG 闭包和普通函数的区别

    闭包和匿名函数是一回事 闭包使用完毕之后不会自动释放,值依然存在 普通函数调用完毕后,值会自动释放

  7. Makefile 简要辅导 【转载】

    A Simple Makefile Tutorial Makefiles are a simple way to organize code compilation. This tutorial do ...

  8. java中Locks的使用

    文章目录 Lock和Synchronized Block的区别 Lock interface ReentrantLock ReentrantReadWriteLock StampedLock Cond ...

  9. Spring Boot 之Spring data JPA简介

    文章目录 添加依赖 添加entity bean 创建 Dao Spring Data Configuration 测试 Spring Boot 之Spring data JPA简介 JPA的全称是Ja ...

  10. Scala教程之:静态类型

    文章目录 泛类型 型变 协变 逆变 不变 类型上界 类型下界 内部类 抽象类型 复合类型 自类型 隐式参数 隐式转换 多态方法 类型推断 Scala是静态类型的,它拥有一个强大的类型系统,静态地强制以 ...