Netty解决TCP的粘包和分包(一)java
关于TCP的粘包和分包:http://my.oschina.net/xinxingegeya/blog/484824git
分包的解决办法:github
一、消息定长,报文大小固定长度,不够空格补全,发送和接收方遵循相同的约定,这样即便粘包了经过接收方编程实现获取定长报文也能区分。编程
二、包尾添加特殊分隔符,例如每条报文结束都添加回车换行符(例如FTP协议)或者指定特殊字符做为报文分隔符,接收方经过特殊分隔符切分报文区分。bootstrap
三、将消息分为消息头和消息体,消息头中包含表示信息的总长度(或者消息体长度)的字段数组
四、更复杂的自定义应用层协议服务器
而在netty提供了两个解码器,能够进行分包的操做,这两个解码器分别是:
网络
DelimiterBasedFrameDecoder(添加特殊分隔符报文来分包)app
FixedLengthFrameDecoder(使用定长的报文来分包)socket
来看一下这两种解码器是如何进行分包的。
先看一个实例,一个netty的example,github地址
https://github.com/netty/netty/tree/master/example/src/main/java/io/netty/example/securechat
只是其中的一个设置编解码器的类。
package com.usoft.chat; import io.netty.channel.ChannelInitializer; import io.netty.channel.ChannelPipeline; import io.netty.channel.socket.SocketChannel; import io.netty.handler.codec.DelimiterBasedFrameDecoder; import io.netty.handler.codec.Delimiters; import io.netty.handler.codec.string.StringDecoder; import io.netty.handler.codec.string.StringEncoder; import io.netty.handler.ssl.SslContext; /** * Creates a newly configured {@link ChannelPipeline} for a new channel. */ public class SecureChatServerInitializer extends ChannelInitializer<SocketChannel> { private final SslContext sslCtx; public SecureChatServerInitializer(SslContext sslCtx) { this.sslCtx = sslCtx; } @Override public void initChannel(SocketChannel ch) throws Exception { ChannelPipeline pipeline = ch.pipeline(); // Add SSL handler first to encrypt and decrypt everything. // In this example, we use a bogus certificate in the server side // and accept any invalid certificates in the client side. // You will need something more complicated to identify both // and server in the real world. pipeline.addLast(sslCtx.newHandler(ch.alloc())); // On top of the SSL handler, add the text line codec. pipeline.addLast(new DelimiterBasedFrameDecoder(8192, Delimiters .lineDelimiter())); pipeline.addLast(new StringDecoder()); pipeline.addLast(new StringEncoder()); // and then business logic. pipeline.addLast(new SecureChatServerHandler()); } }
其中这行代码就是设置解码器来分包的,
pipeline.addLast(new DelimiterBasedFrameDecoder(8192, Delimiters.lineDelimiter()));
以下是这个类的属性字段,
private final ByteBuf[] delimiters; // 分包的分隔符数组 private final int maxFrameLength; //报文(帧)的最大的长度 private final boolean stripDelimiter; // 是否除去分隔符(若是数据包中含有分隔符,不影响) private final boolean failFast; // 为true是说发现读到的数据已经超过了maxFrameLength了,当即报TooLongFrameException,若是为false就是读完整个帧数据后再报 private boolean discardingTooLongFrame; //是否抛弃超长的帧 private int tooLongFrameLength; //就是说出现了超长帧,那这个帧的长度究竟是多少,就是这个长度,通常来讲是在发现当前buffer的可读数据超过最大帧时候进行设置
其实这个例子都不会形成粘包,由于客户端的每次输入而后回车都会使客户端进行一次writeAndFlush。这里只不过是演示了 DelimiterBasedFrameDecoder的用法。同时使用这个解码器也能够实现分包。
实例以下,服务器端代码,
package com.usoft.demo1; 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.FixedLengthFrameDecoder; import io.netty.handler.codec.string.StringDecoder; import io.netty.handler.logging.LogLevel; import io.netty.handler.logging.LoggingHandler; /** * 定长解码 服务器端 * * @author xwalker */ public class Server { public static void main(String[] args) throws Exception { int port = 8000; new Server().bind(port); } public void bind(int port) throws Exception { //接收客户端链接用 EventLoopGroup bossGroup = new NioEventLoopGroup(); //处理网络读写事件 EventLoopGroup workerGroup = new NioEventLoopGroup(); try { //配置服务器启动类 ServerBootstrap b = new ServerBootstrap(); b.group(bossGroup, workerGroup) .channel(NioServerSocketChannel.class) .option(ChannelOption.SO_BACKLOG, 100) .handler(new LoggingHandler(LogLevel.INFO))//配置日志输出 .childHandler(new ChannelInitializer<SocketChannel>() { @Override protected void initChannel(SocketChannel ch) throws Exception { ch.pipeline().addLast(new FixedLengthFrameDecoder(30));//设置定长解码器 长度设置为30 ch.pipeline().addLast(new StringDecoder());//设置字符串解码器 自动将报文转为字符串 ch.pipeline().addLast(new Serverhandler());//处理网络IO 处理器 } }); //绑定端口 等待绑定成功 ChannelFuture f = b.bind(port).sync(); //等待服务器退出 f.channel().closeFuture().sync(); } finally { //释放线程资源 bossGroup.shutdownGracefully(); workerGroup.shutdownGracefully(); } } }
package com.usoft.demo1; import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; import io.netty.channel.ChannelHandlerAdapter; import io.netty.channel.ChannelHandlerContext; /** * 服务器handler * * @author xwalker */ public class Serverhandler extends ChannelHandlerAdapter { private static final String MESSAGE = "It greatly simplifies and streamlines network programming such as TCP and UDP socket server."; int counter = 0; @Override public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { System.out.println("接收客户端msg:[" + msg + "]"); ByteBuf echo = Unpooled.copiedBuffer(MESSAGE.getBytes()); ctx.writeAndFlush(echo); } @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { cause.printStackTrace(); ctx.close(); } }
客户端代码,
package com.usoft.demo1; 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.FixedLengthFrameDecoder; import io.netty.handler.codec.string.StringDecoder; /** * 客户端 * @author xwalker * */ public class Client { /** * 连接服务器 * @param port * @param host * @throws Exception */ public void connect(int port,String host)throws Exception{ //网络事件处理线程组 EventLoopGroup group=new NioEventLoopGroup(); try{ //配置客户端启动类 Bootstrap b=new Bootstrap(); b.group(group).channel(NioSocketChannel.class) .option(ChannelOption.TCP_NODELAY, true)//设置封包 使用一次大数据的写操做,而不是屡次小数据的写操做 .handler(new ChannelInitializer<SocketChannel>() { @Override protected void initChannel(SocketChannel ch) throws Exception { ch.pipeline().addLast(new FixedLengthFrameDecoder(30));//设置定长解码器 ch.pipeline().addLast(new StringDecoder());//设置字符串解码器 ch.pipeline().addLast(new ClientHandler());//设置客户端网络IO处理器 } }); //链接服务器 同步等待成功 ChannelFuture f=b.connect(host,port).sync(); //同步等待客户端通道关闭 f.channel().closeFuture().sync(); }finally{ //释放线程组资源 group.shutdownGracefully(); } } public static void main(String[] args) throws Exception { int port=8000; new Client().connect(port, "127.0.0.1"); } }
package com.usoft.demo1; import io.netty.buffer.Unpooled; import io.netty.channel.ChannelHandlerAdapter; import io.netty.channel.ChannelHandlerContext; /** * 客户端处理器 * * @author xwalker */ public class ClientHandler extends ChannelHandlerAdapter { private static final String MESSAGE = "Netty is a NIO client server framework which enables quick and easy development of network applications such as protocol servers and clients."; public ClientHandler() { } @Override public void channelActive(ChannelHandlerContext ctx) throws Exception { ctx.writeAndFlush(Unpooled.copiedBuffer(MESSAGE.getBytes())); } @Override public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { System.out.println("接收服务器响应msg:[" + msg + "]"); } @Override public void channelReadComplete(ChannelHandlerContext ctx) throws Exception { ctx.flush(); } @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { cause.printStackTrace(); ctx.close(); } }
服务器端打印结果:
receive client msg:[Netty is a NIO client server f] receive client msg:[ramework which enables quick a] receive client msg:[nd easy development of network] receive client msg:[ applications such as protocol]
客户端打印结果:
receive sever msg:[It greatly simplifies and stre] receive sever msg:[amlines network programming su] receive sever msg:[ch as TCP and UDP socket serve] receive sever msg:[r.It greatly simplifies and st] receive sever msg:[reamlines network programming ] receive sever msg:[such as TCP and UDP socket ser] receive sever msg:[ver.It greatly simplifies and ] receive sever msg:[streamlines network programmin] receive sever msg:[g such as TCP and UDP socket s] receive sever msg:[erver.It greatly simplifies an] receive sever msg:[d streamlines network programm] receive sever msg:[ing such as TCP and UDP socket]
客户端须要和服务器端约定每一个包的大小为定长的,这样服务器端才能够根据这个规则来分包。这里也是演示了FixedLengthFrameDecoder解码器的用法。
参考和引用:
http://my.oschina.net/imhoodoo/blog/357290
http://asialee.iteye.com/blog/1783842
============END============