架构师入门笔记十 Netty5快速入门

在了解IO,NIO,AIO知识后,学习Netty5便会轻松很多,本章节主要介绍Netty是如何接收,反馈数据和拆包粘包的问题

1 Netty基础知识

1.1 Netty作用

Netty是一个提供异步事件驱动的网络应用框架,用以快速开发高性能高可靠性 的网络服务器和客户端程序。Netty是一个NIO框架,使用它可以简单快速地开发网络应用程序,比如客户端和服务端的协议。Netty简化了网络程序的开发过程,比如TCP和UDP的 Socket的开发。

1.2 TCP和UDP

TCP(Transmission Control Protocol,传输控制协议)是基于连接的协议,也就是说,在正式收发数据前,必须和对方建立可靠的连接。
UDP(User Data Protocol,用户数据报协议)是与TCP相对应的协议。它是面向非连接的协议,它不与对方建立连接,而是直接就把数据包发送过去!
  TCP UDP
是否连接 面向连接 面向非连接
传输可靠性 可靠 不可靠
应用场合 传输大量数据 传输少量数据
速度 慢 

2 HelloWorld代码

2.1 DISCARD服务: 丢弃服务,丢弃了所有接收到的数据,并不做任何响应。 简单理解就是接收数据,不返回数据
2.2 ECHO服务:响应式协议, 这个协议针对任何接收的数据都会返回一个响应
2.3 代码事例:
首选是服务器处理类
[java]  view plain  copy
  1. import io.netty.buffer.ByteBuf;  
  2. import io.netty.buffer.Unpooled;  
  3. import io.netty.channel.ChannelHandlerAdapter;  
  4. import io.netty.channel.ChannelHandlerContext;  
  5. import io.netty.util.ReferenceCountUtil;  
  6.   
  7. /** 
  8.  * DISCARD协议 丢弃协议,其实就是只接收数据,不做任何处理。 
  9.  * ECHO服务(响应式协议),其实就是返回数据。 
  10.  * 实现步骤: 
  11.  * step1 继承 ChannelHandlerAdapter 
  12.  * step2 覆盖chanelRead()事件处理方法 
  13.  * step3 释放ByteBuffer,ByteBuf是一个引用计数对象,这个对象必须显示地调用release()方法来释放 
  14.  * step4 异常处理,即当Netty由于IO错误或者处理器在处理事件时抛出的异常时。在大部分情况下,捕获的异常应该被记录下来并且把关联的channel给关闭掉。 
  15.  */  
  16. public class DiscardServerHandler extends ChannelHandlerAdapter{  
  17.       
  18.     @Override  
  19.     public void channelRead(ChannelHandlerContext chc, Object msg) {  
  20.         try {  
  21.             // 简单的读写操作  
  22.             ByteBuf buf = (ByteBuf) msg;  
  23.             byte[] req = new byte[buf.readableBytes()];  
  24.             buf.readBytes(req);  
  25.             String body = new String(req, "utf-8");  
  26.             System.out.println("Server :" + body);  
  27.             chc.writeAndFlush(Unpooled.copiedBuffer("卒...... ".getBytes())); // 返回数据  
  28.         } catch (Exception e) {  
  29.             e.printStackTrace();  
  30.         } finally {  
  31.             ReferenceCountUtil.release(msg); // 释放msg  
  32.         }  
  33.     }  
  34.       
  35.     @Override  
  36.     public void exceptionCaught(ChannelHandlerContext chc, Throwable cause) {  
  37.         // 这个方法的处理方式会在遇到不同异常的情况下有不同的实现,比如你可能想在关闭连接之前发送一个错误码的响应消息。  
  38.         cause.printStackTrace();  
  39.         chc.close();  
  40.     }  
  41. }  
然后是服务端Netty启动代码
[java]  view plain  copy
  1. import io.netty.bootstrap.ServerBootstrap;  
  2. import io.netty.channel.ChannelFuture;  
  3. import io.netty.channel.ChannelInitializer;  
  4. import io.netty.channel.ChannelOption;  
  5. import io.netty.channel.EventLoopGroup;  
  6. import io.netty.channel.nio.NioEventLoopGroup;  
  7. import io.netty.channel.socket.SocketChannel;  
  8. import io.netty.channel.socket.nio.NioServerSocketChannel;  
  9.   
  10. /** 
  11.  * step1 创建两个线程组分别负责接收和处理 
  12.  * step2 启动NIO服务辅助类 
  13.  * step3 绑定两个线程组,指定一个通道,关联一个处理类,设置一些相关参数 
  14.  * step4 监听端口 
  15.  * step5 关闭一些资源 
  16.  */  
  17. public class DiscardServer {  
  18.       
  19.     private static final int PORT = 8888// 监听的端口号  
  20.       
  21.     public static void main(String[] args) {  
  22.         // NioEventLoopGroup 是用来处理I/O操作的多线程事件循环器  
  23.         EventLoopGroup bossGroup = new NioEventLoopGroup(); // 用于接收进来的连接  
  24.         EventLoopGroup workerGroup = new NioEventLoopGroup(); // 用于处理进来的连接  
  25.         try {  
  26.             ServerBootstrap bootstrap = new ServerBootstrap(); // ServerBootstrap 是一个启动NIO服务的辅助启动类  
  27.             bootstrap.group(bossGroup, workerGroup) // 绑定俩个线程组  
  28.             .channel(NioServerSocketChannel.class// 指定用 NioServerSocketChannel 通道  
  29.             .childHandler(new ChannelInitializer<SocketChannel>() {  
  30.                 @Override  
  31.                 protected void initChannel(SocketChannel socketChannel) throws Exception {  
  32.                     socketChannel.pipeline().addLast(new DiscardServerHandler()); // DiscardServerHandler是我们自定义的服务器处理类,负责处理连接  
  33.                 }  
  34.             })  
  35.             .option(ChannelOption.SO_BACKLOG, 128// 设置tcp缓冲区  
  36.             .childOption(ChannelOption.SO_KEEPALIVE, true); // 设置保持连接  
  37.               
  38.             ChannelFuture future = bootstrap.bind(PORT).sync(); // 绑定端口  
  39.             future.channel().closeFuture().sync(); // 等待关闭  
  40.         } catch (Exception e) {  
  41.             e.printStackTrace();  
  42.         } finally {  
  43.             workerGroup.shutdownGracefully(); // 关闭线程组,先打开的后关闭  
  44.             bossGroup.shutdownGracefully();  
  45.         }  
  46.     }  
  47.   
  48. }  
客户端代码和服务端代码类似,区别在于:服务端提供监听端口,客户端负责连接端口;服务端的辅助类是ServerBootstrap,而客户端的辅助类是Bootstrap(和ServerSocket,Socket关系很像)
客户端处理类
[java]  view plain  copy
  1. import io.netty.buffer.ByteBuf;  
  2. import io.netty.channel.ChannelHandlerAdapter;  
  3. import io.netty.channel.ChannelHandlerContext;  
  4. import io.netty.util.ReferenceCountUtil;  
  5.   
  6. /** 
  7.  *  
  8.  * 和ServerHandler类似 
  9.  * 
  10.  */  
  11. public class ClientHandler extends ChannelHandlerAdapter{  
  12.           
  13.     public void channelRead(ChannelHandlerContext chc, Object msg) {  
  14.         try {  
  15.             ByteBuf buf = (ByteBuf) msg;  
  16.             byte[] req = new byte[buf.readableBytes()];  
  17.             buf.readBytes(req);  
  18.             String body = new String(req, "utf-8");  
  19.             System.out.println("Client :" + body);  
  20.         } catch (Exception e) {  
  21.             e.printStackTrace();  
  22.         } finally {  
  23.             ReferenceCountUtil.release(msg); // 释放msg  
  24.         }  
  25.     }  
  26.       
  27.     public void exceptionCaught(ChannelHandlerContext chc, Throwable cause) {  
  28.         // 这个方法的处理方式会在遇到不同异常的情况下有不同的实现,比如你可能想在关闭连接之前发送一个错误码的响应消息。  
  29.         cause.printStackTrace();  
  30.         chc.close();  
  31.     }  
  32.   
  33. }  
客户端启动服务类
[java]  view plain  copy
  1. import io.netty.bootstrap.Bootstrap;  
  2. import io.netty.buffer.Unpooled;  
  3. import io.netty.channel.ChannelFuture;  
  4. import io.netty.channel.ChannelInitializer;  
  5. import io.netty.channel.ChannelOption;  
  6. import io.netty.channel.nio.NioEventLoopGroup;  
  7. import io.netty.channel.socket.SocketChannel;  
  8. import io.netty.channel.socket.nio.NioSocketChannel;  
  9.   
  10. public class Client {  
  11.       
  12.     private static final int PORT = 8888;  
  13.     private static final String HOST = "127.0.0.1";  
  14.       
  15.     public static void main(String[] args) {  
  16.         NioEventLoopGroup workerGroup = new NioEventLoopGroup();  
  17.         try {  
  18.             Bootstrap bootstrap = new Bootstrap();  
  19.             bootstrap.group(workerGroup)  
  20.             .channel(NioSocketChannel.class)  
  21.             .handler(new ChannelInitializer<SocketChannel>() {  
  22.                 @Override  
  23.                 protected void initChannel(SocketChannel socketChannel) throws Exception {  
  24.                     socketChannel.pipeline().addLast(new ClientHandler());  
  25.                 }  
  26.             })  
  27.             .option(ChannelOption.SO_KEEPALIVE, true);  
  28.               
  29.             ChannelFuture future = bootstrap.connect(HOST, PORT).sync(); // 建立连接  
  30.             future.channel().writeAndFlush(Unpooled.copiedBuffer("快醒醒,还有几个bug没有改".getBytes())); // 向服务端发送数据  
  31.             future.channel().closeFuture().sync();  
  32.         } catch (Exception e) {  
  33.             e.printStackTrace();  
  34.         } finally {  
  35.             workerGroup.shutdownGracefully();  
  36.         }  
  37.     }  
  38.   
  39. }  
到这里,便是Netty的简单应用。打印的结果:
[java]  view plain  copy
  1. Server :快醒醒,还有几个bug没有改  
  2. Client :卒......   

3 拆包粘包

3.1 使用特殊的分隔符
3.2 限定长度,不推荐。若发送数据的长度不够指定长度,则一直处于等待中。
代码在原来的基础上做了简单修改,可以打开注释自己调试
首选是服务器处理类
[java]  view plain  copy
  1. import io.netty.buffer.ByteBuf;  
  2. import io.netty.buffer.Unpooled;  
  3. import io.netty.channel.ChannelHandlerAdapter;  
  4. import io.netty.channel.ChannelHandlerContext;  
  5. import io.netty.util.ReferenceCountUtil;  
  6.   
  7. /** 
  8.  * DISCARD协议 丢弃协议,其实就是只接收数据,不做任何处理。 
  9.  * ECHO服务(响应式协议),其实就是返回数据。 
  10.  * 实现步骤: 
  11.  * step1 继承 ChannelHandlerAdapter 
  12.  * step2 覆盖chanelRead()事件处理方法 
  13.  * step3 释放ByteBuffer,ByteBuf是一个引用计数对象,这个对象必须显示地调用release()方法来释放 
  14.  * step4 异常处理,即当Netty由于IO错误或者处理器在处理事件时抛出的异常时。在大部分情况下,捕获的异常应该被记录下来并且把关联的channel给关闭掉。 
  15.  */  
  16. public class DiscardServerHandler extends ChannelHandlerAdapter{  
  17.       
  18.     private static final String DELIMITER = "^_^"// 拆包分隔符  
  19.       
  20.     @Override  
  21.     public void channelRead(ChannelHandlerContext chc, Object msg) {  
  22.         try {  
  23.             // 简单的读写操作  
  24.             /* 
  25.             ByteBuf buf = (ByteBuf) msg; 
  26.             byte[] req = new byte[buf.readableBytes()]; 
  27.             buf.readBytes(req); 
  28.             String body = new String(req, "utf-8"); 
  29.             System.out.println("Server :" + body); 
  30.             chc.writeAndFlush(Unpooled.copiedBuffer("卒...... ".getBytes())); // 返回数据 
  31.             */  
  32.             // 加了 StringDecoder 字符串解码器后可以直接读取  
  33.             System.out.println("Server :" + msg);  
  34.             // 分隔符拆包  
  35. //          String response = msg + " , 骗你的" + DELIMITER;  
  36. //          chc.channel().writeAndFlush(Unpooled.copiedBuffer(response.getBytes()));  
  37.             // 限定长度拆包  
  38.             chc.channel().writeAndFlush(Unpooled.copiedBuffer(msg.toString().getBytes()));  
  39.         } catch (Exception e) {  
  40.             e.printStackTrace();  
  41.         } finally {  
  42.             ReferenceCountUtil.release(msg); // 释放msg  
  43.         }  
  44.     }  
  45.       
  46.     @Override  
  47.     public void exceptionCaught(ChannelHandlerContext chc, Throwable cause) {  
  48.         // 这个方法的处理方式会在遇到不同异常的情况下有不同的实现,比如你可能想在关闭连接之前发送一个错误码的响应消息。  
  49.         cause.printStackTrace();  
  50.         chc.close();  
  51.     }  
  52. }  
然后是服务端Netty启动代码
[html]  view plain  copy
  1. import io.netty.bootstrap.ServerBootstrap;  
  2. import io.netty.buffer.ByteBuf;  
  3. import io.netty.buffer.Unpooled;  
  4. import io.netty.channel.ChannelFuture;  
  5. import io.netty.channel.ChannelInitializer;  
  6. import io.netty.channel.ChannelOption;  
  7. import io.netty.channel.EventLoopGroup;  
  8. import io.netty.channel.nio.NioEventLoopGroup;  
  9. import io.netty.channel.socket.SocketChannel;  
  10. import io.netty.channel.socket.nio.NioServerSocketChannel;  
  11. import io.netty.handler.codec.DelimiterBasedFrameDecoder;  
  12. import io.netty.handler.codec.FixedLengthFrameDecoder;  
  13. import io.netty.handler.codec.string.StringDecoder;  
  14.   
  15. /**  
  16.  * step1 创建两个线程组分别负责接收和处理  
  17.  * step2 启动NIO服务辅助类  
  18.  * step3 绑定两个线程组,指定一个通道,关联一个处理类,设置一些相关参数  
  19.  * step4 监听端口  
  20.  * step5 关闭一些资源  
  21.  */  
  22. public class DiscardServer {  
  23.       
  24.     private static final int PORT = 8888; // 监听的端口号  
  25.     private static final String DELIMITER = "^_^"; // 拆包分隔符  
  26.       
  27.     public static void main(String[] args) {  
  28.         // NioEventLoopGroup 是用来处理I/O操作的多线程事件循环器  
  29.         EventLoopGroup bossGroup = new NioEventLoopGroup(); // 用于接收进来的连接  
  30.         EventLoopGroup workerGroup = new NioEventLoopGroup(); // 用于处理进来的连接  
  31.         try {  
  32.             ServerBootstrap bootstrap = new ServerBootstrap(); // ServerBootstrap 是一个启动NIO服务的辅助启动类  
  33.             bootstrap.group(bossGroup, workerGroup) // 绑定俩个线程组  
  34.             .channel(NioServerSocketChannel.class) // 指定用 NioServerSocketChannel 通道  
  35.             .childHandler(new ChannelInitializer<SocketChannel>() {  
  36.                 @Override  
  37.                 protected void initChannel(SocketChannel socketChannel) throws Exception {  
  38.                     // 考虑到tcp拆包粘包的问题,升级代码  
  39.                     // step1 获取特殊分隔符的ByteBuffer  
  40.                     ByteBuf delimiter = Unpooled.copiedBuffer(DELIMITER.getBytes());  
  41.                     // step2 设置特殊分隔符  
  42. //                  socketChannel.pipeline().addLast(new DelimiterBasedFrameDecoder(128, delimiter));  
  43.                     // 还有一种就是指定长度   二选一 (用的比较少)  
  44.                     socketChannel.pipeline().addLast(new FixedLengthFrameDecoder(5));  
  45.                     // step3 设置字符串形式的解码  
  46.                     socketChannel.pipeline().addLast(new StringDecoder());  
  47.                     // step4 设置处理类  
  48.                     socketChannel.pipeline().addLast(new DiscardServerHandler()); // DiscardServerHandler是我们自定义的服务器处理类,负责处理连接  
  49.                 }  
  50.             })  
  51.             .option(ChannelOption.SO_BACKLOG, 128) // 设置tcp缓冲区  
  52.             .childOption(ChannelOption.SO_KEEPALIVE, true); // 设置保持连接  
  53.               
  54.             ChannelFuture future = bootstrap.bind(PORT).sync(); // 绑定端口  
  55.             future.channel().closeFuture().sync(); // 等待关闭  
  56.         } catch (Exception e) {  
  57.             e.printStackTrace();  
  58.         } finally {  
  59.             workerGroup.shutdownGracefully(); // 关闭线程组,先打开的后关闭  
  60.             bossGroup.shutdownGracefully();  
  61.         }  
  62.     }  
  63.   
  64. }  
客户端处理类
[java]  view plain  copy
  1. import io.netty.buffer.ByteBuf;  
  2. import io.netty.channel.ChannelHandlerAdapter;  
  3. import io.netty.channel.ChannelHandlerContext;  
  4. import io.netty.util.ReferenceCountUtil;  
  5.   
  6. /** 
  7.  *  
  8.  * 和ServerHandler类似 
  9.  * 
  10.  */  
  11. public class ClientHandler extends ChannelHandlerAdapter{  
  12.           
  13.     public void channelRead(ChannelHandlerContext chc, Object msg) {  
  14.         try {  
  15.             /* 
  16.             ByteBuf buf = (ByteBuf) msg; 
  17.             byte[] req = new byte[buf.readableBytes()]; 
  18.             buf.readBytes(req); 
  19.             String body = new String(req, "utf-8"); 
  20.             */  
  21.             System.out.println("Client : " + msg);  
  22.         } catch (Exception e) {  
  23.             e.printStackTrace();  
  24.         } finally {  
  25.             ReferenceCountUtil.release(msg); // 释放msg  
  26.         }  
  27.     }  
  28.       
  29.     public void exceptionCaught(ChannelHandlerContext chc, Throwable cause) {  
  30.         // 这个方法的处理方式会在遇到不同异常的情况下有不同的实现,比如你可能想在关闭连接之前发送一个错误码的响应消息。  
  31.         cause.printStackTrace();  
  32.         chc.close();  
  33.     }  
  34.   
  35. }  
客户端启动服务类
[java]  view plain  copy
  1. import io.netty.bootstrap.Bootstrap;  
  2. import io.netty.buffer.ByteBuf;  
  3. import io.netty.buffer.Unpooled;  
  4. import io.netty.channel.ChannelFuture;  
  5. import io.netty.channel.ChannelInitializer;  
  6. import io.netty.channel.ChannelOption;  
  7. import io.netty.channel.nio.NioEventLoopGroup;  
  8. import io.netty.channel.socket.SocketChannel;  
  9. import io.netty.channel.socket.nio.NioSocketChannel;  
  10. import io.netty.handler.codec.DelimiterBasedFrameDecoder;  
  11. import io.netty.handler.codec.FixedLengthFrameDecoder;  
  12. import io.netty.handler.codec.string.StringDecoder;  
  13.   
  14. public class Client {  
  15.       
  16.     private static final int PORT = 8888;  
  17.     private static final String HOST = "127.0.0.1";  
  18.     private static final String DELIMITER = "^_^"// 拆包分隔符  
  19.       
  20.     public static void main(String[] args) {  
  21.         NioEventLoopGroup workerGroup = new NioEventLoopGroup();  
  22.         try {  
  23.             Bootstrap bootstrap = new Bootstrap();  
  24.             bootstrap.group(workerGroup)  
  25.             .channel(NioSocketChannel.class)  
  26.             .handler(new ChannelInitializer<SocketChannel>() {  
  27.                 @Override  
  28.                 protected void initChannel(SocketChannel socketChannel) throws Exception {  
  29.                     // 考虑到tcp拆包粘包的问题,升级代码  
  30.                     ByteBuf delimiter = Unpooled.copiedBuffer(DELIMITER.getBytes());  
  31. //                  socketChannel.pipeline().addLast(new DelimiterBasedFrameDecoder(128, delimiter));  
  32.                     socketChannel.pipeline().addLast(new FixedLengthFrameDecoder(5));  
  33.                     socketChannel.pipeline().addLast(new StringDecoder());  
  34.                       
  35.                     socketChannel.pipeline().addLast(new ClientHandler());  
  36.                 }  
  37.             })  
  38.             .option(ChannelOption.SO_KEEPALIVE, true);  
  39.               
  40.             ChannelFuture future = bootstrap.connect(HOST, PORT).sync(); // 建立连接  
  41. //          future.channel().writeAndFlush(Unpooled.copiedBuffer("快醒醒,还有几个bug没有改".getBytes())); // 向服务端发送数据  
  42. //          future.channel().writeAndFlush(Unpooled.copiedBuffer(("又要加班了"+DELIMITER).getBytes()));  
  43. //          future.channel().writeAndFlush(Unpooled.copiedBuffer(("好开心啊T。T"+DELIMITER).getBytes()));  
  44.             future.channel().writeAndFlush(Unpooled.copiedBuffer("123456789".getBytes())); // 传的个数是9个,只打印了5个,还有4个在等待中  
  45.             future.channel().closeFuture().sync();  
  46.         } catch (Exception e) {  
  47.             e.printStackTrace();  
  48.         } finally {  
  49.             workerGroup.shutdownGracefully();  
  50.         }  
  51.     }  
  52.   
  53. }  

4 优质博客

猜你喜欢

转载自blog.csdn.net/m_jack/article/details/80484647