是什么
见下
NianServer
package com.example.netty.nian;
import com.example.test.TestA;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.logging.LogLevel;
import io.netty.handler.logging.LoggingHandler;
import io.netty.util.CharsetUtil;
public class NianServer {
public static void main(String[] args) throws InterruptedException {
ServerBootstrap serverBootstrap = new ServerBootstrap();
serverBootstrap.group(new NioEventLoopGroup(1), new NioEventLoopGroup(10));
serverBootstrap.channel(NioServerSocketChannel.class);
//半包现象
//serverBootstrap.option(ChannelOption.SO_RCVBUF, 10);
serverBootstrap.childHandler(new ChannelInitializer<NioSocketChannel>() {
@Override
protected void initChannel(NioSocketChannel nioSocketChannel) throws Exception {
ChannelPipeline pipeline = nioSocketChannel.pipeline();
pipeline.addLast("logging", new LoggingHandler(LogLevel.INFO));
pipeline.addLast(new ChannelInboundHandlerAdapter() {
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
TestA.log((ByteBuf) msg);
ByteBuf buf = (ByteBuf) msg;
}
});
}
});
//
serverBootstrap.bind(8080).sync();
}
}
NianClient
package com.example.netty.nian;
import io.netty.bootstrap.Bootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.string.StringDecoder;
import io.netty.handler.codec.string.StringEncoder;
import java.util.Scanner;
public class Nianclient {
public static void main(String[] args) throws InterruptedException {
//
NioEventLoopGroup group = new NioEventLoopGroup();
//注意客户端使用的不是 ServerBootstrap 而是 Bootstrap
Bootstrap bootstrap = new Bootstrap();
bootstrap.group(group);
// 设置客户端通道的实现类(反射)
bootstrap.channel(NioSocketChannel.class);
bootstrap.handler(new ChannelInitializer<NioSocketChannel>() {
@Override
protected void initChannel(NioSocketChannel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();
pipeline.addLast(new ChannelInboundHandlerAdapter() {
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
for (int i = 0; i < 10; i++) {
ByteBuf buffer = ctx.alloc().buffer(16);
buffer.writeBytes(new byte[]{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15});
ctx.writeAndFlush(buffer);
}
}
});
}
});
bootstrap.connect("127.0.0.1", 8080).sync();
}
}
粘包打印
循环发送了十次 一次性接收到了
半包打印
这里需要把服务端的注释打开
循环发送10次16字节的 但是末尾一个8字节的接收
现象分析
粘包
现象,发送 abc def,接收 abcdef
原因
- 应用层:接收方 ByteBuf 设置太大(Netty 默认 1024)
- 滑动窗口:假设发送方 256 bytes 表示一个完整报文,但由于接收方处理不及时且窗口大小足够大,这 256 bytes
字节就会缓冲在接收方的滑动窗口中,当滑动窗口中缓冲了多个报文就会粘包 - Nagle 算法:会造成粘包
半包
现象,发送 abcdef,接收 abc def
原因
- 应用层:接收方 ByteBuf 小于实际发送数据量
- 滑动窗口:假设接收方的窗口只剩了 128 bytes,发送方的报文大小是 256 bytes,这时放不下了,只能先发送前 128
bytes,等待 ack 后才能发送剩余部分,这就造成了半包 - MSS 限制:当发送的数据超过 MSS 限制后,会将数据切分发送,就会造成半包
解决方案
- 短链接,发一个包建立一次连接,发完就断,这样连接建立到连接断开之间就是消息的边界,缺点效率太低
- 每一条消息采用固定长度,缺点浪费空间
- 每一条消息采用分隔符,例如 \n \r,缺点需要转义
- 每一条消息分为 head 和 body,head 中包含 body 的长度
123方案有缺点,我们使用第四种方案
使用LengthFieldBasedFrameDecoder
构造参数介绍
int maxFrameLength---------代表每一个消息帧的最大支持长度
int lengthFieldOffset,长度域的起始位置下标
int lengthFieldLength,长度域占用了几个字节
int lengthAdjustment,长度域后还有几个字节是内容(长度域后有时不直接是内容)
int initialBytesToStrip,解码后需要舍弃的字节的数量,这里指从前到后舍弃的
测试代码
这里使用了EmbeddedChannel来进行测试
package com.example.netty.nian;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufAllocator;
import io.netty.channel.embedded.EmbeddedChannel;
import io.netty.handler.codec.LengthFieldBasedFrameDecoder;
import io.netty.handler.logging.LogLevel;
import io.netty.handler.logging.LoggingHandler;
public class TestLengthFieldDecoder {
public static void main(String[] args) {
EmbeddedChannel channel = new EmbeddedChannel(
new LengthFieldBasedFrameDecoder(1024,0,4,1,0),
new LoggingHandler(LogLevel.INFO)
);
ByteBuf buf = ByteBufAllocator.DEFAULT.buffer();
send(buf, "abc");
send(buf, "www");
channel.writeInbound(buf);
}
public static void send(ByteBuf buf,String content){
byte[] bytes = content.getBytes();
int length = bytes.length;
buf.writeInt(length);
buf.writeByte(1);
buf.writeBytes(bytes);
}
}
输出