Unix/Linux 系統中的 `writev` 系統調用

<摘要>
本文對 Unix/Linux 系統中的 writev 系統調用進行了全面深入的解析。內容涵蓋了其產生的背景(從傳統 write 的局限性到分散/聚集 I/O 概念的引入)、核心概念(如 struct iovec、系統調用流程)。重點剖析了其設計意圖:減少系統調用次數以提升性能、簡化編程模型、以及原子性操作的優點。通過兩個詳盡的代碼實例(HTTP 響應頭和日志記錄系統)及其 Makefile、編譯運行步驟,生動展示了其應用場景和實現流程。文章結合 TCP/IP 協議棧,深入解析了 writev 與網絡交互的內部機制,并通過時序圖清晰描繪了數據從用戶緩沖區到網絡報文的完整旅程。最后,通過對比表格總結了其優缺點,并提供了選型建議,為開發者深入理解和正確使用 writev 提供了系統性的指導。


<解析>

1. 背景與核心概念

1.1 歷史背景與演進脈絡

在計算技術的早期階段,輸入/輸出(I/O)操作是程序性能的主要瓶頸之一。傳統的 Unix I/O 模型建立在readwrite這兩個基本系統調用之上。它們的功能非常直觀:read將數據從文件描述符(如文件、套接字)讀入一個連續的內存緩沖區,而write則將一個連續的內存緩沖區中的數據寫入文件描述符。

這種“一個系統調用,一個緩沖區”的模型在很長一段時間內都是主流。然而,隨著網絡應用和高性能服務器的發展,其局限性日益凸顯。許多應用場景天然地需要處理非連續的多塊數據:

  • 網絡協議棧:例如,一個 HTTP 響應可能由協議頭(Header)和實體內容(Body)組成,這兩部分數據通常存儲在不同的內存區域(例如,頭是常量字符串,體是動態讀取的文件內容或數據庫查詢結果)。使用傳統的write,服務器需要先發送頭,再發送體,這至少需要兩次系統調用。
  • 數據庫系統:一條記錄可能由多個字段組成,這些字段分散在不同的數據結構中。在寫入日志文件(WAL)或進行網絡傳輸時,需要將這些分散的字段組合起來。
  • 科學計算:大型矩陣或數組可能以非連續塊的形式存儲。

writev出現之前,開發者主要有兩種應對策略:

  1. 多次系統調用(Multiple write calls):對每一塊數據分別調用write。這種方法簡單,但性能差。系統調用本身具有不可忽視的開銷,因為它需要從用戶態切換到內核態,處理上下文,然后再切換回來。頻繁的切換會消耗大量的 CPU 周期。此外,對于網絡套接字,多次小數據的write調用可能會導致著名的“Nagle算法”與“TCP_CORK”選項的交互問題,產生不必要的網絡報文延遲。
  2. 內存拷貝(Memory Copy):使用一個大的臨時緩沖區,在用戶空間使用memcpy將多塊數據拼接成一個連續的數據塊,然后只調用一次write。這種方法減少了系統調用,但代價是多次內存拷貝。內存拷貝同樣需要 CPU 時間,尤其當數據量很大時,這種開銷會非常顯著,而且還需要管理臨時緩沖區的生命周期,增加了程序的復雜性。

為了從根本上解決這個問題,分散/聚集 I/O(Scatter/Gather I/O)的概念被引入操作系統。該技術允許一次系統調用操作多個分散的內存緩沖區。對應的系統調用就是readv(聚集讀)和writev(分散寫)。

  • readv:從文件描述符讀入數據,并分散地存儲到多個緩沖區中。
  • writev:從多個緩沖區聚集數據,并一次性寫入文件描述符。

writev系統調用首次出現在 BSD 4.2 Unix 中,后來被 POSIX.1 標準采納,成為如今所有現代 Unix-like 系統(包括 Linux、macOS 和各種BSD)的標準接口。

1.2 核心概念與關鍵術語
  • 分散/聚集 I/O (Scatter/Gather I/O):一種輸入輸出模型,允許單個系統調用從多個內存緩沖區讀取數據(聚集)或將數據寫入多個內存緩沖區(分散)。它是高性能服務器編程的關鍵技術之一。
  • 系統調用 (System Call):操作系統內核為運行在用戶空間的程序提供的接口。是用戶程序請求內核執行特權操作(如 I/O)的唯一方式。writev就是一個系統調用。
  • struct iovec:這是 writev 操作的核心數據結構,用于描述一個內存緩沖區。它在頭文件 <sys/uio.h> 中定義。
    struct iovec {void   *iov_base;  /* Pointer to the start of the buffer. */size_t  iov_len;   /* Size of the buffer in bytes. */
    };
    
    • iov_base:指向緩沖區起始地址的指針。
    • iov_len:該緩沖區的長度。
  • 文件描述符 (File Descriptor):一個非負整數,用于標識一個打開的文件、套接字、管道或其他 I/O 資源。writev 的第一個參數就是一個文件描述符。
  • 原子性 (Atomicity):這是 writev 一個非常重要的特性。對于普通文件,它意味著此次寫操作的數據不會與其他進程的寫操作交織在一起。對于管道和套接字(在 FIFO 模式下),它進一步保證了一次 writev 調用所寫入的數據將會被一次 read 調用完整讀取(只要請求的字節數足夠多),不會被拆散。這對于基于消息的協議至關重要。

2. 設計意圖與考量

writev的設計并非偶然,其背后蘊含著對性能、編程模型和可靠性的深刻考量。

2.1 核心目標:性能優化

這是設計writev最直接、最主要的目標。它通過兩種方式提升性能:

  1. 減少系統調用次數:這是最顯著的收益。將 N 次 write 調用合并為 1 次 writev 調用,減少了 N-1 次用戶態到內核態的上下文切換開銷。在內核處理速度極快而系統調用相對昂貴的場景下(如高性能網絡服務器),這種優化效果極其明顯。
  2. 減少內存拷貝:避免了用戶空間“申請臨時緩沖區 -> 多次memcpy -> write -> 釋放緩沖區”的繁瑣過程。數據直接從其原本的位置被內核讀取并發送,節省了 CPU 周期和內存帶寬。
2.2 核心目標:簡化編程模型

writev 允許程序直接操作分散的數據結構,而無需為了 I/O 操作而去改變它們的內存布局或進行額外的拼接。這使得程序邏輯更清晰,更符合“零拷貝”(Zero-copy)的優化思想。代碼不再需要關心如何管理那個臨時的、僅用于拼接的緩沖區,減少了出錯的可能(如緩沖區溢出)。

2.3 具體考量因素
  1. 原子性保證:如前所述,對于管道和套接字,原子性是一個關鍵特性。設計者確保writev的行為是原子的,這簡化了基于消息的協議實現。接收方可以確信一次read調用獲取的數據正好是發送方一次writev調用發送的完整消息單元(在合理緩沖區大小下),而不會出現消息被截斷或粘合的情況。
  2. 參數設計writev的接口設計得非常通用。
    ssize_t writev(int fd, const struct iovec *iov, int iovcnt);
    
    • fd:目標文件描述符,兼容所有類型。
    • iov:指向iovec結構數組的指針,可以描述任意數量、任意位置、任意大小的緩沖區。
    • iovcnt:指定數組中元素的數量,操作系統通常會對其上限進行限制(如 Linux 的 IOV_MAX,通常為 1024)。這個參數防止了數組越界,提供了安全性。
      這種設計使其能夠適應幾乎所有的分散輸出場景。
  3. 內核實現效率:內核在處理writev時,需要遍歷iov數組,將每個緩沖區地址和長度信息映射到內核空間,然后安排輸出順序。這個開銷遠小于執行多次完整的write系統調用。對于網絡套接字,內核最終通常會將所有分散的數據收集起來,填充到一個或多個 TCP/IP 報文段中再發送出去,這個過程對用戶是透明的。
2.4 權衡與局限性
  • 不總是最佳選擇:如果數據本身已經是連續的,那么直接使用write顯然更簡單、更直接。使用writev來處理單塊數據反而增加了不必要的復雜性(需要構建iovec數組)。
  • 平臺依賴性:雖然writev是 POSIX 標準,但其性能表現和某些具體限制(如IOV_MAX的具體值)可能因操作系統實現而異。
  • 調試復雜性:由于數據來源是分散的,在調試 I/O 問題時,定位是哪個緩沖區出的問題可能會比處理單個緩沖區稍顯復雜。

3. 實例與應用場景

下面通過兩個經典的現實案例來展示writev的應用。

3.1 實例一:HTTP 服務器發送響應

這是writev最經典的應用場景。一個 HTTP 響應通常由狀態行、多個頭部字段、一個空行和響應體組成。這些部分通常來源于不同的地方。

應用場景:一個簡單的 HTTP/1.1 服務器需要向客戶端發送一個成功的響應,包含一個簡單的 HTML 頁面。

具體實現流程

  1. 構建狀態行和頭部字段(通常是字符串常量或小塊內存)。
  2. 從磁盤讀取請求的文件內容到另一個大的內存緩沖區(如通過mmapread)。
  3. 使用writev將頭部和體一次性寫入套接字。

帶注釋的完整代碼

http_server_writev.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <sys/uio.h> // For struct iovec#define PORT 8080
#define RESPONSE_HEADER "HTTP/1.1 200 OK\r\n"         \"Server: MyServer\r\n"        \"Content-Type: text/html\r\n" \"Connection: close\r\n"       \"\r\n" // The empty line ending headers
#define RESPONSE_BODY "<html><body><h1>Hello, writev!</h1></body></html>\r\n"int main() {int server_fd, new_socket;struct sockaddr_in address;int opt = 1;int addrlen = sizeof(address);// 1. Create socket file descriptorif ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) == 0) {perror("socket failed");exit(EXIT_FAILURE);}// 2. Set socket optionsif (setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR | SO_REUSEPORT, &opt, sizeof(opt))) {perror("setsockopt");exit(EXIT_FAILURE);}address.sin_family = AF_INET;address.sin_addr.s_addr = INADDR_ANY;address.sin_port = htons(PORT);// 3. Bind the socket to the network address and portif (bind(server_fd, (struct sockaddr *)&address, sizeof(address)) < 0) {perror("bind failed");exit(EXIT_FAILURE);}// 4. Listen for incoming connectionsif (listen(server_fd, 3) < 0) {perror("listen");exit(EXIT_FAILURE);}printf("Server listening on port %d...\n", PORT);// 5. Accept an incoming connectionif ((new_socket = accept(server_fd, (struct sockaddr *)&address, (socklen_t*)&addrlen)) < 0) {perror("accept");exit(EXIT_FAILURE);}// 6. Prepare the data to be sent using writev// Our response consists of two parts: the header and the body.// We define an array of iovec structures to describe these two buffers.char header_buf[] = RESPONSE_HEADER; // Buffer for header (on stack)char body_buf[] = RESPONSE_BODY;     // Buffer for body (on stack)struct iovec iov[2]; // We have two disjoint buffers// First buffer: HTTP headeriov[0].iov_base = header_buf;iov[0].iov_len = strlen(header_buf);// Second buffer: HTTP response bodyiov[1].iov_base = body_buf;iov[1].iov_len = strlen(body_buf);// 7. Use writev to send both buffers in one system callssize_t bytes_sent = writev(new_socket, iov, 2);if (bytes_sent < 0) {perror("writev failed");} else {printf("Successfully sent %zd bytes of response.\n", bytes_sent);}// 8. Close the client socket and server socketclose(new_socket);close(server_fd);return 0;
}

Makefile

CC=gcc
CFLAGS=-Wallall: http_serverhttp_server: http_server_writev.c$(CC) $(CFLAGS) -o $@ $<clean:rm -f http_server

編譯與運行

  1. 保存代碼到文件,并運行 make 進行編譯。
  2. 運行生成的可執行文件:./http_server
  3. 使用瀏覽器訪問 http://localhost:8080 或使用 curl 命令:curl http://localhost:8080
  4. 服務器終端將打印發送的字節數,客戶端將收到完整的 HTTP 響應。
3.2 實例二:高性能日志記錄系統

日志消息通常包含固定的元數據(時間戳、日志級別、文件名)和可變的消息內容。使用writev可以避免將這兩部分拼接成一個字符串,從而提升日志寫入性能。

應用場景:一個服務程序需要將格式化的日志行寫入文件或標準錯誤。

具體實現流程

  1. 獲取當前時間,格式化成字符串(第一部分緩沖區)。
  2. 定義固定的日志級別和項目標識符字符串(第二、三部分緩沖區)。
  3. 用戶提供的可變消息內容(第四部分緩沖區)。
  4. 換行符(第五部分緩沖區)。
  5. 使用writev將所有部分一次性寫入日志文件描述符。

帶注釋的完整代碼

logger_writev.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <unistd.h>
#include <sys/uio.h> // For struct iovecvoid log_message(int fd, const char *level, const char *filename, const char *message) {// 1. Get current time and format ittime_t now = time(NULL);struct tm *tm_info = localtime(&now);char time_buffer[20]; // Buffer for timestampstrftime(time_buffer, sizeof(time_buffer), "%Y-%m-%d %H:%M:%S", tm_info);// 2. Define other fixed parts of the log messagechar fixed_part[] = " [MyApp] "; // Fixed project identifierchar newline = '\n';// 3. Prepare the iovec array for all 5 parts of our log line.// Format: [Timestamp] [Level] [MyApp] [Filename] Message\n// Example: "2023-10-27 10:11:12 [ERROR] [MyApp] main.c: Connection failed\n"struct iovec iov[6]; // We need 6 segments// Segment 0: Timestampiov[0].iov_base = time_buffer;iov[0].iov_len = strlen(time_buffer);// Segment 1: Space and Leveliov[1].iov_base = " ";iov[1].iov_len = 1;iov[2].iov_base = (void *)level; // Cast away const, we know we won't modify itiov[2].iov_len = strlen(level);// Segment 3: Fixed project identifieriov[3].iov_base = fixed_part;iov[3].iov_len = strlen(fixed_part);// Segment 4: Filename and message// We can combine these into one segment if we want, but we'll use two for demonstration.iov[4].iov_base = (void *)filename;iov[4].iov_len = strlen(filename);iov[5].iov_base = ": ";iov[5].iov_len = 2;// Note: We need a 7th segment for the actual message and a 8th for the newline.// This shows the flexibility, but also the complexity of many segments.// Let's re-design to a simpler 5-segment approach.// --- Re-designed approach with 5 segments ---// We'll let the message include the filename and colon.// This is less flexible but clearer for the example.// A real logger would use a more sophisticated approach, perhaps with a loop to build the iov array.struct iovec final_iov[5];// Segment 0: Timestampfinal_iov[0].iov_base = time_buffer;final_iov[0].iov_len = strlen(time_buffer);// Segment 1: " LEVEL [MyApp] filename: "// We need to create a format string. For simplicity, we snprintf a buffer.// This shows a hybrid approach: sometimes a temp buffer for complex formatting is simpler.char prefix_buffer[256];snprintf(prefix_buffer, sizeof(prefix_buffer), " %s [MyApp] %s: ", level, filename);final_iov[1].iov_base = prefix_buffer;final_iov[1].iov_len = strlen(prefix_buffer);// Segment 2: User messagefinal_iov[2].iov_base = (void *)message;final_iov[2].iov_len = strlen(message);// Segment 3: Newlinefinal_iov[3].iov_base = &newline;final_iov[3].iov_len = 1;// 4. Write the complete log line with one writev call to stderr (fd=2)ssize_t n = writev(fd, final_iov, 4); // 4 segmentsif (n == -1) {perror("writev logging failed"); // Log failure... but where to?}
}int main() {// Log a few messages to stderr (file descriptor 2)log_message(STDERR_FILENO, "INFO", __FILE__, "Server started successfully.");log_message(STDERR_FILENO, "ERROR", __FILE__, "Failed to connect to database.");// Also log to a fileFILE *logfile = fopen("app.log", "a");if (logfile) {log_message(fileno(logfile), "WARN", __FILE__, "Disk space is low.");fclose(logfile);}return 0;
}

說明:這個日志示例比 HTTP 示例更復雜,因為它展示了動態構建 iovec 數組的常見模式。有時,為了生成一個格式化的前綴,使用 snprintf 到一個臨時小緩沖區仍然是最高效和清晰的方法,然后再用 writev 將這個前綴和主體消息一起發送。這仍然比將整個日志行拼接成一個字符串要節省一次大的內存拷貝。

編譯與運行

  1. 編譯:gcc -Wall -o logger logger_writev.c
  2. 運行:./logger
  3. 輸出將會顯示在終端(標準錯誤),同時也會寫入到 app.log 文件中。

4. 交互性內容解析:writev 與網絡交互

writev 用于套接字(Socket)時,它的行為與內核的網絡協議棧(尤其是 TCP)深度交互。

4.1 內核處理流程與報文生成
  1. 用戶空間調用:應用程序調用 writev(sockfd, iov, iovcnt)
  2. 上下文切換:CPU 從用戶態切換到內核態。
  3. 內核空間處理
    • 內核驗證參數和文件描述符的有效性。
    • 內核遍歷 iov 數組,確保所有描述的內存區域對當前進程都是可讀的。
    • 數據仍然位于用戶空間的內存頁中。
  4. 協議棧處理(TCP為例)
    • 數據從用戶緩沖區被“收集”到內核的套接字發送緩沖區(Socket Send Buffer)。這個過程可能涉及頁映射而非直接拷貝(Zero-copy 技術的目標之一,但并非所有情況都能實現)。
    • TCP 協議處理數據:將發送緩沖區中的字節流分割成適合網絡傳輸的報文段(MSS)。writev 的邊界信息在此時通常會丟失。TCP 是字節流協議,它不保留消息邊界。writev 中的多塊數據會被TCP視為一個連續的字節流。
    • 為每個報文段添加 TCP 頭(序列號、確認號等)。
    • 交給 IP 層添加 IP 頭,再交給數據鏈路層。
  5. 報文發送:網卡驅動程序將完整的以太網幀發送到網絡。
  6. 返回用戶空間writev 系統調用返回成功發送的字節總數,上下文切換回用戶態。

重要注意點:雖然 writev 在用戶層面是“分散”的,但在網絡層面,這些數據很可能被整合到一個或多個TCP報文段中發送。writev 的原子性體現在套接字層面(接收方的一次read可能讀到所有數據),而不是網絡報文層面

4.2 時序圖

下面的時序圖描繪了客戶端使用 writev 發送HTTP請求和服務端使用 writev 發送HTTP響應的完整交互過程,以及內核內部的數據流。

ClientClient KernelSocket BufferNetworkServer KernelSocket BufferServerHTTP Request Phasewritev(sockfd, iov_req, 2)(Header + Body)Kernel gathers data fromuser space iov buffersTCP Packet(s)(Stream of bytes)TCP Packet(s)read(...)(Returns all request data)HTTP Response Phasewritev(sockfd, iov_resp, 2)(Header + Body)Kernel gathers response dataTCP Packet(s)(Stream of bytes)TCP Packet(s)read(...)(Returns all response data)ClientClient KernelSocket BufferNetworkServer KernelSocket BufferServer
  • 關鍵交互writev 的調用發生在用戶空間(Client/Server),數據被“聚集”到內核的套接字緩沖區。之后,內核協議棧獨立地將緩沖區中的數據打包成 TCP 報文并通過網絡發送。接收方的內核將報文數據重組到它的接收緩沖區,用戶空間的 read 調用再從該緩沖區中讀取數據。writev 的多緩沖區特性對網絡對端是透明的。

5. 總結與對比

為了更清晰地理解 writev,下表將其與傳統方法進行對比:

特性多次 write 調用用戶緩沖區 + 單次 writewritev
系統調用次數多 (N次)少 (1次)少 (1次)
內存拷貝次數無 (0次)多 (N次 memcpy)無/少 (0次,內核處理)
CPU開銷高 (上下文切換)中 (內存拷貝)
內存開銷高 (臨時緩沖區)
代碼復雜性中高 (緩沖區管理)中 (需管理iovec)
原子性保證有 (管道/套接字)
適用場景簡單程序數據需預處理高性能服務器,多塊數據IO
選型建議:
  • 使用 writev:當你需要將多塊分散在內存中的數據一次性寫入文件或套接字時,尤其是在性能敏感的網絡服務器中(如HTTP服務器、RPC框架、數據庫)。
  • 使用單次 write:當你的數據已經存儲在一塊連續的內存中時。這是最簡單直接的方式。
  • 使用多次 write:當數據塊產生的時機不同,或者邏輯上就需要分多次發送,并且性能不是首要考慮因素時。

writev 是構建高性能、高吞吐量 I/O 密集型應用的重要工具之一,深刻理解其原理和適用場景是現代系統程序員的基本素養。

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

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

相關文章

深入理解 Android targetSdkVersion:從 Google Play 政策到依賴沖突

深入理解 Android targetSdkVersion&#xff1a;從 Google Play 政策到依賴沖突 作為 Android 開發者&#xff0c;你很可能在 Android Studio 中見過這條提示&#xff1a;Google Play requires that apps target API level 33 or higher。它像一個盡職的提醒者&#xff0c;時常…

灰匣(GrayBox)1.0.0 發布【提升系統權限APP】

灰匣是一個提升系統權限的工具&#xff0c;可以配合Root、三方軟件&#xff08;Shizuku&#xff09;以及【設備管理員】&#xff08;設備所有者&#xff09;實現一些高級功能及底層接口&#xff0c;可以自動隔離&#xff08;凍結/禁用&#xff09;不必要的應用&#xff0c;如某…

PAT 1104 Sum of Number Segments

這一題的大意就是找一個數組中的所有子數組&#xff0c;它們的累加和為多少&#xff0c; 題目上給出的數據范圍是O(n^5)那么只能遍歷一次&#xff0c;不能用暴力的方法求出。 看到這一題我有兩個思路&#xff1a; 1.試圖用雙指針和滑動窗口來把O&#xff08;n^2)的時間復雜度降…

[萬字長文]AJAX入門-常用請求方法和數據提交、HTTP協議-報文、接口文檔、案例實戰

本系列可作為前端學習系列的筆記&#xff0c;代碼的運行環境是在VS code中&#xff0c;小編會將代碼復制下來&#xff0c;大家復制下來就可以練習了&#xff0c;方便大家學習。 HTML、CSS、JavaScript系列文章 已經收錄在前端專欄&#xff0c;有需要的寶寶們可以點擊前端專欄查…

Codesy中的UDP發送信息

Codesy UDP通訊 概述 CAA Net Base Services UDP通訊的建立 發送UDP 狀態控制 效果 概述 Codesys中默認安裝的通訊支持很多,不安裝其他的軟件也可以實現TCP通訊。但是,在使用UDP通訊時,因為我們的PLC有兩個網卡,一般我們把第一個網口做編程和HMI用,把的個網口做外部通訊,…

神經網絡之深入理解偏置

&#x1f50d; 1. 表達能力&#xff1a;無偏模型不能表示全體函數族 ? 有偏線性變換&#xff1a; yWxb&#xff08;仿射變換&#xff09; y Wx b \quad \text{&#xff08;仿射變換&#xff09;} yWxb&#xff08;仿射變換&#xff09; 能表示任意線性函數 平移是仿射空間的…

小白必看:AI智能體零基礎搭建全攻略!

寫在前面&#xff1a;別怕&#xff0c;真的不需要技術背景&#xff01; 你是不是經常聽到"AI智能體"、"大模型"這些高大上的詞&#xff0c;總覺得那是技術大牛的專利&#xff1f;別擔心&#xff0c;這篇教程就是為你準備的&#xff01;我們將用最通俗的語…

React state在setInterval里未獲取最新值的問題

目錄 一、問題描述 二、解決方案 方案一&#xff0c;使用函數式更新 方案二&#xff0c;使用 useRef 保存最新值 一、問題描述 在 React 中&#xff0c;當在 setInterval或setTimeout 中使用 setState 時&#xff0c;經常會遇到狀態不是最新值的問題。這是因為閉包導致的&a…

x86 架構 Docker 鏡像遷移至 ARM 環境的詳細指南

目錄 一、問題背景與分析 二、解決步驟 &#xff08;一&#xff09;檢查 docker-compose 版本 &#xff08;二&#xff09;升級 docker-compose 1. 對于 Linux 系統 2. 對于 Windows 系統 &#xff08;三&#xff09;驗證升級 &#xff08;四&#xff09;重新運行 dock…

零代碼部署工業數據平臺:TRAE + TDengine IDMP 實踐

對于編程初學者來說&#xff0c;軟件開發流程中的開發環境配置、安裝異常或報錯往往需要花費大量時間查閱資料和反復試錯&#xff0c;才能正常安裝和啟動某些軟件工具。現在&#xff0c;在 TRAE 的幫助下&#xff0c;即使完全沒有接觸過編程&#xff0c;也能通過自然語言直接表…

史上最全Flink面試題(完整版)

1、簡單介紹一下 FlinkFlink 是一個框架和分布式處理引擎&#xff0c;用于對無界和有界數據流進行有狀態計算。并且 Flink 提供了數據分布、容錯機制以及資源管理等核心功能。Flink提供了諸多高抽象層的API以便用戶編寫分布式任務&#xff1a;DataSet API&#xff0c; 對靜態數…

C# .NET中使用log4Net日志框架指南

C# .NET中使用log4Net日志框架指南 log4Net是Apache基金會開發的一款高效、靈活的日志記錄框架&#xff0c;廣泛應用于.NET生態系統中。它支持多種日志輸出目標&#xff08;如文件、數據庫、控制臺&#xff09;&#xff0c;并提供細粒度的日志級別控制&#xff0c;幫助開發者監…

每日算法刷題Day68:9.10:leetcode 最短路6道題,用時2h30min

一. 單源最短路&#xff1a;Dijkstra 算法 1.套路 1.Dijkstra 算法介紹 (1)定義 g[i][j] 表示節點 i 到節點 j 這條邊的邊權。如果沒有 i 到 j 的邊&#xff0c;則 g[i][j]∞。 (2)定義 dis[i] 表示起點 k 到節點 i 的最短路長度&#xff0c;一開始 dis[k]0&#xff0c;其余 …

Spring Boot + Apache Tika 從文件或文件流中提取文本內容

應用效果&#xff1a;1、安裝 Apache Tika 依賴pom.xml<!-- Apache Tika 從文件中提取結構化文本和元數據 --><dependency><groupId>org.apache.tika</groupId><artifactId>tika-core</artifactId><version>2.9.2</version>&l…

qqq數據結構補充

1.緒論1.存儲方式順序存儲&#xff1a;邏輯相鄰&#xff0c;物理相鄰鏈式存儲&#xff1a;邏輯相鄰&#xff0c;物理不一定相鄰2.線性表1.順序表1.不可擴容數組寫一個順序表1.在頭文件中應有#pragam once&#xff0c;防止頭文件多次編譯&#xff1b;如果頭文件多次編譯&#x…

Anaconda與Jupyter 安裝和使用

Anaconda內部集成了很多科學計算包&#xff0c;并且可以實現環境隔離 1. 安裝Anaconda 定義&#xff1a;Anaconda是一個集成的Python發行版&#xff0c;專為數據科學、機器學習和AI開發而設計。它包含了常用的Python庫、包管理工具&#xff08;Conda&#xff09;和Jupyter No…

5.后臺運行設置和包設計與實現

程序的入口點(想讓其后臺默認.exe進程運行)也可以不通過vs設置也可以通過定義預處理設置第三種就是沒有窗口的變成后臺運行的了 處理client傳來的數據包 第一步&#xff1a;咱們怎么設計一種包呢&#xff1f;FEFF在網絡環境里面出現的概率低所以就采用這個 自己數據包截斷了&am…

【開題答辯全過程】以 基于微信小程序校園綜合服務平臺的設計與實現為例,包含答辯的問題和答案

個人簡介一名14年經驗的資深畢設內行人&#xff0c;語言擅長Java、php、微信小程序、Python、Golang、安卓Android等開發項目包括大數據、深度學習、網站、小程序、安卓、算法。平常會做一些項目定制化開發、代碼講解、答辯教學、文檔編寫、也懂一些降重方面的技巧。感謝大家的…

地級市人口集聚、經濟集聚、產業集聚與綠色經濟效率匹配數據(含區域經濟研究相關的控制變量,Excel|shp|免費數據)

D006 地級市人口集聚、經濟集聚、產業集聚與綠色經濟效率匹配數據&#xff08;含區域經濟研究相關的控制變量&#xff0c;Excel|shp|免費數據&#xff09;數據簡介今天我們分享的數據是2004-2020年地級市人口聚集、經濟聚集與綠色經濟效率匹配數據&#xff0c;并對其進行可視化…

視覺SLAM第7講:視覺里程計2(3D-2D:PnP、3D-3D:ICP)

接上文&#xff0c;視覺SLAM第7講&#xff1a;視覺里程計1&#xff08;特征點法、2D-2D對極約束&#xff09;&#xff0c;本節主要學習3D-2D:PnP、3D-3D:ICP。 目錄 7.7 3D-2D:PnP 7.7.1 直接線性變換&#xff08;DLT&#xff09; 7.7.2 P3P 1.原理 2.小結 7.7.3 最小化重…