一、解决粘包拆包问题的方法
当客户端向服务端发送了一个大的数据包时(如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 895896736@qq.com
* @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 895896736@qq.com
* @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 895896736@qq.com
* @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 895896736@qq.com
* @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 895896736@qq.com
* @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).