kqueue用法簡介

来源:http://www.cnblogs.com/luminocean/archive/2016/06/30/5631336.html
-Advertisement-
Play Games

1.什麼是kqueue和IO復用 kueue是在UNIX上比較高效的IO復用技術。 所謂的IO復用,就是同時等待多個文件描述符就緒,以系統調用的形式提供。如果所有文件描述符都沒有就緒的話,該系統調用阻塞,否則調用返回,允許用戶進行後續的操作。 常見的IO復用技術有select, poll, epol ...


1.什麼是kqueue和IO復用

kueue是在UNIX上比較高效的IO復用技術。
所謂的IO復用,就是同時等待多個文件描述符就緒,以系統調用的形式提供。如果所有文件描述符都沒有就緒的話,該系統調用阻塞,否則調用返回,允許用戶進行後續的操作。
常見的IO復用技術有select, poll, epoll以及kqueue等等。其中epoll為Linux獨占,而kqueue則在許多UNIX系統上存在,包括OS X(好吧,現在叫macOS了。。)

2. 使用概覽

kueue在設計上是非常簡潔的,在易用性上可能比select和epoll更好一些。
使用kqueue的大致代碼如下:(後面會給出一個完整的示例)


const static int FD_NUM = 2 // 要監視多少個文件描述符

int kq = kqueue(); // kqueue對象

// kqueue的事件結構體,不需要直接操作
struct kevent changes[FD_NUM]; // 要監視的事件列表
struct kevent events[FD_NUM]; // kevent返回的事件列表(參考後面的kevent函數)

int stdin_fd = STDIN_FILENO;
int stdout_fd = STDOUT_FILENO;

// 在changes列表中註冊標準輸入流的讀事件 以及 標準輸出流的寫事件
// 最後一個參數可以是任意的附加數據(void * 類型),在這裡給事件附上了當前的文件描述符,後面會用到
EV_SET(&changes[0], stdin_fd, EVFILT_READ, EV_ADD | EV_ENABLE, 0, 0, &stdin_fd); 
EV_SET(&changes[1], stdout_fd, EVFILT_WRITE, EV_ADD | EV_ENABLE, 0, 0, &stdin_fd);

// 進行kevent函數調用,如果changes列表裡有任何就緒的fd,則把該事件對應的結構體放進events列表裡面
// 返回值是這次調用得到了幾個就緒的事件 (nev = number of events)
int nev = kevent(kq, changes, FD_NUM, events, FD_NUM, NULL); // 已經就緒的文件描述符數量
for(int i=0; i<nev; i++){
    struct kevent event = events[i]; // 一個個取出已經就緒的事件

    int ready_fd = *((int *)event.udata); // 從附加數據裡面取迴文件描述符的值
    if( ready_fd == stdin_fd ){
        // 讀取ready_fd
    }else if( ready_fd == stdin_fd ){
        // 寫入ready_fd
    }
}

3. 相關結構體與函數解析

可以看出來,kqueue體系只有三樣東西:struct kevent結構體,EV_SET巨集以及kevent函數。

struct kevent 結構體內容如下:

struct kevent {
    uintptr_t       ident;          /* identifier for this event,比如該事件關聯的文件描述符 */
    int16_t         filter;         /* filter for event,可以指定監聽類型,如EVFILT_READ,EVFILT_WRITE,EVFILT_TIMER等 */
    uint16_t        flags;          /* general flags ,可以指定事件操作類型,比如EV_ADD,EV_ENABLE, EV_DELETE等 */
    uint32_t        fflags;         /* filter-specific flags */
    intptr_t        data;           /* filter-specific data */
    void            *udata;         /* opaque user data identifier,可以攜帶的任意數據 */
};

EV_SET 是用於初始化kevent結構的便利巨集,其簽名為:

EV_SET(&kev, ident, filter, flags, fflags, data, udata);

可以發現和kevent結構體完全對應,除了第一個,它就是你要初始化的那個kevent結構。

kevent 是真正進行IO復用的函數,其簽名為:

int kevent(int kq, 
    const struct kevent *changelist, // 監視列表
    int nchanges, // 長度
    struct kevent *eventlist, // kevent函數用於返回已經就緒的事件列表
    int nevents, // 長度
    const struct timespec *timeout); // 超時限制

4. 完整示例

下麵給出一個完整的示例,這個程式將從標準輸入中讀取數據,寫到標準輸出中。其中輸入輸出全部使用kqueue來進行IO復用。可以使用重定向把文件寫入標準輸入來進行測試。

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/event.h>
#include <errno.h>
#include <string.h>

// 為文件描述符打開對應狀態位的工具函數
void turn_on_flags(int fd, int flags){
    int current_flags;
    // 獲取給定文件描述符現有的flag
    // 其中fcntl的第二個參數F_GETFL表示要獲取fd的狀態
    if( (current_flags = fcntl(fd, F_GETFL)) < 0 ) exit(1);

    // 施加新的狀態位
    current_flags |= flags;
    if( fcntl(fd, F_SETFL, current_flags) < 0 ) exit(1);
}

// 錯誤退出的工具函數
int quit(const char *msg){
    perror(msg);
    exit(1);
}

const static int FD_NUM = 2; // 兩個文件描述符,分別為標準輸入與輸出
const static int BUFFER_SIZE = 1024; // 緩衝區大小

// 完全以IO復用的方式讀入標準輸入流數據,輸出到標準輸出流中
int main(){
    struct kevent changes[FD_NUM];
    struct kevent events[FD_NUM];

    // 創建一個kqueue
    int kq;
    if( (kq = kqueue()) == -1 ) quit("kqueue()");

    // 準備從標準輸入流中讀數據
    int stdin_fd = STDIN_FILENO;
    int stdout_fd = STDOUT_FILENO;

    // 設置為非阻塞
    turn_on_flags(stdin_fd, O_NONBLOCK);
    turn_on_flags(stdout_fd, O_NONBLOCK);

    // 註冊監聽事件
    int k = 0;
    EV_SET(&changes[k++], stdin_fd, EVFILT_READ, EV_ADD | EV_ENABLE, 0, 0, &stdin_fd);
    EV_SET(&changes[k++], stdout_fd, EVFILT_WRITE, EV_ADD | EV_ENABLE, 0, 0, &stdout_fd);

    int nev, nread, nwrote = 0; // 發生事件的數量, 已讀位元組數, 已寫位元組數
    char buffer[BUFFER_SIZE];

    while(1){
        nev = kevent(kq, changes, FD_NUM, events, FD_NUM, NULL); // 已經就緒的文件描述符數量
        if( nev <= 0 ) quit("kevent()");

        int i;
        for(i=0; i<nev; i++){
            struct kevent event = events[i];
            if( event.flags & EV_ERROR ) quit("Event error");

            int ev_fd = *((int *)event.udata);

            // 輸入流就緒 且 緩衝區還有空間能繼續讀
            if( ev_fd == stdin_fd && nread < BUFFER_SIZE ){
                int new_nread;
                if( (new_nread = read(ev_fd, buffer + nread, sizeof(buffer) - nread)) <= 0 )
                    quit("read()"); // 由於可讀事件已經發生,因此如果讀出0個位元組也是不正常的
                
                nread += new_nread; // 遞增已讀數據位元組數
            }

            // 輸出流就緒 且 緩衝區有內容可以寫出
            if( ev_fd == stdout_fd && nread > 0 ){
                if( (nwrote = write(stdout_fd, buffer, nread)) <=0 )
                    quit("write()");

                memmove(buffer, buffer+nwrote, nwrote); // 為了使實現的代碼更簡潔,這裡把還沒有寫出去的數據往前移動
                nread -= nwrote; // 減去已經寫出去的位元組數
            }
        }
    }

    return 0;
}

程式中對stdin和stdout設置非阻塞的原因是我們希望有多少就緒的數據就讀多少,或者能寫入多少進緩衝區就寫入多少。否則在阻塞模式下,如果read沒有填滿buffer(文件沒讀完時),或者還有buffer數據沒寫入時,系統調用(read和write)會阻塞,這會對性能造成很大影響。因此這裡設置為非阻塞模式。


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 幾個重要概念 Slab memcached通過slab機制進行記憶體的分配和回收,slab是一個記憶體塊,它是memcached一次申請記憶體的最小單位,。在啟動memcached的時候一般會使用參數-m指定其可用記憶體,但是並不是在啟動的那一刻所有的記憶體就全部分配出去了,只有在需要的時候才會去申請,而且每 ...
  • 本文轉自http://www.cnblogs.com/yunf/archive/2011/04/12/2013448.html,在此感謝作者yfProgramer。 對於我這種剛學mysql的,還是十分有用的。 雖然現在mysql已經可以利用workbench直接創建資料庫了,但是開頭的對庫和表的增 ...
  • 好久不寫文,最近得空寫一點。Oracle資料庫國內用戶量主要在企業上,其中有一種byte的存儲稱為Blob,並不能直接看。 有時候為了調試需要,可以通過: 這種sql去轉為字元串查看,但是不方便,一次最多轉出2000個位元組。需要通過index拼成完整的文本。 另外一種情況下,如果存儲的是圖片、wor ...
  • 安裝好MySQL以後,系統給了個預設的的密碼,然後說如果忘記了預設的密碼。。。。。。我複製了預設密碼就走過了只一步,這一步就是我漫長旅程的開始。他給的密碼太複雜了,當然我得換一個,而且我還要假裝我不記得密碼了,就這樣我走上了不歸路。。。。。。 這個過程是心酸的,網上的資料多如狗,關鍵是各有各的錯法, ...
  • 當我談論索引時,大家經常會問我在複合非聚集索引里,列的順序是否重要?簡單來說:“看情況”。我們來具體看下為啥“看情況”…… 單例查找(Singleton Lookups) 當在你的表上有進行單例查找的查詢時,在複合非聚集索引里列的順序真的不重要。假設下列查詢: 現在你可以在StateProvince ...
  • 聲明:以下的代碼成果,是參考了網上的injso技術,文章最後會給出地址。 另外一個,injso文章中的代碼實際上不能夠運行起來的,後面出現的代碼都是經過我個人修改和檢測的。 最近因為在學習一些調試的技術,但是很少有提到如何在函數運行時實現函數替換的。 為什麼會想到這一點?因為在學習調試時,難免會看到 ...
  • call和jmp都是跳轉指令,但是call的同時會把pc地址壓入堆棧,並且這兩種方式都有遠和近跳轉。下麵的分析不全,因為沒有在網上找到足夠的資料,個人創造這個情景還是有些困難。 1.例子中的call的機器碼為0xe8。 0x400204ba <+30>: e8 41 b6 05 00 call 0x ...
  • STM32除TIM6和TIM7外都可以產生PWM輸出。高級定時器TIM1和TIM8可以同時產生7路PWM,通用定時器可以產生4路PWM輸出。 1.TIM1 CH1輸出PWM配置步驟 ①開啟TIM1時鐘,配置PA8為復用輸出 APB2外設時鐘使能寄存器(RCC_APB2ENR) APB1外設複位寄存器 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...