栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

Netty进阶-Netty篇

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

Netty进阶-Netty篇

知识点前文请阅读:Netty入门


粘包、半包

服务器端

	public static void main(String[] args) throws InterruptedException {
		NioEventLoopGroup boss = new NioEventLoopGroup();
		NioEventLoopGroup worker = new NioEventLoopGroup();

		try {
			ServerBootstrap serverBootstrap = new ServerBootstrap();
			serverBootstrap.group(boss, worker);
			// 设置服务器端接收缓冲区大小为 10 字节
			serverBootstrap.option(ChannelOption.SO_RCVBUF, 10);
			serverBootstrap.channel(NioServerSocketChannel.class);
			serverBootstrap.childHandler(new ChannelInitializer() {
				@Override
				protected void initChannel(NioSocketChannel ch) throws Exception {
					ch.pipeline().addLast(new LoggingHandler(LogLevel.DEBUG));
				}
			});
			ChannelFuture channelFuture = serverBootstrap.bind(8080).sync();
			channelFuture.channel().closeFuture().sync();
		} catch (InterruptedException e) {
			e.printStackTrace();
		} finally {
			boss.shutdownGracefully();
			worker.shutdownGracefully();
		}
	}

客户端

	public static void main(String[] args) throws InterruptedException {
		NioEventLoopGroup worker = new NioEventLoopGroup();

		try {
			Bootstrap bootstrap = new Bootstrap();
			bootstrap.group(worker);
			bootstrap.channel(NioSocketChannel.class);
			bootstrap.handler(new ChannelInitializer() {
				@Override
				protected void initChannel(NioSocketChannel ch) throws Exception {
					ch.pipeline().addLast(new ChannelInboundHandlerAdapter() {
						
						@Override
						public void channelActive(ChannelHandlerContext ctx) throws Exception {
							// 向服务器端发送 5 次数据,每次 发送16个字节
							for (int i = 0; i < 5; i++) {
								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});
								ctx.writeAndFlush(buf);
							}
						}
					});
				}
			});
			ChannelFuture channelFuture = bootstrap.connect(new InetSocketAddress("localhost", 8080)).sync();
			channelFuture.channel().closeFuture().sync();
		} catch (InterruptedException e) {
			e.printStackTrace();
		} finally {
			worker.shutdownGracefully();
		}
	}

服务器端打印结果

15:23:38 [DEBUG] [nioEventLoopGroup-3-1] i.n.h.l.LoggingHandler - [id: 0x8fc90ea3, L:/127.0.0.1:8080 - R:/127.0.0.1:53151] REGISTERED
15:23:38 [DEBUG] [nioEventLoopGroup-3-1] i.n.h.l.LoggingHandler - [id: 0x8fc90ea3, L:/127.0.0.1:8080 - R:/127.0.0.1:53151] ACTIVE
15:23:38 [DEBUG] [nioEventLoopGroup-3-1] i.n.h.l.LoggingHandler - [id: 0x8fc90ea3, L:/127.0.0.1:8080 - R:/127.0.0.1:53151] READ: 36B		// 可以看到这里接收到 36 字节,客户端每次发送 16 个字节,服务器端这里一次接收了36字节,36 > 16 出现了粘包现象,16:16:4 最后4这里出现了半包现象
         +-------------------------------------------------+
         |  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 |................|
|00000010| 00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f |................|
|00000020| 00 01 02 03                                     |....            |
+--------+-------------------------------------------------+----------------+
15:23:38 [DEBUG] [nioEventLoopGroup-3-1] i.n.h.l.LoggingHandler - [id: 0x8fc90ea3, L:/127.0.0.1:8080 - R:/127.0.0.1:53151] READ COMPLETE
15:23:38 [DEBUG] [nioEventLoopGroup-3-1] i.n.h.l.LoggingHandler - [id: 0x8fc90ea3, L:/127.0.0.1:8080 - R:/127.0.0.1:53151] READ: 40B
         +-------------------------------------------------+
         |  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |
+--------+-------------------------------------------------+----------------+
|00000000| 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f 00 01 02 03 |................|
|00000010| 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f 00 01 02 03 |................|
|00000020| 04 05 06 07 08 09 0a 0b                         |........        |
+--------+-------------------------------------------------+----------------+
15:23:38 [DEBUG] [nioEventLoopGroup-3-1] i.n.h.l.LoggingHandler - [id: 0x8fc90ea3, L:/127.0.0.1:8080 - R:/127.0.0.1:53151] READ COMPLETE
15:23:38 [DEBUG] [nioEventLoopGroup-3-1] i.n.h.l.LoggingHandler - [id: 0x8fc90ea3, L:/127.0.0.1:8080 - R:/127.0.0.1:53151] READ: 4B			// 半包
         +-------------------------------------------------+
         |  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |
+--------+-------------------------------------------------+----------------+
|00000000| 0c 0d 0e 0f                                     |....            |
+--------+-------------------------------------------------+----------------+
15:23:38 [DEBUG] [nioEventLoopGroup-3-1] i.n.h.l.LoggingHandler - [id: 0x8fc90ea3, L:/127.0.0.1:8080 - R:/127.0.0.1:53151] READ COMPLETE

看 READ 类型,可以看到这里接收到 36 字节,客户端每次发送 16 个字节,服务器端这里一次接收了36字节,36 > 16 出现了粘包现象,16:16:4 最后4这里出现了半包现象


现象分析

粘包

现象,发送 abc def,接收 abcdef

原因

应用层:接收方 ByteBuf 设置太大(Netty 默认 1024)

滑动窗口:假设发送方 256 bytes 表示一个完整报文,但由于接收方处理不及时且窗口大小足够大,这 256 bytes 字节就会缓冲在接收方的滑动窗口中,当滑动窗口中缓冲了多个报文就会粘包

Nagle 算法:会造成粘包

 半包

现象,发送 abcdef,接收 abc def

原因

应用层:接收方 ByteBuf 小于实际发送数据量

滑动窗口:假设接收方的窗口只剩了 128 bytes,发送方的报文大小是 256 bytes,这时放不下了,只能先发送前 128 bytes,等待 ack 后才能发送剩余部分,这就造成了半包

MSS 限制:当发送的数据超过 MSS 限制后,会将数据切分发送,就会造成半包

本质是因为 TCP 是流式协议,消息无边界,需要开发人员找出消息的边界

滑动窗口

TCP 以一个段(segment)为单位,每发送一个段就需要进行一次确认应答(ack)处理,但如果这么做,缺点是包的往返时间越长性能就越差

为了解决此问题,引入了窗口概念,窗口大小即决定了无需等待应答而可以继续发送的数据最大值

 窗口实际就起到一个缓冲区的作用,同时也能起到流量控制的作用

图中窗口内的4段(1-1000、1001-2000、2001-3000、3001-4000)数据才允许被发送,当应答未到达前,窗口必须停止滑动如果 1-1000 这个段的数据 ack 回来了,窗口就可以向前滑动,就可以发送 4001-5000 段的数据接收方也会维护一个窗口,只有落在窗口内的数据才能允许接收

// 调整服务器端 滑动窗口大小,一般不用设置,在建立连接后系统自动设置,netty默认的是1024
serverBootstrap.option(ChannelOption.SO_RCVBUF, 10);

// 调整客户端 滑动窗口大小,一般不用设置,在建立连接后系统自动设置,netty默认的是1024
bootstrap.option(ChannelOption.SO_SNDBUF, 10);

// 调整 netty 的接收缓冲区大小(ByteBuf) AdaptiveRecvByteBufAllocator(最小值, 初始值, 最大值)
serverBootstrap.childOption(ChannelOption.RCVBUF_ALLOCATOR, new AdaptiveRecvByteBufAllocator(16, 16, 16));

/ 插播知识点 /

option:全局配置

childOption:单个 channel 连接的配置

MSS 限制

链路层对一次能够发送的最大数据有限制,这个限制称之为 MTU(maximum transmission unit),不同的链路设备的 MTU 值也有所不同,例如

以太网的 MTU 是 1500

FDDI(光纤分布式数据接口)的 MTU 是 4352

本地回环地址的 MTU 是 65535 - 本地测试不走网卡

MSS 是最大段长度(maximum segment size),它是 MTU 刨去 tcp 头和 ip 头后剩余能够作为数据传输的字节数

ipv4 tcp 头占用 20 bytes,ip 头占用 20 bytes,因此以太网 MSS 的值为 1500 - 40 = 1460

TCP 在传递大量数据时,会按照 MSS 大小将数据进行分割发送

MSS 的值在三次握手时通知对方自己 MSS 的值,然后在两者之间选择一个小值作为 MSS

Nagle 算法

即使发送一个字节,也需要加入 tcp(20) 头和 ip(20) 头,也就是总字节数会使用 41 bytes,非常不经济。因此为了提高网络利用率,tcp 希望尽可能发送足够大的数据,这就是 Nagle 算法产生的缘由

该算法是指发送端即使还有应该发送的数据,但如果这部分数据很少的话,则进行延迟发送

如果 SO_SNDBUF 的数据达到 MSS,则需要发送

如果 SO_SNDBUF 中含有 FIN(表示需要连接关闭)这时将剩余数据发送,再关闭

如果 TCP_NODELAY = true,则需要发送

已发送的数据都收到 ack 时,则需要发送

上述条件不满足,但发生超时(一般为 200ms)则需要发送

除上述情况,延迟发送

解决方案

    短链接,发一个包建立一次连接,这样连接建立到连接断开之间就是消息的边界,缺点效率太低

    每一条消息采用固定长度,缺点浪费空间

    每一条消息采用分隔符,例如 n,缺点需要转义

    每一条消息分为 head 和 body,head 中包含 body 的长度


- 解决方案一:短链接

服务器端

	public static void main(String[] args) throws InterruptedException {
		NioEventLoopGroup boss = new NioEventLoopGroup();
		NioEventLoopGroup worker = new NioEventLoopGroup();

		try {
			ServerBootstrap serverBootstrap = new ServerBootstrap();
			serverBootstrap.group(boss, worker);
			// 设置服务器端接收缓冲区大小为 10 字节
			// serverBootstrap.option(ChannelOption.SO_RCVBUF, 10);
			serverBootstrap.channel(NioServerSocketChannel.class);
			serverBootstrap.childHandler(new ChannelInitializer() {
				@Override
				protected void initChannel(NioSocketChannel ch) throws Exception {
					ch.pipeline().addLast(new LoggingHandler(LogLevel.DEBUG));
				}
			});
			ChannelFuture channelFuture = serverBootstrap.bind(8080).sync();
			channelFuture.channel().closeFuture().sync();
		} catch (InterruptedException e) {
			e.printStackTrace();
		} finally {
			boss.shutdownGracefully();
			worker.shutdownGracefully();
		}
	}

客户端

	public static void main(String[] args) throws InterruptedException {
		// 发送5次
		for (int i = 0; i < 5; i++) {
			send();
		}
	}

	private static void send() {
		NioEventLoopGroup worker = new NioEventLoopGroup();

		try {
			Bootstrap bootstrap = new Bootstrap();
			bootstrap.group(worker);
			bootstrap.channel(NioSocketChannel.class);
			bootstrap.handler(new ChannelInitializer() {
				@Override
				protected void initChannel(NioSocketChannel ch) throws Exception {
					ch.pipeline().addLast(new ChannelInboundHandlerAdapter() {
						
						@Override
						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});
							ctx.writeAndFlush(buf);
							// 发送一次消息后,就关闭 channel 通道,关闭连接
							ctx.channel().close();
						}
					});
				}
			});
			ChannelFuture channelFuture = bootstrap.connect(new InetSocketAddress("localhost", 8080)).sync();
			channelFuture.channel().closeFuture().sync();
		} catch (InterruptedException e) {
			e.printStackTrace();
		} finally {
			worker.shutdownGracefully();
		}
	}

服务器端打印

16:37:52 [DEBUG] [nioEventLoopGroup-3-8] i.n.h.l.LoggingHandler - [id: 0xffa06125, L:/127.0.0.1:8080 - R:/127.0.0.1:51412] REGISTERED
16:37:52 [DEBUG] [nioEventLoopGroup-3-8] i.n.h.l.LoggingHandler - [id: 0xffa06125, L:/127.0.0.1:8080 - R:/127.0.0.1:51412] ACTIVE
16:37:52 [DEBUG] [nioEventLoopGroup-3-8] i.n.h.l.LoggingHandler - [id: 0xffa06125, L:/127.0.0.1:8080 - R:/127.0.0.1:51412] 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 |................|
+--------+-------------------------------------------------+----------------+
16:37:52 [DEBUG] [nioEventLoopGroup-3-8] i.n.h.l.LoggingHandler - [id: 0xffa06125, L:/127.0.0.1:8080 - R:/127.0.0.1:51412] READ COMPLETE
16:37:52 [DEBUG] [nioEventLoopGroup-3-8] i.n.h.l.LoggingHandler - [id: 0xffa06125, L:/127.0.0.1:8080 - R:/127.0.0.1:51412] READ COMPLETE
16:37:52 [DEBUG] [nioEventLoopGroup-3-8] i.n.h.l.LoggingHandler - [id: 0xffa06125, L:/127.0.0.1:8080 ! R:/127.0.0.1:51412] INACTIVE
16:37:52 [DEBUG] [nioEventLoopGroup-3-8] i.n.h.l.LoggingHandler - [id: 0xffa06125, L:/127.0.0.1:8080 ! R:/127.0.0.1:51412] UNREGISTERED
16:37:52 [DEBUG] [nioEventLoopGroup-3-1] i.n.h.l.LoggingHandler - [id: 0x3f21d899, L:/127.0.0.1:8080 - R:/127.0.0.1:51429] REGISTERED
16:37:52 [DEBUG] [nioEventLoopGroup-3-1] i.n.h.l.LoggingHandler - [id: 0x3f21d899, L:/127.0.0.1:8080 - R:/127.0.0.1:51429] ACTIVE
16:37:52 [DEBUG] [nioEventLoopGroup-3-1] i.n.h.l.LoggingHandler - [id: 0x3f21d899, L:/127.0.0.1:8080 - R:/127.0.0.1:51429] 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 |................|
+--------+-------------------------------------------------+----------------+
16:37:52 [DEBUG] [nioEventLoopGroup-3-1] i.n.h.l.LoggingHandler - [id: 0x3f21d899, L:/127.0.0.1:8080 - R:/127.0.0.1:51429] READ COMPLETE
16:37:52 [DEBUG] [nioEventLoopGroup-3-1] i.n.h.l.LoggingHandler - [id: 0x3f21d899, L:/127.0.0.1:8080 - R:/127.0.0.1:51429] READ COMPLETE
16:37:52 [DEBUG] [nioEventLoopGroup-3-1] i.n.h.l.LoggingHandler - [id: 0x3f21d899, L:/127.0.0.1:8080 ! R:/127.0.0.1:51429] INACTIVE
16:37:52 [DEBUG] [nioEventLoopGroup-3-1] i.n.h.l.LoggingHandler - [id: 0x3f21d899, L:/127.0.0.1:8080 ! R:/127.0.0.1:51429] UNREGISTERED
......

可以看到可以数据没有粘在一起了,解决了粘包问题

但是短链接不能解决半包问题

测试短链接半包现象代码改造,调整服务器端netty接收缓冲区大小为16个字节,netty最小16个字节,因为取16的倍数(serverBootstrap.childOption(ChannelOption.RCVBUF_ALLOCATOR, new AdaptiveRecvByteBufAllocator(16, 16, 16));)-------  客户端一次发送超过16个字节的数据即可。这里就不再重复贴相似的代码了


- 解决方案二:固定长度

FixedLengthframeDecoder:netty 提供的定长解码器,构造方法参数:和客户端约定传递消息的长度

服务器端

	public static void main(String[] args) throws InterruptedException {
		NioEventLoopGroup boss = new NioEventLoopGroup();
		NioEventLoopGroup worker = new NioEventLoopGroup();

		try {
			ServerBootstrap serverBootstrap = new ServerBootstrap();
			serverBootstrap.group(boss, worker);
			serverBootstrap.channel(NioServerSocketChannel.class);
			serverBootstrap.childHandler(new ChannelInitializer() {
				@Override
				protected void initChannel(NioSocketChannel ch) throws Exception {
					// FixedLengthframeDecoder:netty 提供的定长解码器,构造方法参数:和客户端约定传递消息的长度
					ch.pipeline().addLast(new FixedLengthframeDecoder(10));
					ch.pipeline().addLast(new LoggingHandler(LogLevel.DEBUG));
				}
			});
			ChannelFuture channelFuture = serverBootstrap.bind(8080).sync();
			channelFuture.channel().closeFuture().sync();
		} catch (InterruptedException e) {
			e.printStackTrace();
		} finally {
			boss.shutdownGracefully();
			worker.shutdownGracefully();
		}
	}

客户端

	public static void main(String[] args) throws InterruptedException {
		NioEventLoopGroup worker = new NioEventLoopGroup();
		try {
			Bootstrap bootstrap = new Bootstrap();
			bootstrap.group(worker);
			bootstrap.channel(NioSocketChannel.class);
			bootstrap.handler(new ChannelInitializer() {
				@Override
				protected void initChannel(NioSocketChannel ch) throws Exception {
					ch.pipeline().addLast(new ChannelInboundHandlerAdapter() {
						
						@Override
						public void channelActive(ChannelHandlerContext ctx) throws Exception {
							ByteBuf buf = ctx.alloc().buffer();
							char c = '0';
							for (int i = 0; i < 5; i++) {
								byte[] bytes = fill10Bytes(c, new Random().nextInt(10) + 1);
								c++;
								buf.writeBytes(bytes);
							}
							ctx.writeAndFlush(buf);
						}
					});
				}
			});
			ChannelFuture channelFuture = bootstrap.connect(new InetSocketAddress("localhost", 8080)).sync();
			channelFuture.channel().closeFuture().sync();
		} catch (InterruptedException e) {
			e.printStackTrace();
		} finally {
			worker.shutdownGracefully();
		}
	}

	
	public static byte[] fill10Bytes(char c, int len) {
		byte[] bytes = new byte[10];
		Arrays.fill(bytes, (byte) '_');
		for (int i = 0; i < len; i++) {
			bytes[i] = (byte) c;
		}
		System.out.println(new String(bytes));
		return bytes;
	}

服务器端打印

17:22:44 [DEBUG] [nioEventLoopGroup-3-3] i.n.h.l.LoggingHandler - [id: 0x565dcfb4, L:/127.0.0.1:8080 - R:/127.0.0.1:53524] REGISTERED
17:22:44 [DEBUG] [nioEventLoopGroup-3-3] i.n.h.l.LoggingHandler - [id: 0x565dcfb4, L:/127.0.0.1:8080 - R:/127.0.0.1:53524] ACTIVE
17:22:44 [DEBUG] [nioEventLoopGroup-3-3] i.n.h.l.LoggingHandler - [id: 0x565dcfb4, L:/127.0.0.1:8080 - R:/127.0.0.1:53524] READ: 10B
         +-------------------------------------------------+
         |  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |
+--------+-------------------------------------------------+----------------+
|00000000| 30 30 30 30 30 30 30 5f 5f 5f                   |0000000___      |
+--------+-------------------------------------------------+----------------+
17:22:44 [DEBUG] [nioEventLoopGroup-3-3] i.n.h.l.LoggingHandler - [id: 0x565dcfb4, L:/127.0.0.1:8080 - R:/127.0.0.1:53524] READ: 10B
         +-------------------------------------------------+
         |  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |
+--------+-------------------------------------------------+----------------+
|00000000| 31 31 31 5f 5f 5f 5f 5f 5f 5f                   |111_______      |
+--------+-------------------------------------------------+----------------+
17:22:44 [DEBUG] [nioEventLoopGroup-3-3] i.n.h.l.LoggingHandler - [id: 0x565dcfb4, L:/127.0.0.1:8080 - R:/127.0.0.1:53524] READ: 10B
......

- 解决方案三:分隔符

LinebasedframeDecoder:netty 提供的支持 n 和 rn 符号的解码器,构造参数传遇到换行符的最大长度,当读了指定长度的字节时,还没有遇到换行符,将抛出异常DelimiterbasedframeDecoder:netty 提供的支持 自定义符号的解码器,构造参数传符号的最大长度,和ByteBuf类型的自定义符号

服务器端

	public static void main(String[] args) throws InterruptedException {
		NioEventLoopGroup boss = new NioEventLoopGroup();
		NioEventLoopGroup worker = new NioEventLoopGroup();

		try {
			ServerBootstrap serverBootstrap = new ServerBootstrap();
			serverBootstrap.group(boss, worker);
			serverBootstrap.channel(NioServerSocketChannel.class);
			serverBootstrap.childHandler(new ChannelInitializer() {
				@Override
				protected void initChannel(NioSocketChannel ch) throws Exception {
					// ch.pipeline().addLast(new LinebasedframeDecoder(1024));
					ch.pipeline().addLast(new DelimiterbasedframeDecoder(1024, ByteBufAllocator.DEFAULT.buffer().writeBytes("|".getBytes(StandardCharsets.UTF_8))));
					ch.pipeline().addLast(new LoggingHandler(LogLevel.DEBUG));
				}
			});
			ChannelFuture channelFuture = serverBootstrap.bind(8080).sync();
			channelFuture.channel().closeFuture().sync();
		} catch (InterruptedException e) {
			e.printStackTrace();
		} finally {
			boss.shutdownGracefully();
			worker.shutdownGracefully();
		}
	}

客户端

	public static void main(String[] args) throws InterruptedException {
		NioEventLoopGroup worker = new NioEventLoopGroup();
		try {
			Bootstrap bootstrap = new Bootstrap();
			bootstrap.group(worker);
			bootstrap.channel(NioSocketChannel.class);
			bootstrap.handler(new ChannelInitializer() {
				@Override
				protected void initChannel(NioSocketChannel ch) throws Exception {
					ch.pipeline().addLast(new ChannelInboundHandlerAdapter() {
						
						@Override
						public void channelActive(ChannelHandlerContext ctx) throws Exception {
							ByteBuf buf = ctx.alloc().buffer();
							//buf.writeBytes("hellonworldnhinzhang sann".getBytes(StandardCharsets.UTF_8));
							buf.writeBytes("hello|world|hi|zhang san|".getBytes(StandardCharsets.UTF_8));
							ctx.writeAndFlush(buf);
						}
					});
				}
			});
			ChannelFuture channelFuture = bootstrap.connect(new InetSocketAddress("localhost", 8080)).sync();
			channelFuture.channel().closeFuture().sync();
		} catch (InterruptedException e) {
			e.printStackTrace();
		} finally {
			worker.shutdownGracefully();
		}
	}

服务器端打印

17:44:53 [DEBUG] [nioEventLoopGroup-3-1] i.n.h.l.LoggingHandler - [id: 0x4cd3ff09, L:/127.0.0.1:8080 - R:/127.0.0.1:53948] REGISTERED
17:44:53 [DEBUG] [nioEventLoopGroup-3-1] i.n.h.l.LoggingHandler - [id: 0x4cd3ff09, L:/127.0.0.1:8080 - R:/127.0.0.1:53948] ACTIVE
17:44:53 [DEBUG] [nioEventLoopGroup-3-1] i.n.h.l.LoggingHandler - [id: 0x4cd3ff09, L:/127.0.0.1:8080 - R:/127.0.0.1:53948] 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           |
+--------+-------------------------------------------------+----------------+
17:44:53 [DEBUG] [nioEventLoopGroup-3-1] i.n.h.l.LoggingHandler - [id: 0x4cd3ff09, L:/127.0.0.1:8080 - R:/127.0.0.1:53948] READ: 5B
         +-------------------------------------------------+
         |  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |
+--------+-------------------------------------------------+----------------+
|00000000| 77 6f 72 6c 64                                  |world           |
+--------+-------------------------------------------------+----------------+
17:44:53 [DEBUG] [nioEventLoopGroup-3-1] i.n.h.l.LoggingHandler - [id: 0x4cd3ff09, L:/127.0.0.1:8080 - R:/127.0.0.1:53948] READ: 2B
         +-------------------------------------------------+
         |  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |
+--------+-------------------------------------------------+----------------+
|00000000| 68 69                                           |hi              |
+--------+-------------------------------------------------+----------------+
17:44:53 [DEBUG] [nioEventLoopGroup-3-1] i.n.h.l.LoggingHandler - [id: 0x4cd3ff09, L:/127.0.0.1:8080 - R:/127.0.0.1:53948] READ: 9B
         +-------------------------------------------------+
         |  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |
+--------+-------------------------------------------------+----------------+
|00000000| 7a 68 61 6e 67 20 73 61 6e                      |zhang san       |
+--------+-------------------------------------------------+----------------+
17:44:53 [DEBUG] [nioEventLoopGroup-3-1] i.n.h.l.LoggingHandler - [id: 0x4cd3ff09, L:/127.0.0.1:8080 - R:/127.0.0.1:53948] READ COMPLETE

- 解决方案四:预设长度 LTC解码器

LengthFieldbasedframeDecoder:构造参数传(最大长度,记录长度偏移量,长度占用字节数,长度调整,剥离字节数)

服务器端

	public static void main(String[] args) throws InterruptedException {
		NioEventLoopGroup boss = new NioEventLoopGroup();
		NioEventLoopGroup worker = new NioEventLoopGroup();

		try {
			ServerBootstrap serverBootstrap = new ServerBootstrap();
			serverBootstrap.group(boss, worker);
			serverBootstrap.channel(NioServerSocketChannel.class);
			serverBootstrap.childHandler(new ChannelInitializer() {
				@Override
				protected void initChannel(NioSocketChannel ch) throws Exception {
					// 参数一:最大字节数,
					// 参数二:长度在buf中的偏移量,因为我在buf中先写的是长度,所以长度的偏移量是0,如果先写入2个字节的数据,再写入长度,那么长度的偏移量就是2
					// 参数三:长度占用的字节,我写的是一个int类型,占用4个字节
					// 参数四:需要调整的长度
					// 参数五:数据从头开始需要剥离的长度,现在buf中的数据是 ....hello, world 和 ....hi!,前面的4个点是长度,我们不需要长度,所以需要把长度剥离出去
					ch.pipeline().addLast(new LengthFieldbasedframeDecoder(1024, 0, 4, 0, 4));
					ch.pipeline().addLast(new LoggingHandler(LogLevel.DEBUG));
				}
			});
			ChannelFuture channelFuture = serverBootstrap.bind(8080).sync();
			channelFuture.channel().closeFuture().sync();
		} catch (InterruptedException e) {
			e.printStackTrace();
		} finally {
			boss.shutdownGracefully();
			worker.shutdownGracefully();
		}
	}

客户端

	public static void main(String[] args) throws InterruptedException {
		NioEventLoopGroup worker = new NioEventLoopGroup();
		try {
			Bootstrap bootstrap = new Bootstrap();
			bootstrap.group(worker);
			bootstrap.channel(NioSocketChannel.class);
			bootstrap.handler(new ChannelInitializer() {
				@Override
				protected void initChannel(NioSocketChannel ch) throws Exception {
					ch.pipeline().addLast(new ChannelInboundHandlerAdapter() {
						@Override
						public void channelActive(ChannelHandlerContext ctx) throws Exception {
							ByteBuf buf = ctx.alloc().buffer();

							msg(buf, "hello, world");
							msg(buf, "hi!");
							ctx.writeAndFlush(buf);
						}
					});
				}
			});
			ChannelFuture channelFuture = bootstrap.connect(new InetSocketAddress("localhost", 8080)).sync();
			channelFuture.channel().closeFuture().sync();
		} catch (InterruptedException e) {
			e.printStackTrace();
		} finally {
			worker.shutdownGracefully();
		}
	}

	
	private static void msg(ByteBuf buf, String content) {
		byte[] bytes = content.getBytes(StandardCharsets.UTF_8); // 实际内容
		int length = bytes.length;    // 实际内容长度

		buf.writeInt(length);    // int的长度是4个字节,把长度写入
		buf.writeBytes(bytes);
	}

服务器端打印

18:36:43 [DEBUG] [nioEventLoopGroup-3-1] i.n.h.l.LoggingHandler - [id: 0x21c2c017, L:/127.0.0.1:8080 - R:/127.0.0.1:59190] REGISTERED
18:36:43 [DEBUG] [nioEventLoopGroup-3-1] i.n.h.l.LoggingHandler - [id: 0x21c2c017, L:/127.0.0.1:8080 - R:/127.0.0.1:59190] ACTIVE
18:36:43 [DEBUG] [nioEventLoopGroup-3-1] i.n.h.l.LoggingHandler - [id: 0x21c2c017, L:/127.0.0.1:8080 - R:/127.0.0.1:59190] READ: 12B
         +-------------------------------------------------+
         |  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |
+--------+-------------------------------------------------+----------------+
|00000000| 68 65 6c 6c 6f 2c 20 77 6f 72 6c 64             |hello, world    |
+--------+-------------------------------------------------+----------------+
18:36:43 [DEBUG] [nioEventLoopGroup-3-1] i.n.h.l.LoggingHandler - [id: 0x21c2c017, L:/127.0.0.1:8080 - R:/127.0.0.1:59190] READ: 3B
         +-------------------------------------------------+
         |  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |
+--------+-------------------------------------------------+----------------+
|00000000| 68 69 21                                        |hi!             |
+--------+-------------------------------------------------+----------------+
18:36:43 [DEBUG] [nioEventLoopGroup-3-1] i.n.h.l.LoggingHandler - [id: 0x21c2c017, L:/127.0.0.1:8080 - R:/127.0.0.1:59190] READ COMPLETE

 加深 LengthFieldbasedframeDecoder 解码器的理解,使用 EmbeddedChannel 工具类调试

	public static void main(String[] args) {
		EmbeddedChannel embeddedChannel = new EmbeddedChannel(
				// 参数一:最大字节数,
				// 参数二:长度在buf中的偏移量,因为我在buf中先写的是长度,所以长度的偏移量是0,如果先写入2个字节的数据,再写入长度,那么长度的偏移量就是2
				// 参数三:长度占用的字节,我写的是一个int类型,占用4个字节
				// 参数四:需要调整的长度,现在除了内容以外,在buf中又加了1个字节长度的版本号,所以需要调整的长度是1
				// 参数五:数据从头开始需要剥离的长度,现在buf中的数据是 .....hello, world 和 .....hi!,前面的4个点是长度,第5个点是版本号,我们不需要长度,所以需要把长度剥离出去,最后的数据是 .hello, world 和 .hi!
				new LengthFieldbasedframeDecoder(1024, 0, 4, 1, 4)
				, new LoggingHandler(LogLevel.DEBUG)
		);
		// 4 个字节的长度,然后是实际内容
		ByteBuf buf = ByteBufAllocator.DEFAULT.buffer();

		msg(buf, "hello, world");
		msg(buf, "hi!");
		embeddedChannel.writeInbound(buf);
	}

	private static void msg(ByteBuf buf, String content) {
		byte[] bytes = content.getBytes(StandardCharsets.UTF_8); // 实际内容
		int length = bytes.length;    // 实际内容长度
		// buf.writeBytes(new byte[]{1,2}); // 加入在长度之前多写入了两个字节的数据,那么LengthFieldbasedframeDecoder的第二个参数就是2
		buf.writeInt(length);    // int的长度是4个字节,把长度写入
		buf.writeByte(1);        // 传入其他数据,例如版本号
		buf.writeBytes(bytes);
	}

打印

18:50:42 [DEBUG] [main] i.n.h.l.LoggingHandler - [id: 0xembedded, L:embedded - R:embedded] REGISTERED
18:50:42 [DEBUG] [main] i.n.h.l.LoggingHandler - [id: 0xembedded, L:embedded - R:embedded] ACTIVE
18:50:42 [DEBUG] [main] i.n.h.l.LoggingHandler - [id: 0xembedded, L:embedded - R:embedded] READ: 13B
         +-------------------------------------------------+
         |  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |
+--------+-------------------------------------------------+----------------+
|00000000| 01 68 65 6c 6c 6f 2c 20 77 6f 72 6c 64          |.hello, world   |
+--------+-------------------------------------------------+----------------+
18:50:42 [DEBUG] [main] i.n.h.l.LoggingHandler - [id: 0xembedded, L:embedded - R:embedded] READ: 4B
         +-------------------------------------------------+
         |  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |
+--------+-------------------------------------------------+----------------+
|00000000| 01 68 69 21                                     |.hi!            |
+--------+-------------------------------------------------+----------------+
18:50:42 [DEBUG] [main] i.n.h.l.LoggingHandler - [id: 0xembedded, L:embedded - R:embedded] READ COMPLETE

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/713711.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号