Netty的源码分析和业务场景

Netty 是一个高性能、异步事件驱动的网络应用框架,它基于 Java NIO 构建,广泛应用于互联网、大数据、游戏开发、通信行业等多个领域。以下是对 Netty 的源码分析、业务场景的详细介绍:

源码概述

  1. Netty 的核心组件:Netty 的架构设计围绕着事件驱动的核心思想,主要包括 Channel、EventLoopGroup、ChannelHandlerContext 和 ChannelPipeline 等关键概念。
  2. Channel:是网络连接的抽象表示,每个 Channel 都有一个或多个 ChannelHandler 来处理网络事件,如连接建立、数据接收等。
  3. EventLoopGroup:是一组 EventLoop 的集合,每个 EventLoop 负责处理一组 Channel 的 I/O 事件。当 Channel 的事件触发时,相应的 EventLoop 会调用 ChannelHandler 中的方法进行处理。
  4. ChannelPipeline:是 ChannelHandler 的有序集合,用于处理进来的和出站的数据。通过在 Pipeline 中添加不同的 Handler,可以实现复杂的业务逻辑。
  5. 源码中的关键流程:Netty 的源码分析需要关注的关键流程包括初始化、Channel 的注册、EventLoop 的工作流程、以及连接的建立和绑定过程。

Netty 提供了一个 Echo 示例,用于演示客户端和服务器端的基本通信流程。在这个示例中,客户端发送的消息被服务器端接收并原样返回,展示了 Netty 处理网络通信的基本方法。

下面 V 哥来详细介绍一下这几外关键核心组件。

1. Channel组件

Netty 的 Channel 组件是整个框架的核心之一,它代表了网络中的一个连接,可以是客户端的也可以是服务器端的。Channel 是一个低级别的接口,用于执行网络 I/O 操作。以下是对 Channel 组件的源码分析和解释:

Channel 接口定义

Channel 接口定义了一组操作网络连接的方法,例如绑定、连接、读取、写入和关闭。

public interface Channel extends AttributeMap {/*** Returns the {@link ChannelId} of this {@link Channel}.*/ChannelId id();/*** Returns the parent {@link Channel} of this channel. {@code null} if this is the top-level channel.*/Channel parent();/*** Returns the {@link ChannelConfig} of this channel.*/ChannelConfig config();/*** Returns the local address of this channel.*/SocketAddress localAddress();/*** Returns the remote address of this channel. {@code null} if the channel is not connected.*/SocketAddress remoteAddress();/*** Returns {@code true} if this channel is open and may be used.*/boolean isOpen();/*** Returns {@code true} if this channel is active and may be used for IO.*/boolean isActive();/*** Returns the {@link ChannelPipeline}.*/ChannelPipeline pipeline();/*** Returns the {@link ChannelFuture} which is fired once the channel is registered with its {@link EventLoop}.*/ChannelFuture whenRegistered();/*** Returns the {@link ChannelFuture} which is fired once the channel is deregistered from its {@link EventLoop}.*/ChannelFuture whenDeregistered();/*** Returns the {@link ChannelFuture} which is fired once the channel is closed.*/ChannelFuture whenClosed();/*** Register this channel to the given {@link EventLoop}.*/ChannelFuture register(EventLoop loop);/*** Bind and listen for incoming connections.*/ChannelFuture bind(SocketAddress localAddress);/*** Connect to the given remote address.*/ChannelFuture connect(SocketAddress remoteAddress, SocketAddress localAddress);/*** Disconnect if connected.*/ChannelFuture disconnect();/*** Close this channel.*/ChannelFuture close();/*** Deregister this channel from its {@link EventLoop}.*/ChannelFuture deregister();/*** Write the specified message to this channel.*/ChannelFuture write(Object msg);/*** Write the specified message to this channel and generate a {@link ChannelFuture} which is done* when the message is written.*/ChannelFuture writeAndFlush(Object msg);/*** Flushes all pending messages.*/ChannelFuture flush();// ... 更多方法定义
}

Channel 的关键方法

  • id(): 返回 Channel 的唯一标识符。
  • parent(): 返回父 Channel,如果是顶级 Channel,则返回 null
  • config(): 获取 Channel 的配置信息。
  • localAddress()remoteAddress(): 分别返回本地和远程地址。
  • isOpen()isActive(): 分别检查 Channel 是否打开和激活。
  • pipeline(): 返回与 Channel 关联的 ChannelPipeline,它是处理网络事件的处理器链。
  • register(), bind(), connect(), disconnect(), close(), deregister(): 这些方法用于执行网络 I/O 操作。

Channel 的实现类

Netty 为不同类型的网络通信协议提供了多种 Channel 的实现,例如:

  • NioSocketChannel:用于 NIO 传输的 TCP 协议的 Channel 实现。
  • NioServerSocketChannel:用于 NIO 传输的 TCP 服务器端 Channel 实现。
  • OioSocketChannelOioServerSocketChannel:类似 NIO,但是用于阻塞 I/O。

Channel 的生命周期

  1. 创建Channel 通过其工厂方法创建,通常与特定的 EventLoop 关联。
  2. 注册Channel 必须注册到 EventLoop 上,以便可以处理 I/O 事件。
  3. 绑定/连接:服务器端 Channel 绑定到特定地址并开始监听;客户端 Channel 连接到远程地址。
  4. 读取和写入:通过 Channel 读取和写入数据。
  5. 关闭:关闭 Channel,释放相关资源。

Channel 的事件处理

Channel 的事件处理是通过 ChannelPipelineChannelHandler 完成的。ChannelPipeline 是一个处理器链,负责处理所有的 I/O 事件和 I/O 操作。每个 Channel 都有一个与之关联的 ChannelPipeline,可以通过 Channelpipeline() 方法访问。

异步处理

Channel 的操作(如绑定、连接、写入、关闭)都是异步的,返回一个 ChannelFuture 对象,允许开发者设置回调,当操作完成或失败时执行。

内存管理

Netty 的 Channel 实现还涉及内存管理,使用 ByteBuf 作为数据容器,它是一个可变的字节容器,提供了一系列的操作方法来读写网络数据。

小结

Channel 是 Netty 中的一个核心接口,它定义了网络通信的基本操作。Netty 提供了多种 Channel 的实现,以支持不同的 I/O 模型和协议。通过 Channel,Netty 实现了高性能、异步和事件驱动的网络通信。

2. EventLoopGroup组件

EventLoopGroup 是 Netty 中一个非常重要的组件,它负责管理一组 EventLoop,每个 EventLoop 可以处理多个 Channel 的 I/O 事件。以下是对 EventLoopGroup 组件的详细分析和解释:

EventLoopGroup 接口定义

EventLoopGroup 接口定义了一组管理 EventLoop 的方法,以下是一些关键方法:

public interface EventLoopGroup extends ExecutorService {/*** Returns the next {@link EventLoop} this group will use to handle an event.* This will either return an existing or a new instance depending on the implementation.*/EventLoop next();/*** Shuts down all {@link EventLoop}s and releases all resources.*/ChannelFuture shutdownGracefully();/*** Shuts down all {@link EventLoop}s and releases all resources.*/ChannelFuture shutdownGracefully(long quietPeriod, long timeout, TimeUnit unit);/*** Returns a copy of the list of all {@link EventLoop}s that are part of this group.*/List<EventLoop> eventLoops();
}

EventLoopGroup 的关键方法

  • next(): 返回下一个 EventLoop,用于处理事件。这可以是现有的 EventLoop 或者新创建的实例,具体取决于实现。
  • shutdownGracefully(): 优雅地关闭所有 EventLoop 并释放所有资源。这个方法允许指定一个静默期和一个超时时间,以便在关闭之前等待所有任务完成。
  • eventLoops(): 返回当前 EventLoopGroup 中所有 EventLoop 的列表。

EventLoopGroup 的实现类

Netty 提供了几种 EventLoopGroup 的实现,主要包括:

  • DefaultEventLoopGroup: 默认的 EventLoopGroup 实现,使用 NioEventLoop 作为其 EventLoop 实现。
  • EpollEventLoopGroup: 特定于 Linux 的 EventLoopGroup 实现,使用 EpollEventLoop 作为其 EventLoop 实现,利用 Linux 的 epoll 机制提高性能。
  • OioEventLoopGroup: 阻塞 I/O 模式下的 EventLoopGroup 实现,使用 OioEventLoop 作为其 EventLoop 实现。

EventLoopGroup 的工作原理

  1. 创建: EventLoopGroup 通过其构造函数创建,可以指定线程数。
  2. 注册: Channel 需要注册到 EventLoop 上,以便 EventLoop 可以处理其 I/O 事件。
  3. 事件循环: 每个 EventLoop 在其线程中运行一个事件循环,处理注册到它的 Channel 的 I/O 事件。
  4. 关闭: EventLoopGroup 可以被关闭,释放所有资源。

EventLoopGroup 的线程模型

  • 单线程模型: 一个 EventLoopGroup 只包含一个 EventLoop,适用于小容量应用。
  • 多线程模型: 一个 EventLoopGroup 包含多个 EventLoop,每个 EventLoop 在单独的线程中运行,适用于高并发应用。

EventLoopGroup 的使用场景

  • 服务器端: 在服务器端,通常使用两个 EventLoopGroup。一个用于接受连接(bossGroup),一个用于处理连接(workerGroup)。bossGroup 通常使用较少的线程,而 workerGroup 可以根据需要处理更多的并发连接。
  • 客户端端: 在客户端,通常只需要一个 EventLoopGroup,用于处理所有的连接。

示例代码

以下是如何在 Netty 中使用 EventLoopGroup 的示例代码:

public class NettyServer {public static void main(String[] args) {EventLoopGroup bossGroup = new NioEventLoopGroup(1); // 用于接受连接EventLoopGroup workerGroup = new NioEventLoopGroup(); // 用于处理连接try {ServerBootstrap b = new ServerBootstrap();b.group(bossGroup, workerGroup).channel(NioServerSocketChannel.class).childHandler(new ChannelInitializer<SocketChannel>() {@Overridepublic void initChannel(SocketChannel ch) throws Exception {ChannelPipeline p = ch.pipeline();p.addLast(new LoggingHandler());p.addLast(new MyServerHandler());}});ChannelFuture f = b.bind(8080).sync(); // 绑定端口并启动服务器System.out.println("Server started on port 8080");f.channel().closeFuture().sync();} catch (InterruptedException e) {e.printStackTrace();} finally {bossGroup.shutdownGracefully();workerGroup.shutdownGracefully();}}
}

在这个示例中,bossGroup 用于接受连接,workerGroup 用于处理连接。通过 ServerBootstrap 类配置服务器,并使用 ChannelInitializer 来设置 Channel 的处理器链。

总结

EventLoopGroup 是 Netty 中管理事件循环的核心组件,它通过 EventLoop 处理 I/O 事件,支持高并发和异步操作。通过合理配置 EventLoopGroup,可以显著提高网络应用的性能和可扩展性。

3. ChannelPipeline组件

ChannelPipeline 是 Netty 中的一个核心组件,它负责管理一组 ChannelHandler,并且定义了 I/O 事件和操作如何在这些处理器之间流动。以下是对 ChannelPipeline 组件的详细分析和解释:

ChannelPipeline 接口定义

ChannelPipeline 是一个接口,定义了操作 ChannelHandler 的方法:

public interface ChannelPipeline extends Iterable<ChannelHandler> {/*** Add the specified handler to the context of the current channel.*/void addLast(EventExecutorGroup executor, String name, ChannelHandler handler);/*** Add the specified handlers to the context of the current channel.*/void addLast(EventExecutorGroup executor, ChannelHandler... handlers);// ... 省略其他 addFirst, addBefore, addAfter, remove, replace 方法/*** Get the {@link ChannelHandler} by its name.*/ChannelHandler get(String name);/*** Find the first {@link ChannelHandler} in the {@link ChannelPipeline} that matches the specified class.*/ChannelHandler first();/*** Find the last {@link ChannelHandler} in the {@link ChannelPipeline} that matches the specified class.*/ChannelHandler last();/*** Returns the context object of the specified handler.*/ChannelHandlerContext context(ChannelHandler handler);// ... 省略 contextFor, remove, replace, fireChannelRegistered, fireChannelUnregistered 等方法
}

ChannelPipeline 的关键方法

  • addLast(String name, ChannelHandler handler): 在管道的末尾添加一个新的处理器,并为其指定一个名称。
  • addFirst(String name, ChannelHandler handler): 在管道的开头添加一个新的处理器。
  • addBefore(String baseName, String name, ChannelHandler handler): 在指定处理器前添加一个新的处理器。
  • addAfter(String baseName, String name, ChannelHandler handler): 在指定处理器后添加一个新的处理器。
  • get(String name): 根据名称获取 ChannelHandler
  • first()last(): 分别获取管道中的第一个和最后一个处理器。
  • context(ChannelHandler handler): 获取指定处理器的上下文。

ChannelHandlerContext

ChannelHandlerContextChannelHandlerChannelPipeline 之间的桥梁,提供了访问和管理 ChannelChannelPipelineChannelFuture 的能力:

public interface ChannelHandlerContext extends AttributeMap, ResourceLeakHint {/*** Return the current channel to which this context is bound.*/Channel channel();/*** Return the current pipeline to which this context is bound.*/ChannelPipeline pipeline();/*** Return the name of the {@link ChannelHandler} which is represented by this context.*/String name();/*** Return the {@link ChannelHandler} which is represented by this context.*/ChannelHandler handler();// ... 省略其他方法
}

ChannelPipeline 的工作原理

ChannelPipeline 维护了一个双向链表的 ChannelHandler 集合。每个 Channel 实例都有一个与之关联的 ChannelPipeline。当 I/O 事件发生时,如数据被读取到 Channel,该事件会被传递到 ChannelPipeline,然后按照 ChannelHandler 在管道中的顺序进行处理。

处理器的执行顺序

  • 入站事件:当数据被读取到 Channel 时,事件会从管道的尾部向头部传递,直到某个 ChannelHandler 处理该事件。
  • 出站事件:当需要发送数据时,事件会从管道的头部向尾部传递,直到数据被写出。

源码分析

ChannelPipeline 的实现类 DefaultChannelPipeline 内部使用了一个 ChannelHandler 的双向链表来维护处理器的顺序:

private final AbstractChannelHandlerContext head;
private final AbstractChannelHandlerContext tail;
private final List<ChannelHandler> handlers = new ArrayList<ChannelHandler>();
  • headtail 是链表的头尾节点。
  • handlers 是存储所有处理器的列表。

添加处理器时,DefaultChannelPipeline 会更新链表和列表:

public void addLast(EventExecutorGroup executor, String name, ChannelHandler handler) {if (handler == null) {throw new NullPointerException("handler");}if (name == null) {throw new NullPointerException("name");}AbstractChannelHandlerContext newCtx = new TailContext(this, executor, name, handler);synchronized (this) {if (tail == null) {head = tail = newCtx;} else {tail.next = newCtx;newCtx.prev = tail;tail = newCtx;}handlers.add(newCtx);}
}

小结

ChannelPipeline 是 Netty 中处理网络事件和请求的管道,它通过维护一个 ChannelHandler 的链表来管理事件的流动。通过 ChannelHandlerContextChannelHandler 能够访问和修改 ChannelChannelPipeline 的状态。这种设计使得事件处理流程高度可定制和灵活,是 Netty 高性能和易于使用的关键因素之一。

4. 源码中的关键流程

在 Netty 的 ChannelPipeline 的源码中,关键流程涉及处理器的添加、事件的触发、以及事件在处理器之间的流动。以下是一些关键流程的分析:

1. 处理器的添加

当创建 ChannelPipeline 并准备添加 ChannelHandler 时,需要确定处理器的顺序和位置。Netty 允许开发者在管道的开始、结束或指定位置插入处理器。

ChannelPipeline pipeline = channel.pipeline();
pipeline.addLast("myHandler", new MyChannelHandler());

DefaultChannelPipeline 类中,处理器被添加到一个双向链表中,每个处理器节点(AbstractChannelHandlerContext)保存了指向前一个和后一个处理器的引用。

2. 事件循环和触发

每个 Channel 都与一个 EventLoop 关联,EventLoop 负责处理所有注册到它上面的 Channel 的事件。当 EventLoop 运行时,它会不断地循环,等待并处理 I/O 事件。

// EventLoop 的事件循环
public void run() {for (;;) {// ...processSelectedKeys();// ...}
}

3. 事件的捕获和传递

EventLoop 检测到一个 I/O 事件(如数据到达)时,它会触发相应的操作。对于 ChannelPipeline 来说,这意味着需要调用适当的 ChannelHandler 方法。

// 伪代码,展示了事件如何被传递到 ChannelHandler
if (channelRead) {pipeline.fireChannelRead(msg);
}

4. 入站和出站事件的处理

  • 入站事件(如数据被读取)通常从 ChannelPipeline 的尾部开始传递,沿着管道向前,直到某个处理器处理了该事件。
  • 出站事件(如写数据)则从 ChannelPipeline 的头部开始传递,沿着管道向后,直到数据被写出。
// 入站事件处理
public void channelRead(ChannelHandlerContext ctx, Object msg) {// 处理消息或传递给下一个处理器ctx.fireChannelRead(msg);
}// 出站事件处理
public void write(ChannelHandlerContext ctx, Object msg) {// 写消息或传递给下一个处理器ctx.write(msg);
}

5. 处理器链的遍历

ChannelPipeline 需要能够遍历处理器链,以便按顺序触发事件。这通常通过从 ChannelHandlerContext 获取下一个或前一个处理器来实现。

// 伪代码,展示了如何获取下一个处理器并调用它
ChannelHandlerContext nextCtx = ctx.next();
if (nextCtx != null) {nextCtx.invokeChannelRead(msg);
}

6. 动态修改处理器链

在事件处理过程中,可能需要动态地修改处理器链,如添加新的处理器或移除当前处理器。

pipeline.addLast("newHandler", new AnotherChannelHandler());
pipeline.remove(ctx.handler());

7. 资源管理和清理

Channel 关闭时,ChannelPipeline 需要确保所有的 ChannelHandler 都能够执行它们的清理逻辑,释放资源。

public void channelInactive(ChannelHandlerContext ctx) {// 清理逻辑
}

8. 异常处理

在事件处理过程中,如果抛出异常,ChannelPipeline 需要能够捕获并适当地处理这些异常,避免影响整个管道的运行。

try {// 可能抛出异常的操作
} catch (Exception e) {ctx.fireExceptionCaught(e);
}

小结

ChannelPipeline 的源码中包含了多个关键流程,确保了事件能够按顺序在处理器之间传递,同时提供了动态修改处理器链和异常处理的能力。这些流程共同构成了 Netty 中事件驱动的网络编程模型的基础。

业务场景

  1. 微服务架构:Netty 可以作为 RPC 框架的基础,实现服务间的高效通信。
  2. 游戏服务器:由于游戏行业对延迟和并发要求极高,Netty 的异步非阻塞特性非常适合构建高并发的游戏服务器。
  3. 实时通信系统:Netty 可用于构建如即时消息、视频会议等需要低延迟数据传输的实时通信系统。
  4. 物联网平台:Netty 可以作为设备与云平台之间的通信桥梁,处理大规模的设备连接和数据流。
  5. 互联网行业:在分布式系统中,Netty 常作为基础通信组件被 RPC 框架使用,例如阿里的分布式服务框架 Dubbo 使用 Netty 作为其通信组件。
  6. 大数据领域:Netty 也被用于大数据技术的网络通信部分,例如 Hadoop 的高性能通信组件 Avro 的 RPC 框架就采用了 Netty。

最后

通过深入分析 Netty 的源码和理解其在不同业务场景下的应用,开发者可以更好地利用这一强大的网络编程框架,构建高效、稳定且可扩展的网络应用。

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

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

相关文章

Linux 安装mysql-client-core-8.0

在Linux上安装mysql-client-core-8.0 安装流程 下面是安装mysql-client-core-8.0的步骤和相应的命令&#xff1a; 步骤1&#xff1a;更新系统软件源 我们首先需要更新系统的软件源&#xff0c;以确保我们能够获取到最新的软件包列表。使用以下命令更新软件源&#xff1a; …

Ansible——inventory 主机清单

1、inventory 含义 Inventory支持对主机进行分组&#xff0c;每个组内可以定义多个主机&#xff0c;每个主机都可以定义在任何一个或多个主机组内。 如果是名称类似的主机&#xff0c;可以使用列表的方式标识各个主机。 vim /etc/ansible/hosts [webservers] 192.168.20.11:222…

平凯星辰亮相 2024 中国国际金融展,发布银行交易明细查询白皮书

7 月 19 日至 21 日&#xff0c;备受瞩目的 2024 中国国际金融展&#xff08;以下简称金融展&#xff09;在北京国家会议中心隆重举办。作为中国乃至亚洲最大的金融科技展会&#xff0c;本届金融展以“数字金融引领未来&#xff0c;守正创新共筑金融新生态”为主题&#xff0c;…

vue单页面 全屏 使用 screenfull 实现全屏(左侧菜单栏隐藏)

vue单页面 全屏 使用 screenfull 实现全屏&#xff08;左侧菜单栏隐藏&#xff09;_vue在打开一个界面的时候隐藏侧边栏-CSDN博客

C++_单例模式

目录 1、饿汉方式实现单例 2、懒汉方式实现单例 3、单例模式的总结 结语 前言&#xff1a; 在C中有许多设计模式&#xff0c;单例模式就是其中的一种&#xff0c;该模式主要针对类而设计&#xff0c;确保在一个进程下该类只能实例化出一个对象&#xff0c;因此名为单例。而…

temu数据分析怎么做,temu选品数据分析怎么做

在竞争激烈的电商市场中&#xff0c;数据分析已成为商家提升销售业绩、优化经营策略的重要工具。特别是对于Temu平台的卖家而言&#xff0c;掌握数据分析技巧&#xff0c;特别是选品数据分析&#xff0c;更是至关重要。本文将为您详细解析如何在Temu平台上进行数据分析&#xf…

Docker 安全及日志管理(包含SSL证书)

目录 一、Docker 存在的安全问题 二、Docker 架构缺陷与安全机制 三、Docker 安全基线标准 四、容器相关的常用安全配置方法 五、限制流量流向 六、镜像安全 七、DockerClient 端与 DockerDaemon 的通信安全 https的单向认证流程 https的双向认证流程 八、DockerClie…

子组件不能直接改变父组件传递的“道具”值

引言&#xff1a; 不少人在刚开始使用vue时都会遇到一个报错——Avoid mutating a prop directly since the value will be overwritten whenever the parent component re-renders. Instead, use a data or computed property based on the props value.&#xff08;避免直接改…

Ubuntu 22.04.4 LTS (linux) GoAccess 分析 Nginx 日志

1 安装goaccess sudo apt-get update sudo apt-get install goaccess 2 控制台运行 goaccess -a -d -f /usr/local/openresty/nginx/logs/access.log -p /etc/goaccess/goaccess.conf #sudo vim /etc/goaccess/goaccess.conf time-format %H:%M:%S date-format %d/%b…

iOS ------ Block的相关问题

Block的定义 Block可以截获局部变量的匿名函数&#xff0c; 是将函数及其执行上下文封装起来的对象。 Block的实现 通过Clang将以下的OC代码转化为C代码 // Clang xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m//main.m #import <Foundation/Foundation.…

3.2、数据结构-数组、矩阵和广义表

数组结构 数组是定长线性表在维度上的扩展,即线性表中的元素又是一个线性表。N维数组是一种“同构”的数据结构,其每个数据元素类型相同、结构一致。 一个m行n列的数组表示如下: 其可以表示为行向量形式&#xff08;一行一行的数据&#xff09;或者列向量形式&#xff08;一…

前端开发使用Big.js精算避免误差

1、下载 npm install big.js 全局引入还是局部引入可根据项目框架及个人需求 2、静态引入 < script src https://unpkg.com/big.js6.0.0/big.mjs > </ script > 或者 import Big from https://raw.githubusercontent.com/mikemcl/big.js/v6.0.0/big.mjs; i…

Nginx 怎样处理请求的故障转移?

&#x1f345;关注博主&#x1f397;️ 带你畅游技术世界&#xff0c;不错过每一次成长机会&#xff01; 文章目录 Nginx 怎样处理请求的故障转移&#xff1f;一、理解故障转移的重要性二、Nginx 中的故障检测机制三、Nginx 中的请求分配策略四、Nginx 中的故障转移实现方式五、…

Shell脚本编程(一)

目录 一、Shell命令行的书写规则 二、编写/修改权限及执行shell程序的步骤 1、编写简单Shell程序 2、建立可执行程序 3、执行Shell程序 4、实例 三、Shell程序中使用参数 1、位置参数 2、内部参数 3、实例一 4、实例二 一、Shell命令行的书写规则 在执行Shell命令时…

搭建本地私有知识问答系统:MaxKB + Ollama + Llama3 (wsl网络代理配置、MaxKB-API访问配置)

目录 搭建本地私有知识问答系统:MaxKB、Ollama 和 Llama3 实现指南引言MaxKB+Ollama+Llama 3 Start buildingMaxKB 简介:1.1、docker部署 MaxKB(方法一)1.1.1、启用wls或是开启Hyper使用 WSL 2 的优势1.1.2、安装docker1.1.3、docker部署 MaxKB (Max Knowledge Base)MaxKB …

便携式气象仪:科技赋能,让气象观测更智能

随着科技的快速发展&#xff0c;越来越多的领域受益于技术的进步。其中&#xff0c;气象观测领域也不例外。传统的气象观测设备虽然精确可靠&#xff0c;但往往体积庞大、携带不便&#xff0c;且需要专业人员进行操作和维护。而便携式气象仪的出现&#xff0c;则打破了这一局限…

在WPF中使用WebView2详解

Microsoft Edge WebView2 Microsoft Edge WebView2 控件允许在本机应用中嵌入 web 技术(HTML、CSS 以及 JavaScript)。 WebView2 控件使用 Microsoft Edge 作为绘制引擎&#xff0c;以在本机应用中显示 web 内容。 使用 WebView2 可以在本机应用的不同部分嵌入 Web 代码&…

Golang | Leetcode Golang题解之第284题窥视迭代器

题目&#xff1a; 题解&#xff1a; type PeekingIterator struct {iter *Iterator_hasNext bool_next int }func Constructor(iter *Iterator) *PeekingIterator {return &PeekingIterator{iter, iter.hasNext(), iter.next()} }func (it *PeekingIterator) hasNe…

宿州市各区县两化融合贯标申请认定奖补奖励和认定条件、材料、周期、流程大全

本文将为大家盘点宿州市各区县两化融合贯标申请认定奖补奖励和认定条件、材料、周期、流程等内容&#xff0c;详情如下&#xff0c;埇桥区、砀山县、萧县、灵璧县、泗县需要申报的可指导&#xff01; 宿州市各区县两化融合贯标申请认定奖补奖励&#xff08;仅供参考&#xff0…

学习记录day19——数据结构 查找算法

概念 在给定数据元素的某个值&#xff0c;在查找表中确定一个其关键字等于给定值的数据元素的操作&#xff0c;叫做查找 查找的分类 顺序查找:将待查找数据&#xff0c;进行全部遍历一遍&#xff0c;直到找到要查找的元素 折半查找:每次都去除一半的查找范围的查找方式&#x…