4、解决方案
4.1、短连接
以解决黏包为例。服务端同上,客户端如下
//短连接处理黏包,发送后关闭
public class TestSloveNianbaoClient {
public static void main(String[] args) {
for (int i = 0; i < 3; i++) {
send();
}
}
private static void send() {
NioEventLoopGroup group = new NioEventLoopGroup();
try {
ChannelFuture channelFuture = new Bootstrap()
.group(group)
.channel(NioSocketChannel.class)
.handler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel channel) throws Exception {
channel.pipeline().addLast(new ChannelInboundHandlerAdapter() {
//连接建立成功,active事件
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
ByteBuf buf = ctx.alloc().buffer();
buf.writeBytes("0123456789".getBytes());
ctx.writeAndFlush(buf);
ctx.channel().close();//发送后关闭,就是短连接
}
});
}
})
.connect(new InetSocketAddress(8080)).sync();
channelFuture.channel().closeFuture().sync();
} catch (InterruptedException e) {
throw new RuntimeException(e);
} finally {
group.shutdownGracefully();
}
}
}
不能解决半包,服务端Netyy接受缓冲区大小默认是1024,调小一点就可以看到半包问题
.childOption(ChannelOption.RCVBUF_ALLOCATOR, new AdaptiveRecvByteBufAllocator(16, 16, 16))
4.2、固定长度-定长解码器
客户端和服务端约定数据包长度
服务端
.addLast(new FixedLengthFrameDecoder(10))
//固定长度服务端
@Slf4j
public class TestFixLengthServer {
public static void main(String[] args) {
NioEventLoopGroup boss = new NioEventLoopGroup();
NioEventLoopGroup worker = new NioEventLoopGroup();
try {
ChannelFuture channelFuture = new ServerBootstrap()
.group(boss, worker)
.channel(NioServerSocketChannel.class)
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel channel) throws Exception {
channel.pipeline()
//核心代码,固定长度
.addLast(new FixedLengthFrameDecoder(10))
.addLast(new LoggingHandler());
}
})
.bind(8080).sync();
channelFuture.channel().closeFuture().sync();
} catch (InterruptedException e) {
log.error("server error");
} finally {
boss.shutdownGracefully();
worker.shutdownGracefully();
}
}
}
客户端
//固定长度客户端
public class TestFixLengthClient {
public static void main(String[] args) {
send();
}
private static void send() {
NioEventLoopGroup group = new NioEventLoopGroup();
try {
ChannelFuture channelFuture = new Bootstrap()
.group(group)
.channel(NioSocketChannel.class)
.handler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel channel) throws Exception {
channel.pipeline()
.addLast(new LoggingHandler())
.addLast(new ChannelInboundHandlerAdapter() {
//连接建立成功,active事件
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
ByteBuf buf = ctx.alloc().buffer();
Random random = new Random();
char c = '0';
for (int i = 0; i < 10; i++) {
byte[] bytes = fill(c, random.nextInt(10) + 1);
c++;
buf.writeBytes(bytes);
}
ctx.writeAndFlush(buf);
}
});
}
})
.connect(new InetSocketAddress(8080)).sync();
channelFuture.channel().closeFuture().sync();
} catch (InterruptedException e) {
throw new RuntimeException(e);
} finally {
group.shutdownGracefully();
}
}
private static byte[] fill(char c, int len) {
StringBuilder builder = new StringBuilder();
int i = 10 - len;
for (int i1 = 0; i1 < len; i1++) {
builder.append(c);
}
for (int i1 = 0; i1 < i; i1++) {
builder.append("_");
}
return builder.toString().getBytes();
}
}
结果
-
客户端
14:31:07.271 [nioEventLoopGroup-2-1] DEBUG io.netty.handler.logging.LoggingHandler - [id: 0x651d9086, L:/192.168.1.91:3834 - R:0.0.0.0/0.0.0.0:8080] WRITE: 100B +-------------------------------------------------+ | 0 1 2 3 4 5 6 7 8 9 a b c d e f | +--------+-------------------------------------------------+----------------+ |00000000| 30 30 5f 5f 5f 5f 5f 5f 5f 5f 31 5f 5f 5f 5f 5f |00________1_____| |00000010| 5f 5f 5f 5f 32 5f 5f 5f 5f 5f 5f 5f 5f 5f 33 5f |____2_________3_| |00000020| 5f 5f 5f 5f 5f 5f 5f 5f 34 34 34 34 34 34 34 34 |________44444444| |00000030| 5f 5f 35 35 35 35 35 5f 5f 5f 5f 5f 36 36 36 36 |__55555_____6666| |00000040| 5f 5f 5f 5f 5f 5f 37 5f 5f 5f 5f 5f 5f 5f 5f 5f |______7_________| |00000050| 38 38 38 38 38 38 38 38 38 38 39 39 5f 5f 5f 5f |888888888899____| |00000060| 5f 5f 5f 5f |____ | +--------+-------------------------------------------------+----------------+
-
服务端
14:31:07.272 [nioEventLoopGroup-3-2] DEBUG io.netty.handler.logging.LoggingHandler - [id: 0x753a8804, L:/192.168.1.91:8080 - R:/192.168.1.91:3834] READ: 10B +-------------------------------------------------+ | 0 1 2 3 4 5 6 7 8 9 a b c d e f | +--------+-------------------------------------------------+----------------+ |00000000| 30 30 5f 5f 5f 5f 5f 5f 5f 5f |00________ | +--------+-------------------------------------------------+----------------+ .......... 14:31:07.273 [nioEventLoopGroup-3-2] DEBUG io.netty.handler.logging.LoggingHandler - [id: 0x753a8804, L:/192.168.1.91:8080 - R:/192.168.1.91:3834] READ: 10B +-------------------------------------------------+ | 0 1 2 3 4 5 6 7 8 9 a b c d e f | +--------+-------------------------------------------------+----------------+ |00000000| 31 5f 5f 5f 5f 5f 5f 5f 5f 5f |1_________ | +--------+-------------------------------------------------+----------------+ 一直到9 ........
缺点是,数据包的大小不好把握
- 长度定的太大,浪费
- 长度定的太小,对某些数据包又显得不够
4.3、固定分隔符-行解码器
服务端加入,默认以 \n 或 \r\n 作为分隔符,如果超出指定长度仍未出现分隔符,则抛出异常
ch.pipeline().addLast(new LineBasedFrameDecoder(1024));
服务端
//分隔符解决黏包半包,服务端
@Slf4j
public class TestLineBasedServer {
public static void main(String[] args) {
NioEventLoopGroup boss = new NioEventLoopGroup();
NioEventLoopGroup worker = new NioEventLoopGroup();
try {
ChannelFuture channelFuture = new ServerBootstrap()
.group(boss, worker)
.channel(NioServerSocketChannel.class)
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel channel) throws Exception {
channel.pipeline()
//核心代码,分隔符,1024个字节内没找到分隔符,抛出异常 TooLongFrameException
.addLast(new LineBasedFrameDecoder(1024))
.addLast(new LoggingHandler());
}
})
.bind(8080).sync();
channelFuture.channel().closeFuture().sync();
} catch (InterruptedException e) {
log.error("server error");
} finally {
boss.shutdownGracefully();
worker.shutdownGracefully();
}
}
}
客户端
//分隔符解决黏包半包,客户端
public class TestLineBasedClient {
public static void main(String[] args) {
send();
}
private static void send() {
NioEventLoopGroup group = new NioEventLoopGroup();
try {
ChannelFuture channelFuture = new Bootstrap()
.group(group)
.channel(NioSocketChannel.class)
.handler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel channel) throws Exception {
channel.pipeline()
.addLast(new LoggingHandler())
.addLast(new ChannelInboundHandlerAdapter() {
//连接建立成功,active事件
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
ByteBuf buf = ctx.alloc().buffer();
Random random = new Random();
char c = '0';
for (int i = 0; i < 10; i++) {
StringBuilder builder = makeStr(c, random.nextInt(10) + 1);
c++;
buf.writeBytes(builder.toString().getBytes());
}
ctx.writeAndFlush(buf);
}
});
}
})
.connect(new InetSocketAddress(8080)).sync();
channelFuture.channel().closeFuture().sync();
} catch (InterruptedException e) {
throw new RuntimeException(e);
} finally {
group.shutdownGracefully();
}
}
private static StringBuilder makeStr(char c, int len) {
StringBuilder builder = new StringBuilder(len + 2);
for (int i = 0; i < len; i++) {
builder.append(c);
}
builder.append("\n");
return builder;
}
}
结果
-
客户端
14:54:06.694 [nioEventLoopGroup-2-1] DEBUG io.netty.handler.logging.LoggingHandler - [id: 0x9208b1d6, L:/192.168.1.91:4584 - R:0.0.0.0/0.0.0.0:8080] WRITE: 58B +-------------------------------------------------+ | 0 1 2 3 4 5 6 7 8 9 a b c d e f | +--------+-------------------------------------------------+----------------+ |00000000| 30 30 30 30 30 0a 31 31 0a 32 32 32 32 32 32 32 |00000.11.2222222| |00000010| 0a 33 33 33 33 33 33 33 33 0a 34 34 34 34 34 0a |.33333333.44444.| |00000020| 35 35 0a 36 36 36 0a 37 37 37 0a 38 38 38 38 0a |55.666.777.8888.| |00000030| 39 39 39 39 39 39 39 39 39 0a |999999999. | +--------+-------------------------------------------------+----------------+
-
服务端
14:54:06.696 [nioEventLoopGroup-3-2] DEBUG io.netty.handler.logging.LoggingHandler - [id: 0xe9699553, L:/192.168.1.91:8080 - R:/192.168.1.91:4584] READ: 5B +-------------------------------------------------+ | 0 1 2 3 4 5 6 7 8 9 a b c d e f | +--------+-------------------------------------------------+----------------+ |00000000| 30 30 30 30 30 |00000 | +--------+-------------------------------------------------+----------------+ 14:54:06.696 [nioEventLoopGroup-3-2] DEBUG io.netty.handler.logging.LoggingHandler - [id: 0xe9699553, L:/192.168.1.91:8080 - R:/192.168.1.91:4584] READ: 2B +-------------------------------------------------+ | 0 1 2 3 4 5 6 7 8 9 a b c d e f | +--------+-------------------------------------------------+----------------+ |00000000| 31 31 |11 | +--------+-------------------------------------------------+----------------+ 直到9 ...........
缺点:如果数据本身中包含了分隔符,就解析错错误了
4.4、预设长度-LTC解码器
- lengthFieldOffset - 长度字段偏移量
- lengthFieldLength - 长度字段长度
- lengthAdjustment - 长度字段为基础,还有几个字节是内容
- initialBytesToStrip - 从头剥离几个字节
源码例子说明
lengthFieldOffset = 0
lengthFieldLength = 2
lengthAdjustment = 0
initialBytesToStrip = 0 (= do not strip header)
BEFORE DECODE (14 bytes) AFTER DECODE (14 bytes)
+--------+----------------+ +--------+----------------+
| Length | Actual Content |----->| Length | Actual Content |
| 0x000C | "HELLO, WORLD" | | 0x000C | "HELLO, WORLD" |
+--------+----------------+ +--------+----------------+
lengthFieldOffset = 0
lengthFieldLength = 2
lengthAdjustment = 0
initialBytesToStrip = 2 (= the length of the Length field)
BEFORE DECODE (14 bytes) AFTER DECODE (12 bytes)
+--------+----------------+ +----------------+
| Length | Actual Content |----->| Actual Content |
| 0x000C | "HELLO, WORLD" | | "HELLO, WORLD" |
+--------+----------------+ +----------------+
lengthFieldOffset = 0
lengthFieldLength = 2
lengthAdjustment = 0
initialBytesToStrip = 2 (= the length of the Length field)
BEFORE DECODE (14 bytes) AFTER DECODE (12 bytes)
+--------+----------------+ +----------------+
| Length | Actual Content |----->| Actual Content |
| 0x000C | "HELLO, WORLD" | | "HELLO, WORLD" |
+--------+----------------+ +----------------+
lengthFieldOffset = 2 (= the length of Header 1)
lengthFieldLength = 3
lengthAdjustment = 0
initialBytesToStrip = 0
BEFORE DECODE (17 bytes) AFTER DECODE (17 bytes)
+----------+----------+----------------+ +----------+----------+----------------+
| Header 1 | Length | Actual Content |----->| Header 1 | Length | Actual Content |
| 0xCAFE | 0x00000C | "HELLO, WORLD" | | 0xCAFE | 0x00000C | "HELLO, WORLD" |
+----------+----------+----------------+ +----------+----------+----------------+
lengthFieldOffset = 0
lengthFieldLength = 3
lengthAdjustment = 2 (= the length of Header 1)
initialBytesToStrip = 0
BEFORE DECODE (17 bytes) AFTER DECODE (17 bytes)
+----------+----------+----------------+ +----------+----------+----------------+
| Length | Header 1 | Actual Content |----->| Length | Header 1 | Actual Content |
| 0x00000C | 0xCAFE | "HELLO, WORLD" | | 0x00000C | 0xCAFE | "HELLO, WORLD" |
+----------+----------+----------------+ +----------+----------+----------------+
lengthFieldOffset = 1 (= the length of HDR1)
lengthFieldLength = 2
lengthAdjustment = 1 (= the length of HDR2)
initialBytesToStrip = 3 (= the length of HDR1 + LEN)
BEFORE DECODE (16 bytes) AFTER DECODE (13 bytes)
+------+--------+------+----------------+ +------+----------------+
| HDR1 | Length | HDR2 | Actual Content |----->| HDR2 | Actual Content |
| 0xCA | 0x000C | 0xFE | "HELLO, WORLD" | | 0xFE | "HELLO, WORLD" |
+------+--------+------+----------------+ +------+----------------+
Embedded测试
//测试预设长度解码器
public class TestLengthField {
public static void main(String[] args) {
//参1:最大长度,一般固定
//参2:长度字段偏移量
//参3:长度字段长度
//参4:长度字段为基础,还有几个字节是内容
//参5: 从头剥离几个字节
LengthFieldBasedFrameDecoder decoder = new LengthFieldBasedFrameDecoder(1024, 0, 4, 0, 4);
LoggingHandler handler = new LoggingHandler();
EmbeddedChannel channel = new EmbeddedChannel(decoder, handler);
ByteBuf buf = channel.alloc().buffer();
writeMsg(buf, "hello");
writeMsg(buf, "-world");
channel.writeInbound(buf);
}
private static void writeMsg(ByteBuf buf, String content) {
byte[] bytes = content.getBytes();
int length = bytes.length;
buf.writeInt(length);
buf.writeBytes(bytes);
}
}
结果
15:33:52.243 [main] DEBUG io.netty.handler.logging.LoggingHandler - [id: 0xembedded, L:embedded - R:embedded] READ: 5B
+-------------------------------------------------+
| 0 1 2 3 4 5 6 7 8 9 a b c d e f |
+--------+-------------------------------------------------+----------------+
|00000000| 68 65 6c 6c 6f |hello |
+--------+-------------------------------------------------+----------------+
15:33:52.243 [main] DEBUG io.netty.handler.logging.LoggingHandler - [id: 0xembedded, L:embedded - R:embedded] READ: 6B
+-------------------------------------------------+
| 0 1 2 3 4 5 6 7 8 9 a b c d e f |
+--------+-------------------------------------------------+----------------+
|00000000| 2d 77 6f 72 6c 64 |-world |
+--------+-------------------------------------------------+----------------+
15:33:52.243 [main] DEBUG io.netty.handler.logging.LoggingHandler - [id: 0xembedded, L:embedded - R:embedded] READ COMPLETE
标签:Netty,黏包,bytes,new,半包,5f,buf,void,channel
From: https://www.cnblogs.com/jpymll/p/16821507.html