Netty從0到1系列之Netty啟動細節分析

文章目錄

  • 一、Netty服務器端啟動細節分析
    • 1.1 實現一個簡單的http服務器
    • 1.2 服務器端啟動細節分析
    • 1.3 創建與初始化 NioServerSocketChannel
      • 1.3.1 **通過反射工廠創建 Channel**:
      • 1.3.2 **初始化 Channel**
    • 1.4 注冊到 Boss EventLoopGroup
      • 1.4.1 **異步提交注冊任務**
      • 1.4.2 **EventLoop 執行注冊**
      • 1.4.3 觸發 Handler 添加事件
      • 1.4.4 觸發 Channel 注冊成功事件
    • 1.5 綁定端口與激活
      • 1.5.1 執行綁定操作
      • 1.5.2 標記 Channel 為激活狀態
      • 1.5.3 觸發 Channel 激活事件
      • 1.5.4 自動注冊ACCEPT事件
    • 1.6 接收連接 - ServerBootstrapAcceptor 的工作
    • 1.7 總結與精要分析


推薦閱讀:

【01】Netty從0到1系列之I/O模型
【02】Netty從0到1系列之NIO
【03】Netty從0到1系列之Selector
【04】Netty從0到1系列之Channel
【05】Netty從0到1系列之Buffer(上)
【06】Netty從0到1系列之Buffer(下)
【07】Netty從0到1系列之零拷貝技術
【08】Netty從0到1系列之整體架構、入門程序
【09】Netty從0到1系列之EventLoop
【10】Netty從0到1系列之EventLoopGroup
【11】Netty從0到1系列之Future
【12】Netty從0到1系列之Promise
【13】Netty從0到1系列之Netty Channel
【14】Netty從0到1系列之ChannelFuture
【15】Netty從0到1系列之CloseFuture
【16】Netty從0到1系列之Netty Handler
【17】Netty從0到1系列之Netty Pipeline【上】
【18】Netty從0到1系列之Netty Pipeline【下】
【19】Netty從0到1系列之Netty ByteBuf【上】
【20】Netty從0到1系列之Netty ByteBuf【中】
【21】Netty從0到1系列之Netty ByteBuf【下】
【22】Netty從0到1系列之Netty 邏輯架構【上】
【23】Netty從0到1系列之Netty 邏輯架構【下】


一、Netty服務器端啟動細節分析

1.1 實現一個簡單的http服務器

目標

  • 完成http服務器,請求-響應的過程.

作為開發者來說,只要照葫蘆畫瓢即可輕松上手。大多數場景下,你只需要實現與業務邏輯相關的一系列 ChannelHandler,再加上 Netty 已經預置了 HTTP 相關的編解碼器就可以快速完成服務端框架的搭建。所以,我們只需要兩個類就可以完成一個最簡單的 HTTP 服務器,它們分別為服務器啟動類和業務邏輯處理類,結合完整的代碼實現我將對它們分別進行講解。

  1. 服務器端代碼
package cn.tcmeta.httpserver;import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.http.HttpContentCompressor;
import io.netty.handler.codec.http.HttpObjectAggregator;
import io.netty.handler.codec.http.HttpServerCodec;/*** @author: laoren* @description: 實現一個簡單的http服務器* @version: 1.0.0*/
public class MyHttpSimpleServer {public void doStart(int port) throws InterruptedException {NioEventLoopGroup bossGroup = new NioEventLoopGroup(1);NioEventLoopGroup workerGroup = new NioEventLoopGroup(2);try {ServerBootstrap bootstrap = new ServerBootstrap();bootstrap.group(bossGroup, workerGroup).channel(NioServerSocketChannel.class).childHandler(new ChannelInitializer<NioSocketChannel>() {@Overrideprotected void initChannel(NioSocketChannel ch) throws Exception {ChannelPipeline pipeline = ch.pipeline();// 編碼、解碼處理器pipeline.addLast("codec", new HttpServerCodec());// 壓縮處理器pipeline.addLast("compressor", new HttpContentCompressor());// 合并處理器pipeline.addLast("aggregator", new HttpObjectAggregator(1024 * 1024));// 自定義處理器pipeline.addLast("handler", new MyHttpServerHandler());}}).childOption(ChannelOption.SO_KEEPALIVE, true);ChannelFuture future = bootstrap.bind(port).sync();System.out.println("? MyHttpSimpleServer 啟動,端口: " + port);future.channel().closeFuture().sync();}finally {bossGroup.shutdownGracefully();workerGroup.shutdownGracefully();}}public static void main(String[] args) {try {new MyHttpSimpleServer().doStart(8080);} catch (InterruptedException e) {throw new RuntimeException(e);}}}
  1. 自定義處理器代碼
package cn.tcmeta.httpserver;import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.codec.http.*;
import io.netty.util.CharsetUtil;
import lombok.extern.slf4j.Slf4j;/*** @author: laoren* @description: 自定義處理類* @version: 1.0.0*/
@Slf4j
public class MyHttpServerHandler extends SimpleChannelInboundHandler<FullHttpRequest> {@Overrideprotected void channelRead0(ChannelHandlerContext ctx, FullHttpRequest request) throws Exception {String content =String.format("Receive http request, uri: %s, method: %s, content: %s%n", request.uri(), request.method(), request.content().toString(CharsetUtil.UTF_8));log.info("uri: {}, request method: {}, content: {}", request.uri(), request.method(), request.content());// 處理響應事件FullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1,HttpResponseStatus.OK,Unpooled.wrappedBuffer(content.getBytes()));ctx.writeAndFlush(response).addListener(ChannelFutureListener.CLOSE);}
}
  1. 測試示例
  • 客戶端測試
    在這里插入圖片描述

  • 服務器端日志

? MyHttpSimpleServer 啟動,端口: 8080
15:47:34 [INFO ] [nioEventLoopGroup-3-1] c.t.h.MyHttpServerHandler - uri: /bad-request, request method: GET, content: UnpooledByteBufAllocator$InstrumentedUnpooledUnsafeHeapByteBuf(ridx: 0, widx: 0, cap: 0)Process finished with exit code 130

1.2 服務器端啟動細節分析

  • 階段一: 創建與初始化 NioServerSocketChannel
  • 階段二: 注冊到 Boss EventLoopGroup
  • 階段三: 綁定端口與激活
  • 階段四: 接收連接 - ServerBootstrapAcceptor 的工作
Main ThreadServerBootstrapChannelFactoryNioServerSocketChannelBossGroup (EventLoop)ChannelPipelineHeadContextTailContextServerBootstrapAcceptorJava NIObind(port)newChannel()create & initialize1. 新建 JDK ServerSocketChannel2. 配置非阻塞3. 創建 id, unsafe, pipeline4. pipeline.addHead(Tail)5. pipeline.addTail(Head)init()addLast(ServerBootstrapAcceptor)Pipeline: [Head]... ? [SBA] ? [Tail]register(channel)等待注冊完成 (異步)doRegister()javaChannel().register(selector, 0, this)將JDK Channel注冊到BossGroup的Selector,關注0(無興趣事件),附件為NioServerSocketChannelinvokeHandlerAddedIfNeeded()handlerAdded()fireChannelRegistered()fireChannelRegistered() (從頭開始傳播)doBind()javaChannel().bind()setActive(true)fireChannelActive()fireChannelActive()readIfIsAutoRead()selectionKey().interestOps(OP_ACCEPT)最終在Selector上注冊OP_ACCEPT事件,開始接收新連接Main ThreadServerBootstrapChannelFactoryNioServerSocketChannelBossGroup (EventLoop)ChannelPipelineHeadContextTailContextServerBootstrapAcceptorJava NIO

1.3 創建與初始化 NioServerSocketChannel

當調用 ServerBootstrap.bind(port) 時,Netty 并不會立即進行網絡調用。第一步是創建服務端的“大門”。

1.3.1 通過反射工廠創建 Channel

  • ServerBootstrap 通過其內部的 ChannelFactory(通常是 ReflectiveChannelFactory)來創建一個新的 NioServerSocketChannel 實例。

在這里插入圖片描述

在這里插入圖片描述

相關核心代碼:

  • io/netty/bootstrap/AbstractBootstrap.java
 final ChannelFuture initAndRegister() {Channel channel = null;try {// 1. 通過反射創建NioServerSocketChannel對象channel = channelFactory.newChannel();init(channel);} catch (Throwable t) {}
  • io/netty/channel/ReflectiveChannelFactory.java
@Override
public T newChannel() {try {return constructor.newInstance();} catch (Throwable t) {throw new ChannelException("Unable to create Channel from class " + constructor.getDeclaringClass(), t);}
}
  1. 底層結節實現

NioServerSocketChannel 的構造函數中,Netty 會做幾件關鍵事:

  • SocketChannel.open(): 打開一個新的 Java NIO ServerSocketChannel
  • .configureBlocking(false)立即將其設置為非阻塞模式。這是異步編程的基礎。
  • 創建該 Channel 的核心組件:ChannelId(唯一標識)、Unsafe(負責底層讀寫操作)、以及最重要的 DefaultChannelPipeline

核心代碼:

protected AbstractChannel(Channel parent) {this.parent = parent;id = newId();unsafe = newUnsafe();pipeline = newChannelPipeline();
}
rotected AbstractNioChannel(Channel parent, SelectableChannel ch, int readInterestOp) {super(parent);this.ch = ch;this.readInterestOp = readInterestOp;try {ch.configureBlocking(false); // 設置為非阻塞模式} catch (IOException e) {try {ch.close();} catch (IOException e2) {logger.warn("Failed to close a partially initialized socket.", e2);}throw new ChannelException("Failed to enter non-blocking mode.", e);}
}

在這里插入圖片描述

1.3.2 初始化 Channel

  • 創建完成后,ServerBootstrap 會調用 init(Channel channel) 方法來初始化這個新 Channel。
  • 設置 ChannelOptions 和 Attributes: 將 ServerBootstrap.option()ServerBootstrap.attr() 中設置的參數應用到該 Channel 的配置中。
  • 設置 Pipeline 的“大門保安”
    • 這是最關鍵的一步。Netty 會通過 pipeline.addLast() 方法將一個特殊的 ChannelHandler——ServerBootstrapAcceptor——添加到 Channel 的 Pipeline 中。
    • ServerBootstrapAcceptor 的職責: 它本身是一個 ChannelInboundHandler。它的唯一使命就是在“有連接到來”這個入站事件被觸發時,將新接入的客戶端連接(SocketChannel)正式接手過來。我們會在階段三詳細講解它。
  • 核心代碼
  final ChannelFuture initAndRegister() {Channel channel = null;try {channel = channelFactory.newChannel();init(channel); // 初始化Channel} catch (Throwable t) {}
@Override
void init(Channel channel) {// 設置ChannelOptions setChannelOptions(channel, newOptionsArray(), logger);// 設置AttributessetAttributes(channel, newAttributesArray());// 獲取綁定的PipelineChannelPipeline p = channel.pipeline();final EventLoopGroup currentChildGroup = childGroup;final ChannelHandler currentChildHandler = childHandler;final Entry<ChannelOption<?>, Object>[] currentChildOptions = newOptionsArray(childOptions);final Entry<AttributeKey<?>, Object>[] currentChildAttrs = newAttributesArray(childAttrs);final Collection<ChannelInitializerExtension> extensions = getInitializerExtensions();p.addLast(new ChannelInitializer<Channel>() {@Overridepublic void initChannel(final Channel ch) {final ChannelPipeline pipeline = ch.pipeline();ChannelHandler handler = config.handler();if (handler != null) {pipeline.addLast(handler);}ch.eventLoop().execute(new Runnable() {@Overridepublic void run() {// 將ServerBootstrapAcceptor添加到Pipeline中pipeline.addLast(new ServerBootstrapAcceptor(ch, currentChildGroup, currentChildHandler, currentChildOptions, currentChildAttrs,extensions));}});}});if (!extensions.isEmpty() && channel instanceof ServerChannel) {ServerChannel serverChannel = (ServerChannel) channel;for (ChannelInitializerExtension extension : extensions) {try {extension.postInitializeServerListenerChannel(serverChannel);} catch (Exception e) {logger.warn("Exception thrown from postInitializeServerListenerChannel", e);}}}
}

在這里插入圖片描述

至此,一個功能完備、配置齊全的 NioServerSocketChannel 就準備好了,但它還沒有被賦予“生命”(即注冊到 EventLoop)。

1.4 注冊到 Boss EventLoopGroup

這是 Netty 異步設計的核心體現。注冊操作本身是異步的

1.4.1 異步提交注冊任務

  • ServerBootstrapAbstractUnsafe.register0() 操作封裝成一個任務,提交給 BossGroup 中的一個 EventLoop
  • 主線程(調用 bind() 的線程)在此刻立即返回一個 ChannelFuture,它可以用來監聽注冊(和后續綁定)是否成功。主線程不會被阻塞

在這里插入圖片描述

注冊不會阻塞主線程,直接返回一個ChannelFuture

 ChannelFuture regFuture = config().group().register(channel);if (regFuture.cause() != null) {if (channel.isRegistered()) {channel.close();} else {channel.unsafe().closeForcibly();}}

在這里插入圖片描述

  • bossGroup中的NioEventLoop
    在這里插入圖片描述

  • AbstractUnsafe.register0()注冊的EventLoop

在這里插入圖片描述

1.4.2 EventLoop 執行注冊

  • 被選中的 EventLoop 線程會在其運行周期內的某個時間點執行這個注冊任務。
  • 真正的注冊: 在 doRegister() 方法中,會調用 Java NIO 的 ServerSocketChannel.register(eventLoop.selector, 0, this)
    • 關鍵點 1: 將 JDK 的 ServerSocketChannel 注冊到 EventLoop 持有的 Selector 上。
    • 關鍵點 2興趣操作為 0,表示暫時不監聽任何事件。這是因為此時 Channel 還未激活(未綁定端口),監聽 OP_ACCEPT 沒有意義。
    • 關鍵點 3附件(Attachment) 設置為 Netty 自己的 NioServerSocketChannel 對象。這樣,當 Selector 返回事件時,Netty 可以直接從 SelectionKey 中取出附件,就知道是哪個 Netty Channel 發生了事件。
  1. io/netty/channel/AbstractChannel#register0
private void register0(ChannelPromise promise) {try {// check if the channel is still open as it could be closed in the mean time when the register// call was outside of the eventLoopif (!promise.setUncancellable() || !ensureOpen(promise)) {return;}boolean firstRegistration = neverRegistered;doRegister(); // ????? 核心注冊方法neverRegistered = false;registered = true;// Ensure we call handlerAdded(...) before we actually notify the promise. This is needed as the// user may already fire events through the pipeline in the ChannelFutureListener.pipeline.invokeHandlerAddedIfNeeded();safeSetSuccess(promise);pipeline.fireChannelRegistered();// Only fire a channelActive if the channel has never been registered. This prevents firing// multiple channel actives if the channel is deregistered and re-registered.if (isActive()) {if (firstRegistration) {pipeline.fireChannelActive();} else if (config().isAutoRead()) {// This channel was registered before and autoRead() is set. This means we need to begin read// again so that we process inbound data.//// See https://github.com/netty/netty/issues/4805beginRead();}}} catch (Throwable t) {// Close the channel directly to avoid FD leak.closeForcibly();closeFuture.setClosed();safeSetFailure(promise, t);}
}
  • io/netty/channel/nio/AbstractNioChannel#doRegister()
@Overrideprotected void doRegister() throws Exception {boolean selected = false;for (;;) {try {// 參數1: Selector// 參數2: 0: 表示暫時不監聽任何事件。這是因為此時 Channel 還未激活(未綁定端口),監聽 OP_ACCEPT 沒有意義。// 參數3: this:  附件(Attachment) 設置為 Netty 自己的 NioServerSocketChannel 對象。這樣,當 Selector 返回事件時,Netty 可以直接從 SelectionKey 中取出附件,就知道是哪個 Netty Channel 發生了事件selectionKey = javaChannel().register(eventLoop().unwrappedSelector(), 0, this);return;} catch (CancelledKeyException e) {if (!selected) {// Force the Selector to select now as the "canceled" SelectionKey may still be// cached and not removed because no Select.select(..) operation was called yet.eventLoop().selectNow();selected = true;} else {// We forced a select operation on the selector before but the SelectionKey is still cached// for whatever reason. JDK bug ?throw e;}}}}

在這里插入圖片描述

  • register簽名
// 參數1: Selector
// 參數2: 監聽的事件
// 參數3: 附加數據
public abstract SelectionKey register(Selector sel, int ops, Object att)throws ClosedChannelException;

在這里插入圖片描述

1.4.3 觸發 Handler 添加事件

  • 注冊成功后,EventLoop 線程會回調 ChannelHandler.handlerAdded(ctx) 方法。對于 ServerBootstrapAcceptor 來說,此時它正式“上崗”。

1.4.4 觸發 Channel 注冊成功事件

  • 最后,EventLoop 線程會通過 PipelineHead 開始向后傳播 channelRegistered 事件。此時,所有用戶添加到服務端 Channel 的 ChannelHandler 都會感知到注冊成功。

1.5 綁定端口與激活

注冊成功后,才開始真正的綁定。

1.5.1 執行綁定操作

  • 同樣在 EventLoop 線程中,調用 doBind() 方法。
  • 其內部最終會調用 JDK 的 ServerSocketChannel.bind(socketAddress, backlog) 方法,真正地將套接字綁定到指定的端口
  1. 注冊成功之后,進行綁定操作,調用doBind
// Registration future is almost always fulfilled already, but just in case it's not.
final PendingRegistrationPromise promise = new PendingRegistrationPromise(channel);
regFuture.addListener(new ChannelFutureListener() {@Overridepublic void operationComplete(ChannelFuture future) throws Exception {Throwable cause = future.cause();if (cause != null) {// Registration on the EventLoop failed so fail the ChannelPromise directly to not cause an// IllegalStateException once we try to access the EventLoop of the Channel.promise.setFailure(cause);} else {// Registration was successful, so set the correct executor to use.// See https://github.com/netty/netty/issues/2586promise.registered();// 調用它doBind0(regFuture, channel, localAddress, promise);}}
});

在這里插入圖片描述

在這里插入圖片描述

在這里插入圖片描述

1.5.2 標記 Channel 為激活狀態

  • 綁定成功后,調用 setActive(true) 將 Channel 標記為激活狀態。

1.5.3 觸發 Channel 激活事件

  • 通過 PipelineHead 開始向后傳播 channelActive 事件

1.5.4 自動注冊ACCEPT事件

  • 這是一個精妙的設計!channelActive 事件最終會傳播到 PipelineHeadContext
  • HeadContextchannelActive 方法中會調用 readIfIsAutoRead()
  • 這個方法調用會層層向下,最終到達底層的 AbstractNioChannel,其 doBeginRead() 方法會被調用。
  • 最終操作doBeginRead() 會修改之前注冊到 Selector 上的 SelectionKey 的興趣操作:selectionKey.interestOps(interestOps | OP_ACCEPT)
  • 至此,Netty 服務端才開始正式監聽 OP_ACCEPT 事件,準備接收客戶端的連接。

1.6 接收連接 - ServerBootstrapAcceptor 的工作

當客戶端發起連接,BossGroupEventLoop 輪詢到 OP_ACCEPT 事件時,真正的“魔法”開始了。

  1. 創建子 ChannelEventLoop 線程會調用 NioServerSocketChannelread() 方法,其內部通過 ServerSocketChannel.accept() 創建一個 JDK 的 SocketChannel,并同樣被包裝成 Netty 的 NioSocketChannel

  2. “Acceptor” 接手: 這個新創建的 NioSocketChannel 會作為入站數據,在 Pipeline 中傳播。它首先到達 Head,然后流經 ServerBootstrapAcceptor

  3. 配置并注冊子 ChannelServerBootstrapAcceptorchannelRead 方法被觸發,它負責:

    • ServerBootstrap.childOptions()childAttrs() 設置到子 Channel 上。
    • ServerBootstrap.childHandler() 中設置的 ChannelInitializer 添加到子 Channel 的 Pipeline 中。
  4. 移交完成: 自此,這個客戶端連接生命周期的所有后續 I/O 事件(讀、寫、斷開)都將由 WorkerGroup 中的那個 EventLoop 全權負責。BossGroup 的職責圓滿完成。

1.7 總結與精要分析

階段核心操作執行線程關鍵細節
創建初始化創建 NioServerSocketChannel主線程配置非阻塞,創建 Pipeline,添加 ServerBootstrapAcceptor
注冊將 Channel 注冊到 SelectorBossGroup 線程興趣操作為 0,附件為 Netty Channel,異步操作
綁定調用 JDK bindBossGroup 線程綁定端口,觸發 channelActive 事件
激活監聽注冊 OP_ACCEPT 事件BossGroup 線程HeadContextchannelActive 事件中自動完成
接收連接ServerBootstrapAcceptor 工作BossGroup 線程接收連接,配置子 Channel,將其轉交給 WorkerGroup

設計理念與精妙之處:

  1. 職責分離 (Separation of Concerns)BossGroup 只管接收連接,WorkerGroup 只管處理連接,架構清晰,易于理解和擴展。
  2. 完全異步: 從注冊到綁定,所有耗時操作都封裝成任務提交給 EventLoop,保證啟動過程不阻塞主線程。
  3. 惰性監聽: 先注冊(interestOps=0),后綁定,再設置 OP_ACCEPT。這是一個嚴謹的狀態管理流程,避免了在未準備好時接收到事件。
  4. 統一的抽象: 無論是服務端 Channel 還是客戶端 Channel,都通過 ChannelPipelineEventLoop 進行管理,提供了極其一致和靈活的編程模型。
  5. 高效的連接接收ServerBootstrapAcceptor 內置于框架中,將接收連接和后續處理完美銜接,整個過程都在 EventLoop 線程內完成,無比高效。

Netty 的啟動過程是其 Reactor 線程模型的完美體現,每一個步驟都經過精心設計,以確保在高并發場景下能夠實現最高的性能和可靠性

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/922246.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/922246.shtml
英文地址,請注明出處:http://en.pswp.cn/news/922246.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

一個海康相機OCR的程序

這是一個極其復雜和龐大的??機器視覺檢測程序??&#xff0c;其核心特點是??多重冗余、條件判斷和流程分支??。它并非一個簡單的線性流程&#xff0c;而是一個為應對各種復雜工業場景&#xff08;如光照變化、產品位置偏移、識別難度高等&#xff09;而設計的??決策網…

深入解析:preload與prefetch的區別及最佳實踐

在前端性能優化領域&#xff0c;資源加載策略直接影響頁面的加載速度和用戶體驗。<link>標簽的preload和prefetch屬性是瀏覽器提供的兩種關鍵資源預加載機制&#xff0c;它們都能提前加載資源&#xff0c;但適用場景和行為邏輯卻大不相同。本文將從定義、觸發時機、優先級…

[論文閱讀] 人工智能 + 軟件工程(漏洞檢測)| 工業場景漏洞檢測新突破:CodeBERT跨領域泛化能力評估與AI-DO工具開發

工業場景漏洞檢測新突破&#xff1a;CodeBERT跨領域泛化能力評估與AI-DO工具開發 論文信息 論文原標題&#xff1a;Cross-Domain Evaluation of Transformer-Based Vulnerability Detection: Open-Source vs. Industrial Data引文格式&#xff08;APA&#xff09;&#xff1a;[…

【層面一】C#語言基礎和核心語法-01(類型系統/面向對象/異常處理)

文章目錄1 類型系統1.1 為什么需要類型&#xff1f;1.2 .NET 類型系統的兩大支柱&#xff1a;CTS 和 CLS1.3 最根本的分類&#xff1a;值類型 vs 引用類型1.4 內置類型 vs. 自定義類型1.5 類型轉換1.6 通用基類&#xff1a;System.Object2 面向對象編程2.1 類和對象2.2 接口和類…

Deepseek構建本地知識庫

一.本地部署Deepseek Ollama 介紹 目前市面上主流的&#xff0c;成本最低的部署本地大模型的方法就是通過 Ollama 了&#xff1a; Ollama 是一個開源的本地大語言模型運行框架&#xff0c;專為在本地機器上便捷部署和運行大型語言模型&#xff08;LLM&#xff09;而設計。 核心…

idea自動編譯,idea不重啟項目,加載修改的內容

idea自動編譯&#xff0c;idea不重啟項目&#xff0c;加載修改的內容

幸運盒項目—測試報告

幸運盒測試報告 目錄幸運盒測試報告一. 概要二. 測試環境三. 測試用例腦圖四. 測試用例1. 功能測試1. 注冊功能2. 密碼登錄功能3. 驗證碼登錄功能4. 注冊用戶功能5. 創建獎品功能6. 新建抽獎活動功能8. 獎品列表9. 活動列表2. 界面測試1. 注冊界面2. 密碼登錄界面3. 驗證碼登錄…

Estimator and Confidence interval

Coefficient of determination and sample correlation coefficient R2SSRSSTR^2 \frac{SSR}{SST}R2SSTSSR? SSR∑i1n((yi^?yˉ)2)SSR\sum_{i1}^n((\hat{y_{i}}-\bar{y})^2)SSR∑i1n?((yi?^??yˉ?)2) SST∑i1n((yi?yˉ)2)SST\sum_{i1}^n((y_{i}-\bar{y})^2)SST∑i1n?…

【網絡編程】TCP 服務器并發編程:多進程、線程池與守護進程實踐

半桔&#xff1a;個人主頁&#x1f525; 個人專欄: 《Linux手冊》《手撕面試算法》《網絡編程》&#x1f516;很多人在喧囂聲中登場&#xff0c;也有少數人在靜默中退出。 -張方宇- 文章目錄前言套接字接口TCP服務器TCP 多進程TCP 線程池重寫Task任務放函數對象客戶端重連進程…

還停留在批處理時代嗎?增量計算架構詳解

在當今的數字化環境中&#xff0c;企業不再只是一味地囤積數據——他們癡迷于盡快把數據轉化為可付諸行動的洞察。真正的優勢來自于實時發現變化并立即做出反應&#xff0c;無論是調整推薦策略還是規避危機。 十年前&#xff0c;硬件與平臺技術的進步讓我們能夠從容應對海量數…

DataSet-深度學習中的常見類

深度學習中Dataset類通用的架構思路 Dataset 類設計的必備部分 1. 初始化 __init__ 配置和路徑管理&#xff1a;保存 config&#xff0c;區分 train/val/test 路徑。加載原始數據&#xff1a;CSV、JSON、Numpy、Parquet 等。預處理器/歸一化器&#xff1a;如 StandardScaler&am…

【VC】 error MSB8041: 此項目需要 MFC 庫

? 目錄 ?&#x1f6eb; 導讀問題背景環境1?? 核心原因&#xff1a;MFC 組件缺失或配置不當2?? 解決方案&#xff1a;安裝 MFC 組件并驗證配置2.1 步驟1&#xff1a;檢查并安裝 MFC 組件2.2 步驟2&#xff1a;檢查并修正項目配置2.3 步驟3&#xff1a;針對特定場景的補充方…

Java零基礎學習Day10——面向對象高級

一.認識final1.含義final關鍵字是最終的意思&#xff0c;可以修飾&#xff1a;類&#xff0c;方法&#xff0c;變量修飾類&#xff1a;該類被稱為最終類&#xff0c;特點是不能被繼承修飾方法&#xff1a;該方法被稱為最終方法&#xff0c;特點是不能被重寫了修飾變量&#xff…

Qt中解析JSON文件

Qt中解析JSON文件 在Qt中解析JSON字符串主要有兩種方式&#xff1a;使用QJsonDocument類或使用QJsonDocument結合QVariant。以下是詳細的解析方法&#xff1a; 使用QJsonDocument&#xff08;推薦&#xff09; 這種方式的主要相關類如下&#xff1a; QJsonDocument: QJsonDocum…

深度解析HTTPS:從加密原理到SSL/TLS的演進之路

在互聯網時代,數據安全已成為不可忽視的基石。當我們在瀏覽器地址欄看到"https://"前綴和那把小小的綠色鎖圖標時,意味著正在進行一場受保護的通信。但這層保護究竟是如何實現的?HTTPS、SSL和TLS之間又存在著怎樣的聯系與區別?本文將深入剖析這些技術細節,帶你全…

Flutter 官方 LLM 動態 UI 庫 flutter_genui 發布,讓 App UI 自己生成 UI

今日&#xff0c;Flutter 官方正式發布了它們關于 AI 大模型的 package 項目&#xff1a; genui &#xff0c;它是一個非常有趣和前沿的探索類型的項目&#xff0c;它的目標是幫助開發者構建由生成式 AI 模型驅動的動態、對話式用戶界面&#xff1a; 也就是它與傳統 App 中“寫…

Redis常用數據結構及其底層實現

Redis常用數據結構主要有String List Set Zset Hash BitMap Hyperloglog Stream GeoString:Redis最常用的一種數據結構,Sting類型的數據存儲結構有三種int、embstr、raw1.int:用來存儲long以下的整形embstr raw 都是用來存字符串&#xff0c;其中小于44字節的字符串用embstr存 …

O3.4 opencv圖形拼接+答題卡識別

一圖形拼接邏輯導入必要的庫pythonimport cv2 import numpy as np import sys導入cv2庫用于圖像處理&#xff0c;numpy庫用于數值計算&#xff0c;sys庫用于與 Python 解釋器進行交互&#xff0c;例如退出程序。定義圖像顯示函數def cv_show(name, img):cv2.imshow(name, img)c…

SQL注入常見攻擊點與防御詳解

SQL注入是一種非常常見且危險的Web安全漏洞。攻擊者通過將惡意的SQL代碼插入到應用程序的輸入參數中&#xff0c;欺騙后端數據庫執行這些非預期的命令&#xff0c;從而可能竊取、篡改、刪除數據或獲得更高的系統權限。以下是詳細、準確的SQL注入點分類、說明及舉例&#xff1a;…

EKSPod 資源利用率配置修復:從占位符到完整資源分析系統

概述 在 Kubernetes 集群管理過程中,資源利用率的監控和優化是保證應用性能和成本效益的關鍵環節。近期,我們對 EKSPod 管理界面的資源利用率顯示功能進行了全面修復,將原先簡單的占位符文本升級為完整的資源分析系統。本文將詳細介紹這次修復的背景、方案、實現細節和最終…