Netty 粘包/拆包、解码工具类

1. 概述

1.1 粘包

发送 abc def,接收 abcdef

原因

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

1.2 半包

现象,发送 abcdef,接收 abc def

原因

  • 应用层:接收方 ByteBuf 小于实际发送数据量
  • MSS 限制:当发送的数据超过 MSS 限制后,会将数据切分发送,就会造成半包
  • 滑动窗口: 发送方维护一个发送窗口,而接收方维护一个接收窗口。发送窗口的大小取决于接收方通知的窗口大小,而接收窗口的大小取决于系统资源和当前状态。
  • Nagle 算法: 发送一个字节,也需要加入 tcp 头和 ip 头,也就是总字节数会使用 41 bytes,非常不经济。因此为了提高网络利用率,tcp 希望尽可能发送足够大的数据,这就是 Nagle 算法产生的缘由; 该算法是指发送端即使还有应该发送的数据,但如果这部分数据很少的话,则进行延迟发送

MSS 是最大段长度(maximum segment size),它是 MTU 刨去 tcp 头和 ip 头后剩余能够作为数据传输的字节数,链路层对一次能够发送的最大数据有限制,这个限制称之为 MTU(maximum transmission unit),不同的链路设备的 MTU 值也有所不同, MSS 的值在三次握手时通知对方自己 MSS 的值,然后在两者之间选择一个小值作为 MSS

1.3 解决方案

  • 短链接 发送完报文 就断开 然后重连 在发, 缺点是性能不好
  • 每一条消息采用固定长度,缺点是浪费空间
  • 每一条消息采用分隔符,例如 \n,缺点是不是分隔符的\n字符需要转义
  • 每一条消息发消息长度+消息,根据消息中的长度读取后面的消息

2 代码实例

2.1 粘包/半包实例

2.1.1 服务器

import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.string.StringDecoder;
import io.netty.handler.logging.LogLevel;
import io.netty.handler.logging.LoggingHandler;
import lombok.extern.slf4j.Slf4j;@Slf4j
public class FrameDecoderServer {void start() {// 创建两个EventLoopGroup,一个用于接收连接(boss),一个用于处理连接(worker)NioEventLoopGroup boss = new NioEventLoopGroup(1);NioEventLoopGroup worker = new NioEventLoopGroup();try {// 配置服务器引导程序ServerBootstrap serverBootstrap = new ServerBootstrap();serverBootstrap.channel(NioServerSocketChannel.class); // 指定使用的通道类型serverBootstrap.group(boss, worker); // 关联EventLoopGroup// 配置子通道处理器serverBootstrap.childHandler(new ChannelInitializer<SocketChannel>() {@Overrideprotected void initChannel(SocketChannel ch) throws Exception {// 添加处理日志的处理器ch.pipeline().addLast(new LoggingHandler(LogLevel.DEBUG));// 添加自定义的处理连接激活和断开的处理器ch.pipeline().addLast(new ChannelInboundHandlerAdapter() {@Overridepublic void channelActive(ChannelHandlerContext ctx) throws Exception {log.debug("connected {}", ctx.channel());super.channelActive(ctx);}@Overridepublic void channelInactive(ChannelHandlerContext ctx) throws Exception {log.debug("disconnect {}", ctx.channel());super.channelInactive(ctx);}});// 添加字符串解码器ch.pipeline().addLast(new StringDecoder());// 添加处理实际业务逻辑的处理器ch.pipeline().addLast(new SimpleChannelInboundHandler<String>() {@Overrideprotected void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception {System.out.println("Client received: " + msg);}});}});// 绑定端口并等待服务器启动ChannelFuture channelFuture = serverBootstrap.bind(8080);log.debug("{} binding...", channelFuture.channel());channelFuture.sync();log.debug("{} bound...", channelFuture.channel());// 等待服务器关闭channelFuture.channel().closeFuture().sync();} catch (InterruptedException e) {log.error("server error", e);} finally {// 关闭EventLoopGroupboss.shutdownGracefully();worker.shutdownGracefully();log.debug("stopped");}}/*** 程序入口点。* 创建FrameDecoderServer实例并启动服务器。** @param args 命令行参数*/public static void main(String[] args)   {new FrameDecoderServer().start();}
}

2.1.2 粘包客户端

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 lombok.extern.slf4j.Slf4j;@Slf4j
public class FrameDecoderClient {public static void main(String[] args) {// 创建一个 NIO 事件循环组,用于处理客户端的 I/O 事件。NioEventLoopGroup worker = new NioEventLoopGroup();try {// 配置客户端启动器,设置通道类型、事件循环组和处理器。Bootstrap bootstrap = new Bootstrap();bootstrap.channel(NioSocketChannel.class); // 指定使用 NIO Socket 通道。bootstrap.group(worker); // 设置事件循环组。bootstrap.handler(new ChannelInitializer<SocketChannel>() {@Overrideprotected void initChannel(SocketChannel ch) throws Exception {log.debug("connected...");// 添加自定义的入站处理程序,负责在通道激活时发送数据。ch.pipeline().addLast(new ChannelInboundHandlerAdapter() {/*** 当通道变得可写时,发送数据到服务器。* @param ctx 通道上下文,用于分配缓冲区和写入数据。* @throws Exception 如果操作过程中发生异常。*/@Overridepublic void channelActive(ChannelHandlerContext ctx) throws Exception {log.debug("sending...");// 循环10次,发送相同的数据。for (int i = 0; i < 10; i++) {// 分配一个缓冲区,并写入固定长度的数据。ByteBuf buffer = ctx.alloc().buffer();buffer.writeBytes("11111111".getBytes());// 写入缓冲区并刷新通道,确保数据被发送。ctx.writeAndFlush(buffer);}}});}});// 连接到服务器并等待连接完成。ChannelFuture channelFuture = bootstrap.connect("127.0.0.1", 8080).sync();// 等待通道关闭。channelFuture.channel().closeFuture().sync();} catch (InterruptedException e) {// 记录中断异常。log.error("client error", e);} finally {// 关闭事件循环组,释放资源。worker.shutdownGracefully();}}
}

运行截图:
在这里插入图片描述

2.1.2 半包客户端


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 lombok.extern.slf4j.Slf4j;@Slf4j
public class FrameDecoderClient {public static void main(String[] args) {// 创建一个 NIO 事件循环组,用于处理客户端的 I/O 事件。NioEventLoopGroup worker = new NioEventLoopGroup();try {// 配置客户端启动器,设置通道类型、事件循环组和处理器。Bootstrap bootstrap = new Bootstrap();bootstrap.channel(NioSocketChannel.class); // 指定使用 NIO Socket 通道。bootstrap.group(worker); // 设置事件循环组。bootstrap.handler(new ChannelInitializer<SocketChannel>() {@Overrideprotected void initChannel(SocketChannel ch) throws Exception {log.debug("connected...");// 添加自定义的入站处理程序,负责在通道激活时发送数据。ch.pipeline().addLast(new ChannelInboundHandlerAdapter() {/*** 当通道变得可写时,发送数据到服务器。* @param ctx 通道上下文,用于分配缓冲区和写入数据。* @throws Exception 如果操作过程中发生异常。*/@Overridepublic void channelActive(ChannelHandlerContext ctx) throws Exception {log.debug("sending...");// 循环10次,发送相同的数据。for (int i = 0; i < 1; i++) {// 分配一个缓冲区,并写入固定长度的数据。ByteBuf buffer = ctx.alloc().buffer();StringBuilder str = new StringBuilder();for (int j = 0; j < 10000; j++) {str.append("1");}buffer.writeBytes(str.toString().getBytes());// 写入缓冲区并刷新通道,确保数据被发送。ctx.writeAndFlush(buffer);}}});}});// 连接到服务器并等待连接完成。ChannelFuture channelFuture = bootstrap.connect("127.0.0.1", 8080).sync();// 等待通道关闭。channelFuture.channel().closeFuture().sync();} catch (InterruptedException e) {// 记录中断异常。log.error("client error", e);} finally {// 关闭事件循环组,释放资源。worker.shutdownGracefully();}}
}

在这里插入图片描述

3. 解决方案

3.1 固定长度

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

3.1.1 服务端代码

ch.pipeline().addLast(new FixedLengthFrameDecoder(8)); 固定长度8字节


import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.FixedLengthFrameDecoder;
import io.netty.handler.codec.string.StringDecoder;
import io.netty.handler.logging.LogLevel;
import io.netty.handler.logging.LoggingHandler;
import lombok.extern.slf4j.Slf4j;@Slf4j
public class FixedLengthFrameDecoderServer {void start() {NioEventLoopGroup boss = new NioEventLoopGroup(1);NioEventLoopGroup worker = new NioEventLoopGroup();try {ServerBootstrap serverBootstrap = new ServerBootstrap();serverBootstrap.channel(NioServerSocketChannel.class);serverBootstrap.group(boss, worker);serverBootstrap.childHandler(new ChannelInitializer<SocketChannel>() {@Overrideprotected void initChannel(SocketChannel ch) throws Exception {ch.pipeline().addLast(new LoggingHandler(LogLevel.DEBUG));ch.pipeline().addLast(new ChannelInboundHandlerAdapter() {@Overridepublic void channelActive(ChannelHandlerContext ctx) throws Exception {log.debug("connected {}", ctx.channel());super.channelActive(ctx);}@Overridepublic void channelInactive(ChannelHandlerContext ctx) throws Exception {log.debug("disconnect {}", ctx.channel());super.channelInactive(ctx);}});ch.pipeline().addLast(new FixedLengthFrameDecoder(8));ch.pipeline().addLast(new StringDecoder());ch.pipeline().addLast(new SimpleChannelInboundHandler<String>() {@Overrideprotected void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception {System.out.println("Client received: " + msg);}});}});ChannelFuture channelFuture = serverBootstrap.bind(8080);log.debug("{} binding...", channelFuture.channel());channelFuture.sync();log.debug("{} bound...", channelFuture.channel());channelFuture.channel().closeFuture().sync();} catch (InterruptedException e) {log.error("server error", e);} finally {boss.shutdownGracefully();worker.shutdownGracefully();log.debug("stoped");}}public static void main(String[] args)   {new FixedLengthFrameDecoderServer().start();}}

3.1.2 客户端

客户端代码同2.1.2

3.1.3

运行截图:
在这里插入图片描述

3.1.3 FixedLengthFrameDecoder 源码

FixedLengthFrameDecoder decode 解码时,每次读取固定长度

  protected Object decode(@SuppressWarnings("UnusedParameters") ChannelHandlerContext ctx, ByteBuf in) throws Exception {if (in.readableBytes() < frameLength) {return null;} else {return in.readRetainedSlice(frameLength);}}

看看怎么调用的

io.netty.handler.codec.ByteToMessageDecoder#channelRead

在这里插入图片描述

callDecode(ctx, cumulation, out);

在这里插入图片描述

io.netty.handler.codec.ByteToMessageDecoder#callDecode// 读取所有数据
while (in.isReadable())// 可读数据小于定长 break
if (outSize == out.size()) {if (oldInputLength == in.readableBytes()) {break;}

在这里插入图片描述

io.netty.handler.codec.ByteToMessageDecoder#decodeRemovalReentryProtection

调用

io.netty.handler.codec.FixedLengthFrameDecoder#decode`

在这里插入图片描述

3.2 分隔符

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

ch.pipeline().addLast(new LineBasedFrameDecoder(1024));

3.2.1 服务端代码


import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.LineBasedFrameDecoder;
import io.netty.handler.codec.string.StringDecoder;
import io.netty.handler.logging.LogLevel;
import io.netty.handler.logging.LoggingHandler;
import lombok.extern.slf4j.Slf4j;@Slf4j
public class LineBasedFrameDecoderServer {void start() {NioEventLoopGroup boss = new NioEventLoopGroup(1);NioEventLoopGroup worker = new NioEventLoopGroup();try {ServerBootstrap serverBootstrap = new ServerBootstrap();serverBootstrap.channel(NioServerSocketChannel.class);serverBootstrap.group(boss, worker);serverBootstrap.childHandler(new ChannelInitializer<SocketChannel>() {@Overrideprotected void initChannel(SocketChannel ch) throws Exception {ch.pipeline().addLast(new LoggingHandler(LogLevel.DEBUG));ch.pipeline().addLast(new ChannelInboundHandlerAdapter() {@Overridepublic void channelActive(ChannelHandlerContext ctx) throws Exception {log.debug("connected {}", ctx.channel());super.channelActive(ctx);}@Overridepublic void channelInactive(ChannelHandlerContext ctx) throws Exception {log.debug("disconnect {}", ctx.channel());super.channelInactive(ctx);}});ch.pipeline().addLast(new LineBasedFrameDecoder(1024));ch.pipeline().addLast(new StringDecoder());ch.pipeline().addLast(new SimpleChannelInboundHandler<String>() {@Overrideprotected void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception {System.out.println("Client received: " + msg);}});}});ChannelFuture channelFuture = serverBootstrap.bind(8080);log.debug("{} binding...", channelFuture.channel());channelFuture.sync();log.debug("{} bound...", channelFuture.channel());channelFuture.channel().closeFuture().sync();} catch (InterruptedException e) {log.error("server error", e);} finally {boss.shutdownGracefully();worker.shutdownGracefully();log.debug("stoped");}}public static void main(String[] args)   {new LineBasedFrameDecoderServer().start();}}

3.2.2 客户端代码


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 lombok.extern.slf4j.Slf4j;@Slf4j
public class FrameDecoderClient {public static void main(String[] args) {// 创建一个 NIO 事件循环组,用于处理客户端的 I/O 事件。NioEventLoopGroup worker = new NioEventLoopGroup();try {// 配置客户端启动器,设置通道类型、事件循环组和处理器。Bootstrap bootstrap = new Bootstrap();bootstrap.channel(NioSocketChannel.class); // 指定使用 NIO Socket 通道。bootstrap.group(worker); // 设置事件循环组。bootstrap.handler(new ChannelInitializer<SocketChannel>() {@Overrideprotected void initChannel(SocketChannel ch) throws Exception {log.debug("connected...");// 添加自定义的入站处理程序,负责在通道激活时发送数据。ch.pipeline().addLast(new ChannelInboundHandlerAdapter() {/*** 当通道变得可写时,发送数据到服务器。* @param ctx 通道上下文,用于分配缓冲区和写入数据。* @throws Exception 如果操作过程中发生异常。*/@Overridepublic void channelActive(ChannelHandlerContext ctx) throws Exception {log.debug("sending...");// 循环10次,发送相同的数据。for (int i = 0; i < 10; i++) {// 分配一个缓冲区,并写入固定长度的数据。ByteBuf buffer = ctx.alloc().buffer();StringBuilder str = new StringBuilder();for (int j = 0; j < 9; j++) {str.append("1");}str.append("\n");buffer.writeBytes(str.toString().getBytes());// 写入缓冲区并刷新通道,确保数据被发送。ctx.writeAndFlush(buffer);}}});}});// 连接到服务器并等待连接完成。ChannelFuture channelFuture = bootstrap.connect("127.0.0.1", 8080).sync();// 等待通道关闭。channelFuture.channel().closeFuture().sync();} catch (InterruptedException e) {// 记录中断异常。log.error("client error", e);} finally {// 关闭事件循环组,释放资源。worker.shutdownGracefully();}}
}

3.2.3 运行截图

在这里插入图片描述

3.2.4 LineBasedFrameDecoder#decode

ByteProcessor.FIND_LF 就是 ‘\n’

 protected Object decode(ChannelHandlerContext ctx, ByteBuf buffer) throws Exception {final int eol = findEndOfLine(buffer);if (!discarding) {if (eol >= 0) {final ByteBuf frame;final int length = eol - buffer.readerIndex();final int delimLength = buffer.getByte(eol) == '\r'? 2 : 1;if (length > maxLength) {buffer.readerIndex(eol + delimLength);fail(ctx, length);return null;}if (stripDelimiter) {frame = buffer.readRetainedSlice(length);buffer.skipBytes(delimLength);} else {frame = buffer.readRetainedSlice(length + delimLength);}return frame;} else {final int length = buffer.readableBytes();if (length > maxLength) {discardedBytes = length;buffer.readerIndex(buffer.writerIndex());discarding = true;offset = 0;if (failFast) {fail(ctx, "over " + discardedBytes);}}return null;}} else {if (eol >= 0) {final int length = discardedBytes + eol - buffer.readerIndex();final int delimLength = buffer.getByte(eol) == '\r'? 2 : 1;buffer.readerIndex(eol + delimLength);discardedBytes = 0;discarding = false;if (!failFast) {fail(ctx, length);}} else {discardedBytes += buffer.readableBytes();buffer.readerIndex(buffer.writerIndex());// We skip everything in the buffer, we need to set the offset to 0 again.offset = 0;}return null;}}private void fail(final ChannelHandlerContext ctx, int length) {fail(ctx, String.valueOf(length));}private void fail(final ChannelHandlerContext ctx, String length) {ctx.fireExceptionCaught(new TooLongFrameException("frame length (" + length + ") exceeds the allowed maximum (" + maxLength + ')'));}private int findEndOfLine(final ByteBuf buffer) {int totalLength = buffer.readableBytes();int i = buffer.forEachByte(buffer.readerIndex() + offset, totalLength - offset, ByteProcessor.FIND_LF);if (i >= 0) {offset = 0;if (i > 0 && buffer.getByte(i - 1) == '\r') {i--;}} else {offset = totalLength;}return i;}

3.3 长度域解码器

每一条消息发消息长度+消息,根据消息中的长度读取后面的消息

  // 添加基于长度字段的帧解码器,用于解码客户端发送的数据// 解码器配置:最大帧长度1000,长度字段起始位置0,长度字段长度2字节,不进行长度调整,跳过2个字节ch.pipeline().addLast(new LengthFieldBasedFrameDecoder(1000, 0, 2, 0, 2));

https://www.cnblogs.com/motianlong/p/14465098.html

3.3.1 服务端

import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.LengthFieldBasedFrameDecoder;
import io.netty.handler.codec.string.StringDecoder;
import io.netty.handler.logging.LogLevel;
import io.netty.handler.logging.LoggingHandler;
import lombok.extern.slf4j.Slf4j;@Slf4j
/*** 该类实现了基于长度字段的帧解码器服务器。*/
public class LengthFieldBasedFrameDecoderServer {/*** 启动服务器。*/void start() {// 创建Boss线程组和Worker线程组NioEventLoopGroup boss = new NioEventLoopGroup(1);NioEventLoopGroup worker = new NioEventLoopGroup();try {// 配置服务器引导程序ServerBootstrap serverBootstrap = new ServerBootstrap();serverBootstrap.channel(NioServerSocketChannel.class);serverBootstrap.group(boss, worker);serverBootstrap.childHandler(new ChannelInitializer<SocketChannel>() {@Overrideprotected void initChannel(SocketChannel ch) throws Exception {// 添加日志处理器ch.pipeline().addLast(new LoggingHandler(LogLevel.DEBUG));// 添加自定义的ChannelInboundHandlerAdapter,处理连接激活和不活跃事件ch.pipeline().addLast(new ChannelInboundHandlerAdapter() {@Overridepublic void channelActive(ChannelHandlerContext ctx) throws Exception {log.debug("connected {}", ctx.channel());super.channelActive(ctx);}@Overridepublic void channelInactive(ChannelHandlerContext ctx) throws Exception {log.debug("disconnect {}", ctx.channel());super.channelInactive(ctx);}});// 添加基于长度字段的帧解码器,用于解码客户端发送的数据// 解码器配置:最大帧长度1000,长度字段起始位置0,长度字段长度2字节,不进行长度调整,跳过2个字节ch.pipeline().addLast(new LengthFieldBasedFrameDecoder(1000, 0, 2, 0, 2));// 添加字符串解码器,将字节转换为字符串ch.pipeline().addLast(new StringDecoder());// 添加自定义的ChannelInboundHandler,处理读取到的字符串消息ch.pipeline().addLast(new SimpleChannelInboundHandler<String>() {@Overrideprotected void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception {System.out.println("Client received: " + msg);}});}});// 绑定端口并等待连接ChannelFuture channelFuture = serverBootstrap.bind(8080);log.debug("{} binding...", channelFuture.channel());channelFuture.sync();log.debug("{} bound...", channelFuture.channel());// 等待服务器关闭channelFuture.channel().closeFuture().sync();} catch (InterruptedException e) {log.error("server error", e);} finally {// 关闭线程组boss.shutdownGracefully();worker.shutdownGracefully();log.debug("stopped");}}/*** 程序入口。* @param args 命令行参数*/public static void main(String[] args)   {new LengthFieldBasedFrameDecoderServer().start();}}
3.3.2 客户端代码

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 lombok.extern.slf4j.Slf4j;@Slf4j
public class FrameDecoderClient {public static void main(String[] args) {// 创建一个 NIO 事件循环组,用于处理客户端的 I/O 事件。NioEventLoopGroup worker = new NioEventLoopGroup();try {// 配置客户端启动器,设置通道类型、事件循环组和处理器。Bootstrap bootstrap = new Bootstrap();bootstrap.channel(NioSocketChannel.class); // 指定使用 NIO Socket 通道。bootstrap.group(worker); // 设置事件循环组。bootstrap.handler(new ChannelInitializer<SocketChannel>() {@Overrideprotected void initChannel(SocketChannel ch) throws Exception {log.debug("connected...");// 添加自定义的入站处理程序,负责在通道激活时发送数据。ch.pipeline().addLast(new ChannelInboundHandlerAdapter() {/*** 当通道变得可写时,发送数据到服务器。* @param ctx 通道上下文,用于分配缓冲区和写入数据。* @throws Exception 如果操作过程中发生异常。*/@Overridepublic void channelActive(ChannelHandlerContext ctx) throws Exception {log.debug("sending...");// 循环10次,发送相同的数据。for (int i = 0; i < 10; i++) {// 分配一个缓冲区,并写入固定长度的数据。ByteBuf buffer = ctx.alloc().buffer();StringBuilder str = new StringBuilder();for (int j = 0; j < 9; j++) {str.append("1");}buffer.writeShort(9);buffer.writeBytes(str.toString().getBytes());// 写入缓冲区并刷新通道,确保数据被发送。ctx.writeAndFlush(buffer);}}});}});// 连接到服务器并等待连接完成。ChannelFuture channelFuture = bootstrap.connect("127.0.0.1", 8080).sync();// 等待通道关闭。channelFuture.channel().closeFuture().sync();} catch (InterruptedException e) {// 记录中断异常。log.error("client error", e);} finally {// 关闭事件循环组,释放资源。worker.shutdownGracefully();}}
}

3.3.3 运行截图

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/diannao/39996.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

模拟 ADC 的前端

ADC 的 SPICE 模拟 反复试验的方法将信号发送到 ADC 非常耗时&#xff0c;而且可能有效也可能无效。如果转换器捕获电压信息的关键时刻模拟输入引脚不稳定&#xff0c;则无法获得正确的输出数据。SPICE 模型允许您执行的步是验证所有模拟输入是否稳定&#xff0c;以便没有错误…

尝试修改苍穹外卖为”李小罗餐厅“

学习苍穹外卖后&#xff0c;将其修改为自己所需要的项目&#xff0c;也是对苍穹外卖项目的加深理解 对项目之间的连接等关系进一步清晰&#xff0c;那么便开始吧 d1_开始修改 修改名字为”李小罗餐厅“ src\views\login\index.vue src\router.ts 结果展示 修改进来之后的展示…

上海站圆满结束!MongoDB Developer Day深圳站,周六见!

在过去两个周六的北京和上海 我们见证了两站热情高涨的 MongoDB Developer Day&#xff01; 近200位参会开发者相聚专业盛会 经过全天的动手实操和主题研讨会 MongoDB技能已是Next Level&#xff01; 最后一站Developer Day即将启程 期待本周六与各位在深圳相见&#xff0…

【Docker安装】OpenEuler系统下部署Docker环境

【Docker安装】OpenEuler系统下部署Docker环境 前言一、本次实践介绍1.1 本次实践规划1.2 本次实践简介二、检查本地环境2.1 检查操作系统版本2.2 检查内核版本2.3 检查yum仓库三、卸载Docker四、部署Docker环境4.1 配置yum仓库4.2 检查可用yum仓库4.3 安装Docker4.4 检查Docke…

Python题解Leetcode Hot100之矩阵

1. 矩阵置零 题目描述 给定一个 m x n 的矩阵&#xff0c;如果一个元素为 0 &#xff0c;则将其所在行和列的所有元素都设为 0 。请使用 原地 算法。 解题思路 题目要求进行原地更改&#xff0c;也就是不能使用额外的空间&#xff0c;因此我们可以使用第一行的元素来记录对应的…

【LeetCode】十二、递归:斐波那契 + 反转链表

文章目录 1、递归2、leetcode509&#xff1a;斐波那契数列3、leetcode206&#xff1a;反转链表4、leetcode344&#xff1a;反转字符串 1、递归 函数自己调用自己 递归的4个点&#xff1a; 递归的例子&#xff1a;给一个数n&#xff0c;在斐波那契数列中&#xff0c;找到n对应的…

科研与英文学术论文写作指南——于静老师课程

看到了一个特别棒的科研与英文学术论文写作指南&#xff0c;理论框架实例。主讲人是中科院信息工程研究所的于静老师。推荐理由&#xff1a;写论文和读论文或者讲论文是完全不一样的&#xff0c;即使现在还没有发过论文&#xff0c;但是通过于老师的课程&#xff0c;会给后续再…

LSTM水质预测模型实践

0 引言 随着水质自动站的普及&#xff0c;监测频次越来越高&#xff0c;自动监测越来越准确。 水质站点增多&#xff0c;连续的水质监测数据&#xff0c;给水质预测提供更多的训练基础。 长短时记忆网络(LSTM)适用于多变量、连续、自相关的数据预测。 人工神经网络模型特点为的…

使用requests爬取拉勾网python职位数据

爬虫目的 本文是想通过爬取拉勾网Python相关岗位数据&#xff0c;简单梳理Requests和xpath的使用方法。 代码部分并没有做封装&#xff0c;数据请求也比较简单&#xff0c;所以该项目只是为了熟悉requests爬虫的基本原理&#xff0c;无法用于稳定的爬虫项目。 爬虫工具 这次…

LVS 负载均衡群集

一&#xff1a;LVS群集应用基础 1.1&#xff1a;概述 1.群集的类型 无论是哪种群集&#xff0c; 都至少包括两台节点服务器&#xff0c; 而对外表现为一个整体&#xff0c; 只提供一个访问入口。根据群集所针对的目标差异&#xff0c; 可分为以下三种类型。 负载均衡群集&a…

使用U盘重装系统

目录 一、 制作启动盘 1. 准备一个U盘和一台电脑 2. 下载win10安装包 二、安装操作系统 1. 插入系统安装盘 2. 通过进入BIOS界面进入到我们自己制作的启动盘上 三、安装成功后进行常规设置 一、 制作启动盘 1. 准备一个U盘和一台电脑 注意&#xff1a;提前备份好U盘内的…

JDK1.8下载、安装与配置完整图文2024最新教程

一、报错 运行Pycharm时&#xff0c;报错No JVM installation found. Please install a JDK.If you already have a JDK installed, define a JAVA_HOME variable in Computer >System Properties > System Settings > Environment Variables. 首先可以检查是否已安装…

【C语言】qsort()函数详解:能给万物排序的神奇函数

&#x1f984;个人主页:修修修也 &#x1f38f;所属专栏:C语言 ⚙️操作环境:Visual Studio 2022 目录 一.qsort()函数的基本信息及功能 二.常见的排序算法及冒泡排序 三.逐一解读qsort()函数的参数及其原理 1.void* base 2.size_t num 3.size_t size 4.int (*compar)(c…

节水增效,蜂窝物联智能灌溉助力农业升级!

智能灌溉的优势主要体现在以下几个方面&#xff1a; 1. 提高效率&#xff1a;智能灌溉可以根据作物生长的不同阶段和环境条件自动调整灌溉时间和水量&#xff0c;减少人工干预的频率和时间&#xff0c;提高了灌溉效率。 2. 节约水资源&#xff1a;智能灌溉可以根据土壤湿度和…

Python爬虫实战案例——王者荣耀皮肤抓取

大家好&#xff0c;我是你们的老朋友——南枫&#xff0c;今天我们一起来学习一下该如何抓取大家经常玩的游戏——王者荣耀里面的所有英雄的皮肤。 老规矩&#xff0c;直接上代码&#xff1a; 导入我们需要使用到的&#xff0c;也是唯一用到的库&#xff1a; 我们要抓取皮肤其…

大陆ARS548使用记录

一、Windows连接上位机 雷达是在深圳路达买的&#xff0c;商家给的资料中首先让配置网口&#xff0c;但我在使用过程中一直出现无法连接上位机的情况。接下来说说我的见解和理解。 1.1遇到的问题 按要求配置好端口后上位机无连接不到雷达&#xff0c;但wireshark可以正常抓到数…

PyPDF2拆分PDF文件的高级应用:指定拆分方式

本文目录 前言一、拆分方式选择1、代码讲解2、实现效果图3、完整代码前言 前两篇文章,分别讲解了将使用PyPDF2将PDF文档分割成为单个页面、在分割PDF文档时指定只分割出指定页面,如果你还没有看过,然后有需要的话,可以去看一下,我把文章链接贴到这里: PyPDF2拆分PDF文件…

Nuxt3 的生命周期和钩子函数(九)

title: Nuxt3 的生命周期和钩子函数&#xff08;九&#xff09; date: 2024/7/3 updated: 2024/7/3 author: cmdragon excerpt: 摘要&#xff1a;本文介绍了Nuxt3中与Vite相关的五个生命周期钩子&#xff0c;包括vite:extend、vite:extendConfig、vite:configResolved、vite…

CVE-2024-6387漏洞预警:尽快升级OpenSSH

OpenSSH维护者发布了安全更新&#xff0c;其中包含一个严重的安全漏洞&#xff0c;该漏洞可能导致在基于glibc的Linux系统中使用root权限执行未经身份验证的远程代码。该漏洞的代号为regreSSHion&#xff0c;CVE标识符为CVE-2024-6387。它驻留在OpenSSH服务器组件&#xff08;也…

小型语言模型的兴起

过去几年&#xff0c;我们看到人工智能能力呈爆炸式增长&#xff0c;其中很大一部分是由大型语言模型 (LLM) 的进步推动的。GPT-3 等模型包含 1750 亿个参数&#xff0c;已经展示了生成类似人类的文本、回答问题、总结文档等能力。然而&#xff0c;虽然 LLM 的能力令人印象深刻…