短链接
发完马上关闭,下一次发送再次重新连接
1. public class HelloWorldClient { 2. static final Logger log = LoggerFactory.getLogger(HelloWorldClient.class); 3. 4. public static void main(String[] args) { 5. // 分 10 次发送 6. for (int i = 0; i < 10; i++) { 7. send(); 8. } 9. } 10. 11. private static void send() { 12. NioEventLoopGroup worker = new NioEventLoopGroup(); 13. try { 14. Bootstrap bootstrap = new Bootstrap(); 15. bootstrap.channel(NioSocketChannel.class); 16. bootstrap.group(worker); 17. bootstrap.handler(new ChannelInitializer<SocketChannel>() { 18. @Override 19. protected void initChannel(SocketChannel ch) throws Exception { 20. log.debug("conneted..."); 21. ch.pipeline().addLast(new LoggingHandler(LogLevel.DEBUG)); 22. ch.pipeline().addLast(new ChannelInboundHandlerAdapter() { 23. @Override 24. public void channelActive(ChannelHandlerContext ctx) throws Exception { 25. log.debug("sending..."); 26. ByteBuf buffer = ctx.alloc().buffer(); 27. buffer.writeBytes(new byte[]{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}); 28. ctx.writeAndFlush(buffer); 29. // 发完即关 30. ctx.close(); 31. } 32. }); 33. } 34. }); 35. ChannelFuture channelFuture = bootstrap.connect("localhost", 8080).sync(); 36. channelFuture.channel().closeFuture().sync(); 37. } catch (InterruptedException e) { 38. log.error("client error", e); 39. } finally { 40. worker.shutdownGracefully(); 41. } 42. } 43. }
但是对于半包这种是不好解决掉的,因为接收方的缓冲区大小它是有限的
固定长度
让所有数据包长度固定(假设长度为 8 字节),服务器端加入
ch.pipeline().addLast(new FixedLengthFrameDecoder(8));
FixedLengthFrameDecoder
一种解码器,用于按固定的字节数拆分接收到的 ByteBufs。例如,如果您收到以下四个分段数据包:
+---+----+------+----+
| A | BC | DEFG | HI |
+---+----+------+----+
A FixedLengthFrameDecoder(3) 会将它们解码为以下三个具有固定长度的数据包:
+-----+-----+-----+
| ABC | DEF | GHI |
+-----+-----+-----+
修改客户端,客户端代码如下
1. public class HelloWorldClient { 2. static final Logger log = LoggerFactory.getLogger(HelloWorldClient.class); 3. 4. public static void main(String[] args) { 5. NioEventLoopGroup worker = new NioEventLoopGroup(); 6. try { 7. Bootstrap bootstrap = new Bootstrap(); 8. bootstrap.channel(NioSocketChannel.class); 9. bootstrap.group(worker); 10. bootstrap.handler(new ChannelInitializer<SocketChannel>() { 11. @Override 12. protected void initChannel(SocketChannel ch) throws Exception { 13. log.debug("conneted..."); 14. ch.pipeline().addLast(new LoggingHandler(LogLevel.DEBUG)); 15. 16. ch.pipeline().addLast(new ChannelInboundHandlerAdapter() { 17. @Override 18. public void channelActive(ChannelHandlerContext ctx) throws Exception { 19. log.debug("sending..."); 20. Random r = new Random(); 21. char c = 'a'; 22. ByteBuf buffer = ctx.alloc().buffer(); 23. for (int i = 0; i < 10; i++) { 24. byte[] bytes=new byte[8]; 25. for (int j = 0; j < r.nextInt(8); j++) { 26. bytes[j]=(byte)c; 27. } 28. c++; 29. buffer.writeBytes(bytes); 30. } 31. ctx.writeAndFlush(buffer); 32. } 33. }); 34. } 35. }); 36. ChannelFuture channelFuture = bootstrap.connect("localhost", 8080).sync(); 37. channelFuture.channel().closeFuture().sync(); 38. } catch (InterruptedException e) { 39. log.error("client error", e); 40. } finally { 41. worker.shutdownGracefully(); 42. } 43. } 44. 45. }
这里可以看到客户端是一口气发送完的,但在服务端的解析如下:
缺点是,数据包的大小不好把握
- 长度定的太大,浪费
- 长度定的太小,对某些数据包又显得不够
固定分隔符
服务端加入,默认以 \n 或 \r\n 作为分隔符,如果超出指定长度仍未出现分隔符,则抛出异常
ch.pipeline().addLast(new LineBasedFrameDecoder(1024));
LineBasedFrameDecoder
一个解码器,用于在行尾拆分收到的 ByteBuf。
两者和"\n""\r\n"处理。
字节流应采用 UTF-8 字符编码或 ASCII。当前的实现使用直接byte强制char转换,然后将其与一些低范围的 ASCII 字符(如 '\n' or '\r')进行比较char。UTF-8 未对多字节代码点表示形式使用低范围 [0..0x7F] 字节值,因此此实现完全支持。
客户端在每条消息之后,加入 \n 分隔符
1. public class HelloWorldClient { 2. static final Logger log = LoggerFactory.getLogger(HelloWorldClient.class); 3. 4. public static void main(String[] args) { 5. NioEventLoopGroup worker = new NioEventLoopGroup(); 6. try { 7. Bootstrap bootstrap = new Bootstrap(); 8. bootstrap.channel(NioSocketChannel.class); 9. bootstrap.group(worker); 10. bootstrap.handler(new ChannelInitializer<SocketChannel>() { 11. @Override 12. protected void initChannel(SocketChannel ch) throws Exception { 13. log.debug("connetted..."); 14. ch.pipeline().addLast(new LoggingHandler(LogLevel.DEBUG)); 15. ch.pipeline().addLast(new ChannelInboundHandlerAdapter() { 16. @Override 17. public void channelActive(ChannelHandlerContext ctx) throws Exception { 18. log.debug("sending..."); 19. Random r = new Random(); 20. char c = 'a'; 21. ByteBuf buffer = ctx.alloc().buffer(); 22. for (int i = 0; i < 10; i++) { 23. for (int j = 1; j <= r.nextInt(16)+1; j++) { 24. buffer.writeByte((byte) c); 25. } 26. buffer.writeByte(10);//换行 27. c++; 28. } 29. ctx.writeAndFlush(buffer); 30. } 31. }); 32. } 33. }); 34. ChannelFuture channelFuture = bootstrap.connect("127.0.0.1", 8080).sync(); 35. channelFuture.channel().closeFuture().sync(); 36. 37. } catch (InterruptedException e) { 38. log.error("client error", e); 39. } finally { 40. worker.shutdownGracefully(); 41. } 42. } 43. }
客户端发送的数据
服务器端解析的数据
缺点,处理字符数据比较合适,但如果内容本身包含了分隔符(字节数据常常会有此情况),那么就会解析错误
预设长度
在发送消息前,先约定用定长字节表示接下来数据的长度
1. // 最大长度,长度偏移,长度占用字节,长度调整,剥离字节数 2. ch.pipeline().addLast(new LengthFieldBasedFrameDecoder(1024, 0, 1, 0, 1));
LengthFieldBasedFrameDecoder
一种解码器,它按消息中长度字段的值动态拆分收到的 ByteBufs。当您解码二进制消息时,它特别有用,该二进制消息具有表示消息正文或整个消息长度的整数标头字段。
经典构造办法:
1. public LengthFieldBasedFrameDecoder( 2. int maxFrameLength, 3. int lengthFieldOffset, int lengthFieldLength, 4. int lengthAdjustment, int initialBytesToStrip) { 5. this( 6. maxFrameLength, 7. lengthFieldOffset, lengthFieldLength, lengthAdjustment, 8. initialBytesToStrip, true); 9. }
创建新实例。
参数:
maxFrameLength:最大帧长度 ― 帧的最大长度。如果帧的长度大于此值, TooLongFrameException 将被抛出。
lengthFieldOffset:长度字段偏移量 – 长度字段的偏移量
lengthFieldLength:长度字段长度 – 长度字段的长度
lengthAdjustment:长度调整 – 要添加到长度字段值的补偿值
initialBytesToStrip :剥离字节数 ― 从解码帧中剥离的第一个字节数