高效I/O處理:模型與多路復用的探討

目錄

一、了解IO模型

(一)異步IO和同步IO

(二)五種IO快速回顧

二、IO多路復用

(一)IO 多路復用模型

(二)select 實現原理

(三)poll 實現原理

(四)epoll 實現原理

(五)總結

三、總結

參考推薦閱讀


干貨分享,感謝您的閱讀!

在現代計算機系統中,輸入/輸出(I/O)操作的效率直接影響到整體性能與用戶體驗。無論是大型服務器處理海量請求,還是嵌入式設備與傳感器的實時數據交互,了解 I/O 模型及其多路復用機制顯得尤為重要。本文將深入探討 I/O 的五種主要模型,包括阻塞、非阻塞、同步、異步以及信號驅動 I/O,解析它們的工作原理與適用場景。同時,我們還將聚焦多路復用技術的核心概念與實現方式,揭示其在提升并發處理能力、降低資源消耗中的關鍵作用。通過對這些基礎理論的深入理解,您將能夠更有效地設計與優化高性能應用程序,推動技術的不斷進步與創新。

一、了解IO模型

I/O 模型是指在進行輸入輸出操作時,操作系統和應用程序之間如何進行交互的方式。

(一)異步IO和同步IO

上圖中,阻塞式I/O、非阻塞式I/O、I/O復用、信號驅動式I/O 在操作系統層面都是同步IO,它們都會阻塞在數據從內核空間復制到用戶空間的緩沖區;異步IO模型在兩個階段都不會阻塞調用進程,在操作系統層面實現真正的異步IO。

同步 I/O

  • 同步 I/O 模型是指進程發起一個 I/O 操作后,必須等待這個操作完成才能進行下一步操作。在這種模型下,當應用程序發起一個 I/O 請求時,它會被阻塞,直到操作系統將數據從內核空間復制到用戶空間的緩沖區中,然后應用程序才能繼續執行。
  • 阻塞式 I/O、非阻塞式 I/O、I/O 復用、信號驅動式 I/O 這些模型都屬于同步 I/O,因為它們都需要應用程序等待 I/O 操作完成才能進行下一步操作。

異步 I/O

  • 異步 I/O 模型則不同,當應用程序發起一個 I/O 請求后,它可以立即繼續執行其他任務,不需要等待操作完成。當 I/O 操作完成后,操作系統會通知應用程序。
  • 異步 I/O 模型確實能夠在兩個階段都不阻塞調用進程,因為應用程序發起請求后就可以繼續執行其他任務,而不必等待數據從內核空間復制到用戶空間的緩沖區中。
  • 異步 I/O 模型通常需要操作系統或硬件設備的支持,以便在 I/O 操作完成時通知應用程序。通常涉及到事件驅動的編程模式,比如回調函數或事件循環。異步 I/O 通常用于處理大量的并發連接或需要高性能的應用程序中。

(二)五種IO快速回顧

模型描述適用場景
阻塞式 I/O應用程序發起 I/O 請求后被阻塞,直到操作完成。單任務環境,簡單應用程序,對于少量連接或低并發的應用。
非阻塞式 I/O應用程序發起 I/O 請求后繼續執行,但需要通過輪詢等方式檢查操作是否完成。單任務環境,需要處理多個 I/O 事件,但需要謹慎處理輪詢造成的 CPU 消耗。
I/O 復用允許一個進程同時監視多個文件描述符的 I/O 事件,當某個文件描述符準備好時通知應用程序。需要同時處理多個 I/O 事件的場景,如網絡服務器。
信號驅動式 I/O將信號處理函數與文件描述符關聯,當文件描述符準備好進行 I/O 操作時,觸發相應的信號處理函數。某些需要提高應用程序性能的情況,但在處理復雜 I/O 事件時可能變得復雜。
異步 I/O應用程序發起 I/O 請求后立即返回,當操作完成時,操作系統通知應用程序。需要高性能和高并發的應用程序,可以在兩個階段都不阻塞調用進程。

二、IO多路復用

(一)IO 多路復用模型

在使用多路復用模型時,通常會使用像 select()poll()epoll() 這樣的系統調用。這些調用允許應用程序同時監視多個文件描述符,等待其中任何一個文件描述符就緒(即有數據可讀或可寫)。當文件描述符就緒時,這些系統調用會返回,并告知應用程序哪些文件描述符已經就緒。

然后,應用程序可以進一步操作就緒的文件描述符,比如調用 recvfrom() 函數將數據從內核空間拷貝到用戶空間。盡管這個階段仍然是阻塞的,但因為在 select() 或其他多路復用系統調用中已經知道哪些文件描述符有數據可讀,因此整體上效率會有很大的提升。

(二)select 實現原理

select() 函數是一個用于多路復用 I/O 的系統調用,它允許一個進程監視多個文件描述符的狀態,以確定它們是否處于可讀、可寫或異常狀態。其基本原理是在內核中檢查指定的文件描述符,并在其中任何一個文件描述符就緒時返回。

具體地,select() 函數參數分析如下:

  1. nfds:要檢查的文件描述符的數量,即最大文件描述符加一。
  2. readfds:指向包含要檢查是否可讀的文件描述符集合的指針。
  3. writefds:指向包含要檢查是否可寫的文件描述符集合的指針。
  4. exceptfds:指向包含要檢查是否有異常情況的文件描述符集合的指針。
  5. timeout:超時時間,指定 select() 調用的最長等待時間,當為 NULL 時表示永遠等待。

調用 select() 后,內核會遍歷傳入的文件描述符集合,檢查它們的狀態。如果有任何一個文件描述符就緒(可讀、可寫或異常),select() 就會返回。返回后,可以通過檢查相應的文件描述符集合來確定哪些文件描述符處于就緒狀態。

在實際使用中,通常會使用 FD_SET()FD_CLR()FD_ISSET() 等宏來設置和檢查文件描述符集合。

具體可以看下以下代碼中的標注:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>#define MAXCLINE 5       // 最大連接數
#define MAXBUF 1024      // 緩沖區大小int main(void)
{int sock_fd, i, ret;int fd[MAXCLINE];                    // 存放連接的文件描述符隊列struct sockaddr_in server_addr, client_addr;socklen_t sin_size = sizeof(struct sockaddr_in);char buf[MAXBUF];// 創建 socketsock_fd = socket(AF_INET, SOCK_STREAM, 0);if (sock_fd < 0) {perror("socket");exit(EXIT_FAILURE);}// 綁定 socketserver_addr.sin_family = AF_INET;server_addr.sin_port = htons(8888);server_addr.sin_addr.s_addr = INADDR_ANY;memset(&(server_addr.sin_zero), 0, sizeof(server_addr.sin_zero));if (bind(sock_fd, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0) {perror("bind");exit(EXIT_FAILURE);}// 監聽 socketif (listen(sock_fd, 5) < 0) {perror("listen");exit(EXIT_FAILURE);}// 接受連接,并將連接的文件描述符存入數組for (i = 0; i < MAXCLINE; i++) {fd[i] = accept(sock_fd, (struct sockaddr *)&client_addr, &sin_size);if (fd[i] < 0) {perror("accept");exit(EXIT_FAILURE);}}// 循環監聽連接上的數據是否到達while (1) {fd_set fdsr;                     // 用于存放需要監聽的文件描述符集合FD_ZERO(&fdsr);                  // 清空文件描述符集合// 將要監聽的文件描述符加入集合int max = 0;for (i = 0; i < MAXCLINE; i++) {FD_SET(fd[i], &fdsr);if (fd[i] > max)max = fd[i];}// 調用 select 進行多路復用ret = select(max + 1, &fdsr, NULL, NULL, NULL);if (ret < 0) {perror("select");exit(EXIT_FAILURE);}// 遍歷文件描述符集合,判斷哪些連接有數據到達for (i = 0; i < MAXCLINE; i++) {if (FD_ISSET(fd[i], &fdsr)) {       // 文件描述符有數據到達ret = recv(fd[i], buf, sizeof(buf), 0);if (ret < 0) {perror("recv");exit(EXIT_FAILURE);} else if (ret == 0) {          // 對方關閉連接printf("Connection closed by client.\n");close(fd[i]);FD_CLR(fd[i], &fdsr);       // 從文件描述符集合中清除} else {buf[ret] = '\0';printf("Received message from client %d: %s\n", i + 1, buf);}}}}return 0;
}

select 的執行過程

在服務器進程 A 啟動的時候,要監聽的連接的 socket 文件描述符是 3、4、5,如果這三個連接均沒有數據到達網卡,則進程 A 會讓出 CPU,進入阻塞狀態,同時會將進程 A 的進程描述符和被喚醒時用到的回調函數組成等待隊列項加入到 socket 對象 3、4、5 的進程等待隊列中,注意,這時 select 調用時fdsr 文件描述符集會從用戶空間拷貝到內核空間,如下圖所示:

當網卡接收到數據,然后網卡通過中斷信號通知 CPU 有數據到達,執行中斷程序,中斷程序主要做了兩件事:

  • 將網絡數據寫入到對應 socket 的數據接收隊列里面;
  • 喚醒隊列中的等待進程 A,重新將進程 A 放入 CPU 的運行隊列中;

假設連接 3、5 有數據到達網卡,注意,這時 select 調用結束時,fdsr 文件描述符集會從內核空間拷貝到用戶空間:

(三)poll 實現原理

poll() 函數是一種多路復用 I/O 模型,與 select() 類似,允許一個進程監視多個文件描述符,等待其中任何一個文件描述符就緒(即有數據可讀或可寫):

  1. 準備文件描述符數組:在調用 poll() 函數之前,需要準備一個 struct pollfd 類型的數組,數組的每個元素對應一個待監視的文件描述符。結構體中包含文件描述符的值以及所關心的事件,如可讀、可寫等。

  2. 調用 poll():一旦文件描述符數組準備好,程序會調用 poll() 函數,將數組和數組中元素的個數傳遞給它。poll() 函數會在這些文件描述符中的任何一個就緒時返回。

  3. 檢查返回值:一旦 poll() 函數返回,程序會檢查返回值,以確定哪些文件描述符已經就緒。通常返回值表示就緒文件描述符的個數。

  4. 處理就緒文件描述符:程序員可以遍歷檢查每個文件描述符對應的 pollfd 結構體,以確定哪些文件描述符處于就緒狀態。然后可以執行相應的讀取或寫入操作。

在內核層面,poll() 函數的實現通常會使用輪詢機制,檢查每個文件描述符是否已經就緒。不同于 select()poll() 傳遞的是指向結構體數組的指針,因此無需復制文件描述符集合到內核空間,這在一定程度上減少了開銷。

select() 不同的是,poll() 沒有最大文件描述符數的限制,因為它使用了結構體數組而不是位圖來表示文件描述符。因此poll() 在處理大量文件描述符時,通常比 select() 更有效率。

使用 poll() 函數簡單實現多路復用 I/O:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <errno.h>
#include <string.h>
#include <poll.h>#define MAX_CLIENTS 5
#define PORT 8888int main() {int i, ret, sockfd, newsockfd;struct sockaddr_in serv_addr, cli_addr;socklen_t addrlen = sizeof(struct sockaddr_in);struct pollfd fds[MAX_CLIENTS + 1]; // +1 for the listening socket// Create socketsockfd = socket(AF_INET, SOCK_STREAM, 0);if (sockfd < 0) {perror("socket");exit(EXIT_FAILURE);}// Set up server addressmemset(&serv_addr, 0, sizeof(serv_addr));serv_addr.sin_family = AF_INET;serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);serv_addr.sin_port = htons(PORT);// Bind socketif (bind(sockfd, (struct sockaddr*)&serv_addr, sizeof(serv_addr)) < 0) {perror("bind");exit(EXIT_FAILURE);}// Listen for incoming connectionsif (listen(sockfd, MAX_CLIENTS) < 0) {perror("listen");exit(EXIT_FAILURE);}// Initialize pollfd structuresfds[0].fd = sockfd;fds[0].events = POLLIN;for (i = 1; i <= MAX_CLIENTS; i++) {fds[i].fd = -1;}while (1) {// Call pollret = poll(fds, MAX_CLIENTS + 1, -1);if (ret < 0) {perror("poll");exit(EXIT_FAILURE);}// Check for new connectionif (fds[0].revents & POLLIN) {newsockfd = accept(sockfd, (struct sockaddr*)&cli_addr, &addrlen);if (newsockfd < 0) {perror("accept");exit(EXIT_FAILURE);}// Add new client to fds arrayfor (i = 1; i <= MAX_CLIENTS; i++) {if (fds[i].fd == -1) {fds[i].fd = newsockfd;fds[i].events = POLLIN;break;}}if (i > MAX_CLIENTS) {fprintf(stderr, "Too many clients\n");close(newsockfd);}if (--ret <= 0) continue;}// Check for data from clientsfor (i = 1; i <= MAX_CLIENTS; i++) {if (fds[i].fd != -1 && (fds[i].revents & POLLIN)) {char buffer[1024];ret = recv(fds[i].fd, buffer, sizeof(buffer), 0);if (ret < 0) {perror("recv");close(fds[i].fd);fds[i].fd = -1;} else if (ret == 0) {printf("Client disconnected\n");close(fds[i].fd);fds[i].fd = -1;} else {printf("Received message from client %d: %s\n", i, buffer);}if (--ret <= 0) break;}}}close(sockfd);return 0;
}
  • 在初始化 pollfd 結構體數組之后,通過 poll() 函數等待任何一個文件描述符就緒。
  • 如果監聽套接字(sockfd)上有新連接到來,poll() 函數會檢測到并調用 accept() 接受連接,然后將新的客戶端套接字添加到 pollfd 數組中。
  • 如果某個客戶端套接字上有數據到達,poll() 函數同樣會檢測到并調用 recv() 接收數據,并進行相應的處理。

(四)epoll 實現原理

epoll 是 Linux 系統提供的一種高效的多路復用 I/O 模型,相比于 selectpoll,它在處理大量連接時有更好的性能表現。

數據結構

  1. epoll 使用了三種主要數據結構:epoll_create() 創建的 epoll 實例,epoll_ctl() 用于修改監聽的文件描述符集合,以及 epoll_wait() 用于等待就緒事件的函數。
  2. 內核維護了一個紅黑樹(rbtree),用于存儲需要監聽的文件描述符。這個樹的節點是一個 epitem 結構體,包含文件描述符、事件類型等信息。
  3. 另外,內核還維護了一個鏈表,用于存儲當前就緒的事件,這個鏈表的節點是 rdllink 結構體。

epoll 實例

  • 使用 epoll_create() 創建一個 epoll 實例,返回一個文件描述符,這個描述符代表了一個 epoll 對象。
  • epoll 實例是一個文件描述符,通過對這個描述符進行操作,可以控制對哪些文件描述符進行監聽以及對就緒事件的處理。

添加和刪除文件描述符

  • 使用 epoll_ctl() 函數向 epoll 實例中添加或刪除要監聽的文件描述符。
  • 添加文件描述符時,會創建一個 epitem 結構體,將其插入到紅黑樹中。
  • 刪除文件描述符時,會將對應的 epitem 結構體從紅黑樹中移除。

等待就緒事件

  • 使用 epoll_wait() 函數等待文件描述符的就緒事件。
  • 內核會遍歷紅黑樹,檢查哪些文件描述符已經就緒,將其加入到就緒鏈表中。
  • 用戶空間可以通過 epoll_wait() 返回的事件列表來獲取就緒的文件描述符,并進行相應的處理。

總的來說,epoll 的實現原理涉及了三個主要的數據結構:紅黑樹、鏈表和 epoll 實例。它通過將文件描述符的就緒事件保存在內核空間的數據結構中,避免了 selectpoll 中頻繁的內存復制操作,從而提高了處理大量連接時的效率。

epoll 的基本用法是:

int main(void) {struct epoll_event events[5];int epfd = epoll_create(10);         // 創建一個 epoll 對象......for(i = 0; i < 5; i++){static struct epoll_event ev;.....ev.data.fd = accept(sock_fd, (struct sockaddr *)&client_addr, &sin_size);ev.events = EPOLLIN;epoll_ctl(epfd, EPOLL_CTL_ADD, ev.data.fd, &ev);  // 向 epoll 對象中添加要管理的連接}while(1){nfds = epoll_wait(epfd, events, 5, 10000);   // 等待其管理的連接上的 IO 事件for(i=0; i<nfds; i++){......read(events[i].data.fd, buff, MAXBUF)}}

主要涉及到三個函數:

 // 創建一個 eventpoll 內核對象
int epoll_create(int size);  
// 將連接到socket對象添加到 eventpoll 對象上,epoll_event是要監聽的事件
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);   
// 等待連接 socket 的數據是否到達
int epoll_wait(int epfd, struct epoll_event *events,int maxevents, int timeout);      

epoll_create

epoll_ctl

epoll_wait

(五)總結

下面是使用 selectpollepoll 實現 IO 多路復用的簡單對比分析:

特點selectpollepoll
文件描述符數量限制通常受限于文件描述符數量通常受限于文件描述符數量沒有顯著限制
內核空間數據結構使用位圖表示文件描述符集合使用數組表示文件描述符集合使用紅黑樹存儲文件描述符
內存復制開銷需要將文件描述符集合復制到內核空間需要將文件描述符集合復制到內核空間無需將文件描述符集合復制到內核空間
時間復雜度O(n)O(n)O(log n)
適用場景文件描述符數量少且不頻繁變化文件描述符數量少且不頻繁變化文件描述符數量大且頻繁變化
并發連接處理性能性能較差性能較好性能最佳

從上表中可以看出:

  • selectpoll 的效率都受到文件描述符數量的限制,并且需要將文件描述符集合復制到內核空間,因此在處理大量連接時效率較低。
  • epoll 利用了紅黑樹來存儲文件描述符,避免了內存復制的開銷,并且在文件描述符數量較大且頻繁變化時性能最佳。
  • 因此,對于高并發的網絡應用,通常選擇 epoll 來實現 IO 多路復用,而 selectpoll 則適用于文件描述符數量較少且不頻繁變化的情況。

三、總結

在本文中,我們探討了 I/O 操作的五種主要模型及其在現代計算機系統中的應用,分別是阻塞 I/O、非阻塞 I/O、同步 I/O、異步 I/O 和信號驅動 I/O。通過分析這些模型的特點與適用場景,我們了解到每種模型都有其獨特的優勢與局限性,開發者可以根據具體需求選擇最合適的 I/O 方案。

此外,我們深入討論了多路復用技術,包括 select、poll 和 epoll 等實現方式,揭示了它們在處理高并發請求中的重要性。多路復用不僅能夠有效減少系統資源的浪費,還能夠提升 I/O 操作的響應速度,使得高性能應用得以順暢運行。

通過對 I/O 模型與多路復用技術的深入理解,開發者將能夠更好地優化應用程序,提升整體性能,為用戶提供更加流暢的體驗。隨著技術的不斷發展,掌握這些基礎知識將為您在計算機科學與軟件開發領域的進一步探索打下堅實的基礎。

參考推薦閱讀

徹底搞懂IO模型:五種IO模型透徹分析 | 駿馬金龍

高性能IO模型分析-IO模型簡介(一) - 知乎

https://blog.51cto.com/u_15287666/4917767

IO模型:BIO、NIO、AIO的解析與應用-百度開發者中心

談談你對IO多路復用機制的理解-io 多路復用

https://www.cnblogs.com/yrxing/p/14143644.html

https://www.cnblogs.com/88223100/p/Deeply-learn-the-implementation-principle-of-IO-multiplexing-select_poll_epoll.html

你管這破玩意叫 IO 多路復用?-阿里云開發者社區

select - 徹底搞懂IO多路復用 - 個人文章 - SegmentFault 思否

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

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

相關文章

行列式展開定理(第三種定義) 線性代數

目錄 1.余子式 2代數余子式 3行列式展開公式&#xff08;常用&#xff09; 本篇的用途是關于三階以上行列式的一般解法。因為對于三階以上行列式我們沒有類似于2階和三階一樣的特殊的求值辦法&#xff0c;而對于我們上一篇講的辦法來說又太復雜了&#xff0c;一般考試幾乎不…

一種輕量級IDS,使用新型特征選擇方法進行早期APT檢測

大家讀完覺得有幫助記得關注和點贊&#xff01;&#xff01;&#xff01; 高級持續性威脅 (APT) 是一種多階段、高度復雜且隱蔽的網絡威脅形式&#xff0c;它通過獲得對網絡的未授權訪問來竊取有價值的數據或破壞目標網絡。這些威脅通常在很長一段時間內未被發現&#xff0c;這…

深入理解 let、var 和 const

JavaScript 中的變量聲明有三種主要方式&#xff1a;var、let 和 const。理解它們之間的差異對于編寫清晰、有效的代碼至關重要。本文將深入探討這三種聲明方式的區別、使用場景以及潛在的陷阱。 一、var 關鍵字 1.1 特點 函數作用域&#xff1a;var 聲明的變量在函數內是局…

RT thread 在gd32f303平臺下rtc bug date獲取時間錯誤始終是1970

現象 時間設置指令 date 2025 6 18 10 28 00 時間獲取指令 date date指定顯示設置OK,但是返回的時間始終是Thu Jan 1 08:00:00 1970 msh >date local time: Thu Jan 1 08:00:00 1970 timestamps: 0 timezone: UTC+

jieba中lcut與cut的區別及用法

jieba 庫中的 cut 和 lcut 是中文分詞的核心函數&#xff0c;兩者的核心區別在于??返回類型??和??適用場景??&#xff0c;具體對比如下&#xff1a; ?? 1. ??核心區別?? ??函數????返回類型????特點????等價操作??jieba.cut生成器&#xff08;G…

LoRA、QLoRA是什么

一&#xff1a; LoRA&#xff08;Low-Rank Adaptation&#xff0c;低秩適應&#xff09;是一種高效的大模型參數微調技術&#xff0c;由Meta在2021年提出。它通過凍結預訓練模型參數&#xff0c;僅訓練少量新增的低秩矩陣&#xff0c;大幅減少了需要訓練的參數量&#xff0c;同…

【web應用】在 Vue 3 中實現餅圖:使用 Chart.js實現餅圖顯示數據分析結果

文章目錄 前言一、準備工作二、實現餅圖組件三、關鍵點解析四、實現效果總結 前言 在現代 Web 應用中&#xff0c;數據可視化是不可或缺的一部分。無論是展示統計信息還是監控關鍵指標&#xff0c;圖表都能幫助用戶更直觀地理解數據。在 Vue 3 項目中&#xff0c;我們可以使用…

分頁數據不準問題分析與解決

大綱 &#x1f4d6; 1、場景 &#x1fab5;2、原因 &#x1f525;3、解決方式&#xff1a;游標分頁 &#x1f4cf;4、一點思考&#x1f4a1;5、全表查詢的優化思路 &#x1f345; 記錄一個分頁不準的問題 1、場景 &#x1fab5; 調用一個第三方List接口&#xff08;帶分頁&am…

MyBatis原理剖析(三)--加載配置文件

下面我們正式進入mybatis的源碼學習&#xff0c;之前我們已經了解過mybatis中通過配置文件來保證與數據庫的交互。配置文件分為核心配置文件和映射配置文件&#xff0c;核心配置文件的主要作用就是加載數據庫的一些配置信息而映射配置文件則是執行對應的sql語句。同時核心配置文…

C++(運算符重載)

一.友元 C中使用關鍵字friend可以在類外訪問所有的成員&#xff0c;包括私有成員&#xff08;之前提到過封裝的核心思想是隱藏內部實現細節&#xff0c;通過公共接口控制訪問&#xff09;&#xff0c;所以友元可以突破封裝的限制訪問數據&#xff0c;盲目使用會導致程序穩定性…

XR-RokidAR-UXR3.0-Draggable 腳本解析

using System.Collections.Generic; using Rokid.UXR.Utility; using UnityEngine; using UnityEngine.EventSystems;namespace Rokid.UXR.Interaction {/// <summary>/// Draggable 拖拽組件/// </summary>// [RequireComponent(typeof(RayInteractable))]public …

GitHub 趨勢日報 (2025年06月17日)

&#x1f4ca; 由 TrendForge 系統生成 | &#x1f310; https://trendforge.devlive.org/ &#x1f310; 本日報中的項目描述已自動翻譯為中文 &#x1f4c8; 今日獲星趨勢圖 今日獲星趨勢圖 1022 anthropic-cookbook 986 awesome-llm-apps 910 fluentui-system-icons 754 r…

NodeJS的中間件是什么

說簡單一點&#xff0c;中間件就是在你的請求和業務邏輯之間做一層攔截。 在 Node.js 中&#xff0c;中間件&#xff08;Middleware&#xff09; 是一種函數&#xff0c;它在 請求&#xff08;Request&#xff09;到達路由處理器之前&#xff0c;或在 響應&#xff08;Respons…

MCAL學習(6)——診斷、DCM

1.診斷概述 汽車診斷就是通過汽車總線&#xff08;CAN LIN Eth&#xff09;來進行診斷會話&#xff0c;大部分通過CAN總線通訊進行請求與響應。 1.診斷分層 DCM內部支持UDS服務和OBD服務&#xff08;排放&#xff0c;動力&#xff09;。 以統一診斷服務UDS為例&#xff0c;應…

kafka-生產者-(day-4)

day-3 BufferPool 產生原因&#xff1a;ByteBuffer的創建和釋放都是比較耗費資源的&#xff0c;為了實現內存的高效利用&#xff0c;產生了他。他會對特定大小的ByteBuffer進行管理 BufferPool的字段 free:是一個ArrayDeque隊列&#xff0c;緩存指定大小的ByteBuffer對象Re…

java 驗證ip是否可達

默認IP的設備已開放ping功能 代碼 public class PingTest {public static void main(String[] args) throws Exception {String ip "192.168.21.101";boolean reachable InetAddress.getByName(ip).isReachable(3000);System.out.println(ip (reachable ? &quo…

LeetCode 2187.完成旅途的最少時間

題目&#xff1a; 給你一個數組 time &#xff0c;其中 time[i] 表示第 i 輛公交車完成 一趟旅途 所需要花費的時間。 每輛公交車可以 連續 完成多趟旅途&#xff0c;也就是說&#xff0c;一輛公交車當前旅途完成后&#xff0c;可以 立馬開始 下一趟旅途。每輛公交車 獨立 運…

永磁同步電機無速度算法--基于正切函數鎖相環的滑模觀測器

最近在學習鎖相環&#xff0c;后續會記錄一下了解到的幾種PLL。 一、原理介紹 傳統鎖相環控制框圖如下所示 在電機正轉時&#xff0c;傳統鎖相環可以實現很好的轉速和轉子位置估計&#xff0c;但是當電機反轉&#xff0c;反電動勢符號發生變化&#xff0c;系統估計轉子位置最…

Vim-vimrc 快捷鍵映射

Vim-vimrc 快捷鍵映射 文章目錄 Vim-vimrc 快捷鍵映射Leader 鍵快捷鍵映射&#xff1a;插入特定字符插入 --插入 ##插入 解釋Leader鍵設置快速插入分隔線 Leader 鍵 我們還將 , 設置為 Leader 鍵&#xff0c;使得其他快捷鍵映射更加簡潔。 let mapleader ","快捷鍵…

SylixOS armv7 任務切換

SylixOS 操作系統下&#xff0c;任務切換可以分為兩種 中斷退出時&#xff0c;執行的任務切換&#xff08;_ScheduleInt&#xff09;內核退出時&#xff0c;執行的任務切換&#xff08;_Schedule&#xff09; 下面分別講講這兩種任務切換 1、中斷退出時任務切換 關于 ARM 架…