先看一下下面的例子:
服务端代码为:
public class TimeServer {
public static void main(String[] args) throws InterruptedException {
EventLoopGroup bossGroup=new NioEventLoopGroup();
EventLoopGroup workerGroup=new NioEventLoopGroup();
try{
ServerBootstrap b=new ServerBootstrap();
b.group(bossGroup,workerGroup)
.channel(NioServerSocketChannel.class)
.option(ChannelOption.SO_BACKLOG,1024)
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel channel) {
channel.pipeline().addLast(new TimeServerHandler());
}
});
ChannelFuture f=b.bind(8081).sync();
f.channel().closeFuture().sync();
}finally {
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}
}
class TimeServerHandler extends SimpleChannelInboundHandler {
private int counter;
@Override
protected void channelRead0(ChannelHandlerContext ctx, Object msg) throws Exception {
ByteBuf buf=(ByteBuf)msg;
byte[] req=new byte[buf.readableBytes()];
buf.readBytes(req);
String body=new String(req, StandardCharsets.UTF_8);
System.out.println(body+",counter:"+ (++counter));
}
}
客户端代码为:
public class TimeClient {
public static void main(String[] args) throws InterruptedException {
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 socketChannel){
socketChannel.pipeline().addLast(new TimeClientHandler());
}
});
ChannelFuture f=b.connect("127.0.0.1",8081).sync();
f.channel().closeFuture().sync();
}finally {
group.shutdownGracefully();
}
}
}
public class TimeClientHandler extends ChannelInboundHandlerAdapter {
@Override
public void channelActive(ChannelHandlerContext ctx) {
byte[] req= ("send msg to server\r\n").getBytes(StandardCharsets.UTF_8);
for (int i = 0; i < 3; i++) {
ByteBuf msg= Unpooled.buffer(req.length);
msg.writeBytes(req);
ctx.writeAndFlush(msg);
}
}
}
上面的代码中,客户端会连续发三条信息给服务端,服务端接收信息并进行计数。
或许你会认为服务端会收到三条信息,但事实上服务端可能只会收到一条,结果为:
send msg to server\send msg to server\send msg to server,counter:1
这是因为发生了粘包。粘包指的是多个小的数据包可能被封装成一个大的数据包发送。与之相关的是拆包,拆包指的是一个完整的数据包可能会被 TCP 拆分成多个包进行发送。
出现粘包/拆包的原因有三点:
如果 IP 需要发送一个数据报,并且这个数据报比链路层 MTU 大,则 IP 会通过分片将数据报分解成较小的部分,使每个分片都小于 MTU。 当两台主机之间跨越多个网络通信时,每条链路可能有不同大小的 MTU。在包含所有链路的整个网络路径上,最小的 MTU 称为路径 MTU。
因为底层的 TCP 无法区分上层的应用数据,所以只能依赖上层来解决,主要有四种办法:
Netty 提供了 LineBasedFrameDecoder
和 StringDecoder
解决粘包问题。
public class TimeServer {
public static void main(String[] args) throws InterruptedException {
EventLoopGroup bossGroup=new NioEventLoopGroup();
EventLoopGroup workerGroup=new NioEventLoopGroup();
try{
ServerBootstrap b=new ServerBootstrap();
b.group(bossGroup,workerGroup)
.channel(NioServerSocketChannel.class)
.option(ChannelOption.SO_BACKLOG,1024)
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel channel) { //添加LineBasedFrameDecoder和StringDecoder
channel.pipeline().addLast(new LineBasedFrameDecoder(1024));
channel.pipeline().addLast(new StringDecoder());
channel.pipeline().addLast(new TimeServerHandler());
}
});
ChannelFuture f=b.bind(8081).sync();
f.channel().closeFuture().sync();
}finally {
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}
}
class TimeServerHandler extends SimpleChannelInboundHandler {
private int counter;
@Override
protected void channelRead0(ChannelHandlerContext ctx, Object msg) {
String body=(String) msg; //接收到的是String类型
System.out.println(body+",counter:"+ (++counter));
}
}
LineBasedFrameDecoder
的工作原理是依次遍历 ByteBuf 中的可读字节,判断是否有“\n”或者“\r\n”,如果有,就以此位置为结束位置,从可读索引到结束位置区间的字节就组成了一行。它是以换行符为结束标志的解码器。
StringDecoder
则会将接收到的对象转换成字符串。
参考: