32.Netty源碼之服務端如何處理客戶端新建連接


highlight: arduino-light

服務端如何處理客戶端新建連接

Netty 服務端完全啟動后,就可以對外工作了。接下來 Netty 服務端是如何處理客戶端新建連接的呢? 主要分為四步:

md Boss NioEventLoop 線程輪詢客戶端新連接 OP_ACCEPT 事件; ? 構造 初始化Netty 客戶端 NioSocketChannel; ? 注冊 Netty 客戶端 NioSocketChannel 到 Worker 工作線程中; ? 從 Worker group 選擇一個 eventLoop 工作線程;注冊到選擇的eventLoop的Selector ? 注冊 OP_READ 事件到 NioSocketChannel 的事件集合。 ?

下面我們對每個步驟逐一進行簡單的介紹。

接收新連接

bossGroup的EventLoop是一個線程是一個線程是一個線程。所以等服務器端啟動起來以后就會執行線程的run方法邏輯。

java protected void run() { ? ?for (;;) { ? ? ? ?try { ? ? ? ? ? ?try { ? ? ? ? ? ?switch (selectStrategy.calculateStrategy ? ? ? ? ? ? ? ? ? (selectNowSupplier, hasTasks())) { ? ? ? ? ? ? ? ?case SelectStrategy.CONTINUE: ? ? ? ? ? ? ? ? ? ?continue; ? ? ? ? ? ? ? ?case SelectStrategy.BUSY_WAIT: ? ? ? ? ? ? ? ?case SelectStrategy.SELECT: ? ? ? ? ? ? ? ? ? ?select(wakenUp.getAndSet(false)); // 輪詢 I/O 事件 ? ? ? ? ? ? ? ? ? ?if (wakenUp.get()) { ? ? ? ? ? ? ? ? ? ? ? ?selector.wakeup(); ? ? ? ? ? ? ? ? ? } ? ? ? ? ? ? ? ?default: ? ? ? ? ? ? ? } ? ? ? ? ? } catch (IOException e) { ? ? ? ? ? ? ? ?rebuildSelector0(); ? ? ? ? ? ? ? ?handleLoopException(e); ? ? ? ? ? ? ? ?continue; ? ? ? ? ? } ? ? ? ? ? ?cancelledKeys = 0; ? ? ? ? ? ?needsToSelectAgain = false; ? ? ? ? ? ?final int ioRatio = this.ioRatio; ? ? ? ? ? ?if (ioRatio == 100) { ? ? ? ? ? ? ? ?try { ? ? ? ? ? ? ? ? ? ?// 處理 I/O 事件 ? ? ? ? ? ? ? ? ? ?processSelectedKeys(); ? ? ? ? ? ? ? } finally { ? ? ? ? ? ? ? ? ? ?runAllTasks(); // 處理所有任務 ? ? ? ? ? ? ? } ? ? ? ? ? } else { ? ? ? ? ? ? ? ?final long ioStartTime = System.nanoTime(); ? ? ? ? ? ? ? ?try { ? ? ? ? ? ? ? ? ? ?processSelectedKeys(); // 處理 I/O 事件 ? ? ? ? ? ? ? } finally { ? ? ? ? ? ? ? ? ? ?final long ioTime = System.nanoTime() - ioStartTime; ? ? ? ? ? ? ? ? ? ?// 處理完 I/O 事件,再處理異步任務隊列 ? ? ? ? ? ? ? ? ? ?runAllTasks(ioTime * (100 - ioRatio) / ioRatio); ? ? ? ? ? ? ? } ? ? ? ? ? } ? ? ? } catch (Throwable t) { ? ? ? ? ? ?handleLoopException(t); ? ? ? } ? ? ? ?try { ? ? ? ? ? ?if (isShuttingDown()) { ? ? ? ? ? ? ? ?closeAll(); ? ? ? ? ? ? ? ?if (confirmShutdown()) { ? ? ? ? ? ? ? ? ? ?return; ? ? ? ? ? ? ? } ? ? ? ? ? } ? ? ? } catch (Throwable t) { ? ? ? ? ? ?handleLoopException(t); ? ? ? } ? } } ?

NioEventLoop#processSelectedKeys

java // processSelectedKeys private void processSelectedKeys() { if (selectedKeys != null) { //不用JDK的selector.selectedKeys(), 性能更好(1%-2%),垃圾回收更少 processSelectedKeysOptimized(); } else { processSelectedKeysPlain(selector.selectedKeys()); } }

服務器端監聽 OP_ACCEPT 事件讀取消息

NioEventLoop#processSelectedKeysOptimized

Netty 中 Boss NioEventLoop 專門負責接收新的連接,關于 NioEventLoop 的核心源碼我們下節課會著重介紹,在這里我們只先了解基本的處理流程。當客戶端有新連接接入服務端時,Boss NioEventLoop 會監聽到 OP_ACCEPT 事件,源碼如下所示:

```java private void processSelectedKeysOptimized() { for (int i = 0; i < selectedKeys.size; ++i) { final SelectionKey k = selectedKeys.keys[i]; // null out entry in the array to allow to have it GC'ed once the Channel close // See https://github.com/netty/netty/issues/2363 selectedKeys.keys[i] = null;

//呼應于channel的register中的this: //selectionKey = javaChannel().register(eventLoop()//                            .unwrappedSelector(), 0, this);final Object a = k.attachment();//因為客戶端和服務器端的都繼承自AbstractNioChannelif (a instanceof AbstractNioChannel) {//會進入判斷processSelectedKey(k, (AbstractNioChannel) a);} else {@SuppressWarnings("unchecked")NioTask<SelectableChannel> task = (NioTask<SelectableChannel>) a;processSelectedKey(k, task);}if (needsToSelectAgain) {// null out entries in the array to allow to have it GC'ed once the Channel close// See https://github.com/netty/netty/issues/2363selectedKeys.reset(i + 1);selectAgain();i = -1;}}
}

```

NioEventLoop#processSelectedKey

```java private void processSelectedKey(SelectionKey k, AbstractNioChannel ch) { final AbstractNioChannel.NioUnsafe unsafe = ch.unsafe(); if (!k.isValid()) { final EventLoop eventLoop; try { eventLoop = ch.eventLoop(); } catch (Throwable ignored) { return; }

if (eventLoop != this || eventLoop == null) {return;}// close the channel if the key is not valid anymoreunsafe.close(unsafe.voidPromise());return;}try {int readyOps = k.readyOps();if ((readyOps & SelectionKey.OP_CONNECT) != 0) {int ops = k.interestOps();ops &= ~SelectionKey.OP_CONNECT;k.interestOps(ops);unsafe.finishConnect();}if ((readyOps & SelectionKey.OP_WRITE) != 0) {ch.unsafe().forceFlush();}//處理讀請求(斷開連接)或接入連接if ((readyOps & (SelectionKey.OP_READ | SelectionKey.OP_ACCEPT))!= 0 || readyOps == 0) {//開始處理請求 服務器端處理的是OP_ACCEPT 接收新連接 unsafe.read();}} catch (CancelledKeyException ignored) {unsafe.close(unsafe.voidPromise());}
}

```

NioMessageUnsafe#read

NioServerSocketChannel 所持有的 unsafe 是 NioMessageUnsafe 類型。

我們看下 NioMessageUnsafe.read() 方法中做了什么事。

```java public void read() { assert eventLoop().inEventLoop(); final ChannelConfig config = config(); final ChannelPipeline pipeline = pipeline(); final RecvByteBufAllocator.Handle allocHandle = unsafe().recvBufAllocHandle(); allocHandle.reset(config); boolean closed = false; Throwable exception = null; try { try {

do {//readBuf一開始是一個空List//while 循環不斷讀取 Buffer 中的數據//創建底層SocketChannel并封裝為NioSocketChannel放到readBuf返回1int localRead = doReadMessages(readBuf); //執行完上面的方法 readBuf放的是新創建的NioSocketChannelif (localRead == 0) {break;}if (localRead < 0) {closed = true;break;}allocHandle.incMessagesRead(localRead);//是否需要繼續讀 因為是建立連接 所以總共讀取的字節數是0 不會繼續進入循環//這里一次最多處理16個連接} while (allocHandle.continueReading());} catch (Throwable t) {exception = t;}int size = readBuf.size();//readBuf放的是新創建的NioSocketChannelfor (int i = 0; i < size; i ++) {readPending = false;// 對于服務器端NioServerSocketChannel來說 // handler有// 1.head // 2.ClientLoggingHandler // 3.ServerBootstrapAcceptor// 4.tail// 接下來就是調用服務器端的每個handler的channelRead方法 傳播讀取事件// 比如ClientLoggingHandler的channelRead用于打印接收到的消息到日志// 比如 serverBootStrapAcceptor的channelRead //用于向客戶端的SocketChannel的pipeline添加handler//就是把服務器端方法中的childHandler都添加到客戶端的NioSocketChannel的pipeline//具體看serverBootStrapAcceptor的channelRead 方法pipeline.fireChannelRead(readBuf.get(i)); }readBuf.clear();allocHandle.readComplete();// 傳播讀取完畢事件pipeline.fireChannelReadComplete(); // 省略其他代碼
} finally {if (!readPending && !config.isAutoRead()) {removeReadOp();}
}

} ```

可以看出 read() 方法的核心邏輯就是通過 while 循環不斷讀取數據,然后放入 List 中,這里的數據其實就是新連接。每次最多處理16個。

需要重點跟進一下 NioServerSocketChannel 的 doReadMessages() 方法。

接前面NioMessageUnsafe#read

繼續接著NioMessageUnsafe#read看

1.NioServerSocketChannel #doReadMessages

接收&&創建&初始化客戶端連接

```java protected int doReadMessages(List buf) throws Exception { //Netty 先通過 JDK 底層的 accept() 獲取 JDK 原生的 SocketChannel //想想這里 在NIO編程的時候 是做了判斷 如果是OPACCEPT事件 //執行 SocketChannel sChannel = ssChannel.accept(); //這里的accept方法返回的就是原生的SocketChannel SocketChannel ch = SocketUtils.accept(javaChannel()); try { if (ch != null) { //根據原生的 SocketChannel構造 Netty 客戶端 NioSocketChannel //NioSocketChannel 的創建同樣會完成幾件事: //創建核心成員變量 id、unsafe、pipeline; //注冊 SelectionKey.OPREAD 事件; //設置 Channel 的為非阻塞模式; //新建客戶端 Channel 的配置。 //this是NioServerSocketChannel //最后把NioSocketChannel添加到buf返回1 //super(parent, ch, SelectionKey.OP_READ); //這里不是注冊讀事件只是賦值 buf.add(new NioSocketChannel(this, ch)); return 1; } } catch (Throwable t) { logger.warn("Failed to create a new channel from an accepted socket.", t); try { ch.close(); } catch (Throwable t2) { logger.warn("Failed to close a socket.", t2); } } return 0; }

protected AbstractNioChannel(Channel parent, SelectableChannel ch, int readInterestOp) { super(parent); this.ch = ch; //設置讀事件到NioSocketChannel 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);}
}

public static SocketChannel accept(final ServerSocketChannel serverSocketChannel) throws IOException { try { return AccessController.doPrivileged(new PrivilegedExceptionAction () { @Override public SocketChannel run() throws IOException { // 非阻塞模式下,沒有連接請求時,返回null return serverSocketChannel.accept(); } }); } catch (PrivilegedActionException e) { throw (IOException) e.getCause(); } } ```

這時就開始執行第二個步驟:構造 Netty 客戶端 NioSocketChannel。Netty 先通過 JDK 底層的 accept() 獲取 JDK 原生的 SocketChannel,然后將它封裝成 Netty 自己的 NioSocketChannel。

新建 Netty 的客戶端 Channel 的實現原理與上文中我們講到的創建服務端 Channel 的過程是類似的,只是服務端 Channel 的類型是 NioServerSocketChannel,而客戶端 Channel 的類型是 NioSocketChannel。

NioSocketChannel 的創建同樣會完成幾件事:創建核心成員變量 id、unsafe、pipeline;

注冊 SelectionKey.OP_READ 事件;設置 Channel 的為非阻塞模式;新建客戶端 Channel 的配置。

成功構造客戶端 NioSocketChannel 后,接下來會通過 pipeline.fireChannelRead() 觸發 channelRead 事件傳播。對于服務端來說,此時 Pipeline 的內部結構如下圖所示。

圖片6.png

2.pipeline.fireChannelRead

上文中我們提到了一種特殊的處理器 ServerBootstrapAcceptor,在下面它就發揮了重要的作用。channelRead 事件會傳播到 ServerBootstrapAcceptor.channelRead() 方法,channelRead() 會將客戶端 Channel 分配到工作線程組中去執行。具體實現如下:

觸發服務器端hanlder#channelRead

ServerBootstrapAcceptor#channelRead

java //ServerBootstrapAcceptor負責接收客戶端連接 創建連接后,對連接的初始化工作。 // ServerBootstrapAcceptor.channelRead() 方法 public void channelRead(ChannelHandlerContext ctx, Object msg) { final Channel child = (Channel) msg; //childHandler是我們自定義的EchoServer的代理類 child.pipeline().addLast(childHandler); setChannelOptions(child, childOptions, logger); setAttributes(child, childAttrs); try { // 注冊客戶端 Channel到工作線程組 //1.MultithreadEventLoopGroup#register childGroup.register(child).addListener(new ChannelFutureListener() { @Override public void operationComplete(ChannelFuture future) throws Exception { if (!future.isSuccess()) { forceClose(child, future.cause()); } } }); } catch (Throwable t) { forceClose(child, t); } }

ServerBootstrapAcceptor 開始就把 msg 強制轉換為 Channel。難道不會有其他類型的數據嗎?

因為 ServerBootstrapAcceptor 是服務端 Channel 中一個特殊的處理器,而服務端 Channel 的 channelRead 事件只會在新連接接入時觸發,所以這里拿到的數據都是客戶端新連接。

register():注冊客戶端 Channel

java //MultithreadEventLoopGroup#register //從workGroup中選擇一個EventLoop注冊到channel @Override public ChannelFuture register(Channel channel) { return next().register(channel); }

```java //io.netty.channel.nio.AbstractChannel#register0 private void register0(ChannelPromise promise) { try { if (!promise.setUncancellable() || !ensureOpen(promise)) { return; } boolean firstRegistration = neverRegistered; //綁定選擇器 doRegister(); neverRegistered = false; registered = true; //給客戶端添加處理器 pipeline.invokeHandlerAddedIfNeeded(); safeSetSuccess(promise); pipeline.fireChannelRegistered();

//NioServerSocketChannel的注冊不會走進下面if(isActive())//NioSocketChannel可以走進去if(isActive())。因為accept后就active了。if (isActive()) {if (firstRegistration) {//第一次注冊需要觸發pipeline上的hanlder的read事件//實際上就是注冊OP_ACCEPT/OP_READ事件:創建連接或者讀事件//首先會進入DefaultChannelPipeLine的read方法pipeline.fireChannelActive();} else if (config().isAutoRead()) {//第二次注冊的時候beginRead();}}} catch (Throwable t) {// Close the channel directly to avoid FD leak.closeForcibly();closeFuture.setClosed();safeSetFailure(promise, t);}}

```

客戶端SocketChannel綁定selector
AbstractNioChannel#doRegister

java //io.netty.channel.nio.AbstractNioChannel#doRegister @Override protected void doRegister() throws Exception { boolean selected = false; for (;;) { try { logger.info("initial register: " + 0); //這里的事件類型仍然是0 //attachement是NioSocketChannel 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; } } } }

DefaultChannelPipeline.HeadContext#read

java //io.netty.channel.DefaultChannelPipeline.HeadContext#read @Override public void read(ChannelHandlerContext ctx) { //實際上就是注冊OP_ACCEPT/OP_READ事件:創建連接或者讀事件 unsafe.beginRead(); }

```java @Override public final void beginRead() { assertEventLoop();

if (!isActive()) {return;}try {doBeginRead();} catch (final Exception e) {invokeLater(new Runnable() {@Overridepublic void run() {pipeline.fireExceptionCaught(e);}});close(voidPromise());}}

```

```java @Override protected void doBeginRead() throws Exception { // Channel.read() or ChannelHandlerContext.read() was called final SelectionKey selectionKey = this.selectionKey; if (!selectionKey.isValid()) { return; }

readPending = true;final int interestOps = selectionKey.interestOps();//super(parent, ch, SelectionKey.OP_READ);//假設之前沒有監聽readInterestOp,則監聽readInterestOpif ((interestOps & readInterestOp) == 0) {//NioServerSocketChannel: readInterestOp = OP_ACCEPT = 1 << 4 = 16logger.info("interest ops: " + readInterestOp);selectionKey.interestOps(interestOps | readInterestOp);}
}

```

ServerBootstrapAcceptor 通過 childGroup.register() 方法會完成第三和第四兩個步驟.

1.將 NioSocketChannel 注冊到 Worker 工作線程中

2.并注冊 OP_READ 事件到 NioSocketChannel 的事件集合。

在注冊過程中比較有意思的一點是,它會調用 pipeline.fireChannelRegistered() 方法傳播 channelRegistered 事件,然后再調用 pipeline.fireChannelActive() 方法傳播 channelActive 事件。

兜了一圈,這又會回到之前我們介紹的 readIfIsAutoRead() 方法,此時它會將 SelectionKey.OP_READ 事件注冊到 Channel 的事件集合。

添加自定義handler到客戶端SocketChannel
pipeline.invokeHandlerAddedIfNeeded

總結

java ?接受連接本質: ? selector.select()/selectNow()/select(timeoutMillis) 發現 OP_ACCEPT 事件,處理: ? ?SocketChannel socketChannel = serverSocketChannel.accept() ? ?selectionKey = javaChannel().register(eventLoop().unwrappedSelector(), 0, this); ? ?selectionKey.interestOps(OP_READ); ?

關于服務端如何處理客戶端新建連接的具體源碼,我在此就不繼續展開了。這里留一個小任務,建議你親自動手分析下 childGroup.register() 的相關源碼,從而加深對服務端啟動以及新連接處理流程的理解。有了服務端啟動源碼分析的基礎,再去理解客戶端新建連接的過程會相對容易很多。

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

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

相關文章

VB+SQL自動點歌系統設計與實現

摘 要 隨著社會的發展,人類的進步,21世紀人們的生活的水平有所提高,為了滿足人們對生活的需要,豐富業余生活,就需要有一些娛樂的設施來彌補這些空缺,所以開發了自動點歌系統。 論文詳細論述了系統總體設計思想、數據庫設計以及功能模塊設計等,給出了自動點歌系統一般流程…

算法與數據結構(七)--堆

一.堆 1.堆的定義 堆是計算機科學中一類特殊的數據結構的通常&#xff0c;堆通常可以被看做是一顆完全二叉樹的數組對象。 堆的特性 1.它是完全二叉樹&#xff0c;除了樹的最后一層結點不需要是滿的&#xff0c;其他的每一層從左到右都是滿的&#xff0c;如果最后一層結點不…

管理類聯考——邏輯——真題篇——按知識分類——匯總篇——二、論證邏輯——支持加強——第三節——分類3——類比題干支持

文章目錄 第三節 支持加強-分類3-類比題干支持真題(2017-28)-支持加強-正面支持-表達“確實如此”真題(2017-36)-支持加強-正面支持-表達“確實如此”真題(2017-39)-支持加強-正面支持-方法有效或方法可行,但多半不選擇方法無惡果真題(2017-50)-支持加強真題(2018-2…

linux 文件權限識別及其修改

一、文件權限認識 在 Linux 系統中&#xff0c;一切皆文件&#xff0c;目錄也是一種文件形式叫目錄文件&#xff0c;它們的屬性主要包含&#xff1a;索引節點(inode)&#xff0c;類型、權限屬性、鏈接數、所歸屬的用戶和用戶組、最近修改時間等內容。 如下為根目錄下目錄&…

華創云鼎面試:java后端開發

華創云鼎面試: 1、項目:項目業務介紹、項目人員組成 2、分布式鎖用過哪些 基于數據庫的鎖&#xff1a;可以使用關系型數據庫的事務和行級鎖來實現分布式鎖。通過在數據庫中創建一個標志位或特定的鎖表來表示資源的鎖定狀態&#xff0c;其他進程在訪問該資源之前需要先獲取該鎖…

尋路算法(Java 實例代碼源碼包下載)

目錄 尋路算法 Java 實例代碼 src/runoob/graph/Path.java 文件代碼&#xff1a; 尋路算法 圖的尋路算法也可以通過深度優先遍歷 dfs 實現&#xff0c;尋找圖 graph 從起始 s 點到其他點的路徑&#xff0c;在上一小節的實現類中添加全局變量 from數組記錄路徑&#xff0c;fr…

8月18日,每日信息差

1、中國空間站收獲階段性應用成果。?截至目前&#xff0c;空間站已安排在軌實施了110個空間科學研究與應用項目&#xff0c;涉及空間生命科學與人體研究、微重力物理和空間新技術領域&#xff0c;獲得原始實驗數據近100TB&#xff0c;下行了近300個實驗樣品&#xff0c;部分項…

改進YOLO系列:3.添加SOCA注意力機制

添加SOCA注意力機制 1. SOCA注意力機制論文2. SOCA注意力機制原理3. SOCA注意力機制的配置3.1common.py配置3.2yolo.py配置3.3yaml文件配置1. SOCA注意力機制論文 暫未找到 2. SOCA注意力機制原理 3. SOCA注意力機制的配置 3.1common.py配置 ./models/common.p…

Linux 網絡發包流程

哈嘍大家好&#xff0c;我是咸魚 之前咸魚在《Linux 網絡收包流程》一文中介紹了 Linux 是如何實現網絡接收數據包的 簡單回顧一下&#xff1a; 數據到達網卡之后&#xff0c;網卡通過 DMA 將數據放到內存分配好的一塊 ring buffer 中&#xff0c;然后觸發硬中斷CPU 收到硬中…

Lnton羚通關于Optimization在【PyTorch】中的基礎知識

OPTIMIZING MODEL PARAMETERS &#xff08;模型參數優化&#xff09; 現在我們有了模型和數據&#xff0c;是時候通過優化數據上的參數來訓練了&#xff0c;驗證和測試我們的模型。訓練一個模型是一個迭代的過程&#xff0c;在每次迭代中&#xff0c;模型會對輸出進行猜測&…

python3 0基礎學習----數據結構(基礎+練習)

python 0基礎學習筆記之數據結構 &#x1f4da; 幾種常見數據結構列表 &#xff08;List&#xff09;1. 定義2. 實例&#xff1a;3. 列表中常用方法.append(要添加內容) 向列表末尾添加數據.extend(列表) 將可迭代對象逐個添加到列表中.insert(索引&#xff0c;插入內容) 向指定…

8.17校招 內推 面經

綠泡泡&#xff1a; neituijunsir 交流裙&#xff0c;內推/實習/校招匯總表格 1、校招 | 騰訊2024校園招聘全面啟動(內推) 校招 | 騰訊2024校園招聘全面啟動(內推) 2、校招 | 大華股份2024屆全球校園招聘正式啟動(內推) 校招 | 大華股份2024屆全球校園招聘正式啟動(內推) …

國家一帶一路和萬眾創業創新的方針政策指引下,Live Market探索跨境產業的創新發展

現代社會&#xff0c;全球經濟互聯互通&#xff0c;跨境產業也因此而崛起。為了推動跨境產業的創新發展&#xff0c;中國政府提出了“一帶一路”和“萬眾創業、萬眾創新”的方針政策&#xff0c;旨在促進全球經濟的互聯互通和創新發展。在這個大環境下&#xff0c;Live Market積…

Mariadb高可用MHA

本節主要學習了Mariadb高可用MHA的概述&#xff0c;案例如何構建MHA 提示&#xff1a;以下是本篇文章正文內容&#xff0c;下面案例可供參考 一、概述 1、概念 MHA&#xff08;MasterHigh Availability&#xff09;是一套優秀的MySQL高可用環境下故障切換和主從復制的軟件。…

合宙Air724UG LuatOS-Air LVGL API--簡介

為何是 LVGL LVGL 是一個開源的圖形庫&#xff0c;它提供了創建嵌入式 GUI 所需的一切&#xff0c;具有易于使用的圖形元素、漂亮的視覺效果和低內存占用的特點。 LVGL特點&#xff1a; 強大的 控件 &#xff1a;按鈕、圖表、列表、滑動條、圖像等 高級圖形引擎&#xff1a;動…

BIO、NIO和AIO

一.引言 何為IO 涉及計算機核心(CPU和內存)與其他設備間數據遷移的過程&#xff0c;就是I/O。數據輸入到計算機內存的過程即輸入&#xff0c;反之輸出到外部存儲&#xff08;比如數據庫&#xff0c;文件&#xff0c;遠程主機&#xff09;的過程即輸出。 I/O 描述了計算機系統…

插入排序優化——超越歸并排序的超級算法

插入排序及優化 插入排序算法算法講解數據模擬代碼 優化思路一、二分查找二、copy函數 優化后代碼算法的用途題目&#xff1a;數星星&#xff08;POJ2352 star&#xff09;輸入輸出格式輸入格式&#xff1a;輸出格式 輸入輸出樣例輸入樣例輸出樣例 題目講解步驟如下AC 代碼 插入…

HIVE SQL實現分組字符串拼接concat

在Mysql中可以通過group_concat()函數實現分組字符串拼接&#xff0c;在HIVE SQL中可以使用concat_ws()collect_set()/collect_list()函數實現相同的效果。 實例&#xff1a; abc2014B92015A82014A102015B72014B6 1.concat_wscollect_list 非去重拼接 select a ,concat_ws(-…

Linux系統中基于NGINX的代理緩存配置指南

作為一名專業的爬蟲程序員&#xff0c;你一定知道代理緩存在加速網站響應速度方面的重要性。而使用NGINX作為代理緩存服務器&#xff0c;能夠極大地提高性能和效率。本文將為你分享Linux系統中基于NGINX的代理緩存配置指南&#xff0c;提供實用的解決方案&#xff0c;助你解決在…

C語言刷題訓練DAY.8

1.計算單位階躍函數 解題思路&#xff1a; 這個非常簡單&#xff0c;只需要if else語句即可完成 解題代碼&#xff1a; #include <stdio.h>int main() {int t 0;while(scanf("%d",&t)!EOF){if (t > 0)printf("1\n");else if (t < 0)pr…