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============