由於TCP無法理解上層的業務數據,所以底層無法保證數據包不被拆分和重組
所以我們需要在上層設計協議棧來解決這個問題。
主流的協議方案有
1. 消息定長
2. 在包尾部增加換行回車符
3. 將消息分成消息頭和消息體
4. 其他
爲了展現未考慮TCP粘包導致的功能異常情況,將上一篇文章的時間服務器的channelRead方法改造一下。
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
ByteBuf buf = (ByteBuf) msg;
byte[] req = new byte[buf.readableBytes()];
buf.readBytes(req);
String body = new String(req, "UTF-8");
body = body.substring(0, req.length - System.getProperty("line.separator").length());
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);
}
每收到一條消息後,就計數一次,然後發送應答消息給客戶端。
TimClientHandler也改造成如下
當和服務器端建立好鏈接之後,循環發送100條消息。
public class TimeClientHandler extends ChannelHandlerAdapter {
private static final Logger logger = Logger.getLogger(TimeClientHandler.class.getName());
private int counter;
private byte[] req;
/**
* Creates a client-side handler.
*/
public TimeClientHandler() {
req = ("QUERY TIME ORDER" + System.getProperty("line.separator")).getBytes();
}
@Override
public void channelActive(ChannelHandlerContext ctx) {
ByteBuf message;
for (int i = 0; i < 100; i++) {
message = Unpooled.buffer(req.length);
message.writeBytes(req);
ctx.writeAndFlush(message);
}
}
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
ByteBuf buf = (ByteBuf) msg;
byte[] req = new byte[buf.readableBytes()];
buf.readBytes(req);
String body = new String(req, "UTF-8");
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();
}
}
那麼按照設計,服務器端應該會收到100條請求,並且counter會變成100.
但是實際上的運行結果是服務器端只收到了兩條消息,第一條包括57條“QUERY TIME ORDER"指令,第二條包括43條”QUERY TIME ORDER"指令,這說明發生了TCP粘包,程序並沒有像我們設計的那樣,100條QUERY TIME ORDER指令分開發送,而是“粘”在一起發送了。
如何解決這個問題,利用Netty提供的編碼解碼器即可。
ServerBootstrap b = new ServerBootstrap();
b.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
.option(ChannelOption.SO_BACKLOG, 1024)
.childHandler(new ChildChannelHandler());
private class ChildChannelHandler extends ChannelInitializer<SocketChannel> {
@Override
protected void initChannel(SocketChannel socketChannel) {
socketChannel.pipeline().addLast(new LineBasedFrameDecoder(1024));
socketChannel.pipeline().addLast(new StringDecoder());
socketChannel.pipeline().addLast(new TimeServerHandler());
}
}
LineBasedFrameDecoder 按照換行符作爲結束標誌的解碼器
StringDecoder 將接受對象轉換成字符串。