【sylar-webserver】8 HOOK模塊

文章目錄

  • 知識點
    • HOOK實現方式
      • 非侵入式hook
      • 侵入式hook ???
    • 覆蓋系統調用接口
    • 獲取被全局符號介入機制覆蓋的系統調用接口
  • 具體實現
    • C++ 模板成員函數繼承 和 成員函數指針類型匹配 ?????
    • FdCtx 和 FdManager ??
      • 判斷socket的小技巧
      • FdCtx
      • FdManager
    • connect hook ?
    • do_io模板 ?????
    • 記錄超時信息,阻塞信息

在寫之前模塊的時候,我一直在困惑 協程是如何高效工作的,畢竟協程阻塞線程也就阻塞了。
HOOK模塊解開了我的困惑。😎

知識點

HOOK實現方式

動態鏈接中的hook實現

hook的實現機制,通過動態庫的全局符號介入功能,用自定義的接口來替換掉同名的系統調用接口。由于系統調用接口基本上是由C標準函數庫 libc 提供的,所以這里要做的事情就是用自定義的動態庫來覆蓋掉 libc 中的同名符號。

基于動態鏈接的hook有兩種方式:

非侵入式hook

第一種是外掛式hook,也稱為非侵入式hook,通過優先加自定義載動態庫來實現對后加載的動態庫進行hook,這種hook方式不需要重新編譯代碼,考慮以下例子:

#include <unistd.h>
#include <string.h>int main(){write(STDOUT_FILENO, "hello world\n", strlen("hello world\n")); // 調用系統調用write寫標準輸出文件描述符return 0;
}

編譯運行

# gcc main.c
# ./a.out
hello world

ldd命令查看可執行程序的依賴的共享庫

# ldd ./a.out linux-vdso.so.1 (0x00007ffde42a4000)libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f80ec76e000)/lib64/ld-linux-x86-64.so.2 (0x00007f80ecd61000)

可以看到其依賴libc共享庫,write系統調用就是由libc提供的。

下面在不重新編譯代碼的情況下,用自定義的動態庫來替換掉可執行程序a.out中的write實現,新建hook.cc

#include <unistd.h>
#include <sys/syscall.h>
#include <string.h>ssize_t write(int fd, const void *buf, size_t count) {syscall(SYS_write, STDOUT_FILENO, "12345\n", strlen("12345\n"));
}
gcc -fPIC -shared hook.cc -o libhook.so	# 把hook.cc編譯成動態庫

通過設置 LD_PRELOAD環境變量,將libhoook.so設置成優先加載,從面覆蓋掉libc中的write函數,如下:

# LD_PRELOAD="./libhook.so" ./a.out 
12345

LD_PRELOAD環境變量,它指明了在運行a.out之前,系統會優先把libhook.so加載到了程序的進程空間,使得在a.out運行之前,其全局符號表中就已經有了一個write符號,這樣在后續加載libc共享庫時,由于全局符號介入機制,libc中的write符號不會再被加入全局符號表,所以全局符號表中的write就變成了我們自己的實現。?

侵入式hook ???

libco,libgo 也是使用這種方式

第二種方式的hook是侵入式的,需要改造代碼或是重新編譯一次以指定動態庫加載順序。

覆蓋系統調用接口

unsigned int sleep(unsigned int seconds){... 
}

直接寫入文件,只需要比 libc 提前鏈接即可。

獲取被全局符號介入機制覆蓋的系統調用接口

dslym 函數原型

#define _GNU_SOURCE
#include <dlfcn.h>void *dlsym(void *handle, const char *symbol);
  • 鏈接需要指定 -ldl 參數。
  • 使用dlsym找回被覆蓋的符號,第一個參數固定為RTLD_NEXT,第二個參數是符號的名稱。

具體實現

CMakeLists.txt

set(LIBSsylaryaml-cpppthreaddl
)
extern "C"{// sleep // 定義了函數指針類型 sleep_fun// 該類型對應原生 sleep 函數的簽名(接收 unsigned int 參數,返回 unsigned int)typedef unsigned int (*sleep_fun)(unsigned int seconds);// 聲明外部的全局函數指針變量 sleep_f,用于保存原始 sleep 函數的地址// 通過 sleep_f 仍能調用原版函數extern sleep_fun sleep_f;
}
#define HOOK_FUN(XX) \XX(sleep)void hook_init(){static bool is_inited = false;if(is_inited){return;}//保存原函數:hook_init() 通過 dlsym(RTLD_NEXT, "sleep") 獲取系統原版 sleep 函數的地址,保存到 sleep_f 指針
#define XX(name) name ## _f = (name ## _fun)dlsym(RTLD_NEXT, #name);HOOK_FUN(XX);
#undef XX 
}extern "C" {
#define XX(name) name ## _fun name ## _f = nullptr;		// 初始化 sleep_fun sleep_f = nullptr;HOOK_FUN(XX);
#undef XX// sleep
unsigned int sleep(unsigned int seconds){if(!sylar::t_hook_enable){return sleep_f(seconds);}sylar::Fiber::ptr fiber = sylar::Fiber::GetThis();sylar::IOManager* iom = sylar::IOManager::GetThis();/*** C++規定成員函數指針的類型包含類信息,即使存在繼承關系,&IOManager::schedule 和 &Scheduler::schedule 屬于不同類型。* 通過強制轉換,使得類型系統接受子類對象iom調用基類成員函數的合法性。* * schedule是模板函數* 子類繼承的是模板的實例化版本,而非原始模板* 直接取地址會導致函數簽名包含子類類型信息* * std::bind 的類型安全機制* bind要求成員函數指針類型與對象類型嚴格匹配。當出現以下情況時必須轉換:* * 總結,當需要綁定 子類對象調用父類模板成員函數,父類函數需要強轉成父類* (存在多繼承或虛繼承導致this指針偏移)* * 或者* std::bind(&Scheduler::schedule, static_cast<Scheduler*>(iom), fiber, -1)* */iom->addTimer(seconds * 1000 , std::bind((void(sylar::Scheduler::*)(sylar::Fiber::ptr, int thread))&sylar::IOManager::schedule, iom, fiber, -1));sylar::Fiber::GetThis()->yield();return 0;
}

C++ 模板成員函數繼承 和 成員函數指針類型匹配 ?????

  1. 模板成員函數繼承的指針類型問題
// 基類 Scheduler 的模板函數
class Scheduler {
public:template<class FiberOrCb>void schedule(FiberOrCb fc, int thread = -1); // 模板函數
};// 子類 IOManager 繼承模板函數的實例化版本
class IOManager : public Scheduler {// 繼承 schedule<sylar::Fiber::ptr> 的實例化版本
};
  • 問題本質:當子類繼承模板成員函數時,&IOManager::schedule 的類型實際上時 void (IOManager::*)(sylar::Fiber::ptr, int)
  • (我的理解是模板函數的參數類型不確定,必須顯示的轉成確定的函數類型指針)
  • 類型不匹配:std::bind 要求成員函數指針類型必須與對象類型嚴格匹配。
  1. 多繼承場景下的 this 指針偏移風險
// 強制轉換的語法含義
(void(sylar::Scheduler::*)(sylar::Fiber::ptr, int)) &sylar::IOManager::schedule
  • 類型安全:通過強制轉換為基類成員函數指針類型:
    • 確保調用時正確進行 this 指針調整
    • 避免多繼承場景下潛在的指針偏移錯誤
iom->addTimer(usec / 1000,std::bind((void(Scheduler::*)(Fiber::ptr, int))  // 關鍵轉換&IOManager::schedule,  // 原始成員函數指針iom,   // IOManager* 類型的對象fiber, // 參數1-1     // 參數2)
);

FdCtx 和 FdManager ??

FdManager::get(fd) | ||
new FdCtx(fd) | ||
FdCtx::init()	// 獲取到fd的基礎信息 m_isInit,m_isSocket,m_sysNonblock(默認true), m_userNonblock(默認false,通過hook fcntl操作記錄),m_isClosed, m_recvTimeout(-1),m_sendTimeout(-1)

m_userNonblock 阻塞屬性 通過 hook fcntl -> setUserNonblock ?
m_recvTimeout,m_sendTimeout 超時事件 通過 hook setsockopt -> setTimeout 設置?

判斷socket的小技巧

		/*** stat族 ?* * 獲取fd信息* int fstat(int filedes, struct stat *buf);* 返回值: 執行成功則返回0,失敗返回-1,錯誤代碼存于errno* * 查看 stat 里的 st_mode 屬性* * 常用宏S_ISLNK(st_mode):是否是一個連接.S_ISREG是否是一個常規文件.S_ISDIR是否是一個目錄S_ISCHR是否是一個字符設備.S_ISBLK是否是一個塊設備S_ISFIFO是否是一個FIFO文件.S_ISSOCK是否是一個SOCKET文件. */struct stat fd_stat;if(-1 == fstat(m_fd, &fd_stat)){m_isInit = false;m_isSocket = false;}else{m_isInit = true;m_isSocket = S_ISSOCK(fd_stat.st_mode);}

FdCtx


class FdCtx : public std::enable_shared_from_this<FdCtx>{
public:typedef std::shared_ptr<FdCtx> ptr;FdCtx(int fd);~FdCtx();bool init();bool isInit() const {return m_isInit;}bool isSocket() const {return m_isSocket;}bool isClose() const {return m_isClosed;}void setUserNonblock(bool v){m_userNonblock = v;}bool getUserNonblock() const {return m_userNonblock;}void setSysNonblock(bool v){m_sysNonblock = v;}bool getSysNonblock() const {return m_sysNonblock;}/*** @brief 設置超時時間* @param[in] type 類型SO_RCVTIMEO(讀超時), SO_SNDTIMEO(寫超時)* @param[in] v 時間毫秒*/void setTimeout(int type, uint64_t v);/*** @brief 獲取超時時間* @param[in] type 類型SO_RCVTIMEO(讀超時), SO_SNDTIMEO(寫超時)* @return 超時時間毫秒*/int getTimeout(int type);private:// 使用位域,可能考慮到會有大量的fd連接,節省空間。?bool m_isInit: 1;bool m_isSocket: 1;bool m_sysNonblock: 1;   // 是否 hook 非阻塞bool m_userNonblock: 1;  // 是否 用戶主動設置 非阻塞bool m_isClosed: 1;int m_fd;uint64_t m_recvTimeout;  // 讀超時時間毫秒uint64_t m_sendTimeout;  // 寫超時時間毫秒
};

FdManager

class FdManager{
public:typedef RWMutex RWMutexType;FdManager();/*** @brief 獲取/創建文件句柄類FdCtx* @param[in] fd 文件句柄* @param[in] auto_create 是否自動創建* @return 返回對應文件句柄類FdCtx::ptr*/FdCtx::ptr get(int fd, bool auto_create = false);/*** @brief 刪除文件句柄類* @param[in] fd 文件句柄*/void del(int fd);private:RWMutexType m_mutex;std::vector<FdCtx::ptr> m_datas;
};

connect hook ?

int connect_with_timeout(int fd, const struct sockaddr *addr, socklen_t addrlen, uint64_t timeout_ms){... /*** 非阻塞connect調用會立即返回EINPROGRESS錯誤碼,表示連接正在建立* 此時不需要也不能重復調用connect,否則可能觸發EALREADY錯誤 (和 do_io 不同的地方) ?* 通過等待WRITE事件即可判斷連接是否建立完成*/int n = connect_f(fd, addr, addrlen);if(n == 0){return 0;}else if(n != -1 || errno != EINPROGRESS){ return n;}// 下面和 do_io 類似sylar::IOManager* iom = sylar::IOManager::GetThis();sylar::Timer::ptr timer;std::shared_ptr<timer_info> tinfo(new timer_info);std::weak_ptr<timer_info> winfo(tinfo);if(timeout_ms != (uint64_t)-1){iom->addConditionTimer(timeout_ms, [winfo, fd, iom](){auto it = winfo.lock();if(!it || it->cancelled){return;}it->cancelled = ETIMEDOUT;iom->cancelEvent(fd, sylar::IOManager::Event::WRITE);}, winfo);}int rt = iom->addEvent(fd, sylar::IOManager::Event::WRITE);if(rt == 0){sylar::Fiber::GetThis()->yield();if(timer){timer->cancel();}if(tinfo->cancelled){errno = tinfo->cancelled;return -1;}}else{if(timer) {timer->cancel();}SYLAR_LOG_ERROR(g_logger) << "connect addEvent(" << fd << ", WRITE) error";}int error = 0;socklen_t len = sizeof(int);// 非阻塞 connect 操作返回 EINPROGRESS 后,通過監聽寫事件完成連接建立,此時需要檢查實際連接結果if(-1 == getsockopt(fd, SOL_SOCKET, SO_ERROR, &error, &len)){return -1;}if(!error){return 0;}else{errno = error;return -1;}
}int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen){// static uint64_t s_connect_timeout = -1;return connect_with_timeout(sockfd, addr, addrlen, sylar::s_connect_timeout);
}

do_io模板 ?????

/*** 重點 !!!* * 模板函數,通用的 read-write api hook 操作* * Args&& 萬能引用,根據傳入實參自動推導* * 這里Args,可能是左值,也可能是右值* * std::forward 保持參數的原始值類別 */ 
template<typename OriginFun, typename ... Args> // 常用?
static ssize_t do_io(int fd, 						OriginFun fun, 					// hook的原庫函數const char* hook_fun_name, 		// debug輸出,hook的函數名uint32_t event, int timeout_so,     			// 讀 / 寫 超時 宏標簽Args&&... args)
{// Scheduler::run() 設置當前線程是否hook ?if(!sylar::t_hook_enable){return fun(fd, std::forward<Args>(args)...);}// fd 添加到 FdMgrsylar::FdCtx::ptr ctx = sylar::FdMgr::GetInstance()->get(fd);if(!ctx){return fun(fd, std::forward<Args>(args)...);}// 如果ctx關閉if(ctx->isClose()){errno = EBADF;return -1;}// 不是 socket 或者 用戶設定了非阻塞// 用戶設定了非阻塞,意味著自行處理非阻塞邏輯f(!ctx->isSocket() || ctx->getUserNonblock()){return fun(fd, std::forward<Args>(args)...);}uint64_t to = ctx->getTimeout(timeout_so);	// 獲取時間超時時間,通過setsockopt hook寫入/*
struct timer_info{int cancelled = 0;
};*/std::shared_ptr<timer_info> tinfo(new timer_info);retry:ssize_t n = fun(fd, std::forward<Args>(args)...);while(n == -1 && errno == EINTR){		// 系統調用被信號中斷n = fun(fd, std::forward<Args>(args)...);}if(n == -1 && errno == RAGAIN){			// 非阻塞操作無法立即完成sylar::IOManager* iom = sylar::IOManager::GetThis();sylar::Timer::ptr timer;std::weak_ptr<timer_info> winfo(tinfo);if(to != (uint64_t)-1){// 添加一個條件定時器,如果 tinfo 還在意味著 fd還沒等到event觸發。// 到了超時時間,就直接取消事件。timer = iom->addConditionTimer(to, [iom, winfo, fd, event](){auto it = winfo.lock();if(!it || it->cancelled){ // 雙重驗證?return;}it->cancelled = ETIMEDOUT;// cancelEvent 取消事件觸發條件,直接觸發事件 ?iom->cancelEvent(fd, (sylar::IOManager::Event)event);}, winfo);}// 沒傳入fd,把當前協程傳入。當事件觸發,會回到這個協程繼續運行int rt = iom->addEvent(fd, (sylar::IOManager::Event)event); // 正式 注冊事件 ?if(rt != 0){	// 添加失敗// 定時器刪除if(timer){timer->cancel();   // 刪除定時器的權利 交給了定時器}return rt;}else{sylar::Fiber::GetThis()->yield();/*再次回到這里,兩種情況:1. 定時器觸發之前,事件觸發2. 定時器觸發,事件超時*/if(timer){timer->cancel();}if(tinfo->cancelled){		// 2. 超時errno = tinfo->cancelled;return -1;}goto retry;		// 1. 重新操作 fd}}return n;
}

使用案例

int accept(int s, struct sockaddr *addr, socklen_t *addrlen){int fd = do_io(s, accept_f, "accept", sylar::IOManager::Event::READ, SO_RCVTIMEO, addr, addrlen);if(fd != -1){sylar::FdMgr::GetInstance()->get(fd, true);}return fd;
}ssize_t write(int fd, const void *buf, size_t count){return do_io(fd, write_f, "write", sylar::IOManager::Event::WRITE, SO_SNDTIMEO, buf, count);
}

記錄超時信息,阻塞信息

// 增加fd事件超時選項,設置了超時事件,上面的hook才會有定時器,不然fd事件會一直存在
int setsockopt(int sockfd, int level, int optname, const void *optval, socklen_t optlen){if(!sylar::t_hook_enable){return setsockopt_f(sockfd, level, optname, optval, optlen);}if(level == SOL_SOCKET){if(optname == SO_RCVTIMEO || optname == SO_SNDTIMEO){   // 超時事件設置sylar::FdCtx::ptr ctx = sylar::FdMgr::GetInstance()->get(sockfd);if(ctx){const timeval* v = (const timeval*)optval;ctx->setTimeout(optname, v->tv_sec* 1000 + v->tv_usec / 1000);}}}return setsockopt_f(sockfd, level, optname, optval, optlen);
}
int fcntl(int fd, int cmd, ... /* arg */ ){va_list va;va_start(va, cmd);switch(cmd){case F_SETFL:{int arg = va_arg(va, int);va_end(va);// 獲取 FdCtxsylar::FdCtx::ptr ctx = sylar::FdMgr::GetInstance()->get(fd);if(!ctx || ctx->isClose() || !ctx->isSocket()){return fcntl_f(fd, cmd, arg);}// 檢查args,用戶是否設置 非阻塞。// FdCtx里的m_userNonblock,這里設置。 ?ctx->setUserNonblock(arg & O_NONBLOCK);// 要執行了,所以把 hook 非阻塞直接加上。if(ctx->getSysNonblock()){arg |= O_NONBLOCK;}else{arg &= ~O_NONBLOCK;}return fcntl_f(fd, cmd, arg);}break;case F_GETFL:{va_end(va);int arg = fcntl_f(fd, cmd);sylar::FdCtx::ptr ctx = sylar::FdMgr::GetInstance()->get(fd);if(!ctx || ctx->isClose() || !ctx->isSocket()){return arg;}// 設置 用戶是否判斷 非阻塞。if(ctx->getUserNonblock()){return arg | O_NONBLOCK;}else{ // 如果之前就沒有,那么需要恢復默認。(Hook默認加上了非阻塞)?return arg & ~O_NONBLOCK;}}break;case F_DUPFD:case F_DUPFD_CLOEXEC:case F_SETFD:case F_SETOWN:case F_SETSIG:case F_SETLEASE:case F_NOTIFY:#ifdef F_SETPIPE_SZcase F_SETPIPE_SZ:#endif{int arg = va_arg(va, int);va_end(va);return fcntl_f(fd, cmd, arg); }break;case F_GETFD:case F_GETOWN:case F_GETSIG:case F_GETLEASE:#ifdef F_GETPIPE_SZcase F_GETPIPE_SZ:#endif{va_end(va);return fcntl_f(fd, cmd);}break;case F_SETLK:case F_SETLKW:case F_GETLK:{struct flock* arg = va_arg(va, struct flock*);va_end(va);return fcntl_f(fd, cmd, arg);}break;case F_GETOWN_EX:case F_SETOWN_EX:{struct f_owner_exlock* arg = va_arg(va, struct f_owner_exlock*);va_end(va);return fcntl_f(fd, cmd, arg);}break;default:va_end(va);return fcntl_f(fd, cmd);}
}// ioctl 用于 設備驅動程序中設備控制接口函數 ? 沒用過
int ioctl(int d, unsigned long int request, ...){va_list va;va_start(va, request);void* arg = va_arg(va, void*);va_end(va);// FIONBIO(設置非阻塞模式)if(FIONBIO == request){ // 主要用于處理文件描述符的非阻塞模式設置bool user_nonblock = !!*(int*)arg;   // 將參數轉換為布爾值sylar::FdCtx::ptr ctx = sylar::FdMgr::GetInstance()->get(d);if(!ctx || ctx->isClose() || !ctx->isSocket()){return ioctl_f(d, request, arg);}ctx->setUserNonblock(user_nonblock);}return ioctl_f(d, request, arg);
}

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

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

相關文章

SpringAI+DeepSeek大模型應用開發——1 AI概述

AI領域常用詞匯 LLM&#xff08;LargeLanguage Model&#xff0c;大語言模型&#xff09; 能理解和生成自然語言的巨型AI模型&#xff0c;通過海量文本訓練。例子&#xff1a;GPT-4、Claude、DeepSeek、文心一言、通義干問。 G&#xff08;Generative&#xff09;生成式: 根據上…

SpringBoot 基本原理

SpringBoot 為我們做的自動配置&#xff0c;確實方便快捷&#xff0c;但一直搞不明白它的內部啟動原理&#xff0c;這次就來一步步解開 SpringBoot 的神秘面紗&#xff0c;讓它不再神秘。 目錄 SpringBootApplication 背后的秘密 Configuration ComponentScan EnableAutoC…

2025.4.17總結

工作&#xff1a;今天對需求的測試設計進行了完善&#xff0c;然后&#xff0c;對測試設計進行了評審&#xff0c;最后提了個問題單。 反思這個過程&#xff0c;要說不足的地方&#xff0c;就是評審的時候總覺得自己吐字不清晰&#xff0c;表達能力早就想提升了&#xff0c;但…

2021-11-14 C++三七二十一數

緣由c編程怎么寫&#xff0c;緊急求解-編程語言-CSDN問答 void 三七二十一數() {//緣由https://ask.csdn.net/questions/7566632?spm1005.2025.3001.5141int n 0, a 0, b 0, p 1;std::cin >> n;while (n--){std::cin >> a >> b;while (a<b){if (a %…

大模型面經 | DeepSpeed中ZeRO-1、ZeRO-2和ZeRO-3的區別是什么?

大家好,我是皮先生!! 今天給大家分享一些關于大模型面試常見的面試題,希望對大家的面試有所幫助。 往期回顧: 大模型面經 | 春招、秋招算法面試常考八股文附答案(RAG專題一) 大模型面經 | 春招、秋招算法面試常考八股文附答案(RAG專題二) 大模型面經 | 春招、秋招算法…

spring boot 文件上傳

1.編寫文件上傳的表單頁面 <!DOCTYPE html> <html lang"en" xmlns:th"http://www.thymeleaf.org"> <head><meta charset"UTF-8"><meta http-equiv"Content-Type" content"text/html; charsetUTF-8&qu…

機器學習核心算法全解析:從基礎到進階的 18 大算法模型

在機器學習領域&#xff0c;算法模型是解決實際問題的核心工具。 不同的算法適用于不同的數據場景和任務需求&#xff0c;理解它們的原理與應用是掌握機器學習的關鍵。 以下將詳細解析 18 個核心算法模型&#xff0c;涵蓋監督學習、無監督學習、集成學習和深度學習等多個領域…

5G網絡切片:精準分配資源,提升網絡效率的關鍵技術

5G網絡切片&#xff1a;精準分配資源&#xff0c;提升網絡效率的關鍵技術 隨著5G技術的廣泛應用&#xff0c;網絡切片&#xff08;Network Slicing&#xff09;作為其核心創新之一&#xff0c;正在改變傳統網絡架構。它通過將物理網絡劃分為多個邏輯網絡&#xff08;切片&…

Spring Boot中Excel處理完全指南

文章目錄 1. Excel處理基礎知識1.1 為什么需要在應用中處理Excel文件&#xff1f;1.2 Java中的Excel處理庫介紹1.2.1 Apache POI1.2.2 EasyExcel1.2.3 JExcel1.2.4 Apache POI SXSSF 1.3 Spring Boot中集成Excel處理 2. 在Spring Boot中集成Excel處理庫2.1 集成Apache POI2.1.1…

Elasticsearch 8.18 中提供了原生連接 (Native Joins)

作者&#xff1a;來自 Elastic Costin Leau 探索 LOOKUP JOIN&#xff0c;這是一條在 Elasticsearch 8.18 的技術預覽中提供的新 ES|QL 命令。 很高興宣布 LOOKUP JOIN —— 這是一條在 Elasticsearch 8.18 的技術預覽中提供的新 ES|QL 命令&#xff0c;旨在執行左 joins 以進行…

2025年滲透測試面試題總結-拷打題庫03(題目+回答)

網絡安全領域各種資源&#xff0c;學習文檔&#xff0c;以及工具分享、前沿信息分享、POC、EXP分享。不定期分享各種好玩的項目及好用的工具&#xff0c;歡迎關注。 目錄 2025年滲透測試面試題總結-拷打題庫03 一、Windows與Linux系統提權思路 Windows提權 Linux提權 二、…

【華為】OSPF震蕩引起CPU占用率高怎么解決?

原創&#xff1a;廈門微思網絡 現象描述 如圖所示&#xff0c;Switch_1、Switch_2、Switch_3和Switch_4配置了OSPF協議&#xff0c;發現Switch_1設備的CPU占用率高&#xff0c;ROUT任務占用率明顯高于其他任務并且產生路由震蕩。 故障組網圖 原因分析 網絡中IP地址沖突導致…

Everything 安裝教程與使用教程(附安裝包)

文章目錄 前言一、Everything 介紹二、Everything 安裝教程1.Everything 安裝包下載2.選擇安裝文件3.選擇安裝語言4.接受許可協議5.選擇安裝位置6.配置安裝選項7.完成安裝 三、Everything 使用教程1.啟動軟件2.簡單關鍵詞搜索3.按類型搜索 前言 在日常使用電腦時&#xff0c;隨…

極狐GitLab CI/CD 流水線計算分鐘數如何管理?

極狐GitLab 是 GitLab 在中國的發行版&#xff0c;關于中文參考文檔和資料有&#xff1a; 極狐GitLab 中文文檔極狐GitLab 中文論壇極狐GitLab 官網 計算分鐘管理 (PREMIUM SELF) 在極狐GitLab 16.1 中&#xff0c;從 CI/CD 分鐘數重命名為計算配額或計算分鐘數。 管理員可…

Containerd 1.7.2 離線安裝與配置全指南(生產級優化)

Containerd 1.7.2 離線安裝與配置全指南&#xff08;生產級優化&#xff09; 摘要&#xff1a;本文詳細講解在無外網環境下部署 Containerd 1.7.2 容器運行時的完整流程&#xff0c;涵蓋二進制包安裝、私有鏡像倉庫配置、Systemd服務集成等關鍵步驟&#xff0c;并提供生產環境…

33-公交車司機管理系統

技術&#xff1a; 基于 B/S 架構 SpringBootMySQLvueelementui 環境&#xff1a; Idea mysql maven jdk1.8 node 用戶端功能 1.首頁:展示車輛信息及車輛位置和線路信息 2.模塊:車輛信息及車輛位置和線路信息 3.公告、論壇 4.在線留言 5.個人中心:修改個人信息 司機端功能…

基于 OpenCV 的圖像與視頻處理

基于 OpenCV 的圖像處理 一、實驗背景 OpenCV 是一個開源的計算機視覺庫&#xff0c;廣泛應用于圖像處理、視頻分析、目標檢測等領域。通過學習 OpenCV&#xff0c;可以快速實現圖像和視頻的處理功能&#xff0c;為復雜的應用開發 奠定基礎。本實驗旨在通過實際代碼示例&…

Linux 常用指令用戶手冊

Linux 常用指令用戶手冊 適合新手入門 & 日常速查 目錄 基礎操作文件與目錄管理權限與所有權文本處理壓縮與解壓系統監控網絡操作進程管理實用小技巧 1. 基礎操作 1.1 查看系統信息 # 查看內核版本 uname -a# 查看系統發行版信息&#xff08;適用于 Debian/Ubuntu&…

長效IP與短效IP:如何選擇適合業務的代理類型

在當今數據驅動的互聯網環境中&#xff0c;代理IP已成為企業運營、數據采集和網絡安全的關鍵工具。其中長效IP與短效IP作為兩種主流代理類型&#xff0c;因特性差異被應用于不同場景。本文將深入解析二者的區別&#xff0c;并提供實際場景中的選擇建議。 一、長效IP與短效IP&a…

數據結構|排序算法(三)選擇排序 堆排序 歸并排序

一、選擇排序 1.算法思想 選擇排序&#xff08;Selection Sort&#xff09;是一種簡單直觀的排序算法&#xff0c;其基本思想是&#xff1a;每次都從待排序部分中選出最小的一個數據和待排序的第一個數據交換。 將待排序序列分為已排序和未排序兩部分&#xff0c;初始時已排…