使用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. PHP代码审计理解(二)----齐博CMS7.0文件覆盖

    0x00 前言 因为我是跟着视频操作的,这回真的没理解为什么定位到了这个存在漏洞的文件... /do/fujsarticle.php 因为没有前文,所以这里无法分析这个$FileName为什么可以$_ ...

  2. jmeter事务控制器

    jmeter事务控制器常用于压力测试时如果一个功能包括多个请求时,需要测试这个功能的压力情况,则需要把多个请求放到一个事务控制器里面

  3. curl发送多维数组

    //通过curl模拟post的请求: function SendDataByCurl($url,$data=array()){ //对空格进行转义 $url = str_replace(' ','+' ...

  4. vue显示富文本

    来源:https://segmentfault.com/q/1010000013952512 用  v-html 属性解决

  5. 列表按钮功能的设置和DOM的使用

    HTML: <foreach name="fulltime_list" item="v"> <tr> <td></td ...

  6. redis部署与卸载

    1.先到Redis官网(redis.io)下载redis安装包 cd /tmp wget http://download.redis.io/releases/redis-4.0.10.tar.gz 2 ...

  7. [Windows] DiskPart commands

    https://docs.microsoft.com/en-us/windows-server/administration/windows-commands/diskpart

  8. @SessionAttributes 和 @SessionAttribute的区别

    @SessionAttributes 和 @SessionAttribute的区别 Spring MVC中有两个长得非常像的注解:@SessionAttributes 和 @SessionAttrib ...

  9. c语言解一元二次方程

    C语言解一元二次方程,输入系数a,b,c; #include <stdio.h> #include <math.h> int main(int argc, char *argv ...

  10. js 之 for循环

    js之 for循环 普通for 循环 语法 for ([initialization]; [condition]; [final-expression]) statement initializati ...