netty解决tcp/udp 粘包,拆包问题
在通讯协议中,粘包,拆包是常发生的事情,而netty则很好的给出了三种解决方式,下面分别介绍:
一;利用LineBasedFrameDecoder解决TCP粘包问题
直接上代码,
Client类:
package stickorApartPackageResolveOne.one; import io.netty.bootstrap.Bootstrap; import io.netty.channel.ChannelFuture; import io.netty.channel.ChannelInitializer; import io.netty.channel.ChannelOption; 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.LineBasedFrameDecoder; import io.netty.handler.codec.string.StringDecoder; /** * @ProjectName: nettyTes * @Package: stickorApartPackageResolveOne.one * @ClassName: Client * @Author: Administrator * @Description: ${description} * @Date: 2019/10/12 16:48 * @Version: 1.0 * *LineBasedFrameDecoder以换行符为结束标志的解码器,支持携带结束符或者不携带结束符两种解码方式, * * 同时支持配置单行的最大长度。如果连续读取到最大长度后仍然没有发现换行符, * * 就会抛出异常,同时忽略掉之前读到的异常码流。 * * StringDecoder的功能非常简单,就是将接收到的对象转换成字符串, * * 然后继续调用后面的handler。LineBasedFrameDecoder+StringDecoder组合就是按行切换的文本解码器, * * 它被设计用来支持TCP的粘包和拆包。 */ public class Client { public static void main(String[] args) { int port = 8080; String host = "127.0.0.1"; new Client().connect(port, host); } private void connect(int port, String host) { EventLoopGroup eventLoopGroup = new NioEventLoopGroup(); try { Bootstrap bst = new Bootstrap(); bst.group(eventLoopGroup); bst.channel(NioSocketChannel.class); bst.option(ChannelOption.TCP_NODELAY, true); bst.handler(new ChannelInitializer<SocketChannel>() { @Override protected void initChannel(SocketChannel socketChannel) throws Exception { socketChannel.pipeline().addLast(new LineBasedFrameDecoder(1024)); socketChannel.pipeline().addLast(new StringDecoder()); socketChannel.pipeline().addLast(new ClientHandler()); } }); ChannelFuture f = bst.connect(host, port).sync(); f.channel().closeFuture().sync(); } catch (InterruptedException e) { e.printStackTrace(); } finally { eventLoopGroup.shutdownGracefully(); } } }
ClientHandler类
package stickorApartPackageResolveOne.one; import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; import io.netty.channel.ChannelHandlerAdapter; import io.netty.channel.ChannelHandlerContext; import java.util.logging.Logger; /** * @ProjectName: nettyTes * @Package: stickorApartPackageResolveOne.one * @ClassName: ClientHandler * @Author: Administrator * @Description: ${description} * @Date: 2019/10/12 17:03 * @Version: 1.0 */ public class ClientHandler extends ChannelHandlerAdapter { private static final Logger logger = Logger .getLogger(ClientHandler.class.getName()); private int counter; private byte[] req; public ClientHandler() { req = ("QUERY TIME ORDER" + System.getProperty("line.separator") ).getBytes(); System.out.print(req.length); } @Override public void channelActive(ChannelHandlerContext ctx) { ByteBuf message = null; for (int i = 0; i < 30; i++) { message = Unpooled.buffer(req.length); message.writeBytes(req); ctx.writeAndFlush(message); } } @Override public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { String body = (String) msg; System.out.println("Now is : " + body + " ; the counter is : " + ++counter); } @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { // 释放资源 logger.warning("Unexpected exception from downstream : " + cause.getMessage()); ctx.close(); } }
Server类
package stickorApartPackageResolveOne.one; import io.netty.bootstrap.ServerBootstrap; import io.netty.channel.ChannelFuture; import io.netty.channel.ChannelInitializer; import io.netty.channel.ChannelOption; 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.LineBasedFrameDecoder; import io.netty.handler.codec.string.StringDecoder; /** * @ProjectName: nettyTes * @Package: stickorApartPackageResolveOne * @ClassName: Timeserver * @Author: Administrator * @Description: ${description} * @Date: 2019/10/12 16:03 * @Version: 1.0 */ public class Server { public static void main(String args[]) { int port = 8080; new Server().bind(port); } private void bind(int port) { EventLoopGroup eventLoopGroup = new NioEventLoopGroup(); EventLoopGroup eventLoopGroup1 = new NioEventLoopGroup(); try { ServerBootstrap sbt = new ServerBootstrap(); sbt.group(eventLoopGroup, eventLoopGroup1); sbt.channel(NioServerSocketChannel.class); sbt.option(ChannelOption.SO_BACKLOG, 1024); sbt.childHandler(new ChannelInitializer<SocketChannel>() { @Override protected void initChannel(SocketChannel socketChannel) throws Exception { //利用LineBasedFrameDecoder解决TCP粘包问题 socketChannel.pipeline().addLast(new LineBasedFrameDecoder(1024)); socketChannel.pipeline().addLast(new StringDecoder()); socketChannel.pipeline().addLast(new ServerHandler()); } }); ChannelFuture cf = sbt.bind(port).sync(); cf.channel().closeFuture().sync(); } catch (InterruptedException e) { e.printStackTrace(); } finally { eventLoopGroup.shutdownGracefully(); eventLoopGroup1.shutdownGracefully(); } } }
ServerHandler类
package stickorApartPackageResolveOne.one; import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; import io.netty.channel.ChannelHandlerAdapter; import io.netty.channel.ChannelHandlerContext; /** * @ProjectName: nettyTes * @Package: stickorApartPackageResolveOne * @ClassName: ServerHandler * @Author: Administrator * @Description: ${description} * @Date: 2019/10/12 16:20 * @Version: 1.0 */ public class ServerHandler extends ChannelHandlerAdapter { int counter; @Override public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { String body = (String)msg; System.out.println("The time server receive order : " + body + " ; the counter is : " + ++counter); String currentTime = "QUERY TIME ORDER".equalsIgnoreCase(body) ? new java.util.Date(System.currentTimeMillis()).toString() : "BAD ORDER"; currentTime = currentTime + System.getProperty("line.separator"); ByteBuf resp = Unpooled.copiedBuffer(currentTime.getBytes()); ctx.writeAndFlush(resp); } @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { ctx.close(); } }
先直接上结果:
客户端:
服务端:
LineBasedFrameDecoder和StringDecoder的原理分析
LineBasedFrameDecoder的工作原理是它依次遍历ByteBuf中的可读字节,判断看是否有\n或者以\r\n气如果有,就以此位置为结束位置,从可读索引到结束位置区间的字节就组成了一行。它是以换行符为结束标志的解码器,支持携带结束符或者不携带结束符两种解码方式,同时支持配置单行的最大长度。如果连续读取到最大长度后仍然没有发现换行符,就会抛出异常,同时忽略掉之前读到的异常码流。
StringDecoder的功能非常简单,就是将接收到的对象转换成字符串,然后继续调用后面的handler。LineBasedFrameDecoder+StringDecoder组合就是按行切换的文本解码器,它被设计用来支持TCP的粘包和拆包。