Netty Review - Netty与Protostuff:打造高效的网络通信

文章目录

  • 概念
  • Pre
  • Pom
  • Server & Client
  • ProtostuffUtil 解读
  • 测试
  • 小结

在这里插入图片描述


概念

在这里插入图片描述


Pre

每日一博 - Protobuf vs. Protostuff:性能、易用性和适用场景分析


Pom

    	<dependency><groupId>com.dyuproject.protostuff</groupId><artifactId>protostuff-api</artifactId><version>1.2.2</version></dependency><dependency><groupId>com.dyuproject.protostuff</groupId><artifactId>protostuff-core</artifactId><version>1.2.2</version></dependency><dependency><groupId>com.dyuproject.protostuff</groupId><artifactId>protostuff-runtime</artifactId><version>1.2.2</version></dependency>

Server & Client

package com.artisan.codec.protostuff;import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;/*** @author 小工匠* @version 1.0* @mark: show me the code , change the world*/
// 定义Netty服务器类
public class NettyServer {// main方法为程序入口点public static void main(String[] args) throws Exception {// 创建主从线程组,用于处理Netty的事件循环EventLoopGroup bossGroup = new NioEventLoopGroup(1);EventLoopGroup workerGroup = new NioEventLoopGroup();try {// 创建ServerBootstrap实例,用于设置服务器参数ServerBootstrap serverBootstrap = new ServerBootstrap();// 配置服务器线程组serverBootstrap.group(bossGroup, workerGroup)// 设置使用的Channel类型.channel(NioServerSocketChannel.class)// 设置Channel初始化处理器.childHandler(new ChannelInitializer<SocketChannel>() {@Overrideprotected void initChannel(SocketChannel ch) throws Exception {// 获取Channel的PipelineChannelPipeline pipeline = ch.pipeline();// 添加自定义的处理器pipeline.addLast(new NettyServerHandler());}});// 打印启动信息System.out.println("netty server start。。");// 绑定端口并启动服务器ChannelFuture channelFuture = serverBootstrap.bind(9876).sync();// 等待服务器Channel关闭channelFuture.channel().closeFuture().sync();} finally {// 优雅地关闭主从线程组bossGroup.shutdownGracefully();workerGroup.shutdownGracefully();}}
}

这段代码首先设置了两个EventLoopGroup,一个用于接受连接(boss),另一个用于处理已接受连接的IO操作(worker)。然后创建了一个ServerBootstrap实例来配置和启动服务器。在ServerBootstrap中指定了使用的EventLoopGroupChannel类型以及子Channel的初始化处理器。初始化处理器中添加了一个自定义的NettyServerHandler,这应该是处理网络事件和业务逻辑的地方。

服务器启动后,会绑定到本地端口9876,并等待连接。程序最后会优雅地关闭线程组,释放资源。

需要注意的是,这段代码缺少了NettyServerHandler类的定义,这应该是处理网络事件和业务逻辑的具体实现。同时,这段代码没有异常处理和资源管理的健壮性考虑,例如可能需要捕获并处理Exception等。


NettyServerHandler的类,该类继承了ChannelInboundHandlerAdapter,表示一个自定义的Netty通道入站处理器。处理器中重写了channelReadexceptionCaught方法,分别用于处理通道读取事件和异常事件。

在这里插入图片描述

package com.artisan.codec.protostuff;
// 引入Netty相关类
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
/*** @author 小工匠* @version 1.0* @mark: show me the code , change the world*/
// 定义Netty服务器处理器类
public class NettyServerHandler extends ChannelInboundHandlerAdapter {// 重写channelRead方法,处理通道读取事件@Overridepublic void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {// 获取客户端发送的ByteBuf数据ByteBuf buf = (ByteBuf) msg;// 将ByteBuf数据转换为字节数组byte[] bytes = new byte[buf.readableBytes()];buf.readBytes(bytes);// 使用ProtostuffUtil工具类对字节数组进行反序列化操作System.out.println("从客户端读取到Object:" + ProtostuffUtil.deserializer(bytes, Artisan.class));}// 重写exceptionCaught方法,处理异常事件@Overridepublic void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {// 打印异常堆栈信息cause.printStackTrace();// 关闭通道ctx.close();}
}

channelRead方法中,当通道读取到数据时,会将接收到的ByteBuf对象转换为字节数组,并使用ProtostuffUtil工具类的deserializer方法进行反序列化操作,将字节数组还原为Artisan对象。Artisan类是客户端发送的数据对应的Java对象。

exceptionCaught方法中,当发生异常时,会打印异常堆栈信息,并关闭通道。这有助于及时发现并处理异常,避免程序出现异常无法处理的情况。


这段代码是一个使用Netty框架的简单客户端程序。客户端程序的主要作用是连接到服务器,并发送或接收数据。下面是对这段代码的解读和增加的中文注释:

package com.artisan.codec.protostuff;
// 引入Netty相关类
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
/*** @author 小工匠* @version 1.0* @mark: show me the code , change the world*/
// 定义Netty客户端类
public class NettyClient {// main方法为程序入口点public static void main(String[] args) throws Exception {// 创建事件循环组EventLoopGroup group = new NioEventLoopGroup();try {// 创建Bootstrap实例,用于设置客户端参数Bootstrap bootstrap = new Bootstrap();// 配置客户端事件循环组bootstrap.group(group).channel(NioSocketChannel.class)// 设置客户端Channel初始化处理器.handler(new ChannelInitializer<SocketChannel>() {@Overrideprotected void initChannel(SocketChannel ch) throws Exception {// 获取Channel的PipelineChannelPipeline pipeline = ch.pipeline();// 添加自定义的处理器pipeline.addLast(new NettyClientHandler());}});// 打印启动信息System.out.println("netty client start。。");// 连接到服务器ChannelFuture channelFuture = bootstrap.connect("127.0.0.1", 9876).sync();// 等待客户端Channel关闭channelFuture.channel().closeFuture().sync();} finally {// 优雅地关闭事件循环组group.shutdownGracefully();}}
}

这段代码首先创建了一个NioEventLoopGroup,用于处理Netty的事件循环。然后创建了一个Bootstrap实例来配置和启动客户端。在Bootstrap中指定了使用的事件循环组、Channel类型以及子Channel的初始化处理器。初始化处理器中添加了一个自定义的NettyClientHandler,这是处理网络事件和业务逻辑的地方。

客户端启动后,会连接到服务器127.0.0.1的端口9876。程序最后会优雅地关闭事件循环组,释放资源。


NettyClientHandler的类,该类继承了ChannelInboundHandlerAdapter,表示一个自定义的Netty通道入站处理器。处理器中重写了channelReadchannelActive方法,分别用于处理通道读取事件和通道激活事件。

在这里插入图片描述

package com.artisan.codec.protostuff;import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import java.util.Arrays;
/*** @author 小工匠* @version 1.0* @mark: show me the code , change the world*/
// 定义Netty客户端处理器类
public class NettyClientHandler extends ChannelInboundHandlerAdapter {// 重写channelRead方法,处理通道读取事件@Overridepublic void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {// 打印服务器发送的消息System.out.println("收到服务器消息:" + msg);}// 重写channelActive方法,处理通道激活事件@Overridepublic void channelActive(ChannelHandlerContext ctx) throws Exception {// 打印客户端处理器发送数据信息System.out.println("NettyClientHandler发送数据");// 使用ProtostuffUtil工具类对对象进行序列化操作Artisan artisan = new Artisan(1, "artisan", new Address("xx", Arrays.asList("code", "run")));ByteBuf buf = Unpooled.copiedBuffer(ProtostuffUtil.serializer(artisan));// 向服务器发送序列化后的数据ctx.writeAndFlush(buf);}
}

这段代码定义了一个名为NettyClientHandler的Netty通道入站处理器,主要用于处理通道读取事件和通道激活事件。具体功能如下:

  1. 重写channelRead方法:当通道读取到数据时,该方法会被调用,并打印服务器发送的消息。
  2. 重写channelActive方法:当通道激活时(即成功连接到服务器),该方法会被调用。在该方法中,使用ProtostuffUtil工具类对Artisan对象进行序列化操作,并将序列化后的数据发送给服务器。

注意:在实际使用中,建议在channelActive方法最后添加buf.release();来释放ByteBuf对象,避免内存泄漏。


package com.artisan.codec.protostuff;import java.io.Serializable;/*** @author 小工匠* @version 1.0* @mark: show me the code , change the world*/
public class Artisan implements Serializable {private int id;private String name;private Address address;public Artisan() {}public Artisan(int id, String name) {super();this.id = id;this.name = name;}public Artisan(int id, String name, Address address) {this.id = id;this.name = name;this.address = address;}public int getId() {return id;}public void setId(int id) {this.id = id;}public String getName() {return name;}public void setName(String name) {this.name = name;}public Address getAddress() {return address;}public void setAddress(Address address) {this.address = address;}@Overridepublic String toString() {return "Artisan{" +"id=" + id +", name='" + name + '\'' +", address=" + address +'}';}
}
package com.artisan.codec.protostuff;import java.util.List;/*** @author 小工匠* @version 1.0* @mark: show me the code , change the world*/
public class Address {private String location;private List<String> hobbies;public String getLocation() {return location;}public void setLocation(String location) {this.location = location;}public List<String> getHobbiies() {return hobbies;}public void setHobbies(List<String> hobbies) {this.hobbies = hobbies;}public Address() {}public Address(String location) {this.location = location;}public Address(String location, List<String> hobbies) {this.location = location;this.hobbies = hobbies;}@Overridepublic String toString() {return "Address{" +"location='" + location + '\'' +", hobbies=" + hobbies +'}';}
}

ProtostuffUtil 解读

package com.artisan.codec.protostuff;import com.dyuproject.protostuff.LinkedBuffer;
import com.dyuproject.protostuff.ProtostuffIOUtil;
import com.dyuproject.protostuff.Schema;
import com.dyuproject.protostuff.runtime.RuntimeSchema;import java.util.Arrays;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;/*** @author 小工匠* @version 1.0* @mark: show me the code , change the world* @desc: protostuff 序列化工具类,基于protobuf封装*/
public class ProtostuffUtil {// 使用ConcurrentHashMap缓存Schema,提高性能private static Map<Class<?>, Schema<?>> cachedSchema = new ConcurrentHashMap<Class<?>, Schema<?>>(); // 定义一个ConcurrentHashMap,用于缓存Schema// 获取类的Schema,如果缓存中没有则创建一个新的Schema并缓存起来private static <T> Schema<T> getSchema(Class<T> clazz) { // 定义一个泛型方法,用于获取类的SchemaSchema<T> schema = (Schema<T>) cachedSchema.get(clazz); // 从缓存中获取Schemaif (schema == null) { // 如果Schema为空schema = RuntimeSchema.getSchema(clazz); // 创建一个新的Schemaif (schema != null) { // 如果新的Schema不为空cachedSchema.put(clazz, schema); // 将新的Schema添加到缓存中}}return schema; // 返回Schema}/*** 序列化** @param obj 要序列化的对象* @return 序列化后的字节流*/public static <T> byte[] serializer(T obj) { // 定义一个泛型方法,用于序列化对象@SuppressWarnings("unchecked")Class<T> clazz = (Class<T>) obj.getClass(); // 获取对象的类类型LinkedBuffer buffer = LinkedBuffer.allocate(LinkedBuffer.DEFAULT_BUFFER_SIZE); // 分配一个LinkedBuffer缓冲区try {Schema<T> schema = getSchema(clazz); // 获取对象的Schemareturn ProtostuffIOUtil.toByteArray(obj, schema, buffer); // 将对象序列化为字节流} catch (Exception e) {throw new IllegalStateException(e.getMessage(), e); // 如果出现异常,则抛出自定义异常} finally {buffer.clear(); // 清理LinkedBuffer缓冲区}}/*** 反序列化** @param data  序列化的字节流* @param clazz 对象的类类型* @return 反序列化后的对象*/public static <T> T deserializer(byte[] data, Class<T> clazz) { // 定义一个泛型方法,用于反序列化字节流try {T obj = clazz.newInstance(); // 创建一个新的对象实例Schema<T> schema = getSchema(clazz); // 获取对象的SchemaProtostuffIOUtil.mergeFrom(data, obj, schema); // 将字节流反序列化为对象return obj; // 返回反序列化后的对象} catch (Exception e) {throw new IllegalStateException(e.getMessage(), e); // 如果出现异常,则抛出自定义异常}}public static void main(String[] args) {byte[] userBytes = ProtostuffUtil.serializer(new Artisan(1, "artisan", new Address("ssss", Arrays.asList("code", "run"))));Artisan artisan = ProtostuffUtil.deserializer(userBytes, Artisan.class);System.out.println(artisan);}
}

ProtostuffUtil提供了对Google的Protocol Buffers(protobuf)序列化格式的封装。该工具类使用com.dyuproject.protostuff库,这是一个Google protobuf的Java扩展库,提供了更简单、更灵活的API。

  1. 缓存Schema: ProtostuffUtil使用一个ConcurrentHashMap来缓存不同类的Schema。这样做可以避免在每次序列化或反序列化时都创建新的Schema,从而提高性能。

    private static Map<Class<?>, Schema<?>> cachedSchema = new ConcurrentHashMap<>();
    
  2. 获取Schema: 工具类提供了一个泛型方法getSchema(Class<T> clazz)来获取某个类的Schema。如果Schema已经在缓存中,就直接返回;否则,创建一个新的Schema并将其添加到缓存中。

    private static <T> Schema<T> getSchema(Class<T> clazz) {Schema<T> schema = cachedSchema.get(clazz);if (schema == null) {schema = RuntimeSchema.getSchema(clazz);if (schema != null) {cachedSchema.put(clazz, schema);}}return schema;
    }
    
  3. 序列化: serializer(T obj)方法用于将对象序列化为字节流。这个方法使用ProtostuffIOUtil.toByteArray方法完成序列化,并返回序列化后的字节流。

    public static <T> byte[] serializer(T obj) {@SuppressWarnings("unchecked")Class<T> clazz = (Class<T>) obj.getClass();LinkedBuffer buffer = LinkedBuffer.allocate(LinkedBuffer.DEFAULT_BUFFER_SIZE);try {Schema<T> schema = getSchema(clazz);return ProtostuffIOUtil.toByteArray(obj, schema, buffer);} catch (Exception e) {throw new IllegalStateException(e.getMessage(), e);} finally {buffer.clear();}
    }
    
  4. 反序列化: deserializer(byte[] data, Class<T> clazz)方法用于将字节流反序列化为对象。这个方法使用ProtostuffIOUtil.mergeFrom方法完成反序列化,并返回反序列化后的对象。

    public static <T> T deserializer(byte[] data, Class<T> clazz) {try {T obj = clazz.newInstance();Schema<T> schema = getSchema(clazz);ProtostuffIOUtil.mergeFrom(data, obj, schema);return obj;} catch (Exception e) {throw new IllegalStateException(e.getMessage(), e);}
    }
    

总体而言,该工具类简化了protobuf的序列化和反序列化操作,并提供了Schema的缓存机制以提高性能。


测试

启动Server和Client后

在这里插入图片描述


小结

Protostuff是一个用于Java对象的序列化和反序列化的库,它的主要特点和功能如下:

  1. 高性能:Protostuff的序列化和反序列化操作非常快,对于大量数据的处理具有较高的性能优势。
  2. 简单易用:Protostuff的API设计简单明了,使用起来非常方便,可以快速实现对象的序列化和反序列化操作。
  3. 可扩展性:Protostuff允许用户自定义序列化和反序列化的逻辑,提供了丰富的扩展点,满足不同场景的需求。

总的来说,Protostuff是一个高效、简单、可扩展的Java序列化库,适用于多种场景,特别是对于大量数据和高性能要求的应用场景有较好的表现。

在这里插入图片描述

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

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

相关文章

打破Tomcat中的双亲委派机制:探讨与实践

目录 引言 1. 双亲委派机制概述 2. 打破双亲委派机制的场景 3. Tomcat中的类加载器体系 4. 打破双亲委派机制的方法 4.1 在catalina.properties中配置common.loader 4.2 在META-INF/context.xml中配置Loader元素 4.3 编写自定义的类加载器 5. 潜在的问题与解决方案 5…

Android13 Wifi启动流程分析

Android13 Wifi启动流程分析 文章目录 Android13 Wifi启动流程分析一、正常开关wifi 启动流程1、WifiManager2、WifiServiceImpl3、ActiveModeWarden4、ConcreteClientModeManager5、WifiNative6、WifiVendorHal7、HalDeviceManager8、wifi.cpp 二、重启设备时自动开启wifi流程…

关于LayUI弹出层表单提交两次问题

在弹出层点击 注册 按钮表单会提交两次 但是在单独页面点击是正常的 经过排查发现&#xff0c;问题在于主页面和注册页面重复引用了layui.js&#xff0c;页面要被渲染两次 <script src"../layui/layui.js"></script>

【IntelliJ IDEA】打开项目Git突然无法识别解决方案

这个问题也是我今天突然偶尔遇到的&#xff0c;当时没在意&#xff0c;项目打开之后又关闭&#xff0c;后来很久才又打开&#xff0c;发现项目明明有git版本控制的&#xff0c;咋突然开发工具右下角没有标识了&#xff0c;然后检查了一下git配置还报错了。 其实从图上我们可以看…

Debian在升级过程中报错

当我们在升级的过程中出现如下报错信息 报错信息如下所示&#xff1a; The following signatures couldnt be verified because the public key is not available: NO_PUBKEY ED444FF07D8D0BF6 W: GPG error: http://mirrors.jevincanders.net/kali kali-rolling InRelease: …

使用VSC从零开始Vue.js——备赛笔记——2024全国职业院校技能大赛“大数据应用开发”赛项——任务3:数据可视化

使用Visual Studio Code&#xff08;VSC&#xff09;进行Vue开发非常方便&#xff0c;下面是一些基本步骤&#xff1a; 一、下载和安装Vue 官网下载地址Download | Node.js Vue.js是基于Node.js的&#xff0c;所以首先需要安装Node.js&#xff0c;官网下载地址&#xff1a;No…

jetbrains idea 报错 java.lang.ClassNotFoundException 之后自动搜索包导入包

-- 搜索类所在的包 导入包 搜索包 mac环境 pom中右键或者 cmdn

利用ffmpeg cv2取h265码流视频(转换图片灰屏问题解决)

利用海康威视相机拍出来的视频是H265格式的&#xff0c;相比于常规的H264编码&#xff0c;压缩率更高&#xff0c;但因此如果直接用正常取流方法读取&#xff0c;会出现无法读取的情况 1. 如图h265码流取出图片为灰屏 2 、解决灰屏问题 import subprocess import cv2# 将h265流…

Pooling方法总结(语音识别)

Pooling layer将变长的frame-level features转换为一个定长的向量。 1. Statistics Pooling 链接&#xff1a;http://danielpovey.com/files/2017_interspeech_embeddings.pdf The default pooling method for x-vector is statistics pooling. The statistics pooling laye…

node.js mongoose index(索引)

目录 简介 索引类型 单索引 复合索引 文本索引 简介 在 Mongoose 中&#xff0c;索引&#xff08;Index&#xff09;是一种用于提高查询性能的数据结构&#xff0c;它可以加速对数据库中文档的检索操作 索引类型 单索引、复合索引、文本索引、多键索引、哈希索引、地理…

【单调栈】LeetCode2334:元素值大于变化阈值的子数组

作者推荐 map|动态规划|单调栈|LeetCode975:奇偶跳 涉及知识点 单调栈 题目 给你一个整数数组 nums 和一个整数 threshold 。 找到长度为 k 的 nums 子数组&#xff0c;满足数组中 每个 元素都 大于 threshold / k 。 请你返回满足要求的 任意 子数组的 大小 。如果没有这…

CentOS 7 Tomcat服务的安装

前提 安装ava https://blog.csdn.net/qq_36940806/article/details/134945175?spm1001.2014.3001.5501 1. 下载 wget https://mirrors.tuna.tsinghua.edu.cn/apache/tomcat/tomcat-9/v9.0.84/bin/apache-tomcat-9.0.84.tar.gzps: 可选择自己需要的版本下载安装https://mirr…