Libevent(3)之使用教程(2)創建事件

Libevent(3)之使用教程(2)創建事件


Author: Once Day Date: 2025年6月29日

一位熱衷于Linux學習和開發的菜鳥,試圖譜寫一場冒險之旅,也許終點只是一場白日夢…

漫漫長路,有人對你微笑過嘛…

本文檔翻譯于:Fast portable non-blocking network programming with Libevent

全系列文章可參考專欄: 十年代碼訓練_Once-Day的博客-CSDN博客

參考文章:

  • 詳解libevent網絡庫(一)—框架的搭建_libevent詳解-CSDN博客
  • 深度思考高性能網絡庫Libevent,從13個維度來解析Libevent到底是怎么回事 - 知乎
  • 深入淺出理解libevent——2萬字總結_libev 堆-CSDN博客
  • Fast portable non-blocking network programming with Libevent
  • libevent
  • C++網絡庫:Libevent網絡庫的原理及使用方法 - 知乎
  • 深入理解libevent事件庫的原理與實踐技巧-騰訊云開發者社區-騰訊云

文章目錄

  • Libevent(3)之使用教程(2)創建事件
        • 4. 創建event_base
          • 4.1 設置默認的event_base
          • 4.2 構建復雜的event_base
          • 4.3 查看 event_base 的后端方法
          • 4.4 釋放 event_base
          • 4.5 在 event_base 上設置優先級
          • 4.6 Libevent 舊版本中的 "當前"event_base
        • 5. 使用event loop
          • 5.1 運行 event_base 事件循環
          • 5.2 停止循環
          • 5.3 重新檢查事件
          • 5.4 檢查內部時間緩存
          • 5.5 輸出 event_base 的狀態
          • 5.6 遍歷 event_base 中的所有事件

4. 創建event_base

在使用任何有趣的 Libevent 函數之前,需要分配一個或多個 event_base 結構。每個 event_base 結構都包含一組事件,并且能夠通過輪詢來確定哪些事件處于活動狀態。

如果一個 event_base 配置了鎖定機制,那么在多個線程之間對其進行訪問是安全的。不過,它的循環只能在單個線程中運行。如果你希望有多個線程對 IO 進行輪詢,那么每個線程都需要有一個 event_base。

每個 event_base 都有一個 “方法”,也就是它用于確定哪些事件就緒的后端。已識別的方法包括:

  • select
  • poll
  • epoll
  • kqueue
  • devpoll
  • evport
  • win32

用戶可以通過環境變量禁用特定的后端。如果你想關閉 kqueue 后端,設置 EVENT_NOKQUEUE 環境變量即可,其他后端的關閉方式以此類推。如果想在程序內部關閉后端,請參考下面關于 event_config_avoid_method () 的說明。

4.1 設置默認的event_base

event_base_new () 函數會分配并返回一個具有默認設置的新事件基礎。它會檢查環境變量,并返回一個指向新 event_base 的指針。如果出現錯誤,則返回 NULL。

在選擇方法時,它會挑選操作系統支持的最快方法。

struct event_base *event_base_new(void);

對于大多數程序來說,這就是你所需要的全部。

event_base_new () 函數在 < event2/event.h> 中聲明。它最早出現在 Libevent 1.4.3 版本中。

4.2 構建復雜的event_base

如果想更精確地控制所獲取的 event_base 類型,就需要使用 event_config。event_config 是一種不透明的結構,用于存儲對 event_base 的偏好設置。當需要一個 event_base 時,可將 event_config 傳遞給 event_base_new_with_config () 函數。

struct event_config *event_config_new(void);
struct event_base *event_base_new_with_config(const struct event_config *cfg);
void event_config_free(struct event_config *cfg);

要通過這些函數分配一個 event_base,需先調用 event_config_new () 來分配一個新的 event_config,然后調用其他相關函數向其告知你的需求,最后調用 event_base_new_with_config () 以獲取新的 event_base。使用完畢后,可通過 event_config_free () 釋放 event_config。

int event_config_avoid_method(struct event_config *cfg, const char *method);enum event_method_feature {EV_FEATURE_ET = 0x01,EV_FEATURE_O1 = 0x02,EV_FEATURE_FDS = 0x04,
};
int event_config_require_features(struct event_config *cfg,enum event_method_feature feature);enum event_base_config_flag {EVENT_BASE_FLAG_NOLOCK = 0x01,EVENT_BASE_FLAG_IGNORE_ENV = 0x02,EVENT_BASE_FLAG_STARTUP_IOCP = 0x04,EVENT_BASE_FLAG_NO_CACHE_TIME = 0x08,EVENT_BASE_FLAG_EPOLL_USE_CHANGELIST = 0x10,EVENT_BASE_FLAG_PRECISE_TIMER = 0x20
};
int event_config_set_flag(struct event_config *cfg,enum event_base_config_flag flag);

調用 event_config_avoid_method 函數可以告知 Libevent 避免使用特定名稱的可用后端。調用 event_config_require_feature () 函數可以告知 Libevent 不要使用無法提供所有指定功能的后端。調用 event_config_set_flag () 函數可以告知 Libevent 在構建事件基礎時設置以下一個或多個運行時標志。

event_config_require_features 所識別的功能值包括:

  • EV_FEATURE_ET:要求后端方法支持邊緣觸發的 IO。
  • EV_FEATURE_O1:要求后端方法支持添加、刪除單個事件或單個事件變為活動狀態時均為 O (1) 操作。
  • EV_FEATURE_FDS:要求后端方法能夠支持任意文件描述符類型,而不僅僅是套接字。

event_config_set_flag () 所識別的選項值包括:

  • EVENT_BASE_FLAG_NOLOCK:不為 event_base 分配鎖。設置此選項可能會節省一點鎖定和釋放 event_base 的時間,但會導致從多個線程訪問它時既不安全也無法正常工作。
  • EVENT_BASE_FLAG_IGNORE_ENV:在選擇要使用的后端方法時,不檢查 EVENT_* 環境變量。使用此標志前請慎重考慮:這會增加用戶調試程序與 Libevent 之間交互的難度。
  • EVENT_BASE_FLAG_STARTUP_IOCP:僅在 Windows 系統上,此標志會使 Libevent 在啟動時啟用所有必要的 IOCP 調度邏輯,而不是按需啟用。
  • EVENT_BASE_FLAG_NO_CACHE_TIME:不在事件循環準備運行超時回調時檢查當前時間,而是在每個超時回調之后檢查。這可能會消耗比預期更多的 CPU 資源,所以要格外注意!
  • EVENT_BASE_FLAG_EPOLL_USE_CHANGELIST:告知 Libevent,如果決定使用 epoll 后端,那么使用更快的基于 “變更列表(changelist)” 的后端是安全的。當同一個文件描述符在調用后端的調度函數之間多次修改狀態時,epoll-changelist 后端可以避免不必要的系統調用,但如果向 Libevent 提供了通過 dup () 或其變體克隆的文件描述符,它也可能觸發內核漏洞,導致錯誤結果。如果使用 epoll 以外的后端,此標志無效。你也可以通過設置 EVENT_EPOLL_USE_CHANGELIST 環境變量來開啟 epoll-changelist 選項。
  • EVENT_BASE_FLAG_PRECISE_TIMER:默認情況下,Libevent 會嘗試使用操作系統提供的最快可用計時機制。如果存在一種速度較慢但時間精度更高的計時機制,此標志會告知 Libevent 改用該計時機制。如果操作系統沒有提供這種速度較慢但精度更高的機制,此標志則無效。

上述用于操作 event_config 的函數在成功時均返回 0,失敗時返回 - 1。

注意,很容易出現這種情況:配置的 event_config 要求操作系統不支持的后端。例如,在 Libevent 2.0.1-alpha 版本中,Windows 系統沒有 O (1) 后端,Linux 系統也沒有同時提供 EV_FEATURE_FDS 和 EV_FEATURE_O1 功能的后端。如果你的配置無法被 Libevent 滿足,event_base_new_with_config () 將返回 NULL。

int event_config_set_num_cpus_hint(struct event_config *cfg, int cpus)

該函數目前僅在 Windows 系統使用 IOCP 時有用,但未來可能會在其他平臺上發揮作用。調用此函數會告知 event_config,其生成的 event_base 在多線程環境下應盡量充分利用指定數量的 CPU。請注意,這只是一個提示:最終事件基礎實際使用的 CPU 數量可能會多于或少于你所選擇的數量。

int event_config_set_max_dispatch_interval(struct event_config *cfg,const struct timeval *max_interval, int max_callbacks,int min_priority);

此函數通過限制在檢查更多高優先級事件之前可以調用的低優先級事件回調的數量,來防止優先級反轉。如果 max_interval 不為空,事件循環會在每個回調之后檢查時間,若已超過 max_interval,則重新掃描高優先級事件。如果 max_callbacks 為非負值,那么在調用了 max_callbacks 個回調之后,事件循環也會檢查更多事件。這些規則適用于任何優先級不低于 min_priority 的事件。

struct event_config *cfg;
struct event_base *base;
int i;/* My program wants to use edge-triggered events if at all possible.  SoI'll try to get a base twice: Once insisting on edge-triggered IO, andonce not. */
for (i=0; i<2; ++i) {cfg = event_config_new();/* I don't like select. */event_config_avoid_method(cfg, "select");if (i == 0)event_config_require_features(cfg, EV_FEATURE_ET);base = event_base_new_with_config(cfg);event_config_free(cfg);if (base)break;/* If we get here, event_base_new_with_config() returned NULL.  Ifthis is the first time around the loop, we'll try again withoutsetting EV_FEATURE_ET.  If this is the second time around theloop, we'll give up. */
}

示例:優先選擇邊緣觸發的后端:

struct event_config *cfg;
struct event_base *base;
int i;/* My program wants to use edge-triggered events if at all possible.  SoI'll try to get a base twice: Once insisting on edge-triggered IO, andonce not. */
for (i=0; i<2; ++i) {cfg = event_config_new();/* I don't like select. */event_config_avoid_method(cfg, "select");if (i == 0)event_config_require_features(cfg, EV_FEATURE_ET);base = event_base_new_with_config(cfg);event_config_free(cfg);if (base)break;/* If we get here, event_base_new_with_config() returned NULL.  Ifthis is the first time around the loop, we'll try again withoutsetting EV_FEATURE_ET.  If this is the second time around theloop, we'll give up. */
}

示例:避免優先級反轉:

struct event_config *cfg;
struct event_base *base;cfg = event_config_new();
if (!cfg)/* Handle error */;/* I'm going to have events running at two priorities.  I expect thatsome of my priority-1 events are going to have pretty slow callbacks,so I don't want more than 100 msec to elapse (or 5 callbacks) beforechecking for priority-0 events. */
struct timeval msec_100 = { 0, 100*1000 };
event_config_set_max_dispatch_interval(cfg, &msec_100, 5, 1);base = event_base_new_with_config(cfg);
if (!base)/* Handle error */;event_base_priority_init(base, 2);

這些函數和類型在 <event2/event.h> 中聲明。

EVENT_BASE_FLAG_IGNORE_ENV 標志最早出現在 Libevent 2.0.2-alpha 版本中。EVENT_BASE_FLAG_PRECISE_TIMER 標志最早出現在 Libevent 2.1.2-alpha 版本中。event_config_set_num_cpus_hint () 函數是在 Libevent 2.0.7-rc 版本中新增的,而 event_config_set_max_dispatch_interval () 函數則是在 2.1.1-alpha 版本中新增的。本節中的其他所有內容均最早出現在 Libevent 2.0.1-alpha 版本中。

4.3 查看 event_base 的后端方法

有時你可能想了解某個 event_base 實際支持哪些特性,或者它正在使用哪種方法。

const char **event_get_supported_methods(void);

event_get_supported_methods()函數會返回一個指針,指向當前 Libevent 版本所支持的方法名稱數組。該數組的最后一個元素為 NULL。

int i;
const char **methods = event_get_supported_methods();
printf("Starting Libevent %s.  Available methods are:\n",event_get_version());
for (i=0; methods[i] != NULL; ++i) {printf("    %s\n", methods[i]);
}

此函數返回的是 Libevent 編譯時支持的方法列表。但在 Libevent 實際運行時,操作系統可能并不支持其中的全部方法。例如,在某些版本的 OSX 系統中,kqueue 可能存在嚴重漏洞而無法使用。

const char *event_base_get_method(const struct event_base *base);
enum event_method_feature event_base_get_features(const struct event_base *base);

event_base_get_method()調用會返回某個 event_base 實際使用的方法名稱。event_base_get_features()調用則會返回一個位掩碼,代表該 event_base 支持的特性。

struct event_base *base;
enum event_method_feature f;base = event_base_new();
if (!base) {puts("Couldn't get an event_base!");
} else {printf("Using Libevent with backend method %s.",event_base_get_method(base));f = event_base_get_features(base);if ((f & EV_FEATURE_ET))printf("  Edge-triggered events are supported.");if ((f & EV_FEATURE_O1))printf("  O(1) event notification is supported.");if ((f & EV_FEATURE_FDS))printf("  All FD types are supported.");puts("");
}

這些函數均定義在<event2/event.h>中。event_base_get_method()最早在 Libevent 1.4.3 版本中可用,其他函數則最早出現在 Libevent 2.0.1-alpha 版本中。

4.4 釋放 event_base

當你不再需要某個 event_base 時,可以使用event_base_free()函數釋放它。

需要注意的是,該函數不會釋放任何當前與該 event_base 關聯的事件,也不會關閉這些事件對應的套接字,更不會釋放它們的指針。

event_base_free()函數定義在<event2/event.h>中,最早在 Libevent 1.2 版本中實現。

4.5 在 event_base 上設置優先級

Libevent 支持為事件設置多個優先級。不過,默認情況下,一個 event_base 僅支持單個優先級級別。你可以通過調用event_base_priority_init()函數來設置 event_base 上的優先級數量。

int event_base_priority_init(struct event_base *base, int n_priorities);

該函數成功時返回 0,失敗時返回 - 1。參數base是要修改的 event_base,n_priorities是要支持的優先級數量,其值必須至少為 1。新事件可用的優先級編號范圍是從 0(最重要)到n_priorities-1(最不重要)。

存在一個常量EVENT_MAX_PRIORITIES,它規定了n_priorities的上限。如果調用此函數時n_priorities的值高于該上限,會導致錯誤。

你必須在任何事件進入活動狀態之前調用此函數。最佳做法是在創建 event_base 之后立即調用它。

要查看某個 event_base 當前支持的優先級數量,可以調用 event_base_getnpriorities () 函數。

int event_base_get_npriorities(struct event_base *base);

該函數的返回值等于 event_base 中配置的優先級數量。例如,如果event_base_get_npriorities()返回 3,那么有效的優先級值為 0、1 和 2。

默認情況下,所有與該 event_base 關聯的新事件都會被初始化為n_priorities / 2的優先級。

event_base_priority_init函數定義在<event2/event.h>中,自 Libevent 1.0 版本起可用。event_base_get_npriorities()函數則是在 Libevent 2.1.1-alpha 版本中新增的。

4.6 Libevent 舊版本中的 "當前"event_base

早期版本的 Libevent 嚴重依賴 "當前"event_base 的概念。"當前"event_base 是一個跨所有線程共享的全局設置。如果你忘記指定想要使用的 event_base,系統會默認使用當前的 event_base。由于 event_base 本身并非線程安全的,這種設計很容易導致錯誤。

在早期版本中,替代event_base_new()的函數是:

struct event_base *event_init(void);

該函數的功能類似于event_base_new(),但它會將新創建的 event_base 設置為當前的 event_base。而且,當時沒有其他方法可以更改當前的 event_base。

本節中介紹的一些 event_base 函數在早期版本中存在操作當前 event_base 的變體。這些變體函數的行為與當前版本的對應函數相同,只是它們不需要傳入 base 參數。

5. 使用event loop
5.1 運行 event_base 事件循環

一旦有了一個注冊了某些事件的 event_base(關于如何創建和注冊事件,請參見下一節),可能希望 Libevent 等待事件發生并通知事件。

#define EVLOOP_ONCE             0x01
#define EVLOOP_NONBLOCK         0x02
#define EVLOOP_NO_EXIT_ON_EMPTY 0x04int event_base_loop(struct event_base *base, int flags);

默認情況下,event_base_loop()函數會運行 event_base,直到其中不再有任何已注冊的事件。為了運行事件循環,它會反復檢查是否有任何已注冊的事件被觸發(例如,讀事件的文件描述符是否準備好讀取,或者超時事件的超時時間是否已到期)。一旦事件被觸發,它會將所有觸發的事件標記為 “活動的”,并開始執行這些事件的回調函數。

可以通過在flags參數中設置一個或多個標志來改變event_base_loop()的行為:

  • 如果設置了EVLOOP_ONCE,事件循環會等待直到有事件變為活動狀態,然后執行所有活動事件,直到沒有更多可執行的事件后返回。
  • 如果設置了EVLOOP_NONBLOCK,事件循環不會等待事件觸發,只會檢查是否有事件可以立即觸發,如果有則執行它們的回調函數。

通常情況下,一旦沒有掛起或活動的事件,事件循環就會退出。可以通過傳遞EVLOOP_NO_EXIT_ON_EMPTY標志來覆蓋此行為 —— 例如,當打算從其他線程添加事件時。如果設置了EVLOOP_NO_EXIT_ON_EMPTY,事件循環會一直運行,直到有人調用event_base_loopbreak()event_base_loopexit(),或者發生錯誤。

event_base_loop()完成運行時,返回值規則如下:

  • 正常退出時返回 0;
  • 因后端出現未處理的錯誤而退出時返回 - 1;
  • 因不再有掛起或活動的事件而退出時返回 1。

為了便于理解,以下是event_base_loop算法的大致總結:

while (any events are registered with the loop,or EVLOOP_NO_EXIT_ON_EMPTY was set) {if (EVLOOP_NONBLOCK was set, or any events are already active)If any registered events have triggered, mark them active.elseWait until at least one event has triggered, and mark it active.for (p = 0; p < n_priorities; ++p) {if (any event with priority of p is active) {Run all active events with priority of p.break; /* Do not run any events of a less important priority */}}if (EVLOOP_ONCE was set or EVLOOP_NONBLOCK was set)break;
}

為方便使用,也可以調用:

int event_base_dispatch(struct event_base *base);

event_base_dispatch()調用與event_base_loop()功能相同,但不設置任何標志。因此,它會一直運行,直到沒有更多已注冊的事件,或者調用了event_base_loopbreak()event_base_loopexit()

這些函數都定義在<event2/event.h>中,自 Libevent 1.0 版本起就已存在。

5.2 停止循環

如果希望正在運行的事件循環在所有事件被移除之前停止,可以調用兩個略有差異的函數。

int event_base_loopexit(struct event_base *base, const struct timeval *tv);
int event_base_loopbreak(struct event_base *base);

event_base_loopexit()函數會告知 event_base 在經過指定時間后停止循環。如果tv參數為 NULL,event_base 會立即停止循環(無延遲)。如果 event_base 當前正在為任何活動事件運行回調函數,它會繼續執行這些回調,直到全部完成后才退出。

event_base_loopbreak()函數則會告知 event_base 立即退出循環。它與event_base_loopexit(base, NULL)的區別在于:如果 event_base 當前正在為活動事件運行回調函數,event_base_loopbreak()會在完成當前正在處理的回調后立即退出。

還需注意,當事件循環未運行時,event_base_loopexit(base, NULL)event_base_loopbreak(base)的行為不同:loopexit會安排下一次事件循環在執行完下一輪回調后停止(類似以EVLOOP_ONCE標志調用的效果);而loopbreak僅能停止當前正在運行的循環,若事件循環未運行則無效果。

這兩個函數成功時均返回 0,失敗時返回 - 1。

示例:立即關閉事件循環:

#include <event2/event.h>/* Here's a callback function that calls loopbreak */
void cb(int sock, short what, void *arg)
{struct event_base *base = arg;event_base_loopbreak(base);
}void main_loop(struct event_base *base, evutil_socket_t watchdog_fd)
{struct event *watchdog_event;/* Construct a new event to trigger whenever there are any bytes toread from a watchdog socket.  When that happens, we'll call thecb function, which will make the loop exit immediately withoutrunning any other active events at all.*/watchdog_event = event_new(base, watchdog_fd, EV_READ, cb, base);event_add(watchdog_event, NULL);event_base_dispatch(base);
}

示例:運行事件循環 10 秒后退出:

#include <event2/event.h>void run_base_with_ticks(struct event_base *base)
{struct timeval ten_sec;ten_sec.tv_sec = 10;ten_sec.tv_usec = 0;/* Now we run the event_base for a series of 10-second intervals, printing"Tick" after each.  For a much better way to implement a 10-secondtimer, see the section below about persistent timer events. */while (1) {/* This schedules an exit ten seconds from now. */event_base_loopexit(base, &ten_sec);event_base_dispatch(base);puts("Tick");}
}

有時你可能需要判斷event_base_dispatch()event_base_loop()是正常退出,還是因調用event_base_loopexit()event_base_break()而退出。可以使用以下函數來判斷是loopexit還是break被調用:

int event_base_got_exit(struct event_base *base);
int event_base_got_break(struct event_base *base);

這兩個函數分別在循環被event_base_loopexit()event_base_break()停止時返回true,否則返回false。它們的值會在下次啟動事件循環時重置。

這些函數均聲明在<event2/event.h>中。event_base_loopexit()最早在 Libevent 1.0c 版本中實現;event_base_loopbreak()最早在 Libevent 1.4.3 版本中實現。

5.3 重新檢查事件

通常情況下,Libevent 會先檢查事件,然后執行所有最高優先級的活動事件,接著再次檢查事件,依此類推。但有時你可能希望在當前回調執行完畢后立即暫停 Libevent,并要求它重新掃描事件。類似于event_base_loopbreak(),你可以使用event_base_loopcontinue()函數來實現這一點。

int event_base_loopcontinue(struct event_base *);

如果當前沒有在執行事件回調,調用event_base_loopcontinue()不會產生任何效果。

該函數是在 Libevent 2.1.2-alpha 版本中引入的。

5.4 檢查內部時間緩存

有時你希望在事件回調內部獲取當前時間的近似值,并且不想自己調用gettimeofday()(可能是因為你的操作系統將gettimeofday()實現為系統調用,而你正試圖避免系統調用帶來的開銷)。

在回調函數內部,你可以向 Libevent 詢問其在開始執行本輪回調時所記錄的當前時間:

int event_base_gettimeofday_cached(struct event_base *base, struct timeval *tv_out);

event_base_gettimeofday_cached()函數會在 event_base 當前正在執行回調時,將其 tv_out 參數的值設置為緩存的時間。否則,它會調用evutil_gettimeofday()來獲取實際的當前時間。該函數成功時返回 0,失敗時返回負值。

需要注意的是,由于 timeval 是在 Libevent 開始運行回調時緩存的,所以它至少會有一點不準確。如果你的回調函數執行時間很長,這個時間可能會非常不準確。要強制立即更新緩存,可以調用以下函數:

int event_base_update_cache_time(struct event_base *base);

該函數成功時返回 0,失敗時返回 - 1。如果 event_base 沒有運行其事件循環,調用該函數沒有任何效果。

event_base_gettimeofday_cached()函數是在 Libevent 2.0.4-alpha 版本中新增的。Libevent 2.1.1-alpha 版本添加了event_base_update_cache_time()函數。

5.5 輸出 event_base 的狀態
void event_base_dump_events(struct event_base *base, FILE *f);

為了幫助調試程序(或調試 Libevent 本身),有時你可能需要獲取 event_base 中所有已添加事件及其狀態的完整列表。調用event_base_dump_events()函數可以將該列表寫入指定的標準 I/O 文件。

該列表旨在方便人類閱讀,其格式在 Libevent 的未來版本中可能會發生變化。

此函數是在 Libevent 2.0.1-alpha 版本中引入的。

5.6 遍歷 event_base 中的所有事件
typedef int (*event_base_foreach_event_cb)(const struct event_base *,const struct event *, void *);int event_base_foreach_event(struct event_base *base,event_base_foreach_event_cb fn,void *arg);

你可以使用event_base_foreach_event()函數遍歷與某個 event_base 關聯的所有當前活動或掛起的事件。所提供的回調函數會對每個事件調用一次,調用順序不確定。event_base_foreach_event()的第三個參數會作為第三個參數傳遞給每次回調調用。

回調函數必須返回 0 以繼續遍歷,或返回其他整數以停止遍歷。回調函數最終返回的值也會成為event_base_foreach_event()的返回值。

注意:你的回調函數不得修改它所接收的任何事件,不得向該 event_base 添加或移除任何事件,也不得通過其他方式修改與該 event_base 關聯的任何事件,否則可能會導致未定義行為,包括但不限于程序崩潰和堆破壞。

event_base_foreach_event()調用期間,event_base 的鎖會被持有 —— 這會阻止其他線程對該 event_base 執行任何有效操作,因此請確保你的回調函數不會耗時過長。







Alt

Once Day

也信美人終作土,不堪幽夢太匆匆......

如果這篇文章為您帶來了幫助或啟發,不妨點個贊👍和關注,再加上一個小小的收藏?!

(。???。)感謝您的閱讀與支持~~~

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

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

相關文章

Kotlin 作用域函數 let 的實現原理

Kotlin 中的 let 是一個 標準庫擴展函數&#xff0c;它廣泛用于作用域函數&#xff08;Scope Functions&#xff09;中&#xff0c;尤其適用于對可空對象&#xff08;nullable&#xff09;做非空判斷并執行代碼塊的場景。 示例代碼 val name: String? "123" name?…

從FDTD仿真到光學神經網絡:機器學習在光子器件設計中的前沿應用工坊

FDTD仿真與光學神經網絡的基礎概念 FDTD&#xff08;時域有限差分&#xff09;是一種數值方法&#xff0c;用于求解麥克斯韋方程組&#xff0c;廣泛應用于光子器件設計。光學神經網絡通過光波導、衍射元件等物理結構實現矩陣運算&#xff0c;具有低能耗、高并行的優勢。 機器學…

在Ubutu22系統上面離線安裝Go語言環境【教程】

0.引言 Go語言&#xff08;又稱Golang&#xff09;是Google開發的一種靜態強類型、編譯型、并發型編程語言&#xff0c;由Robert Griesemer、Rob Pike和Ken Thompson于2007年開始設計&#xff0c;2009年正式發布。 1.到官網下載壓縮包 2.從win10系統離線上傳壓縮包給ubuntu22…

CMake實踐:CMake3.30版本之前和之后鏈接boost的方式差異

目錄 1.背景 2.boost引入CMake時機 3.CMake 3.30 之前&#xff08;含 3.29&#xff09;鏈接 Boost 的方式 4.CMake 3.30 及之后鏈接 Boost 的方式 5.CMake3.30后引入Boost的步驟 6.遷移建議&#xff08;3.30 之前 → 3.30 之后&#xff09; 7.CMake 3.30 移除FindBoost的…

告別掛馬風險!PBootCMS完美替代方案BadouCMS

開發企業網站時一直比較喜歡用pbootcms,標簽套用很簡單&#xff0c;使用也方便。 但是pbootcms一直有被掛馬的問題&#xff0c;官方好像也不怎么更新了&#xff01;換過好幾個cms&#xff0c;比如eyoucms、dedecms、帝國等等&#xff0c;感覺都不怎么能用得習慣&#xff0c;還…

開發者如何集成AI繪畫?智創聚合API簡化Midjourney接入

在 AI 繪畫領域&#xff0c;Midjourney 的大名如雷貫耳&#xff0c;其強大的圖像生成能力&#xff0c;能將我們腦海中的奇思妙想&#xff0c;迅速轉化為精美的視覺畫面&#xff0c;深受設計師、藝術家以及廣大創意愛好者的青睞。然而&#xff0c;使用 Midjourney 的過程中&…

pycharm回車、刪除、方向鍵和快捷鍵等不能使用原因

解決方法 &#xff1a;菜單欄中的Tools取消勾選Vim Emulator 原因 &#xff1a;新版的pycharm安裝中&#xff0c;默認安裝了vim擴展&#xff0c;一旦安裝了pycharm在編寫代碼時會默認使用Vim編輯器

修復ffmpeg.dll丟失錯誤|6種解決ffmpeg.dll方法詳細教程

看到電腦提示“ffmpeg.dll丟失”&#xff0c;很多人會懵。ffmpeg.dll 是個處理視頻、音頻的關鍵文件。它要是沒了或壞了&#xff0c;軟件就打不開或直接閃退。常見原因是軟件安裝不全、文件被刪、或者中病毒。下面說說它是干嘛的&#xff0c;再給解決辦法。一.ffmpeg.dll 到底是…

OkHttp 與 Stetho 結合使用:打造強大的 Android 網絡調試工具鏈

前言在 Android 應用開發過程中&#xff0c;網絡請求的調試一直是一個重要但具有挑戰性的環節。Facebook 開發的 Stetho 是一個強大的調試工具&#xff0c;當它與 OkHttp 結合使用時&#xff0c;可以為我們提供前所未有的網絡請求洞察能力。本文將詳細介紹如何將這兩者結合使用…

LangGraph教程10:LangGraph ReAct應用

文章目錄 ReAct 預構建的代理 向 ReAct 代理添加記憶 向 ReAct 代理添加系統提示 向 ReAct 代理添加人機交互 ReAct 官方文檔地址:https://langchain-ai.github.io/langgraph/how-tos/#prebuilt-react-agent 中文文檔地址:https://www.aidoczh.com/langgraph/how-tos/#react…

安卓第一個項目

測試所有攝像頭 安卓CameraX&#xff1a;https://developer.android.com/media/grow/spatial-audio?hlzh-cn 1、MainActivity.java // 定義包名 package com.mms.densenapplication;// 引入 AppCompatActivity&#xff0c;支持兼容性更強的 Activity import androidx.appcompa…

Google Gemini 體驗

文章中代碼倉庫 gemini 谷歌推出的 AI 只能模型 Gemini官網Gemini ChatGemini開發者文檔Gemini SDK 所有模型 模型變體輸入輸出優化目標Gemini 2.5 Pro gemini-2.5-pro音頻、圖片、視頻、文本和 PDF文本增強的思考和推理能力、多模態理解能力、高級編碼能力等Gemini 2.5 Fla…

Trae安裝指定版本的插件

前情 Trae是屬于國產的跟 Cursor類似的AI編程IDE&#xff0c;我也是第一時間體驗Trae的&#xff0c;雖然相比Cursor弱了一些&#xff0c;但是也絕對勝任了&#xff0c;前端因為排隊問題我轉戰了Cursor&#xff0c;等到Trae出收費模式前&#xff0c;我已經辦了Cursor會員了&…

【技術追蹤】用于醫學圖像合成和分割的噪聲一致孿生擴散模型(CVPR-2025)

孿生擴散模型&#xff0c;生成息肉圖像用于提升分割性能&#xff01; 論文&#xff1a;Noise-Consistent Siamese-Diffusion for Medical Image Synthesis and Segmentation 代碼&#xff1a;https://github.com/Qiukunpeng/Siamese-Diffusion 0、摘要 深度學習已徹底革新醫學影…

Crontab詳解

crontab是Unix/Linux系統中用于設置周期性任務的工具&#xff0c;通過編輯配置文件實現定時執行命令或腳本。以下是其語法規則和核心要點&#xff1a; 一、基本格式 * * * * * command - - - - - | | | | | | | | | ----…

中國1km逐月潛在蒸散發數據集 - matlab按shp批量裁剪

中國1km逐月潛在蒸散發數據集 - matlab按shp批量裁剪 1. 數據概述 2 利用掩膜文件對數據進行裁剪 3 完整代碼 4 結語 本篇繼續處理氣象數據,中國1km逐月潛在蒸散發數據集同前節介紹的中國1km降水數據集一樣,都可以從國家青藏高原科學數據中心獲得,數據具有同樣的空間分辨率(…

Node.js鏈接MySql

前言&#xff1a; 在現代 Web 開發和后端服務中&#xff0c;Node.js 因其高性能和異步特性被廣泛使用。MySQL 作為流行的關系型數據庫之一&#xff0c;提供了穩定高效的數據存儲和管理能力。將 Node.js 與 MySQL 結合&#xff0c;可以構建強大的數據驅動型應用。 一、環境準備…

Charles 的 Windows proxy 對爬取瑞數6 網站接口數據的作用分析

其實本文還是源于上個月的這篇文章 ??▼ 耗時兩天半&#xff0c;利用 DrissionPage繞過瑞數6&#xff0c;爬取某藥*局數據經歷~ 不同點是&#xff0c;當時爬取的是列表頁&#xff08;已爬完&#xff09;&#xff0c;后面爬取的是詳情頁&#xff01;懂的都懂&#xff0c;差別還…

PHP 測驗

PHP 測驗 引言 PHP 作為一種流行的開源服務器端腳本語言,被廣泛應用于網頁開發、服務器端編程等領域。為了幫助大家更好地理解和掌握 PHP,我們特此推出本 PHP 測驗。通過以下問題,您可以檢驗自己的 PHP 知識水平,同時也能了解自己在哪些方面需要加強。 測驗內容 問題一…

階段1--Linux中的文件服務器(FTP、NAS、SSH)

目錄 一、FTP Server 1.1.簡介 1.2.FTP基礎 1.2.1.控制端口 1.2.2.數據端口 1.3.FTP Server默認配置 1.3.1.安裝vsftp 1.3.2.準備分發的文件 1.3.3.啟動服務 1.3.4.關閉防火墻 1.4.FTP Client&#xff08;默認僅能下載文件&#xff09; 1.4.1.LinuxFTP客戶端程序1&#xff1a;l…