netty粘包(一)消息定长 实践
服务端:
package com.jds.test.pack1; import io.netty.bootstrap.ServerBootstrap; import io.netty.channel.ChannelFuture; import io.netty.channel.ChannelInitializer; import io.netty.channel.ChannelPipeline; import io.netty.channel.EventLoopGroup; import io.netty.channel.nio.NioEventLoopGroup; import io.netty.channel.socket.SocketChannel; import io.netty.channel.socket.nio.NioServerSocketChannel; import io.netty.handler.codec.FixedLengthFrameDecoder; import io.netty.handler.codec.string.StringDecoder; import io.netty.handler.codec.string.StringEncoder; /** * Created by sunyuming on 18/8/1. */ public class Server4 { public static void main(String[] args) { //boss线程监听端口,worker线程负责数据读写 EventLoopGroup boss = new NioEventLoopGroup(); EventLoopGroup worker = new NioEventLoopGroup(); try{ //辅助启动类 ServerBootstrap bootstrap = new ServerBootstrap(); //设置线程池 bootstrap.group(boss,worker); //设置socket工厂 bootstrap.channel(NioServerSocketChannel.class); //设置管道工厂 bootstrap.childHandler(new ChannelInitializer<SocketChannel>() { @Override protected void initChannel(SocketChannel socketChannel) throws Exception { //获取管道 ChannelPipeline pipeline = socketChannel.pipeline(); //定长解码类 pipeline.addLast(new FixedLengthFrameDecoder(14)); //字符串解码类 pipeline.addLast(new StringDecoder()); pipeline.addLast(new StringEncoder()); //处理类 pipeline.addLast(new ServerHandler4()); } }); //绑定端口 ChannelFuture future = bootstrap.bind(8866).sync(); System.out.println("server start ...... "); //等待服务端监听端口关闭 future.channel().closeFuture().sync(); } catch (InterruptedException e) { e.printStackTrace(); }finally { //优雅退出,释放线程池资源 boss.shutdownGracefully(); worker.shutdownGracefully(); } } }
package com.jds.test.pack1; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.SimpleChannelInboundHandler; import java.net.InetAddress; /** * Created by sunyuming on 18/8/1. */ class ServerHandler4 extends SimpleChannelInboundHandler<String> { //用于记录次数 private int count = 0; //读取客户端发送的数据 @Override protected void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception { System.out.println("RESPONSE--------"+msg+";"+" @ "+ ++count); ctx.writeAndFlush(msg); } //新客户端接入 @Override public void channelActive(ChannelHandlerContext ctx) throws Exception { System.out.println("channelActive"); } //客户端断开 @Override public void channelInactive(ChannelHandlerContext ctx) throws Exception { System.out.println("channelInactive"); } //异常 @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { //关闭通道 ctx.channel().close(); //打印异常 cause.printStackTrace(); } }
客户端:
package com.jds.test.pack1; import io.netty.bootstrap.Bootstrap; import io.netty.channel.ChannelFuture; import io.netty.channel.ChannelInitializer; import io.netty.channel.ChannelPipeline; import io.netty.channel.EventLoopGroup; import io.netty.channel.nio.NioEventLoopGroup; import io.netty.channel.socket.SocketChannel; import io.netty.channel.socket.nio.NioSocketChannel; import io.netty.handler.codec.FixedLengthFrameDecoder; import io.netty.handler.codec.string.StringDecoder; import io.netty.handler.codec.string.StringEncoder; import java.net.InetSocketAddress; /** * Created by sunyuming on 18/8/1. */ public class Client4 { public static void main(String[] args) { //worker负责读写数据 EventLoopGroup worker = new NioEventLoopGroup(); try { //辅助启动类 Bootstrap bootstrap = new Bootstrap(); //设置线程池 bootstrap.group(worker); //设置socket工厂 bootstrap.channel(NioSocketChannel.class); //设置管道 bootstrap.handler(new ChannelInitializer<SocketChannel>() { @Override protected void initChannel(SocketChannel socketChannel) throws Exception { //获取管道 ChannelPipeline pipeline = socketChannel.pipeline(); //定长解码类 pipeline.addLast(new FixedLengthFrameDecoder(14)); //字符串编码类 pipeline.addLast(new StringEncoder()); pipeline.addLast(new StringDecoder()); //处理类 pipeline.addLast(new ClientHandler4()); } }); //发起异步连接操作 ChannelFuture futrue = bootstrap.connect(new InetSocketAddress("127.0.0.1",8866)).sync(); //等待客户端链路关闭 futrue.channel().closeFuture().sync(); } catch (InterruptedException e) { e.printStackTrace(); } finally { //优雅的退出,释放NIO线程组 worker.shutdownGracefully(); } } }
package com.jds.test.pack1; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.SimpleChannelInboundHandler; /** * Created by sunyuming on 18/8/1. */ class ClientHandler4 extends SimpleChannelInboundHandler<String> { //接受服务端发来的消息 @Override protected void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception { System.out.println("server response : "+msg); } //与服务器建立连接 @Override public void channelActive(ChannelHandlerContext ctx) throws Exception { //给服务器发消息 String s = "--"; //发送50次消息 for (int i = 0; i < 5; i++) { ctx.channel().writeAndFlush("I am client,"+s); } } //与服务器断开连接 @Override public void channelInactive(ChannelHandlerContext ctx) throws Exception { System.out.println("channelInactive"); } //异常 @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { //关闭管道 ctx.channel().close(); //打印异常信息 cause.printStackTrace(); } }
(一) 都14
服务端输出:
server start ......
channelActive
RESPONSE--------I am client,--; @ 1
RESPONSE--------I am client,--; @ 2
RESPONSE--------I am client,--; @ 3
RESPONSE--------I am client,--; @ 4
RESPONSE--------I am client,--; @ 5
客户端输出:
server response : I am client,--
server response : I am client,--
server response : I am client,--
server response : I am client,--
server response : I am client,--
(二)服务端13,客户端14
服务端:
server start ......
channelActive
RESPONSE--------I am client,-; @ 1
RESPONSE---------I am client,; @ 2
RESPONSE----------I am client; @ 3
RESPONSE--------,--I am clien; @ 4
RESPONSE--------t,--I am clie; @ 5
总向客户端发行字节13*5
客户端:
server response : I am client,--
server response : I am client,--
server response : I am client,--
server response : I am client,--
客户端收到13*5=65,65/14=4.6
(三)都13
服务端:
channelActive
RESPONSE--------I am client,-; @ 1
RESPONSE---------I am client,; @ 2
RESPONSE----------I am client; @ 3
RESPONSE--------,--I am clien; @ 4
RESPONSE--------t,--I am clie; @ 5
客户端:
server response : I am client,-
server response : -I am client,
server response : --I am client
server response : ,--I am clien
server response : t,--I am clie
(四)服务端去除,客户端13
服务端:
server start ......
channelActive
RESPONSE--------I am client,--I am client,--I am client,--I am client,--I am client,--; @ 1
总向客户端发行字节14*5=70
客户端:
server response : I am client,-
server response : -I am client,
server response : --I am client
server response : ,--I am clien
server response : t,--I am clie
客户端收到14*5=70,70/13=5.4
(五)都去除
服务端:
server start ......
channelActive
RESPONSE--------I am client,--I am client,--I am client,--I am client,--I am client,--; @ 1
客户端:
server response : I am client,--I am client,--I am client,--I am client,--I am client,--
参考:
https://blog.csdn.net/baiye_xing/article/details/73189191
【Netty入门】解决TCP粘包/分包的实例
================================================================================
补充:
源码中为单向发送,服务端只有StringDecoder,客户端只有StringEncoder,本例测试双向发包拆包,故两端都要加上编码器和解码器
8.22之所以未在exceptionCaught中报错,是因为write和writeAndFlush为出站操作,且为异步,不会立即抛出异常,捕获异常方式有二:
1)
ChannelFuture channelFuture = ctx.writeAndFlush(newMsg); channelFuture.addListener(new ChannelFutureListener() { @Override public void operationComplete(ChannelFuture future) throws Exception { if(!future.isSuccess()) { future.cause().printStackTrace(); future.channel().close(); } } });
可见抛出异常:
java.lang.UnsupportedOperationException: unsupported message type: String
2)
8.20,再补充:
serverhandler的channelRead0函数中直接将msg
ctx.writeAndFlush(msg);
write是个异步操作,而SimpleChannelInboundHandler的channelRead在其channelRead0后会释放msg:
ReferenceCountUtil.release(msg);
故该句应改为:
StringBuilder stringBuilder = new StringBuilder().append(msg);
String newMsg = stringBuilder.toString();
ctx.writeAndFlush(newMsg);
小结:
如果使用SimpleChannelInboundHandler,注意msg的复制;
如果使用ChannelInboundHandlerAdapter,注意msg的释放和转发;
2019.12.5 注意,此时的msg已经是java对象,所以如果要往下传递ServerHandler5,不需要复制