偷偷摘套内射激情视频,久久精品99国产国产精,中文字幕无线乱码人妻,中文在线中文a,性爽19p

聊一聊 .NET在Linux下的IO多路復(fù)用select和epoll

系統(tǒng) Linux
在windows平臺上,相信很多人都知道.NET異步機(jī)制是借助了Windows自帶的?IO完成端口?實(shí)現(xiàn)的異步交互,那在 Linux 下.NET 又是怎么玩的呢?主要還是傳統(tǒng)的 select,poll,epoll 的IO多路復(fù)用,在 coreclr源代碼中我們都能找到它們的影子。

一、背景

1. 講故事

在windows平臺上,相信很多人都知道.NET異步機(jī)制是借助了Windows自帶的 IO完成端口 實(shí)現(xiàn)的異步交互,那在 Linux 下.NET 又是怎么玩的呢?主要還是傳統(tǒng)的 select,poll,epoll 的IO多路復(fù)用,在 coreclr源代碼中我們都能找到它們的影子。

select & poll

在平臺適配層的 pal.cpp 文件中,有這樣的一句話。

#if HAVE_POLL
#include <poll.h>
#else
#include "pal/fakepoll.h"
#endif  // HAVE_POLL

簡而言之就是在不支持 poll 的linux版本中使用 select(fakepoll) 模擬,參考代碼如下:

圖片圖片

  2. epoll

同樣的在 linux 中你也會(huì)發(fā)現(xiàn)很多,截圖如下:

圖片圖片

二、select IO多路復(fù)用

1. select 解讀

在沒有 select 之前,我們需要手工管理多句柄的收發(fā),在使用select IO多路復(fù)用技術(shù)之后,這些多句柄管理就由用戶轉(zhuǎn)交給linux系統(tǒng)了,這個(gè)也可以從核心的 select 函數(shù)看出。

int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);
  • readfds,writefds,exceptfds

這三個(gè)字段依次監(jiān)視著哪些句柄已成可讀狀態(tài),哪些句柄已成可寫狀態(tài),哪些句柄已成異常狀態(tài),那技術(shù)上是如何實(shí)現(xiàn)的呢?在libc 中定義了一個(gè) bit 數(shù)組,剛好文件句柄fd值作為 bit數(shù)組的索引,linux 在內(nèi)核中只需要掃描 __fds_bits 中哪些位為1 即可找到需要監(jiān)控的句柄。

/* fd_set for select and pselect.  */
typedef struct
  {
    /* XPG4.2 requires this member name.  Otherwise avoid the name
       from the global namespace.  */
#ifdef __USE_XOPEN
    __fd_mask fds_bits[__FD_SETSIZE / __NFDBITS];
# define __FDS_BITS(set) ((set)->fds_bits)
#else
    __fd_mask __fds_bits[__FD_SETSIZE / __NFDBITS];
# define __FDS_BITS(set) ((set)->__fds_bits)
#endif
  } fd_set;
  •  nfds,timeout

為了減少掃描范圍,提高程序性能,需要用戶指定一個(gè)最大的掃描值到 nfds 上。后面的timeout即超時(shí)時(shí)間。

2. select 的一個(gè)小例子

說了再多還不如一個(gè)例子有說服力,我們使用 select 機(jī)制對 Console 控制臺句柄 (STDIN_FILENO) 進(jìn)行監(jiān)控,一旦有數(shù)據(jù)進(jìn)來立馬輸出,參考代碼如下:

#include <stdio.h>
#include <sys/select.h>
#include <unistd.h>

int main()
{
    fd_set readfds;
    struct timeval timeout;
    char buf[256];

    printf("Enter text (press Ctrl+D to end):\n");

    while (1)
    {
        FD_ZERO(&readfds);
        FD_SET(STDIN_FILENO, &readfds);
        timeout.tv_sec = 5; // 5秒超時(shí)
        timeout.tv_usec = 0;

        int ready = select(STDIN_FILENO + 1, &readfds, NULL, NULL, &timeout);

        if (ready == -1)
        {
            perror("select");
            break;
        }
        elseif (ready == 0)
        {
            printf("\nTimeout (5秒無輸入).\n");
            break;
        }
        elseif (FD_ISSET(STDIN_FILENO, &readfds))
        {
            // 使用 fgets 逐行讀取
            if (fgets(buf, sizeof(buf), stdin) != NULL)
            {
                printf("You entered: %s", buf); // 輸出整行(包含換行符)
            }
            else
            {
                printf("\nEnd of input (Ctrl+D pressed).\n");
                break;
            }
        }
    }

    return0;
}

圖片圖片

稍微解釋下代碼邏輯。

/* Standard file descriptors.  */
#define STDIN_FILENO 0 /* Standard input.  */
#define STDOUT_FILENO 1 /* Standard output.  */
#define STDERR_FILENO 2 /* Standard error output.  */
  • 將 STDIN_FILENO=0 塞入到可讀句柄監(jiān)控 (readfds) 中。
  • 數(shù)據(jù)進(jìn)來之后 select 被喚醒,執(zhí)行后續(xù)邏輯。
  • 通過 FD_ISSET 判斷 bit=0 的位置(STDIN_FILENO)是否可用,可用的話讀取數(shù)據(jù)。

如果大家對 select 底層代碼感興趣,可以看下 linux 的 do_select 簡化實(shí)現(xiàn),大量的遍歷邏輯(bit)。

static noinline_for_stack int do_select(int n, fd_set_bits *fds, struct timespec64 *end_time)
{
for (;;) {
unsignedlong *rinp, *routp, *rexp, *inp, *outp, *exp;
bool can_busy_loop = false;

  inp = fds->in; outp = fds->out; exp = fds->ex;
  rinp = fds->res_in; routp = fds->res_out; rexp = fds->res_ex;

for (i = 0; i < n; ++rinp, ++routp, ++rexp) {
   in = *inp++; out = *outp++; ex = *exp++;
   all_bits = in | out | ex;

   for (j = 0; j < BITS_PER_LONG; ++j, ++i, bit <<= 1) {
    mask = select_poll_one(i, wait, in, out, bit,busy_flag);
    if ((mask & POLLIN_SET) && (in & bit)) {
     res_in |= bit;
     retval++;
     wait->_qproc = NULL;
    }
    if ((mask & POLLOUT_SET) && (out & bit)) {
     res_out |= bit;
     retval++;
     wait->_qproc = NULL;
    }
    if ((mask & POLLEX_SET) && (ex & bit)) {
     res_ex |= bit;
     retval++;
     wait->_qproc = NULL;
    }
   }
  }

if (!poll_schedule_timeout(&table, TASK_INTERRUPTIBLE, to, slack))
   timed_out = 1;
 }

return retval;
}

三、epoll IO多路復(fù)用

1. epoll 解讀

現(xiàn)在主流的軟件(Redis,Nigix) 都是采用 epoll,它解決了select低效的遍歷,畢竟數(shù)組最多支持1024個(gè)bit位,一旦句柄過多會(huì)影響異步讀取的效率。epoll的底層借助了。

  • 紅黑樹:對句柄進(jìn)行管理,復(fù)雜度為 O(logN)。
  • 就緒隊(duì)列:一旦句柄變得可讀或可寫,內(nèi)核會(huì)直接將句柄送到就緒隊(duì)列。

libc中使用 epoll_wait 函數(shù)監(jiān)視著就緒隊(duì)列,一旦有數(shù)據(jù)立即提取,復(fù)雜度 O(1),其實(shí)這個(gè)機(jī)制和 Windows 的IO完成端口 已經(jīng)很靠近了,最后配一下參考代碼。

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

#define MAX_EVENTS 10   // 最大監(jiān)聽事件數(shù)
#define TIMEOUT_MS 5000 // epoll_wait 超時(shí)時(shí)間(毫秒)

int main()
{
    int epoll_fd, nfds;                        // epoll 文件描述符和返回的事件數(shù)
    struct epoll_event ev, events[MAX_EVENTS];// epoll 事件結(jié)構(gòu)體
    char buf[256];

    // 創(chuàng)建 epoll 實(shí)例
    epoll_fd = epoll_create1(0);
    if (epoll_fd == -1)
    {
        perror("epoll_create1");
        exit(EXIT_FAILURE);
    }

    // 配置并添加標(biāo)準(zhǔn)輸入到 epoll 監(jiān)聽
    ev.events = EPOLLIN;       // 監(jiān)聽文件描述符的可讀事件(輸入)
    ev.data.fd = STDIN_FILENO; // 監(jiān)聽標(biāo)準(zhǔn)輸入(文件描述符 0)

    if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, STDIN_FILENO, &ev) == -1)
    {
        perror("epoll_ctl: STDIN_FILENO");
        exit(EXIT_FAILURE);
    }

    printf("Enter text line by line (press Ctrl+D to end):\n");

    // 主循環(huán):監(jiān)聽事件
    while (1)
    {
        // 等待事件發(fā)生或超時(shí)
        nfds = epoll_wait(epoll_fd, events, MAX_EVENTS, TIMEOUT_MS);

        if (nfds == -1)
        {
            perror("epoll_wait");
            break;
        }
        elseif (nfds == 0)
        {
            printf("\nTimeout (5秒無輸入).\n");
            break;
        }

        // 處理所有觸發(fā)的事件
        for (int n = 0; n < nfds; ++n)
        {
            if (events[n].data.fd == STDIN_FILENO)
            {
                // 使用 fgets 逐行讀取輸入
                if (fgets(buf, sizeof(buf), stdin) != NULL)
                {
                    printf("You entered: %s", buf);
                }
                else
                {
                    // 輸入結(jié)束(用戶按下 Ctrl+D)
                    printf("\nEnd of input (Ctrl+D pressed).\n");
                    break;
                }
            }
        }
    }

    close(epoll_fd);
    return0;
}

圖片圖片

四、總結(jié)

說了這么多,文尾總結(jié)下目前主流的 epoll 和 iocp 各自的特點(diǎn)。

特性

epoll (Linux)

IOCP (Windows)

模型

事件驅(qū)動(dòng) (Reactor)

完成端口 (Proactor)

核心思想

通知可讀寫事件

通知I/O操作完成

適用場景

高并發(fā)網(wǎng)絡(luò)編程

高并發(fā)I/O操作

編程復(fù)雜度

較低

較高

網(wǎng)絡(luò)I/O性能

極佳(百萬級連接)

優(yōu)秀

磁盤I/O支持

有限

完善

CPU利用率

內(nèi)存開銷


責(zé)任編輯:武曉燕 來源: 一線碼農(nóng)聊技術(shù)
相關(guān)推薦

2021-05-31 06:50:47

SelectPoll系統(tǒng)

2023-03-01 14:32:31

redisIOEpoll

2021-03-05 11:26:42

面試Java程序

2023-12-13 09:45:49

模型程序

2023-01-09 10:04:47

IO多路復(fù)用模型

2022-09-12 06:33:15

Select多路復(fù)用

2024-08-08 14:57:32

2023-12-06 07:16:31

Go語言語句

2021-07-16 11:48:26

模型 .NET微軟

2020-10-14 09:11:44

IO 多路復(fù)用實(shí)現(xiàn)機(jī)

2022-08-26 00:21:44

IO模型線程

2019-02-13 14:15:59

Linux版本Fedora

2020-06-28 09:30:37

Linux內(nèi)存操作系統(tǒng)

2025-05-13 07:10:31

2023-11-07 08:19:35

IO多路復(fù)用磁盤、

2024-12-30 00:00:05

2021-01-26 05:06:24

LinuxXargs 命令

2021-01-04 08:09:07

Linux內(nèi)核Watchdog

2025-04-24 10:05:51

2021-08-26 09:31:40

Nacos配置注冊
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號