一、解決粘包拆包問題的方法
當客戶端向服務端發送了一個大的數據包時(如600M),TCP幾乎不會一次把這個包完整的發送到服務端,TCP分把這個包分包,分幾次發給服務端,至於TCP要分多少次,這是不可預測的。應用層如何正確的去區別每一條信息呢?下面介紹第一種分包的方法:以回車換行符作爲消息的結束符,FTP也是採用這種方式,這個結束符也有點像C中String的`\0`.
二、利用結束符解包的思路
分析過程:
1.消息中包含回車換行符(\n或\r\n),否則這條消息是不合規則的
2.獲取到回車換行符的位置,得到這個位置後,就可以知道這個消息的 長度,根據這個長度讀取數據.
如下圖:
+
Server;
/********************************************************************
*
********************************************************************
* Netty 學習
********************************************************************
*/
package com.netty;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
/**
*
* @author Jack Lei
* @email [email protected]
* @date 2016年3月13日 下午7:58:09
*/
public class Server {
public Server(int port) {
EventLoopGroup boss = new NioEventLoopGroup();
EventLoopGroup work = new NioEventLoopGroup();
ServerBootstrap boot = new ServerBootstrap();
boot.group(boss, work)
.channel(NioServerSocketChannel.class)
.option(ChannelOption.TCP_NODELAY, true)
.childHandler(new ServerChannelInitializer());
try {
ChannelFuture future = boot.bind(port).sync();
future.channel().closeFuture().sync();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
boss.shutdownGracefully();
work.shutdownGracefully();
}
}
public static void main(String[] args) {
new Server(8090);
}
}
/********************************************************************
*
********************************************************************
* Netty 學習
********************************************************************
*/
package com.netty;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.socket.SocketChannel;
import com.netty.codec.PacketDecoder;
import com.netty.handler.ServerHandler;
/**
*
* @author Jack Lei
* @email [email protected]
* @date 2016年3月16日 上午1:28:55
*/
public class ServerChannelInitializer extends ChannelInitializer<SocketChannel> {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ch.pipeline()
.addLast(new PacketDecoder(30))
.addLast(new ServerHandler());
}
}
/********************************************************************
*
********************************************************************
* Netty 學習
********************************************************************
*/
package com.netty.handler;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
/**
*
* @author Jack Lei
* @email [email protected]
* @date 2016年3月16日 上午1:30:03
*/
public class ServerHandler extends ChannelInboundHandlerAdapter {
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
ByteBuf buf = (ByteBuf) msg;
short packetId = buf.readShort();
int bodySize = buf.readableBytes();
byte[] body = new byte[bodySize];
buf.readBytes(body);
System.out.println("recive packet from client packetId = " + packetId + " , body = " + new String(body));
}
}
/********************************************************************
*
********************************************************************
* Netty 學習
********************************************************************
*/
package com.netty.codec;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.ByteToMessageDecoder;
import java.util.List;
/**
*
* @author Jack Lei
* @email [email protected]
* @date 2016年3月16日 上午1:31:08
*/
public class PacketDecoder extends ByteToMessageDecoder {
/* 一條消息的最大長度* */
private int lineLength;
public PacketDecoder(int lineLenth) {
this.lineLength = lineLenth;
}
@Override
protected void decode( ChannelHandlerContext ctx,
ByteBuf in,
List<Object> out) throws Exception {
int endPostion = findEndPostion(in);
if (endPostion != -1) {
int endWordLength = in.getByte(endPostion) == '\n' ? 1 : 2;
int bodySize = (endPostion - in.readerIndex()) + endWordLength;
out.add(in.readBytes(bodySize));
} else {
// 在指定消息長度的內沒有讀到結束符,就說明讀到的是不符合規則的數據包
if (in.readableBytes() >= lineLength) {
throw new Exception("消息不完整。");
}
}
}
private int findEndPostion(ByteBuf buf) {
int size = buf.writerIndex();
for (int index = buf.readerIndex(); index < size; index++) {
if (buf.getByte(index) == '\n') {
return index;
// 13 10
} else if (buf.getByte(index) == '\r' && index < size - 1 && buf.getByte(index + 1) == '\n') {
return index;
}
}
return -1;
}
}
Client:
/********************************************************************
*
********************************************************************
* Netty 學習
********************************************************************
*/
package com.netty;
import io.netty.bootstrap.Bootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
/**
*
* @author Jack Lei
* @email [email protected]
* @date 2016年3月13日 下午8:05:24
*/
public class Client {
final short packetId = 10001;
public Client(String host, int port) {
EventLoopGroup work = new NioEventLoopGroup();
Bootstrap boot = new Bootstrap();
boot.group(work)
.channel(NioSocketChannel.class)
.handler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ch.pipeline().addLast(new ChannelInboundHandlerAdapter() {
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
for (int i = 0; i < 12000; i++) {
ByteBuf buf = Unpooled.buffer();
buf.writeShort(packetId);
// 每條請求消息以換行符分隔
String body = "測試請求->" + i + System.getProperty("line.separator");
buf.writeBytes(body.getBytes());
System.out.println("send packet to Server packetId " + packetId + ", size = " + (body.getBytes().length + 2) + " , body = " + body);
ctx.writeAndFlush(buf);
}
}
});
}
});
try {
ChannelFuture future = boot.connect(host, port).sync();
future.channel().closeFuture().sync();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
work.shutdownGracefully();
}
}
public static void main(String[] args) {
new Client("127.0.0.1", 8090);
}
}
三、LineBaseFrameDecoder
LineBaseFrameDecoder,是Netty提供的一個可以解決TCP粘包拆包的類,也是以回車換行符作爲消息結束的標誌,經常搭配StringDecoder一起使用。爲了方便理解解碼器,我寫的PacketDecoder就是簡化了LineBaseFrameDecoder.關於解碼器還有其他幾種寫法,如限定消息的長度,通過定義消息長度來標識消息的長度,想了解的可以看下這幾個類的源碼(DelimiterBaseFrameDecoder,FixedLengthFrameDecoder).