目录
1、粘包与半包
1.1、现象分析
1.2、解决方案
1.2.1、短连接
1.2.2、定长解码器
1.2.3、基于分割符的解码器
1.2.4、LTC解码器
2、协议设计与解析
2.1、自定义协议要素
TCP以一个段(segment)为单位,每发送一个段就要进行一次确认应答(ack)处理,但如果这么做,缺点是包的往返时间越长性能就越差。为了解决此问题,引入了窗口概念,窗口大小即决定了无需等待应答而可以继续发送的数据最大值。
窗口实际就起到一个缓冲区的作用,同时也能起到流量控制的作用。
粘包现象:发送abc def,接收abcdef。原因:
半包现象:发送abcdef,接收abc def。原因:
本质是因为TCP是流式协议,消息无边界。
package com.clp.nettyPlus;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.AdaptiveRecvByteBufAllocator;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.logging.LogLevel;
import io.netty.handler.logging.LoggingHandler;
import lombok.extern.slf4j.Slf4j;
@Slf4j
class HelloWorldServer {
public static void main(String[] args) {
new HelloWorldServer().start();
}
void start() {
NioEventLoopGroup boss = new NioEventLoopGroup();
NioEventLoopGroup worker = new NioEventLoopGroup();
try {
ServerBootstrap serverBootstrap = new ServerBootstrap()
// .option(ChannelOption.SO_RCVBUF, 10) //设置系统的接收缓冲区(接收的滑动窗口)
//调整netty的接收缓冲区,即ByteBuf(最小值、初始值、最大值)
.childOption(ChannelOption.RCVBUF_ALLOCATOR, new AdaptiveRecvByteBufAllocator(16,16,16))
.group(boss, worker)
.channel(NioServerSocketChannel.class)
.childHandler(new ChannelInitializer() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
//调试打印出服务端受到的信息
ch.pipeline().addLast(new LoggingHandler(LogLevel.DEBUG));
}
});
ChannelFuture channelFuture = serverBootstrap.bind(8080).sync();
channelFuture.channel().closeFuture().sync();
} catch (InterruptedException e) {
log.error("server error", e);
} finally {
boss.shutdownGracefully();
worker.shutdownGracefully();
}
}
}
package com.clp.nettyPlus;
import io.netty.bootstrap.Bootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class HelloWorldClient {
static final Logger log = LoggerFactory.getLogger(HelloWorldClient.class);
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
send();
}
System.out.println("finish");
}
public static void send() {
NioEventLoopGroup worker = new NioEventLoopGroup();
try {
Bootstrap bootstrap = new Bootstrap()
.group(worker)
.channel(NioSocketChannel.class)
.handler(new ChannelInitializer() {
@Override
protected void initChannel(SocketChannel channel) throws Exception {
channel.pipeline().addLast(new ChannelInboundHandlerAdapter() {
@Override //在连接 channel 建立成功后触发active事件
public void channelActive(ChannelHandlerContext ctx) throws Exception {
ByteBuf buf = ctx.alloc().buffer(16); //创建字节缓冲区
buf.writeBytes(new byte[]{0, 1, 2, 3, 4, 5, 6, 7, 8, 9,
10, 11, 12, 13, 14, 15, 16,17});
ctx.writeAndFlush(buf); //发送这些数据
ctx.channel().close(); //断开连接
super.channelActive(ctx);
}
});
}
});
ChannelFuture channelFuture = bootstrap.connect("localhost", 8080).sync();
channelFuture.channel().closeFuture().sync();
} catch (InterruptedException e) {
log.error("client error", e);
} finally {
worker.shutdownGracefully();
}
}
}
结果:
09:29:32.081 [nioEventLoopGroup-3-6] DEBUG io.netty.channel.DefaultChannelPipeline - Discarded inbound message PooledUnsafeDirectByteBuf(ridx: 0, widx: 2, cap: 16) that reached at the tail of the pipeline. Please check your pipeline configuration.
09:29:32.081 [nioEventLoopGroup-3-6] DEBUG io.netty.channel.DefaultChannelPipeline - Discarded message pipeline : [LoggingHandler#0, DefaultChannelPipeline$TailContext#0]. Channel : [id: 0x38d017e9, L:/127.0.0.1:8080 - R:/127.0.0.1:57609].
09:29:32.081 [nioEventLoopGroup-3-6] DEBUG io.netty.handler.logging.LoggingHandler - [id: 0x38d017e9, L:/127.0.0.1:8080 - R:/127.0.0.1:57609] READ COMPLETE
09:29:32.081 [nioEventLoopGroup-3-6] DEBUG io.netty.handler.logging.LoggingHandler - [id: 0x38d017e9, L:/127.0.0.1:8080 - R:/127.0.0.1:57609] READ COMPLETE
09:29:32.081 [nioEventLoopGroup-3-6] DEBUG io.netty.handler.logging.LoggingHandler - [id: 0x38d017e9, L:/127.0.0.1:8080 ! R:/127.0.0.1:57609] INACTIVE
09:29:32.081 [nioEventLoopGroup-3-6] DEBUG io.netty.handler.logging.LoggingHandler - [id: 0x38d017e9, L:/127.0.0.1:8080 ! R:/127.0.0.1:57609] UNREGISTERED
09:29:32.081 [nioEventLoopGroup-3-7] DEBUG io.netty.handler.logging.LoggingHandler - [id: 0x630e6f03, L:/127.0.0.1:8080 - R:/127.0.0.1:57626] REGISTERED
09:29:32.081 [nioEventLoopGroup-3-7] DEBUG io.netty.handler.logging.LoggingHandler - [id: 0x630e6f03, L:/127.0.0.1:8080 - R:/127.0.0.1:57626] ACTIVE
09:29:32.081 [nioEventLoopGroup-3-7] DEBUG io.netty.handler.logging.LoggingHandler - [id: 0x630e6f03, L:/127.0.0.1:8080 - R:/127.0.0.1:57626] READ: 16B
+-------------------------------------------------+
| 0 1 2 3 4 5 6 7 8 9 a b c d e f |
+--------+-------------------------------------------------+----------------+
|00000000| 00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f |................|
+--------+-------------------------------------------------+----------------+
09:29:32.081 [nioEventLoopGroup-3-7] DEBUG io.netty.channel.DefaultChannelPipeline - Discarded inbound message PooledUnsafeDirectByteBuf(ridx: 0, widx: 16, cap: 16) that reached at the tail of the pipeline. Please check your pipeline configuration.
09:29:32.081 [nioEventLoopGroup-3-7] DEBUG io.netty.channel.DefaultChannelPipeline - Discarded message pipeline : [LoggingHandler#0, DefaultChannelPipeline$TailContext#0]. Channel : [id: 0x630e6f03, L:/127.0.0.1:8080 - R:/127.0.0.1:57626].
09:29:32.081 [nioEventLoopGroup-3-7] DEBUG io.netty.handler.logging.LoggingHandler - [id: 0x630e6f03, L:/127.0.0.1:8080 - R:/127.0.0.1:57626] READ COMPLETE
09:29:32.081 [nioEventLoopGroup-3-7] DEBUG io.netty.handler.logging.LoggingHandler - [id: 0x630e6f03, L:/127.0.0.1:8080 - R:/127.0.0.1:57626] READ: 2B
+-------------------------------------------------+
| 0 1 2 3 4 5 6 7 8 9 a b c d e f |
+--------+-------------------------------------------------+----------------+
|00000000| 10 11 |.. |
+--------+-------------------------------------------------+----------------+
...
lengthFieldOffset:长度字段偏移量,表明长度字段部分从哪开始
lengthFieldLength:长度字段本身占用的字节数
lengthAdjustment:长度之后还有几个字节才是内容
initialBytesToStrip:经过解析之后,从头开始要剥离几个字节
package com.clp.decodeEncode;
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, 5),
new LoggingHandler(LogLevel.DEBUG)
);
//发送 4 字节内容的长度,然后是实际内容
ByteBuf buffer = ByteBufAllocator.DEFAULT.buffer();
write(buffer, "Hello, world");
write(buffer, "Hi!");
channel.writeInbound(buffer);
}
private static void write(ByteBuf buffer, String content) {
byte[] bytes = content.getBytes(); //实际内容
int length = bytes.length; //实际内容长度
buffer.writeInt(length); //大端表示法写入长度
buffer.writeByte(1); //加上版本号 1字节
buffer.writeBytes(bytes); //写入内容
}
}
结果:
+-------------------------------------------------+
| 0 1 2 3 4 5 6 7 8 9 a b c d e f |
+--------+-------------------------------------------------+----------------+
|00000000| 48 65 6c 6c 6f 2c 20 77 6f 72 6c 64 |Hello, world |
+--------+-------------------------------------------------+----------------+
10:32:24.729 [main] DEBUG io.netty.handler.logging.LoggingHandler - [id: 0xembedded, L:embedded - R:embedded] READ: 3B
+-------------------------------------------------+
| 0 1 2 3 4 5 6 7 8 9 a b c d e f |
+--------+-------------------------------------------------+----------------+
|00000000| 48 69 21 |Hi! |
+--------+-------------------------------------------------+----------------+
10:32:24.729 [main] DEBUG io.netty.handler.logging.LoggingHandler - [id: 0xembedded, L:embedded - R:embedded] READ COMPLETE