拆包,粘包。
其实就是我们对通道进行读写数据的时候,tcp协议因为各种原因,对数据的分发次数进行控制,可能多个数据包整合成一个数据,一次过发送,也可能是一个数据包,由于过大,被拆成几份,进行分发。
下面写一个例子验证一下。
需要用到的demo,在这个demo的基础上进行调整。
对客户端处理器适配器的调整
其实只是添加了俩个字段,用于统计客户端发送多少条信息,服务端给客户端返回多少信息。
package client.handler.adapter;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
public class TimeClientChannelHandlerAdapter extends ChannelInboundHandlerAdapter{
private int count;
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
String req = "what time it is";
ctx.writeAndFlush(Unpooled.copiedBuffer(req.getBytes()));
}
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
ByteBuf req = (ByteBuf) msg;
byte[] reqinfo = new byte[req.readableBytes()];
req.readBytes(reqinfo);
System.out.println("now is " + new String(reqinfo, "UTF-8"));
System.out.println("客户端接收到第" + ++count + "条信息");
}
}
对于服务端处理器适配器的调整
package server.handler.adapter;
import org.joda.time.DateTime;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
public class TimeServerChannelHandlerAdapter extends ChannelInboundHandlerAdapter{
private int count;
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
ByteBuf req = (ByteBuf)msg;
byte[] reqInfo = new byte[req.readableBytes()];
req.readBytes(reqInfo);
System.out.println(new String(reqInfo, "UTF-8"));
System.out.println("服务端接收到第" + ++count + "条信息");
}
@Override
public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
ctx.writeAndFlush(Unpooled.copiedBuffer(DateTime.now().toString().getBytes()));
}
}
服务端运行结果
客户端运行结果
从上述运行结果中看出,目前是没有什么问题的。接下来,进行调整。客户端改为遍历发送消息。
package client.handler.adapter;
import java.util.stream.Stream;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
public class TimeClientChannelHandlerAdapter extends ChannelInboundHandlerAdapter{
private int count;
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
Stream.iterate(0, i -> i + 1).limit(100).forEach(i -> {
String req = "what time it is";
ctx.writeAndFlush(Unpooled.copiedBuffer(req.getBytes()));
});
}
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
ByteBuf req = (ByteBuf) msg;
byte[] reqinfo = new byte[req.readableBytes()];
req.readBytes(reqinfo);
System.out.println("now is " + new String(reqinfo, "UTF-8"));
System.out.println("客户端接收到第" + ++count + "条信息");
}
}
服务端运行结果
客户端运行结果
从上面俩个图看出,客户端将100个消息,压缩成2个消息进行发送,但是服务端也并没有返回2个响应,而是只响应了一次。
这里面就涉及到我们说的粘包和拆包。客户端将100个消息合并成2个数据包,分别发送,而服务端,也是将返回结果进行了粘包,只发送了一次。
那么要如何解决问题?
netty本身已经提供了组件给我们使用
如LineBasedFrameDecoder(根据换行符作为分割符)
如DelimiterBasedFrameDecoder (可以指定特定的符号作为分割符)
如StringDecoder
下面利用这些组件做调整,服务端和客户端按如下进行调整即可。
package client.handler.adapter;
import java.util.stream.Stream;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
public class TimeClientChannelHandlerAdapter extends ChannelInboundHandlerAdapter{
private int count;
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
Stream.iterate(0, i -> i + 1).limit(100).forEach(i -> {
String req = "what time it is" + System.getProperty("line.separator");
ctx.writeAndFlush(Unpooled.copiedBuffer(req.getBytes()));
});
}
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
String info = (String)msg;
System.out.println("now is " + info);
System.out.println("客户端接收到第" + ++count + "条信息");
}
}
package server.handler.adapter;
import org.joda.time.DateTime;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
public class TimeServerChannelHandlerAdapter extends ChannelInboundHandlerAdapter{
private int count;
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
System.out.println((String)msg);
System.out.println("服务端接收到第" + ++count + "条信息");
String msgInfo = DateTime.now().toString() + System.getProperty("line.separator");
ctx.writeAndFlush(Unpooled.copiedBuffer(msgInfo.getBytes()));
}
}
运行结果如下,可以看出粘包拆包的问题已经被解决了。
服务端运行结果
客户端运行结果
这里面要注意的点。
1.读取消息的时候不能跟之前一样直接使用ByteBuf进行强转,需要使用String类型,因为是使用StringDecoder最后解码,加入输出结果的是利用toString方法生成的String对象。
网友评论