【java】-Netty--ByteBufUtil

本文介绍如何使用hexDump()函数将ByteBuf内容转换为十六进制字符串,实现报文的16进制解码与加密传输。通过具体代码示例,展示字符串的16进制加密过程。

将报文直接写文16进制(解码)

hexDump():能够将参数ByteBuf的内容转换为十六进制的字符串

byte[] data = msg.getData();
        // wl 解析出报文中的时间 yyMM 808_2019_time
        String s = ByteBufUtil.hexDump(data);

字符串进行16进制加密传输


 buffer.writeBytes(ByteBufUtil.decodeHexDump(phone20Str(phoneNum)));
<think>我们被要求提供一个使用JavaNetty连接TCP的示例程序代码。根据引用内容,我们可以知道Netty是一个高性能的网络框架,提供了处理粘包/拆包的机制(如LengthFieldBasedFrameDecoder),以及使用EventLoopGroup来处理并发连接。此外,引用中还提到了长连接的实现(通过ScheduledFuture发送心跳包)。 我们将构建一个简单的TCP客户端和服务器示例。服务器将监听端口,客户端将连接服务器并发送一条消息,然后服务器将回复一条消息。 步骤: 1. 添加Netty依赖(在Maven项目中) 2. 创建服务器端: - 创建两个EventLoopGroup(bossGroup和workerGroup) - 配置服务器启动器(ServerBootstrap) - 设置Channel类型(NioServerSocketChannel) - 设置处理器(包括处理粘包/拆包的LengthFieldBasedFrameDecoder和自定义的业务处理器) 3. 创建客户端: - 创建一个EventLoopGroup - 配置客户端启动器(Bootstrap) - 设置Channel类型(NioSocketChannel) - 设置处理器(同样包括LengthFieldBasedFrameDecoder和自定义的业务处理器) 4. 编写业务处理器(处理消息和发送消息) 注意:由于引用中提到粘包/拆包问题,我们将使用LengthFieldBasedFrameDecoder来解决。我们假设每条消息的前4个字节表示消息体的长度。 我们将按照以下格式设计消息: +------------------+------------------+ | 长度字段(4字节) | 消息体 | +------------------+------------------+ 因此,在服务器和客户端的ChannelPipeline中,第一个处理器应该是LengthFieldBasedFrameDecoder,用于根据长度字段拆分数据包。 另外,为了简化,我们使用字符串消息,并配合StringDecoder和StringEncoder(或者使用自己的编解码器)。但注意,实际应用中可能需要更复杂的编解码。 这里我们使用自定义的编解码: - 在LengthFieldBasedFrameDecoder之后,我们将接收到的ByteBuf转换为字符串(使用UTF-8编码)。 - 发送时,先将字符串编码为字节,然后加上4字节的长度前缀。 但是,Netty提供了一些现成的编解码器,我们可以使用LengthFieldPrepender来添加长度前缀(它会自动在消息前面加上长度字段),这样我们发送时只需要发送字符串的字节,由LengthFieldPrepender添加长度头。 因此,我们的ChannelPipeline将包括: 1. LengthFieldBasedFrameDecoder: 用于解码(处理接收) 2. LengthFieldPrepender: 用于编码(处理发送)——添加长度头 3. StringDecoder和StringEncoder: 用于字符串和ByteBuf的转换(或者自定义的编解码器,这里我们使用StringDecoder和StringEncoder,但注意它们是基于换行的,不适合我们这里。因此,我们将使用自定义的简单编解码器,实际上我们可以使用ByteBuf到字符串的转换) 另一种做法:我们可以不使用StringEncoder和StringDecoder,而是自定义处理器来转换。但为了简单,我们使用如下组合: - 解码:LengthFieldBasedFrameDecoder -> 自定义处理器(将ByteBuf转为String) - 编码:LengthFieldPrepender -> 自定义处理器(将String转为ByteBuf) 或者,我们可以使用Netty提供的ObjectEncoder和ObjectDecoder,但这里我们使用简单字符串。 考虑到简单性,我们将自定义两个处理器: - 一个用于将接收到的ByteBuf(已经是拆包后的完整帧)转换为字符串(DecoderHandler) - 一个用于将字符串转换为ByteBuf,并自动加上长度前缀(实际上LengthFieldPrepender已经帮我们加长度前缀了,所以我们只需将字符串转为ByteBuf) 但是,我们可以这样配置Pipeline: - 在服务器端和客户端的Pipeline中,添加: pipeline.addLast(new LengthFieldBasedFrameDecoder(1024, 0, 4, 0, 4)); // 最大帧长1024,长度字段在0位置,4字节,去掉长度字段后剩下的就是消息体 pipeline.addLast(new LengthFieldPrepender(4)); // 在发送的消息前面加上4字节的长度字段 pipeline.addLast(new StringDecoder(CharsetUtil.UTF_8)); // 将ByteBuf解码为字符串(注意:这里要求前面的帧解码器已经将消息拆成单个包) pipeline.addLast(new StringEncoder(CharsetUtil.UTF_8)); // 将字符串编码为ByteBuf 但是,注意StringDecoder和StringEncoder并不是基于长度域的,而是基于换行符的(实际上StringDecoder是TextWebSocketFrame用的,不是我们这里要的)。所以,我们使用自定义的编解码器。 修改方案:我们使用ByteToMessageDecoder和MessageToByteEncoder来自定义字符串编解码,但这样工作量稍大。因此,我们可以使用Netty提供的编解码器组合: - 使用LengthFieldBasedFrameDecoder和LengthFieldPrepender处理长度域。 - 然后使用ByteBuf到字符串的转换:我们可以通过添加两个处理器,一个负责将ByteBuf转为String(继承ByteToMessageDecoder),一个负责将String转为ByteBuf(继承MessageToByteEncoder<String>)。 但是,Netty提供了一个方便的类:MessageToMessageCodec,可以同时处理编码和解码。但我们这里简单处理,分别写两个处理器。 然而,为了快速示例,我们可以使用如下方式: - 解码:LengthFieldBasedFrameDecoder -> 自定义处理器(继承SimpleChannelInboundHandler<ByteBuf>),在这个处理器里,我们将ByteBuf转为字符串,然后传递给下一个处理器(业务处理器)。 - 编码:在发送字符串时,我们先将其转为ByteBuf,然后通过LengthFieldPrepender加上长度头。所以我们需要两个步骤:一个处理器将字符串转为ByteBuf(可以使用NettyByteBufUtil.writeUtf8),然后通过LengthFieldPrepender发送。 但这样需要两个处理器来配合。为了简化,我们可以将编码和解码都放在一个编解码器中。然而,Netty的Pipeline是支持多个处理器的,我们可以这样: Pipeline的顺序: inbound(入站,即接收): LengthFieldBasedFrameDecoder -> 自定义解码器(ByteBufToString) -> 业务处理器 outbound(出站,即发送): 业务处理器 -> 自定义编码器(StringToByteBuf) -> LengthFieldPrepender 但是,注意LengthFieldPrepender是一个出站处理器,它会在数据前面加上长度头。而我们的自定义编码器应该在LengthFieldPrepender之前,因为LengthFieldPrepender需要处理ByteBuf类型。 因此,我们定义: - 自定义编码器:将字符串转为ByteBuf(不带长度头) - 然后经过LengthFieldPrepender,它会在ByteBuf前面添加长度头。 所以,出站处理器的顺序是:业务处理器 -> 自定义编码器 -> LengthFieldPrepender 入站:LengthFieldBasedFrameDecoder -> 自定义解码器 -> 业务处理器 但是,这样写比较麻烦。我们有一个更简单的方式:使用Netty提供的DelimiterBasedFrameDecoder或LineBasedFrameDecoder?不行,因为我们的消息没有分隔符,而是长度域。 因此,我们回到使用LengthFieldBasedFrameDecoder和LengthFieldPrepender,然后使用两个简单的转换处理器: 自定义解码器(继承ByteToMessageDecoder)将ByteBuf转为字符串,但注意LengthFieldBasedFrameDecoder已经将包拆好了,所以每个ByteBuf是一个完整的包,我们直接读取即可。 自定义编码器(继承MessageToByteEncoder<String>)将字符串转为ByteBuf(不带长度头),然后交给LengthFieldPrepender添加长度头(LengthFieldPrepender会处理ByteBuf并添加长度头)。 但是,实际上,LengthFieldPrepender只能处理ByteBuf类型,所以如果我们发送的是String,那么需要先转成ByteBuf,然后经过LengthFieldPrepender。因此,我们需要两个出站处理器:一个将String转成ByteBuf,另一个是LengthFieldPrepender。 所以,我们这样构建Pipeline: // 入站处理器链 ch.pipeline().addLast(new LengthFieldBasedFrameDecoder(1024*1024, 0, 4, 0, 4)); // 参数:最大帧长度、长度字段偏移、长度字段长度、长度调整值(调整长度字段值)、从帧中剥离的字节数(去掉长度字段) ch.pipeline().addLast(new ByteBufToStringDecoder()); // 将ByteBuf转为String ch.pipeline().addLast(new ServerHandler()); // 业务处理器 // 出站处理器链(注意顺序:从后往前添加,所以先添加的处理器在出站时后执行) ch.pipeline().addLast(new LengthFieldPrepender(4)); // 在消息前面加上4字节的长度头 ch.pipeline().addLast(new StringToByteBufEncoder()); // 将String转为ByteBuf 但是,注意Netty的Pipeline是双向的,添加顺序很重要。我们按照以下顺序添加: pipeline.addLast("frameDecoder", new LengthFieldBasedFrameDecoder(1024,0,4,0,4)); pipeline.addLast("stringDecoder", new ByteBufToStringDecoder()); pipeline.addLast("frameEncoder", new LengthFieldPrepender(4)); pipeline.addLast("stringEncoder", new StringToByteBufEncoder()); pipeline.addLast("businessHandler", new ServerHandler()); 入站事件(接收)的流向:frameDecoder -> stringDecoder -> businessHandler 出站事件(发送)的流向:businessHandler -> stringEncoder -> frameEncoder 注意:在ChannelPipeline中,入站处理器的顺序是添加顺序,出站处理器的顺序是添加顺序的逆序(当发送时,会从后往前找出站处理器)。但通常我们按照逻辑顺序添加,Netty内部会处理。 但是,更常见的做法是使用ChannelInitializer,在initChannel方法中按顺序添加处理器。 我们决定简化:使用Netty提供的StringEncoder和StringDecoder,但它们是针对换行符的,所以不合适。而且StringEncoder实际上是MessageToMessageEncoder,它不能直接用在LengthFieldPrepender前面。 因此,我们自定义两个处理器: 1. ByteBufToStringDecoder: 继承ByteToMessageDecoder,将ByteBuf转换为String,然后传递给下一个处理器。 2. StringToByteBufEncoder: 继承MessageToByteEncoder<String>,将String编码为ByteBuf。 这样,我们的Pipeline配置: pipeline.addLast(new LengthFieldBasedFrameDecoder(1024*1024, 0, 4, 0, 4)); // 最大长度1MB pipeline.addLast(new ByteBufToStringDecoder()); pipeline.addLast(new LengthFieldPrepender(4)); pipeline.addLast(new StringToByteBufEncoder()); pipeline.addLast(new BusinessHandler()); // 业务处理器,处理String类型的消息 注意:LengthFieldBasedFrameDecoder的参数说明: maxFrameLength: 最大帧长度(超过将抛出异常) lengthFieldOffset: 长度字段的偏移量(从0开始) lengthFieldLength: 长度字段本身的长度(4字节,即int) lengthAdjustment: 长度调整值,在长度字段的值上加上这个值(我们不需要调整,所以为0) initialBytesToStrip: 从帧中剥离的字节数(这里我们去掉长度字段,所以是4。但是,如果我们希望业务处理器收到的是包含长度字段的消息,则设为0,但通常业务处理器不需要长度字段,所以去掉) 但是,去掉长度字段后,剩下的就是消息体(字节形式)。然后我们的ByteBufToStringDecoder将消息体(ByteBuf)转为字符串。 发送时,业务处理器发出一个字符串,经过StringToByteBufEncoder将字符串转为ByteBuf(不带长度头),然后经过LengthFieldPrepender,它会在ByteBuf前面加上4字节的长度字段(值为ByteBuf的可读字节数),形成带有长度头的消息。 下面我们编写代码: 由于时间关系,我们只写一个简单的示例,包括客户端和服务器。服务器监听8888端口,客户端连接后发送一条消息,服务器回复。 自定义ByteBufToStringDecoder和StringToByteBufEncoder: 实际上,我们可以使用以下方式替代自定义的编解码器: pipeline.addLast(new StringDecoder(CharsetUtil.UTF_8)); pipeline.addLast(new StringEncoder(CharsetUtil.UTF_8)); 但是,注意:StringDecoder和StringEncoder是用于文本行协议或基于分隔符的协议的。如果我们使用LengthFieldBasedFrameDecoder拆包,那么每个ByteBuf应该是一个完整的包,然后StringDecoder可以将每个ByteBuf转为字符串。同样,StringEncoder将字符串转为ByteBuf,然后交给LengthFieldPrepender加上长度头。 所以,我们可以这样: // 入站 pipeline.addLast(new LengthFieldBasedFrameDecoder(1024,0,4,0,4)); pipeline.addLast(new StringDecoder(CharsetUtil.UTF_8)); // 将ByteBuf转为String // 出站 pipeline.addLast(new LengthFieldPrepender(4)); // 注意:LengthFieldPrepender处理ByteBuf,所以它必须在StringEncoder之后(因为StringEncoder出站时把String转成ByteBuf,然后LengthFieldPrepender再给它加长度头) pipeline.addLast(new StringEncoder(CharsetUtil.UTF_8)); // 将String转为ByteBuf // 业务处理器 pipeline.addLast(new ServerHandler()); 但是,注意Pipeline的添加顺序:入站事件按照添加顺序执行,出站事件按照添加的逆序执行。上面添加顺序为: LengthFieldBasedFrameDecoder (inbound) StringDecoder (inbound) LengthFieldPrepender (outbound) StringEncoder (outbound) ServerHandler (既是inbound也是outbound?我们将在ServerHandler中处理入站消息,并可能发送出站消息) 这样,当接收到数据时,先经过LengthFieldBasedFrameDecoder拆包,然后经过StringDecoder转成字符串,然后到达ServerHandler的channelRead0方法(如果ServerHandler继承SimpleChannelInboundHandler<String>)。 而当ServerHandler发送一个字符串时,出站事件首先经过StringEncoder(因为它是最后一个添加的出站处理器?注意出站处理器的顺序是从后往前:ServerHandler -> StringEncoder -> LengthFieldPrepender?) 但实际上,在ChannelPipeline中,处理器的顺序是: head -> [LengthFieldBasedFrameDecoder, StringDecoder, LengthFieldPrepender, StringEncoder, ServerHandler] -> tail 入站事件:head -> LengthFieldBasedFrameDecoder -> StringDecoder -> ServerHandler -> tail 出站事件:tail -> ServerHandler -> StringEncoder -> LengthFieldPrepender -> head 所以,当ServerHandler发送消息(字符串)时,先经过StringEncoder(将字符串转为ByteBuf),然后经过LengthFieldPrepender(在ByteBuf前面加上长度头),然后发送出去。 但是,注意:LengthFieldPrepender是出站处理器,它处理的是出站数据(从tail到head方向)。而StringEncoder也是出站处理器。 因此,这个顺序是正确的。 所以,我们可以利用Netty自带的StringEncoder和StringDecoder来简化。 下面开始编写代码。 注意:由于我们使用Netty,需要添加Netty依赖。假设使用Maven,在pom.xml中添加: <dependency> <groupId>io.netty</groupId> <artifactId>netty-all</artifactId> <version>4.1.68.Final</version> <!-- 版本根据实际情况 --> </dependency> 我们分两步:服务器和客户端。 服务器端代码: 1. 创建两个EventLoopGroup:bossGroup和workerGroup。 2. 创建ServerBootstrap,配置参数。 3. 绑定端口。 客户端代码: 1. 创建一个EventLoopGroup。 2. 创建Bootstrap,配置参数。 3. 连接服务器。 业务处理器(ServerHandler和ClientHandler)分别处理消息。 由于篇幅,我们将代码整合在一个示例中,但实际使用时可能需要分开。 下面是一个简单的示例: </think>以下是一个使用 JavaNetty 实现 TCP 连接的完整示例程序,包含服务器和客户端代码,解决了粘包/拆包问题并支持长连接: ### 服务端代码 ```java 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.SocketChannel; import io.netty.channel.socket.nio.NioServerSocketChannel; import io.netty.handler.codec.LengthFieldBasedFrameDecoder; import io.netty.handler.codec.LengthFieldPrepender; public class NettyTcpServer { private static final int PORT = 8888; public static void main(String[] args) throws Exception { // 主从线程组模型 EventLoopGroup bossGroup = new NioEventLoopGroup(1); // 接收连接 EventLoopGroup workerGroup = new NioEventLoopGroup(); // 处理I/O try { ServerBootstrap bootstrap = new ServerBootstrap(); bootstrap.group(bossGroup, workerGroup) .channel(NioServerSocketChannel.class) .childHandler(new ChannelInitializer<SocketChannel>() { @Override protected void initChannel(SocketChannel ch) { ChannelPipeline pipeline = ch.pipeline(); // 解决粘包/拆包 (最大帧长1KB) pipeline.addLast(new LengthFieldBasedFrameDecoder(1024, 0, 4, 0, 4)); pipeline.addLast(new LengthFieldPrepender(4)); // 业务处理器 pipeline.addLast(new TcpServerHandler()); } }) .option(ChannelOption.SO_BACKLOG, 128) .childOption(ChannelOption.SO_KEEPALIVE, true); // 绑定端口启动服务 ChannelFuture future = bootstrap.bind(PORT).sync(); System.out.println("TCP Server started on port: " + PORT); future.channel().closeFuture().sync(); } finally { workerGroup.shutdownGracefully(); bossGroup.shutdownGracefully(); } } // 业务处理器 private static class TcpServerHandler extends ChannelInboundHandlerAdapter { @Override public void channelRead(ChannelHandlerContext ctx, Object msg) { ByteBuf in = (ByteBuf) msg; try { System.out.println("Server received: " + in.toString(io.netty.util.CharsetUtil.UTF_8)); // 响应客户端 ctx.writeAndFlush(ctx.alloc().buffer().writeBytes("ACK".getBytes())); } finally { in.release(); } } @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { cause.printStackTrace(); ctx.close(); } } } ``` ### 客户端代码 ```java 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.SocketChannel; import io.netty.channel.socket.nio.NioSocketChannel; import io.netty.handler.codec.LengthFieldBasedFrameDecoder; import io.netty.handler.codec.LengthFieldPrepender; public class NettyTcpClient { private static final String HOST = "127.0.0.1"; private static final int PORT = 8888; public static void main(String[] args) throws Exception { EventLoopGroup group = new NioEventLoopGroup(); try { Bootstrap bootstrap = new Bootstrap(); bootstrap.group(group) .channel(NioSocketChannel.class) .handler(new ChannelInitializer<SocketChannel>() { @Override protected void initChannel(SocketChannel ch) { ChannelPipeline pipeline = ch.pipeline(); // 粘包/拆包处理 pipeline.addLast(new LengthFieldBasedFrameDecoder(1024, 0, 4, 0, 4)); pipeline.addLast(new LengthFieldPrepender(4)); // 业务处理器 pipeline.addLast(new TcpClientHandler()); } }); // 建立长连接 ChannelFuture future = bootstrap.connect(HOST, PORT).sync(); future.channel().closeFuture().sync(); } finally { group.shutdownGracefully(); } } // 业务处理器 private static class TcpClientHandler extends ChannelInboundHandlerAdapter { @Override public void channelActive(ChannelHandlerContext ctx) { // 连接建立后发送数据 ByteBuf message = ctx.alloc().buffer(); message.writeBytes("Hello Server".getBytes()); ctx.writeAndFlush(message); } @Override public void channelRead(ChannelHandlerContext ctx, Object msg) { ByteBuf in = (ByteBuf) msg; try { System.out.println("Client received: " + in.toString(io.netty.util.CharsetUtil.UTF_8)); } finally { in.release(); } } @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { cause.printStackTrace(); ctx.close(); } } } ``` ### 关键实现说明 1. **粘包/拆包解决方案** - 使用 `LengthFieldBasedFrameDecoder` + `LengthFieldPrepender` 组合 - 报文格式:`[4字节长度][实际数据]` - 自动处理TCP数据流的分帧问题[^1] 2. **线程模型优化** - 服务端采用主从 Reactor 模式:`bossGroup` 处理连接,`workerGroup` 处理 I/O - 每个 `EventLoop` 可处理多个连接,实现高并发[^3] 3. **长连接支持** - 设置 `SO_KEEPALIVE` 启用 TCP 心跳检测 - 配合 `ScheduledFuture` 可实现自定义心跳机制[^4] 4. **内存管理** - 使用 `ByteBuf.release()` 显式释放内存 - 建议实际项目启用 Netty 的内存池优化 ### 运行流程 1. 启动 `NettyTcpServer` 2. 启动 `NettyTcpClient` 3. 客户端自动发送 "Hello Server" 4. 服务端回复 "ACK" 5. 双方保持长连接状态 ### 性能优化建议 1. 对于百万级连接: ```java // 调整系统参数 .option(ChannelOption.SO_BACKLOG, 10240) .childOption(ChannelOption.TCP_NODELAY, true) ``` 2. 使用 `EpollEventLoopGroup`(Linux 系统) 3. 添加空闲状态检测处理器 `IdleStateHandler` 4. 启用 Netty 的 `ByteBuf` 对象池[^2]
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值