【Java知识】高性能网络应用框架Netty应知应会

文章目录

    • 概述
    • 线程模型
    • IO模型
    • 代码示例
      • 服务端代码示例
      • 客户端代码示例
      • 代码说明:
    • 自定义协议实现
      • 自定义协议格式
      • 自定义编码器(Encoder)
      • 自定义解码器(Decoder)
      • 业务处理器(Handler)
      • 在Netty服务器管道中使用自定义编码器和解码器
      • 代码说明:

概述

Netty是一个异步事件驱动的网络应用框架,用于快速开发高性能、高可靠性的协议服务器和客户端。它基于Java NIO库,封装了复杂的底层细节,使得编写高性能网络应用程序变得简单。以下是Netty的一些核心特性和设计原则:

  1. 高性能:Netty使用了异步非阻塞的I/O模型,极大地提高了系统的吞吐量和性能,同时降低了延迟。

  2. 高扩展性:Netty的架构设计使得其非常容易扩展和定制,能够满足不同应用的需求。

  3. 丰富的功能:Netty支持多种传输协议、编解码器、SSL/TLS加密等,功能非常丰富。

  4. 易于使用:Netty提供了简洁易用的API,使得开发者可以快速上手。

  5. 安全性:Netty提供了完整的SSL/TLS和StartTLS支持,确保了数据传输的安全性。

  6. 社区活跃:Netty是一个活跃的开源项目,版本迭代周期短,bug修复速度快,社区提供了大量的文档和示例。

Netty的架构设计非常灵活且高度可扩展。其核心思想是基于事件驱动模型,通过事件循环机制(EventLoop)来管理I/O操作。Netty的架构通常分为三层:

  • 通信调度层(Reactor):负责监听网络的读写和连接操作,将网络层的数据读取到内存缓冲区,然后触发各种网络事件。
  • 责任链层(Pipeline):负责事件在责任链中的有序传播,同时负责动态地编排责任链。责任链可以选择监听和处理自己关心的事件。
  • 业务逻辑编排层(Service ChannelHandler):通常包括纯粹的业务逻辑编排和其他的应用层协议插件,用于特定协议相关的会话和链路管理。

Netty的高性能还体现在以下几个方面:

  • IO线程模型:同步非阻塞,用最少的资源做更多的事。
  • 内存零拷贝:尽量减少不必要的内存拷贝,实现了更高效率的传输。
  • 内存池设计:申请的内存可以重用,主要指直接内存。内部实现是用一颗二叉查找树管理内存分配情况。
  • 串行化处理读写:避免使用锁带来的性能开销。
  • 高性能序列化协议:支持protobuf等高性能序列化协议。

Netty适用于多种场景,包括但不限于互联网行业的分布式系统、游戏行业的服务器通信、大数据领域的高性能通信和序列化组件等。通过Netty,开发者可以轻松构建高性能、高可靠的网络应用程序。

线程模型

Netty的线程模型是基于Java NIO的多路复用IO机制构建的,它采用了事件驱动和异步处理的设计,以提高性能和可伸缩性。Netty的线程模型主要由以下几个部分组成:
netty-thread-model

  1. EventLoopGroup

    • EventLoopGroup是一个抽象类,负责管理一组线程(EventLoop),这些线程负责处理Channel的IO操作和事件。
    • Netty提供了两种类型的EventLoopGroup实现:NioEventLoopGroup(用于NIO传输)和EpollEventLoopGroup(用于Linux系统的Epoll传输)。
  2. EventLoop

    • EventLoopEventLoopGroup中的一个线程,负责处理多个Channel的所有事件和IO操作。
    • 每个EventLoop都是一个事件循环,不断地等待、处理和分发事件。
  3. Channel

    • Channel是Netty网络操作的抽象,代表一个网络连接。
    • 每个Channel都注册到一个EventLoop上,由该EventLoop负责处理所有的事件。
  4. ChannelPipeline

    • ChannelPipeline是处理或拦截Channel事件和数据的处理器链。
    • 每个Channel都有一个自己的ChannelPipeline,用于定义数据如何被处理。
  5. ChannelHandler

    • ChannelHandler是处理事件和数据的逻辑单元。
    • ChannelHandler可以被添加到ChannelPipeline中,用于处理入站数据、出站数据和各种事件(如连接、断开连接等)。

Netty的线程模型通常有两种配置方式:

  1. 主从Reactor模式(多线程)

    • Boss Group:负责接受客户端的连接请求(处理服务器的绑定和连接请求)。
    • Worker Group:负责处理已经被接受的连接(处理IO操作,如读、写、事件处理等)。
    • Boss Group通常配置为1-2个线程,因为它只需要处理连接请求,而Worker Group的线程数可以根据服务器的硬件和负载情况来配置。
  2. 单线程模型

    • 对于简单的应用,可以使用单个EventLoopGroup来处理所有的事件和IO操作。
    • 这种模型适用于轻量级的应用或开发测试环境。

Netty的线程模型设计使得它可以高效地处理大量并发连接,同时避免了多线程编程中的锁竞争和上下文切换问题。通过将IO操作和事件处理分离到不同的线程,Netty能够提供高性能和高吞吐量的网络通信能力。

在实际应用中,Netty的线程模型可以根据具体的业务需求和系统资源进行调整和优化,以达到最佳的性能表现。

IO模型

Netty的底层IO模型主要基于Java NIO,它采用了异步非阻塞的IO操作,这意味着在Netty中,所有的IO操作都会立即返回,不会阻塞当前线程。这种模型使得Netty能够高效地处理大量并发连接。下面是Netty底层IO模型的一些关键特性:

  1. 异步非阻塞IO:Netty的IO操作是异步的,这意味着当一个IO操作(如读取或写入)被发起时,它不会立即完成,而是返回一个ChannelFuture,这个ChannelFuture可以用来查询操作的结果或在操作完成时执行回调。

  2. 主从Reactor多线程模型:Netty采用了主从Reactor模型,其中主Reactor(BossGroup)负责接受新的连接,从Reactor(WorkerGroup)负责处理已建立连接的IO操作。这种模型可以有效地分散处理网络事件的负载,提高系统的吞吐量。

  3. 零拷贝(Zero-Copy):Netty利用了操作系统的零拷贝机制,减少了数据在用户空间和内核空间之间的拷贝,从而提高了数据传输效率。例如,使用FileChanneltransferTo方法可以实现数据的直接传输,避免了不必要的内存拷贝。

  4. 直接内存访问:Netty使用直接内存(Direct Buffers)进行Socket的读写操作,这样可以减少内存拷贝操作,提高性能。直接内存是在JVM堆外分配的,不会受到JVM垃圾回收的影响,从而减少了延迟和提高吞吐量。

  5. 内存池设计:Netty内部实现了一个复杂的内存池管理系统,用于高效地分配和释放内存。这个系统可以减少内存分配和回收的开销,提高性能。

  6. 串行化处理读写:Netty的事件处理是串行化的,这意味着在同一个线程内,所有的事件都是顺序处理的,避免了多线程竞争和同步锁的问题,从而提高了性能。

  7. 高性能序列化协议:Netty支持多种高性能的序列化协议,如Protobuf,这些协议可以进一步提高数据传输的效率。

  8. IO多路复用:Netty使用Selector来监听多个Channel的IO事件,这样单个线程可以管理多个网络连接,这是NIO的核心特性之一。

这些特性共同构成了Netty的高性能IO模型,使其成为开发高性能网络应用程序的首选框架之一。

代码示例

当然,下面是一个使用Netty开发服务端和客户端的简单示例,以及相应的代码说明。

服务端代码示例

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;public class EchoServer {private int port;public EchoServer(int port) {this.port = port;}public void start() throws Exception {// 创建两个EventLoopGroup,一个用于处理服务器的绑定和接收连接请求,另一个用于处理已经接受的连接EventLoopGroup bossGroup = new NioEventLoopGroup();EventLoopGroup workerGroup = new NioEventLoopGroup();try {// 创建ServerBootstrap实例,它是Netty服务器端启动的核心ServerBootstrap b = new ServerBootstrap();b.group(bossGroup, workerGroup).channel(NioServerSocketChannel.class) // 指定使用NIO传输Channel.childHandler(new ChannelInitializer<SocketChannel>() { // 指定Channel的处理器@Overrideprotected void initChannel(SocketChannel ch) {ch.pipeline().addLast(new EchoServerHandler());}});// 绑定端口并启动服务器ChannelFuture f = b.bind(port).sync();// 等待服务器socket关闭f.channel().closeFuture().sync();} finally {// 优雅关闭EventLoopGroup,释放资源bossGroup.shutdownGracefully();workerGroup.shutdownGracefully();}}private static class EchoServerHandler extends ChannelInboundHandlerAdapter {@Overridepublic void channelRead(ChannelHandlerContext ctx, Object msg) {// 当读取到数据时,打印并回显数据System.out.println("Server received: " + msg);ctx.write(msg);}@Overridepublic void channelReadComplete(ChannelHandlerContext ctx) {// 读取完成后刷新缓冲区,发送回显的数据ctx.flush();}@Overridepublic void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {// 出现异常时,打印异常信息并关闭连接cause.printStackTrace();ctx.close();}}public static void main(String[] args) throws Exception {int port = 8080;new EchoServer(port).start();}
}

客户端代码示例

import io.netty.bootstrap.Bootstrap;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;public class EchoClient {private final String host;private final int port;public EchoClient(String host, int port) {this.host = host;this.port = port;}public void start() throws Exception {EventLoopGroup workerGroup = new NioEventLoopGroup();try {Bootstrap b = new Bootstrap(); // 创建Bootstrap实例,它是Netty客户端启动的核心b.group(workerGroup).channel(NioSocketChannel.class) // 指定使用NIO传输Channel.handler(new ChannelInitializer<SocketChannel>() { // 指定Channel的处理器@Overrideprotected void initChannel(SocketChannel ch) {ch.pipeline().addLast(new EchoClientHandler());}});// 连接到服务器ChannelFuture f = b.connect(host, port).sync();// 等待连接完成f.channel().closeFuture().sync();} finally {// 优雅关闭EventLoopGroup,释放资源workerGroup.shutdownGracefully();}}private static class EchoClientHandler extends ChannelInboundHandlerAdapter {@Overridepublic void channelActive(ChannelHandlerContext ctx) {// 当连接激活时,发送消息ctx.writeAndFlush("Hello from client!");}@Overridepublic void channelRead(ChannelHandlerContext ctx, Object msg) {// 当读取到数据时,打印数据System.out.println("Client received: " + msg);}@Overridepublic void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {// 出现异常时,打印异常信息并关闭连接cause.printStackTrace();ctx.close();}}public static void main(String[] args) throws Exception {new EchoClient("localhost", 8080).start();}
}

代码说明:

  1. EchoServerEchoClient 类分别定义了服务端和客户端的启动逻辑。

  2. EventLoopGroup 是Netty中的一个核心组件,用于处理事件。bossGroup 负责接收连接,workerGroup 负责处理已经建立的连接。

  3. ServerBootstrap(服务端)和 Bootstrap(客户端)是Netty中用于设置和启动网络操作的辅助启动类。

  4. ChannelInitializer 是一个特殊的处理器,用于向Channel的pipeline中添加处理器。

  5. EchoServerHandlerEchoClientHandler 是自定义的处理器,它们继承自 ChannelInboundHandlerAdapter。这些处理器负责处理入站数据和出站数据。

  6. channelRead 方法在处理器中被调用,当有数据可读时触发。

  7. channelReadComplete 方法在读取数据完成后被调用,通常用于刷新发送缓冲区。

  8. exceptionCaught 方法在发生异常时被调用,用于异常处理和资源清理。

  9. channelActive 方法在客户端连接建立后被调用,此时可以开始发送数据。

  10. channelWriteAndFlush 方法用于发送数据,并刷新发送缓冲区。

要运行这些示例,你需要将Netty库添加到项目的依赖中。这些示例展示了Netty的基本使用,包括服务端和客户端的启动、消息的发送和接收,以及异常处理。

自定义协议实现

在Netty中实现自定义协议通常需要定义协议格式、编码器(Encoder)、解码器(Decoder)和业务处理器(Handler)。下面是一个简单的自定义协议的实现示例,包括代码说明。

自定义协议格式

假设我们定义一个简单的协议,包含一个消息长度(2字节)、消息类型(1字节)和消息内容(变长)。

public class MyMessage {private int length;private byte type;private byte[] content;// 构造函数、getter和setter方法省略
}

自定义编码器(Encoder)

编码器负责将高级数据结构或对象转换成字节序列,以便可以在网络上传输。

import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.MessageToByteEncoder;public class MyMessageEncoder extends MessageToByteEncoder<MyMessage> {@Overrideprotected void encode(ChannelHandlerContext ctx, MyMessage msg, ByteBuf out) {// 编码消息长度(2字节)out.writeShort(msg.getLength());// 编码消息类型(1字节)out.writeByte(msg.getType());// 编码消息内容(变长)byte[] content = msg.getContent();if (content != null) {out.writeBytes(content);}}
}

自定义解码器(Decoder)

解码器负责将接收到的字节序列解码成可识别的消息对象。

import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.ByteToMessageDecoder;import java.util.List;public class MyMessageDecoder extends ByteToMessageDecoder {@Overrideprotected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {// 确保有足够的可读字节if (in.readableBytes() < 3) {return;}// 标记读取位置in.markReaderIndex();// 读取消息长度(2字节)int length = in.readShort();// 检查是否有足够的数据if (in.readableBytes() < length) {// 如果没有足够的数据,重置读取位置并返回in.resetReaderIndex();return;}// 读取消息类型(1字节)byte type = in.readByte();// 读取消息内容(变长)byte[] content = new byte[length - 2]; // 减去长度和类型字段占用的字节in.readBytes(content);// 创建消息对象MyMessage msg = new MyMessage();msg.setLength(length);msg.setType(type);msg.setContent(content);// 添加到输出列表out.add(msg);}
}

业务处理器(Handler)

业务处理器负责处理解码后的消息,并执行相应的业务逻辑。

import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;public class MyBusinessHandler extends ChannelInboundHandlerAdapter {@Overridepublic void channelRead(ChannelHandlerContext ctx, Object msg) {// 处理消息MyMessage message = (MyMessage) msg;System.out.println("Received message: " + new String(message.getContent()));}@Overridepublic void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {cause.printStackTrace();ctx.close();}
}

在Netty服务器管道中使用自定义编码器和解码器

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;public class MyServer {public void start(int port) throws Exception {EventLoopGroup bossGroup = new NioEventLoopGroup();EventLoopGroup workerGroup = new NioEventLoopGroup();try {ServerBootstrap b = new ServerBootstrap();b.group(bossGroup, workerGroup).channel(NioServerSocketChannel.class).childHandler(new ChannelInitializer<SocketChannel>() {@Overrideprotected void initChannel(SocketChannel ch) {ChannelPipeline p = ch.pipeline();// 添加自定义解码器和编码器p.addLast(new MyMessageDecoder());p.addLast(new MyMessageEncoder());// 添加业务处理器p.addLast(new MyBusinessHandler());}});ChannelFuture f = b.bind(port).sync();f.channel().closeFuture().sync();} finally {workerGroup.shutdownGracefully();bossGroup.shutdownGracefully();}}public static void main(String[] args) throws Exception {int port = 8080;new MyServer().start(port);}
}

代码说明:

  1. MyMessage:定义了自定义协议的消息格式,包括长度、类型和内容。

  2. MyMessageEncoder:实现了MessageToByteEncoder,负责将MyMessage对象编码成字节序列。

  3. MyMessageDecoder:实现了ByteToMessageDecoder,负责将字节序列解码成MyMessage对象。

  4. MyBusinessHandler:实现了ChannelInboundHandlerAdapter,负责处理解码后的消息,并执行业务逻辑。

  5. MyServer:定义了Netty服务器的启动逻辑,包括创建ServerBootstrap实例、配置管道和启动服务器。

通过这个示例,你可以看到如何在Netty中实现自定义协议,并在服务器端处理这些协议消息。这个示例涵盖了编码、解码和业务处理的整个过程。

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

如若内容造成侵权/违法违规/事实不符,请联系一条长河网进行投诉反馈,一经查实,立即删除!

相关文章

AUTOSAR 规范中的设计模式:传感器执行器模式

在 AUTOSAR Adaptive Platform (AP) 规范中,传感器执行器模式是一种典型的设计模式,主要用于实时控制系统中,用来实现传感器数据的获取和执行器指令的发送。该模式通过分离传感器和执行器的实现,使其独立运行并且能够通过某种通信机制进行数据交换,以确保数据的实时性和系…

Linux:编辑器Vim和Makefile

✨✨所属专栏&#xff1a;Linux✨✨ ✨✨作者主页&#xff1a;嶔某✨✨ vim的三种常用模式 分别是命令模式&#xff08;command mode&#xff09;、插入模式&#xff08;Insert mode&#xff09;和底行模式&#xff08;last line mode&#xff09; 各模式的功能区分如下&…

【Linux】【进程控制】API汇总整理

进程控制是操作系统中一个非常重要的概念&#xff0c;它涉及到创建、管理和终止进程的能力。进程控制包括一系列操作&#xff0c;如创建新进程、等待进程结束、发送信号给进程等。下面是进程控制中一些常见的操作及其相关API&#xff1a; 进程控制概述 进程控制是指操作系统提…

HTML练习题:彼岸的花(web)

展示效果: 代码: <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title>彼岸の花</title><style…

【安全性分析】正式安全分析与非正式安全分析

安全性分析-系列文章目录 第一章 【安全性分析】正式安全分析与非正式安全分析 第二章 【安全性分析】BAN逻辑 (BAN Logic) 文章目录 安全性分析-系列文章目录前言一、正式安全分析1. 理想化模型(如随机预言机模型)2. 标准模型(Standard Model)3. 形式化验证4. 数学证明二…

HTML小阶段二维表和思维导图

下面是对标签、元素、属性的对比二维表&#xff0c;通过对比3w1h&#xff08;what是什么、where用在哪、why为什么要用、how如何用&#xff09;来学习区分学习标签、元素、属性 标签 元素 属性 what &#xff08;Tags&#xff09;标签是用来标记内容块或标明元素内容意义 …

NIO 核心知识总结

在传统的 Java I/O 模型&#xff08;BIO&#xff09;中&#xff0c;I/O 操作是以阻塞的方式进行的。也就是说&#xff0c;当一个线程执行一个 I/O 操作时&#xff0c;它会被阻塞直到操作完成。这种阻塞模型在处理多个并发连接时可能会导致性能瓶颈&#xff0c;因为需要为每个连…

助力风力发电风机设备智能化巡检,基于YOLOv7全系列【tiny/l/x】参数模型开发构建无人机巡检场景下风机叶片缺陷问题智能化检测预警模型

在全球能源转型的大潮中&#xff0c;清洁环境能源的发展已成为各国关注的焦点。风力发电作为其中的佼佼者&#xff0c;以其可再生、无污染的特点&#xff0c;受到了广泛的青睐。然而&#xff0c;风力发电设施大多建于人迹罕至的地区&#xff0c;设备庞大且复杂&#xff0c;其维…

Apache POI(java操作Miscrosoft Office)

Apache POI 1.1 介绍 Apache POI 是一个处理Miscrosoft Office各种文件格式的开源项目。简单来说就是&#xff0c;我们可以使用 POI 在 Java 程序中对Miscrosoft Office各种文件进行读写操作。 一般情况下&#xff0c;POI 都是用于操作 Excel 文件。 Apache POI 的应用场景&a…

C++ | Leetcode C++题解之第537题复数乘法

题目&#xff1a; 题解&#xff1a; class Solution { public:string complexNumberMultiply(string num1, string num2) {regex re("\\|i"); vector<string> complex1(sregex_token_iterator(num1.begin(), num1.end(), re, -1), std::sregex_token_iterator…

Java ==> String类(字符串)

文章目录 一、认识String类1、创建String对象2、不可变的String3、字符串常量池 二、字符串常用操作1、字符串比较1.1 用“”比较1.2 用equals()方法比较1.3用compareTo()方法进行比较 2、获取字符串长度3、字符串查找4、字符串转换4.1valueOf()数值转换为字符串4.2字母大小写转…

Qt中的Model与View 4:QStandardItemModel与QTableView

目录 QStandardItemModel API QTableView 导航 视觉外观 坐标系统 API 样例&#xff1a;解析一个表格txt文件 QStandardItemModel QStandardItemModel 可用作标准 Qt 数据类型的存储库。它是模型/视图类之一&#xff0c;是 Qt 模型/视图框架的一部分。它提供了一种基于…

【SpringMVC】传递json,获取url参数,上传文件

【传递json数据】 【json概念】 一种轻量级数据交互格式&#xff0c;有自己的格式和语法&#xff0c;使用文本表示一个对象或数组的信息&#xff0c;其本质上是字符串&#xff0c;负责在不同的语言中数据传递与交换 json数据以字符串的形式体现 【json字符串与Java对象互转…

Java JUC(四) 自定义线程池实现与原理分析

目录 一. 阻塞队列 BlockingQue 二. 拒绝策略 RejectPolicy 三. 线程池 ThreadPool 四. 模拟运行 在 Java基础&#xff08;二&#xff09; 多线程编程 中&#xff0c;我们简单介绍了线程池 ThreadPoolExecutor 的核心概念与基本使用。在本文中&#xff0c;我们将基于前面学…

go-logger v0.27.0 - 并发性能为官方库 10 倍

go-logger是一个高性能的 golang 日志库&#xff0c;旨在提供快速、轻量级的日志记录功能 Github 使用文档 v0.27.0 更新内容 优化内存分配优化写数据性能增加日志属性自定义函数增加各个日志级别格式化打印函数 说明 性能优化是该版本最重要的更新内容。性能优化的结果&…

【华为HCIP实战课程31(完整版)】中间到中间系统协议IS-IS路由汇总详解,网络工程师

一、IS-IS的汇总 1、可以有效减少在LSP中发布的路由条目,减小对系统资源的占用。 2、会减少LSP报文的扩散,接收到该LSP报文的其他设备路由表中只会出现一条聚合路由。 3、可以避免网络中的路由震荡,提高了网络的稳定性。 4、被聚合的路由可以是IS-IS路由,也可以是被引入…

邮件发送excel带预览excel功能

excel 打开后的内容: 思路&#xff1a; 1、邮件发送excel 是作为附件发送出去的&#xff1b; 2、excel 预览是&#xff0c;必须另外点击预览按钮&#xff0c;并不能直接预览邮件内容然后在邮件主体内容展示出来 根据以上两点基本没法实现 邮件发送后邮件自带 预览功能。 伪方法…

HCIA(DHCP服务)

第三节 开启DHCP服务 创建地址池 调用全局服务 [R1]dhcp enable 开启DHCP服务 [R1]ip pool AA 创建地址池 [R1-ip-pool-AA]network 192.168.1.0 mask 24 写入网段 [R1-ip-pool-AA]gateway-list 192.168.1.1 写入网关 [R1-ip-pool-AA]dns-list 8.8.8.8 114.1…

java项目之文理医院预约挂号系统源码(springboot)

风定落花生&#xff0c;歌声逐流水&#xff0c;大家好我是风歌&#xff0c;混迹在java圈的辛苦码农。今天要和大家聊的是一款基于springboot的文理医院预约挂号系统。项目源码以及部署相关请联系风歌&#xff0c;文末附上联系信息。 项目简介&#xff1a; 本系统的使用角色可…

A4-C四驱高防变电站巡检机器人

在电力行业数字化、智能化转型进程中&#xff0c;搭载多模态成像传感器的变电站巡检机器人、视频监控设备逐渐取代传统人工&#xff0c;成为变电设备状态监测的主要工具。变电站巡检机器人具有全天候、非接触式、多参量测量等特点&#xff0c;结合内置人工智能算法完成仪表识别…