精品欧美一区二区三区在线观看 _久久久久国色av免费观看性色_国产精品久久在线观看_亚洲第一综合网站_91精品又粗又猛又爽_小泽玛利亚一区二区免费_91亚洲精品国偷拍自产在线观看 _久久精品视频在线播放_美女精品久久久_欧美日韩国产成人在线

小米C++軟開二面:select、poll、epoll 三者之間的區(qū)別,epoll 為什么高效?

系統(tǒng) Linux
select 是最早被廣泛使用的 I/O 多路復(fù)用技術(shù),它就像是一個基礎(chǔ)款的 “服務(wù)助手”,在網(wǎng)絡(luò)編程的歷史長河中,曾發(fā)揮過重要的作用 ;select 函數(shù)的使用需要傳入三個文件描述符集合(可讀集合 readfds、可寫集合 writefds、異常集合 exceptfds),分別用于監(jiān)聽文件描述符的讀、寫和異常事件。

IO 多路復(fù)用技術(shù)允許一個線程同時監(jiān)視多個文件描述符(可以理解為每個客人的 “服務(wù)需求信號”),當(dāng)其中任何一個或多個文件描述符就緒(有數(shù)據(jù)可讀、可寫或發(fā)生異常,就好比客人有新的需求,如點菜、加飲料等)時,內(nèi)核就會通知應(yīng)用程序進(jìn)行相應(yīng)的處理 。這樣,應(yīng)用程序就無需為每個文件描述符單獨創(chuàng)建線程,避免了線程資源的浪費和頻繁的上下文切換,大大提高了系統(tǒng)的效率和性能。

在 Linux 系統(tǒng)中,常見的 IO 多路復(fù)用技術(shù)有 select、poll 和 epoll。它們就像是不同等級的 “服務(wù)助手”,雖然都能實現(xiàn)一個線程處理多個 I/O 流的功能,但在性能、使用方式和適用場景上卻各有千秋。接下來,就讓我們深入了解一下這三位 “服務(wù)助手”。

一、生活實例舉例

在正式介紹 select、poll、epoll 之前,我們先來設(shè)想一個場景。假如你是一個便利店老板,你的店里有很多顧客進(jìn)進(jìn)出出,你需要關(guān)注每個顧客的行為,比如是否有新顧客進(jìn)門,是否有顧客需要結(jié)賬,是否有顧客存在偷竊行為。在這個場景中,顧客就相當(dāng)于文件描述符(fd),而你關(guān)注的這些行為就相當(dāng)于文件描述符上發(fā)生的事件。

  • select 方式:select 就像是你雇了一個保安,這個保安會幫你盯著所有的顧客。他會告訴你有顧客發(fā)生了某些事件,但是他不會告訴你具體是哪個顧客發(fā)生了什么事件。所以,當(dāng)保安通知你有事件發(fā)生時,你就需要一個個地去問每個顧客 “你是不是要結(jié)賬了?”“你是不是有什么問題?”,直到找到那個發(fā)生事件的顧客。而且,這個保安一次最多只能幫你盯著 1024 個顧客。
  • poll 方式:poll 也像是一個保安,他和 select 保安的功能差不多,也是會告訴你有顧客發(fā)生了事件,但同樣不會告訴你具體是哪個顧客。不過,poll 保安比 select 保安厲害的地方在于,他沒有顧客數(shù)量的限制,理論上可以幫你盯著無數(shù)個顧客。但是,當(dāng)他通知你有事件發(fā)生時,你還是得像問 select 保安那樣,一個個地去問顧客發(fā)生了什么事。
  • epoll 方式:epoll 則像是一個超級智能監(jiān)控系統(tǒng),這個系統(tǒng)不僅能監(jiān)控?zé)o數(shù)個顧客,而且當(dāng)有顧客發(fā)生事件時,它會直接告訴你是哪個顧客發(fā)生了什么事件。比如,它會直接告訴你 “3 號顧客要結(jié)賬了”“5 號顧客有偷竊行為”,你可以直接根據(jù)這些信息去處理相應(yīng)的事件,而不需要再一個個地去詢問顧客。

二、select:基礎(chǔ)但有限的 “監(jiān)控”

2.1 select的工作原理

select 是最早被廣泛使用的 I/O 多路復(fù)用技術(shù),它就像是一個基礎(chǔ)款的 “服務(wù)助手”,在網(wǎng)絡(luò)編程的歷史長河中,曾發(fā)揮過重要的作用 ;select 函數(shù)的使用需要傳入三個文件描述符集合(可讀集合 readfds、可寫集合 writefds、異常集合 exceptfds),分別用于監(jiān)聽文件描述符的讀、寫和異常事件。同時,還需要傳入一個超時時間參數(shù) timeout,用于控制 select 函數(shù)的阻塞時間。

在使用 select 之前,我們需要先初始化這三個文件描述符集合,將需要監(jiān)聽的文件描述符添加到相應(yīng)的集合中。例如,在一個簡單的 TCP 服務(wù)器中,我們可能會將監(jiān)聽套接字添加到讀集合中,以監(jiān)聽客戶端的連接請求。

#include <sys/select.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <stdio.h>

#define PORT 8888
#define MAX_CLIENTS 100

int main() {
    int server_fd, new_socket, client_fds[MAX_CLIENTS];
    struct sockaddr_in address;
    int addrlen = sizeof(address);
    fd_set read_fds, temp_fds;
    int max_sd;

    // 創(chuàng)建套接字
    server_fd = socket(AF_INET, SOCK_STREAM, 0);
    address.sin_family = AF_INET;
    address.sin_addr.s_addr = INADDR_ANY;
    address.sin_port = htons(PORT);

    // 綁定套接字
    bind(server_fd, (struct sockaddr *)&address, sizeof(address));

    // 監(jiān)聽連接
    listen(server_fd, 3);

    // 初始化客戶端文件描述符數(shù)組
    for (int i = 0; i < MAX_CLIENTS; i++) {
        client_fds[i] = -1;
    }

    // 初始化文件描述符集合
    FD_ZERO(&read_fds);
    FD_SET(server_fd, &read_fds);
    max_sd = server_fd;

    while (1) {
        temp_fds = read_fds;

        // 調(diào)用select函數(shù),等待事件發(fā)生
        int activity = select(max_sd + 1, &temp_fds, NULL, NULL, NULL);

        if (activity < 0) {
            perror("select error");
            break;
        } else if (activity > 0) {
            // 檢查是否有新的連接請求
            if (FD_ISSET(server_fd, &temp_fds)) {
                new_socket = accept(server_fd, (struct sockaddr *)&address, (socklen_t *)&addrlen);
                for (int i = 0; i < MAX_CLIENTS; i++) {
                    if (client_fds[i] == -1) {
                        client_fds[i] = new_socket;
                        break;
                    }
                }
                FD_SET(new_socket, &read_fds);
                if (new_socket > max_sd) {
                    max_sd = new_socket;
                }
            }

            // 檢查已連接客戶端是否有數(shù)據(jù)可讀
            for (int i = 0; i < MAX_CLIENTS; i++) {
                int sd = client_fds[i];
                if (sd != -1 && FD_ISSET(sd, &temp_fds)) {
                    char buffer[1024] = {0};
                    int valread = read(sd, buffer, 1024);
                    if (valread == 0) {
                        // 客戶端關(guān)閉連接
                        close(sd);
                        FD_CLR(sd, &read_fds);
                        client_fds[i] = -1;
                    } else {
                        // 處理客戶端數(shù)據(jù)
                        printf("Received from client: %s\n", buffer);
                    }
                }
            }
        }
    }

    close(server_fd);
    return 0;
}

當(dāng)我們調(diào)用 select 函數(shù)時,它會將這三個文件描述符集合從用戶空間拷貝到內(nèi)核空間,然后內(nèi)核會遍歷這些文件描述符,檢查是否有任何一個文件描述符上有事件發(fā)生(例如,是否有數(shù)據(jù)可讀、是否可以寫入數(shù)據(jù)、是否發(fā)生異常等)。如果有事件發(fā)生,內(nèi)核會將對應(yīng)的文件描述符在相應(yīng)的集合中標(biāo)記出來,然后將這三個集合從內(nèi)核空間拷貝回用戶空間 。select 函數(shù)返回后,我們需要再次遍歷這三個文件描述符集合,使用 FD_ISSET 宏來檢查哪些文件描述符上有事件發(fā)生,并進(jìn)行相應(yīng)的處理。例如,如果某個文件描述符在可讀集合中被標(biāo)記,那么我們就可以對該文件描述符進(jìn)行讀操作。

select 的工作方式就像是一個大管家,它通過檢查一組文件描述符(fd)的狀態(tài),來判斷是否有 I/O 事件發(fā)生。它維護(hù)了三個集合:讀集合、寫集合和異常集合。在調(diào)用 select 時,我們把需要監(jiān)控的 fd 分別加入到這三個集合中,然后 select 就會去檢查這些 fd 是否有可讀、可寫或異常的事件發(fā)生。如果有,select 就會返回,告訴我們哪些 fd 有事件發(fā)生。為了更形象地理解,我們可以把 fd 想象成一個個的小房間,每個房間都可能發(fā)生一些事件,比如有人敲門(可讀事件)、可以把東西送進(jìn)去(可寫事件)或者房間里發(fā)生了異常情況(異常事件)。select 就像是一個保安,它會在所有的房間外面巡邏,檢查每個房間的情況。如果有房間發(fā)生了事件,保安就會回來告訴我們是哪些房間發(fā)生了事件。

2.2 select 的特點與局限性

盡管 select 曾經(jīng)在網(wǎng)絡(luò)編程中占據(jù)重要地位,但隨著高并發(fā)需求的不斷增長,它的缺點也逐漸暴露出來,就像一位能力有限的 “服務(wù)助手”,在面對大規(guī)模的 “客人” 時,顯得力不從心。

首先,select 對單個進(jìn)程可監(jiān)視的文件描述符數(shù)量存在限制,這個限制通常是 1024 個(在 Linux 系統(tǒng)中,可通過修改宏定義 FD_SETSIZE 來改變這個值,但這并不是一個理想的解決方案,因為它會帶來兼容性和維護(hù)性的問題)。在如今的高并發(fā)場景下,一個服務(wù)器可能需要同時處理成千上萬的連接,1024 個文件描述符的限制顯然無法滿足需求。例如,一個大型的在線游戲服務(wù)器,可能需要同時處理數(shù)萬玩家的連接,如果使用 select,根本無法實現(xiàn)如此大規(guī)模的連接管理。

其次,select 每次調(diào)用都需要將整個文件描述符集合從用戶空間拷貝到內(nèi)核空間,在內(nèi)核檢查完事件后,又需要將結(jié)果從內(nèi)核空間拷貝回用戶空間。這種頻繁的數(shù)據(jù)拷貝操作會帶來較大的開銷,降低系統(tǒng)的性能。想象一下,每次服務(wù)員都要將所有客人的信息在兩個地方來回傳遞,不僅浪費時間,還容易出錯。

最后,select 返回后,我們需要遍歷整個文件描述符集合,才能找到哪些文件描述符上有事件發(fā)生,這個過程的時間復(fù)雜度為 O (n)。當(dāng)文件描述符數(shù)量較多時,遍歷的時間開銷會非常大,導(dǎo)致系統(tǒng)的響應(yīng)速度變慢。例如,當(dāng)有 1000 個文件描述符時,select 返回后,我們需要進(jìn)行 1000 次檢查,才能確定哪些文件描述符就緒,這無疑會消耗大量的時間和資源。

2.3 select的適用場景

由于 select 的局限性,它更適用于小規(guī)模連接和處理簡單的并發(fā)場景。比如一些簡單的服務(wù)器程序,并發(fā)連接數(shù)較少,對性能要求不是特別高,這時使用 select 就可以滿足需求,因為它代碼實現(xiàn)簡單,易于理解和使用 。就像一個小商店,顧客數(shù)量不多,老板自己就可以輕松地照顧到每個顧客的需求,不需要太復(fù)雜的管理方式。

三、poll:改進(jìn)但仍有不足的 “監(jiān)控”

3.1 poll 的工作原理

poll 是對 select 的改進(jìn),它就像是 select 的進(jìn)階版 “服務(wù)助手”,在功能上與 select 有許多相似之處,但也有一些自己的特點;poll 同樣用于監(jiān)聽多個文件描述符上的事件,以確定哪些文件描述符可以進(jìn)行讀、寫或發(fā)生了異常等操作 。與 select 不同的是,poll 使用一個 pollfd 結(jié)構(gòu)體數(shù)組來表示文件描述符集合。每個 pollfd 結(jié)構(gòu)體包含三個成員:fd(文件描述符)、events(表示需要監(jiān)聽的事件,如 POLLIN 表示可讀事件,POLLOUT 表示可寫事件等)和 revents(表示實際發(fā)生的事件,由內(nèi)核在 poll 函數(shù)返回時設(shè)置)。

下面是一個使用 poll 的簡單示例代碼,展示了如何使用 poll 監(jiān)聽套接字的讀事件:

#include <poll.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <unistd.h>

#define PORT 8888
#define MAX_CLIENTS 100

int main() {
    int server_fd, new_socket, client_fds[MAX_CLIENTS];
    struct sockaddr_in address;
    int addrlen = sizeof(address);
    struct pollfd fds[MAX_CLIENTS + 1];
    int num_fds = 1;

    // 創(chuàng)建套接字
    server_fd = socket(AF_INET, SOCK_STREAM, 0);
    address.sin_family = AF_INET;
    address.sin_addr.s_addr = INADDR_ANY;
    address.sin_port = htons(PORT);

    // 綁定套接字
    bind(server_fd, (struct sockaddr *)&address, sizeof(address));

    // 監(jiān)聽連接
    listen(server_fd, 3);

    // 初始化客戶端文件描述符數(shù)組
    for (int i = 0; i < MAX_CLIENTS; i++) {
        client_fds[i] = -1;
    }

    // 初始化pollfd數(shù)組
    fds[0].fd = server_fd;
    fds[0].events = POLLIN;

    while (1) {
        // 調(diào)用poll函數(shù),等待事件發(fā)生
        int activity = poll(fds, num_fds, -1);

        if (activity < 0) {
            perror("poll error");
            break;
        } else if (activity > 0) {
            // 檢查是否有新的連接請求
            if (fds[0].revents & POLLIN) {
                new_socket = accept(server_fd, (struct sockaddr *)&address, (socklen_t *)&addrlen);
                for (int i = 0; i < MAX_CLIENTS; i++) {
                    if (client_fds[i] == -1) {
                        client_fds[i] = new_socket;
                        fds[num_fds].fd = new_socket;
                        fds[num_fds].events = POLLIN;
                        num_fds++;
                        break;
                    }
                }
            }

            // 檢查已連接客戶端是否有數(shù)據(jù)可讀
            for (int i = 1; i < num_fds; i++) {
                int sd = fds[i].fd;
                if (fds[i].revents & POLLIN) {
                    char buffer[1024] = {0};
                    int valread = read(sd, buffer, 1024);
                    if (valread == 0) {
                        // 客戶端關(guān)閉連接
                        close(sd);
                        for (int j = i; j < num_fds - 1; j++) {
                            fds[j] = fds[j + 1];
                            client_fds[j - 1] = client_fds[j];
                        }
                        num_fds--;
                    } else {
                        // 處理客戶端數(shù)據(jù)
                        printf("Received from client: %s\n", buffer);
                    }
                }
            }
        }
    }

    close(server_fd);
    return 0;
}

在使用 poll 時,我們將需要監(jiān)聽的文件描述符及其感興趣的事件填充到 pollfd 數(shù)組中,然后調(diào)用 poll 函數(shù)。poll 函數(shù)會將這個數(shù)組從用戶空間拷貝到內(nèi)核空間,內(nèi)核會遍歷這個數(shù)組,檢查每個文件描述符上的事件是否發(fā)生 。如果有事件發(fā)生,內(nèi)核會將對應(yīng)的文件描述符在 revents 成員中標(biāo)記出來,然后將數(shù)組從內(nèi)核空間拷貝回用戶空間 ;poll 函數(shù)返回后,我們通過檢查 pollfd 數(shù)組中每個元素的 revents 成員,來確定哪些文件描述符上有事件發(fā)生,并進(jìn)行相應(yīng)的處理。例如,如果某個文件描述符的 revents 成員中設(shè)置了 POLLIN 標(biāo)志,就表示該文件描述符有數(shù)據(jù)可讀。

poll的工作方式是,將用戶傳入的pollfd數(shù)組拷貝到內(nèi)核空間,然后內(nèi)核會遍歷這個數(shù)組,查詢每個fd對應(yīng)的設(shè)備狀態(tài)。如果設(shè)備狀態(tài)滿足events中設(shè)置的事件(比如可讀、可寫、異常等),就會在revents中標(biāo)記該事件。當(dāng)所有的fd都檢查完后,poll函數(shù)返回,返回值表示有多少個fd的狀態(tài)發(fā)生了變化。

我們還是以便利店的例子來說明,poll就像是一個升級版的保安,他手里拿著一個顧客信息表(pollfd數(shù)組),上面記錄了每個顧客的信息以及需要關(guān)注的事件(events)。保安會逐個檢查每個顧客的情況,然后把發(fā)生了事件的顧客信息記錄在表格的另一欄(revents)中。最后,保安把這個表格交還給老板,老板可以根據(jù)表格上的信息知道哪些顧客發(fā)生了什么事件。

3.2 poll 相對 select 的改進(jìn)

poll 相對于 select 有一些明顯的改進(jìn),這使得它在處理高并發(fā)場景時比 select 更具優(yōu)勢,就像一位經(jīng)過培訓(xùn)提升的 “服務(wù)助手”,能力有所增強(qiáng)。

首先,poll 解決了 select 文件描述符數(shù)量的限制問題。在 select 中,由于使用固定大小的 fd_set 來表示文件描述符集合,導(dǎo)致單個進(jìn)程可監(jiān)視的文件描述符數(shù)量受到 FD_SETSIZE 的限制(通常為 1024)。而 poll 使用 pollfd 結(jié)構(gòu)體數(shù)組,理論上對文件描述符的數(shù)量沒有限制(實際受限于系統(tǒng)資源,如內(nèi)存等),這使得它能夠處理更多的并發(fā)連接。例如,在一個需要支持大量并發(fā)連接的網(wǎng)絡(luò)服務(wù)器中,poll 可以輕松應(yīng)對成千上萬的客戶端連接,而 select 則會因為文件描述符數(shù)量的限制而無法滿足需求。

其次,poll 在編程接口上更加靈活和簡潔。select 需要分別處理讀、寫和異常三個文件描述符集合,并且每次調(diào)用 select 之前都需要重新設(shè)置這些集合。而 poll 將監(jiān)聽事件和返回事件合并在一個 pollfd 結(jié)構(gòu)體中,只需要維護(hù)一個數(shù)組,使用起來更加方便。例如,在添加或修改一個文件描述符的監(jiān)聽事件時,poll 只需要修改 pollfd 數(shù)組中對應(yīng)元素的 events 成員即可,而 select 則需要分別在三個文件描述符集合中進(jìn)行操作,代碼更加繁瑣。

3.3 poll的特點與局限性

poll 相比于 select 有一些優(yōu)點:

  • 無連接數(shù)限制:理論上對文件描述符的數(shù)量沒有限制,不像 select 受限于FD_SETSIZE。這就好比保安可以管理無數(shù)個顧客,而沒有數(shù)量上限。
  • 接口使用方便:pollfd結(jié)構(gòu)體中包含了要監(jiān)視的events和發(fā)生的revents,對于監(jiān)聽集合的輸入輸出是分離的,不需要在調(diào)用poll函數(shù)之前重新對參數(shù)進(jìn)行設(shè)置 。不像 select,每次調(diào)用都要重新設(shè)置fd_set集合。這就好比保安每次巡邏回來,不需要重新整理顧客信息表,只需要查看哪些顧客發(fā)生了事件即可。

然而,poll 也存在一些局限性:

  • 效率問題:雖然 poll 沒有了文件描述符數(shù)量的限制,但它和 select 一樣,每次調(diào)用都需要遍歷整個文件描述符集合,時間復(fù)雜度為 O (n)。當(dāng)文件描述符數(shù)量很多時,效率仍然較低。就像保安檢查顧客時,還是需要一個個地檢查,不管顧客是否有事件發(fā)生,都要檢查一遍,當(dāng)顧客數(shù)量很多時,這個過程會非常耗時。
  • 數(shù)據(jù)復(fù)制開銷:每次調(diào)用 poll 都需要將pollfd數(shù)組從用戶態(tài)拷貝到內(nèi)核態(tài),當(dāng)fd很多時,這個開銷也不容小覷。這就好比每次保安巡邏都需要把顧客信息表從老板那里拿到自己手里,這個過程也會消耗一定的時間和精力。
  • 觸發(fā)方式單一:poll 只支持水平觸發(fā)(LT),這意味著只要文件描述符上還有未處理的數(shù)據(jù),它就會一直通知應(yīng)用程序,可能會導(dǎo)致應(yīng)用程序重復(fù)處理一些事件。而在一些場景下,邊緣觸發(fā)(ET)會更加高效,它只會在文件描述符狀態(tài)發(fā)生變化時通知應(yīng)用程序。

3.4 poll 的適用場景

由于 poll 沒有連接數(shù)限制且使用相對簡單,適用于連接數(shù)較多但活躍連接比例不確定的場景 。例如,一些即時通訊服務(wù)器,可能會有大量的用戶連接,但并不是每個用戶都會頻繁發(fā)送消息,這時使用 poll 就可以較好地管理這些連接。在這種場景下,poll 的無連接數(shù)限制可以滿足大量用戶連接的需求,而水平觸發(fā)雖然可能會導(dǎo)致一些重復(fù)通知,但對于即時通訊這種對實時性要求較高的場景來說,影響相對較小 。就像便利店在節(jié)假日可能會迎來大量顧客,雖然不是每個顧客都會馬上有需求,但使用 poll 這種方式可以有效地管理這些顧客,及時響應(yīng)有需求的顧客。

四、epoll:高效強(qiáng)大的 “監(jiān)控”

在 select 和 poll 逐漸難以滿足高并發(fā)需求的情況下,epoll 這位 “王者選手” 閃亮登場,它就像是一位擁有超能力的 “超級服務(wù)助手”,專為解決高并發(fā)場景下的 I/O 處理難題而生 。

epoll 是 Linux 內(nèi)核為處理大批量文件描述符而改進(jìn)的 poll,是 select/poll 的增強(qiáng)版本,誕生于 Linux 2.5.44 內(nèi)核版本 。它能顯著提高程序在大量并發(fā)連接中只有少量活躍的情況下的系統(tǒng) CPU 利用率 。epoll 采用了與 select 和 poll 截然不同的設(shè)計理念,摒棄了傳統(tǒng)的輪詢方式,采用事件驅(qū)動機(jī)制,使得它在高并發(fā)場景下能夠如魚得水,高效地處理大量的 I/O 請求。

4.1 epoll的工作方式

①水平觸發(fā)(LT):持續(xù)通知的 “貼心管家”

水平觸發(fā)(LT)可是 epoll 的默認(rèn)工作模式,就像一位貼心管家,時刻關(guān)注著文件描述符的狀態(tài)。當(dāng)某個文件描述符處于就緒狀態(tài),比如有數(shù)據(jù)可讀或者可寫,內(nèi)核就會通知應(yīng)用程序。要是應(yīng)用程序這次沒處理完數(shù)據(jù),或者沒來得及處理,別擔(dān)心,下次調(diào)用 epoll_wait 時,內(nèi)核依舊會不厭其煩地再次通知,直到數(shù)據(jù)被處理完或者緩沖區(qū)里沒數(shù)據(jù)可讀、可寫了為止。

舉個例子,在處理 HTTP 報文時,數(shù)據(jù)可能是一段段陸續(xù)到達(dá)的。使用 LT 模式,只要緩沖區(qū)還有沒讀完的報文片段,每次 epoll_wait 都會把對應(yīng)的文件描述符事件返回,讓應(yīng)用程序可以分次從容地解析報文,不用擔(dān)心錯過任何數(shù)據(jù),大大降低了編程復(fù)雜度,對新手程序員那是相當(dāng)友好,就像有個老師在旁邊,不停提醒你還有作業(yè)沒做完呢。

②邊緣觸發(fā)(ET):高效靈敏的 “情報員”

邊緣觸發(fā)(ET)模式則像一位高效靈敏的情報員,奉行 “只報新事” 原則。只有在文件描述符的狀態(tài)發(fā)生改變時,比如從無數(shù)據(jù)變?yōu)橛袛?shù)據(jù)可讀,或者從不可寫變?yōu)榭蓪懀瑑?nèi)核才會觸發(fā)事件通知應(yīng)用程序。一旦通知了,它就默認(rèn)你知曉此事,后續(xù)除非狀態(tài)再次改變,否則不會重復(fù)通知。這意味著應(yīng)用程序得打起十二分精神,在收到通知后,必須立刻、馬上處理數(shù)據(jù),而且要盡可能把當(dāng)前就緒的數(shù)據(jù)一次性處理完。

比如說讀取大型文件,使用 ET 模式,一旦檢測到文件描述符可讀,就得趕緊用 while 循環(huán)一股腦把數(shù)據(jù)全讀完,不然下次 epoll_wait 可不會再提醒你還有剩余數(shù)據(jù)。要是讀數(shù)據(jù)時遇到 EAGAIN 或 EWOULDBLOCK 錯誤,那就說明這次數(shù)據(jù)真讀完了。這種模式雖然編程難度稍高,需要精細(xì)處理數(shù)據(jù),但減少了不必要的喚醒次數(shù),系統(tǒng)開銷小,在追求極致性能的場景下,那可是 “利器”,能讓數(shù)據(jù)如閃電般高效流轉(zhuǎn)。

當(dāng)然,在 LT 模式下開發(fā)基于 epoll 的應(yīng)用要簡單一些,不太容易出錯,而在 ET 模式下事件發(fā)生時,如果沒有徹底地將緩沖區(qū)的數(shù)據(jù)處理完,則會導(dǎo)致緩沖區(qū)的用戶請求得不到響應(yīng)。注意,默認(rèn)情況下 Nginx 采用 ET 模式使用 epoll 的。

4.2 epoll核心數(shù)據(jù)結(jié)構(gòu)

(1)紅黑樹 —— 精準(zhǔn)管理的 “魔法樹”

紅黑樹在 epoll 里可是扮演著 “大管家” 的關(guān)鍵角色,它專門負(fù)責(zé)存儲和管理海量的文件描述符。這棵樹有著獨特的 “魔力”,它是一種自平衡的二叉搜索樹,意味著無論插入、刪除還是查找操作,時間復(fù)雜度都能穩(wěn)穩(wěn)地保持在 O (log n)。

想象一下,在高并發(fā)場景下,每秒有成千上萬個新連接涌入,每個連接對應(yīng)一個文件描述符。要是沒有紅黑樹,查找一個特定的文件描述符就如同大海撈針,效率極其低下。而有了紅黑樹,就好比給每個文件描述符都安排了一個專屬的智能導(dǎo)航。當(dāng)需要添加新連接(即新文件描述符)時,它能快速指引插入位置;要關(guān)閉某個連接刪除對應(yīng)描述符,也能迅速定位并移除,絲毫不亂。舉個例子,在大型在線游戲服務(wù)器里,同時在線玩家眾多,網(wǎng)絡(luò)連接頻繁變動,紅黑樹就能高效管理這些連接,確保游戲運行順暢,玩家操作即時響應(yīng),不會因連接管理混亂而卡頓。

(2)就緒鏈表 —— 即時響應(yīng)的 “情報站”

就緒鏈表就像是 epoll 的 “情報收集站”。當(dāng)某個被監(jiān)聽的文件描述符狀態(tài)發(fā)生變化,比如有數(shù)據(jù)可讀、可寫,內(nèi)核立馬知曉,并通過回調(diào)機(jī)制,閃電般地將這個就緒的文件描述符添加到就緒鏈表中。這個鏈表通常是用雙向鏈表實現(xiàn),插入和刪除操作那叫一個快,時間復(fù)雜度僅 O (1)。

打個比方,這就好比快遞驛站收到了你的包裹(數(shù)據(jù)就緒),立馬把你的取件碼(文件描述符)放到一個專門的 “待取貨架”(就緒鏈表)上,你一來就能快速拿到包裹。對于應(yīng)用程序而言,當(dāng)調(diào)用 epoll_wait 時,根本不用費時費力去遍歷所有文件描述符,直接到這個 “情報站”—— 就緒鏈表瞅一眼,就能瞬間獲取所有已就緒的文件描述符,第一時間進(jìn)行數(shù)據(jù)讀寫操作,大大提升了響應(yīng)速度,讓數(shù)據(jù)處理快如閃電。

(3)mmap—— 高效傳輸?shù)?“隱形橋梁”

mmap 堪稱 epoll 實現(xiàn)高效的幕后英雄,它搭建起了內(nèi)核空間與用戶空間的 “隱形橋梁”—— 共享內(nèi)存。在傳統(tǒng)的 I/O 操作里,數(shù)據(jù)從內(nèi)核緩沖區(qū)拷貝到用戶緩沖區(qū),這個過程就像搬運工來回搬貨,費時費力,還增加系統(tǒng)開銷。

但有了 mmap 就不一樣了,它直接讓內(nèi)核空間和用戶空間共享同一塊內(nèi)存區(qū)域,數(shù)據(jù)來了,雙方都能直接訪問,減少了數(shù)據(jù)拷貝次數(shù)。這就好比圖書館有個公共書架,管理員(內(nèi)核)和讀者(用戶程序)都能直接在上面取放書籍(數(shù)據(jù)),無需來回搬運。像是視頻流處理應(yīng)用,大量視頻數(shù)據(jù)頻繁傳輸,mmap 使得數(shù)據(jù)能快速從內(nèi)核流向用戶空間,減少傳輸延遲,讓視頻播放流暢無卡頓,極大提升了系統(tǒng)整體性能。

4.3 三個核心 API

epoll 提供了三個核心 API,它們是 epoll 實現(xiàn)高效 I/O 多路復(fù)用的關(guān)鍵工具,就像 “超級服務(wù)助手” 的三件 “法寶”,各司其職,協(xié)同工作。

(1)epoll_create:用于創(chuàng)建一個 epoll 實例,并返回一個文件描述符(epfd),這個文件描述符將用于后續(xù)對 epoll 實例的操作 。在創(chuàng)建 epoll 實例時,內(nèi)核會為其分配相應(yīng)的數(shù)據(jù)結(jié)構(gòu),包括紅黑樹和就緒鏈表等,并初始化相關(guān)的參數(shù)。例如:

int epfd = epoll_create1(0);
if (epfd == -1) {
    perror("epoll_create1");
    exit(EXIT_FAILURE);
}

(2)epoll_ctl:用于控制 epoll 實例,它可以向 epoll 實例中添加、刪除或修改文件描述符及其感興趣的事件 。epoll_ctl 函數(shù)有四個參數(shù):epfd(epoll 實例的文件描述符)、op(操作類型,如 EPOLL_CTL_ADD 表示添加,EPOLL_CTL_DEL 表示刪除,EPOLL_CTL_MOD 表示修改)、fd(要操作的文件描述符)和 event(指向 epoll_event 結(jié)構(gòu)體的指針,用于指定事件類型和關(guān)聯(lián)的數(shù)據(jù))。例如,當(dāng)我們要將一個監(jiān)聽套接字添加到 epoll 實例中,監(jiān)聽讀事件時,可以這樣使用:

struct epoll_event event;
event.events = EPOLLIN;
event.data.fd = listen_sock;
if (epoll_ctl(epfd, EPOLL_CTL_ADD, listen_sock, &event) == -1) {
    perror("epoll_ctl: listen_sock");
    exit(EXIT_FAILURE);
}

(3)epoll_wait:用于等待文件描述符上的事件發(fā)生,它會阻塞調(diào)用線程,直到有事件觸發(fā)或超時 。epoll_wait 函數(shù)有四個參數(shù):epfd(epoll 實例的文件描述符)、events(指向 epoll_event 結(jié)構(gòu)體數(shù)組的指針,用于返回發(fā)生的事件)、maxevents(events 數(shù)組的大小,即一次最多處理的事件數(shù))和 timeout(等待事件發(fā)生的超時時間,以毫秒為單位,如果設(shè)置為 - 1,則表示無限等待)。當(dāng)有事件發(fā)生時,epoll_wait 會將就緒的事件從就緒鏈表中取出,填充到 events 數(shù)組中,并返回就緒事件的數(shù)量。例如:

struct epoll_event events[MAX_EVENTS];
int nfds = epoll_wait(epfd, events, MAX_EVENTS, -1);
if (nfds == -1) {
    perror("epoll_wait");
    exit(EXIT_FAILURE);
}
for (int n = 0; n < nfds; ++n) {
    if (events[n].events & EPOLLIN) {
        handle_incoming_data(events[n].data.fd);
    }
}

4.4 epoll實現(xiàn)原理

我們以 linux 內(nèi)核 2.6 為例,說明一下 epoll 是如何高效的處理事件的。當(dāng)某一個進(jìn)程調(diào)用 epoll_create 方法的時候,Linux 內(nèi)核會創(chuàng)建一個 eventpoll 結(jié)構(gòu)體,這個結(jié)構(gòu)體中有兩個重要的成員。

  • 第一個是 rb_root rbr,這是紅黑樹的根節(jié)點,存儲著所有添加到 epoll 中的事件,也就是這個 epoll 監(jiān)控的事件。
  • 第二個是 list_head rdllist 這是一個雙向鏈表,保存著將要通過 epoll_wait 返回給用戶的、滿足條件的事件。

每一個 epoll 對象都有一個獨立的 eventpoll 結(jié)構(gòu)體,這個結(jié)構(gòu)體會在內(nèi)核空間中創(chuàng)造獨立的內(nèi)存,用于存儲使用 epoll_ctl 方法向 epoll 對象中添加進(jìn)來的事件。這些事件都會掛到 rbr 紅黑樹中,這樣就能夠高效的識別重復(fù)添加的節(jié)點。

所有添加到 epoll 中的事件都會與設(shè)備(如網(wǎng)卡等)驅(qū)動程序建立回調(diào)關(guān)系,也就是說,相應(yīng)的事件發(fā)生時會調(diào)用這里的方法。這個回調(diào)方法在內(nèi)核中叫做 ep_poll_callback,它把這樣的事件放到 rdllist 雙向鏈表中。在 epoll 中,對于每一個事件都會建立一個 epitem 結(jié)構(gòu)體。

當(dāng)調(diào)用 epoll_wait 檢查是否有發(fā)生事件的連接時,只需要檢查 eventpoll 對象中的 rdllist 雙向鏈表中是否有 epitem 元素,如果 rdllist 鏈表不為空,則把這里的事件復(fù)制到用戶態(tài)內(nèi)存中的同時,將事件數(shù)量返回給用戶。通過這種方法,epoll_wait 的效率非常高。epoll-ctl 在向 epoll 對象中添加、修改、刪除事件時,從 rbr 紅黑樹中查找事件也非常快。這樣,epoll 就能夠輕易地處理百萬級的并發(fā)連接。

五、epoll 高效的原因

epoll 在高并發(fā)場景下的高效表現(xiàn),得益于其多方面的優(yōu)化設(shè)計,就像一位擁有多項絕技的 “超級英雄”,在應(yīng)對高并發(fā)挑戰(zhàn)時游刃有余。

5.1 事件驅(qū)動機(jī)制

epoll 摒棄了 select 和 poll 的傳統(tǒng)輪詢方式,采用了高效的事件驅(qū)動機(jī)制 。在傳統(tǒng)的輪詢方式中,就好比在一個巨大的倉庫里,不管貨物有沒有變化,都要逐個去查看,在連接數(shù)量眾多時,大量的時間和資源就浪費在了這些無效的檢查上。而 epoll 采用事件驅(qū)動機(jī)制,當(dāng)文件描述符狀態(tài)發(fā)生變化,比如有數(shù)據(jù)可讀或可寫時,內(nèi)核會主動發(fā)出通知,應(yīng)用程序只需關(guān)注這些有事件發(fā)生的文件描述符即可,這大大減少了無效操作。例如,在一個擁有大量并發(fā)連接的服務(wù)器中,epoll 能夠精準(zhǔn)地定位到那些有數(shù)據(jù)傳輸?shù)幕钴S連接,避免對眾多無事件連接的遍歷,從而顯著提高了系統(tǒng)的效率。

5.2 數(shù)據(jù)結(jié)構(gòu)優(yōu)勢

epoll 的數(shù)據(jù)結(jié)構(gòu)設(shè)計堪稱精妙絕倫,為其高效運行提供了有力支持 。紅黑樹用于管理注冊的文件描述符,其插入、刪除和查找操作的時間復(fù)雜度僅為 O (log N),遠(yuǎn)優(yōu)于傳統(tǒng)線性結(jié)構(gòu),這使得 epoll 在處理大量文件描述符時能夠快速定位和操作,不會因為文件描述符數(shù)量的增加而導(dǎo)致性能大幅下降。

當(dāng)文件描述符就緒時,內(nèi)核將其從紅黑樹移至就緒鏈表,epoll_wait 只需遍歷該鏈表,就能獲取就緒事件,提升了事件獲取效率 。相比之下,select 和 poll 在獲取就緒事件時需要遍歷整個文件描述符集合,時間復(fù)雜度為 O (n),當(dāng)文件描述符數(shù)量較多時,效率會顯著降低。例如,在一個處理數(shù)萬并發(fā)連接的服務(wù)器中,epoll 通過就緒鏈表能夠快速獲取就緒事件,而 select 和 poll 則需要花費大量時間遍歷所有連接,導(dǎo)致系統(tǒng)響應(yīng)速度變慢。

5.3 數(shù)據(jù)傳輸優(yōu)化

epoll 借助 mmap 技術(shù)在內(nèi)核與用戶空間建立共享內(nèi)存,減少了數(shù)據(jù)在內(nèi)核緩沖區(qū)與用戶空間應(yīng)用程序緩沖區(qū)之間的拷貝次數(shù),提高了傳輸效率 。在傳統(tǒng)的數(shù)據(jù)傳輸過程中,數(shù)據(jù)從內(nèi)核緩沖區(qū)到用戶空間應(yīng)用程序緩沖區(qū),往往需要多次拷貝,這無疑增加了時間和資源開銷。而 epoll 通過共享內(nèi)存,讓數(shù)據(jù)傳輸更直接高效,減少了拷貝次數(shù),加快了數(shù)據(jù)傳輸速度,就像開辟了一條數(shù)據(jù)傳輸?shù)?“高速公路”。

epoll 支持水平觸發(fā)(LT)和邊緣觸發(fā)(ET)兩種模式 。水平觸發(fā)是默認(rèn)的工作方式,只要文件描述符上有指定的事件(如數(shù)據(jù)可讀),每次調(diào)用 epoll_wait 都會返回此事件,除非事件被處理(如數(shù)據(jù)被讀走)。邊緣觸發(fā)則更為高效,僅在文件描述符狀態(tài)變化瞬間通知一次應(yīng)用程序,促使應(yīng)用程序一次性處理完相關(guān)數(shù)據(jù),減少了 epoll_wait 的調(diào)用次數(shù),進(jìn)一步提升了效率 。例如,在一些對實時性要求較高的場景中,如網(wǎng)絡(luò)監(jiān)控系統(tǒng),使用邊緣觸發(fā)模式可以減少不必要的系統(tǒng)調(diào)用,提高系統(tǒng)的響應(yīng)速度。

六、I/O多路復(fù)用高頻面試題

問題 1:什么是 I/O 多路復(fù)用?

I/O 多路復(fù)用是一種同步 I/O 模型,允許一個線程監(jiān)視多個文件句柄,一旦某個文件句柄就緒(通常是可讀、可寫或出現(xiàn)異常等情況),就能通知應(yīng)用程序進(jìn)行相應(yīng)的讀寫操作。沒有文件句柄就緒時會阻塞應(yīng)用程序,交出 CPU。其可讓服務(wù)器用單線程支持更多并發(fā)連接請求。

問題 2:為什么需要 I/O 多路復(fù)用機(jī)制?

無此機(jī)制時,處理并發(fā) I/O 存在問題。例如,同步阻塞 I/O(BIO)用單線程無法處理并發(fā),多線程方式在請求增多時,大量線程占用內(nèi)存且切換開銷大。同步非阻塞 I/O(NIO)需輪詢所有文件描述符,即便無讀寫事件也輪詢,浪費 CPU 資源。I/O 多路復(fù)用通過 select、epoll 等系統(tǒng)調(diào)用獲取有事件的文件描述符列表處理請求,減少資源消耗,提升并發(fā)處理能力。

問題 3:I/O 多路復(fù)用的常見實現(xiàn)方式有哪些?

主要有 select、poll、epoll 和 kqueue 等。其中 select 幾乎各平臺都支持,但文件描述符數(shù)量常限制為 1024。poll 類似 select 但無文件描述符數(shù)量限制。epoll 是 Linux 特有,高效且無文件描述符限制,支持水平與邊緣觸發(fā)。kqueue 用于 FreeBSD、Mac OS X 等,能處理多種事件,性能出色。

問題 4:select 有哪些缺點?

  • 單個進(jìn)程可監(jiān)視的文件描述符數(shù)量受限,由 FD_SETSIZE 設(shè)置,默認(rèn) 1024。
  • 每次調(diào)用都需把文件描述符集合從用戶態(tài)拷貝至內(nèi)核態(tài),文件描述符多時開銷大。
  • 掃描 socket 是線性輪詢,高并發(fā)場景下效率低。
  • 僅支持水平觸發(fā)模式。

問題 5:poll 和 select 的區(qū)別是什么?

poll 功能與 select 基本類似,主要區(qū)別是 poll 通過基于鏈表存儲數(shù)據(jù),沒有文件描述符數(shù)量的限制。不過,它每次調(diào)用時,仍需把文件描述符集合從用戶態(tài)拷貝到內(nèi)核態(tài),且掃描時也是線性掃描,高并發(fā)時效率低。

問題 6:epoll 為什么效率高?

  • 事件通知機(jī)制:采用事件驅(qū)動,通過 epoll_ctl 注冊文件描述符,就緒時內(nèi)核用類似回調(diào)的機(jī)制激活,epoll_wait 接收通知,不必像 select/poll 輪詢,時間復(fù)雜度達(dá) O (1)。
  • 減少內(nèi)存拷貝:借助 mmap () 文件映射內(nèi)存加速與內(nèi)核空間消息傳遞,減少數(shù)據(jù)拷貝開銷。
  • 無顯著連接限制:可打開的文件描述符上限較高,1G 內(nèi)存機(jī)器可監(jiān)聽約 10 萬個端口。

問題 7:epoll 的 LT 和 ET 模式區(qū)別是什么?

LT(水平觸發(fā))是默認(rèn)模式,只要文件描述符有數(shù)據(jù)可讀或可寫,每次 epoll_wait 都會返回其事件提醒操作。ET(邊緣觸發(fā))是高速模式,僅在文件描述符狀態(tài)從不可讀 / 寫變?yōu)榭勺x / 寫時通知一次。ET 效率高,但需應(yīng)用程序一次處理完數(shù)據(jù),常需讀到 EAGAIN 錯誤確保讀完緩沖區(qū)數(shù)據(jù)。

問題 8:I/O 多路復(fù)用適用于什么場景?

適用于需處理大量并發(fā)連接,且每個連接讀寫操作不頻繁的場景,像 Redis、Nginx 等常處理大量并發(fā)客戶端請求的服務(wù)器程序。它們通過 I/O 多路復(fù)用機(jī)制,用少量線程高效管理眾多客戶端連接。

問題 9:epoll 存在缺點嗎?

epoll 僅能在 Linux 操作系統(tǒng)上使用,限制了基于其開發(fā)的程序的跨平臺能力。若程序需跨平臺,用 select 或考慮封裝支持多平臺的 I/O 多路復(fù)用抽象層更合適。

問題 10:select、poll、epoll 是同步 I/O 還是異步 I/O?

它們均屬同步 I/O。因即便 select、poll、epoll 可幫獲取就緒的文件描述符,但實際讀寫數(shù)據(jù)過程仍需應(yīng)用程序自行負(fù)責(zé),讀寫操作會阻塞線程,而異步 I/O 是內(nèi)核完成數(shù)據(jù)從內(nèi)核空間到用戶空間的拷貝等工作后通知應(yīng)用程序。

責(zé)任編輯:武曉燕 來源: 深度Linux
相關(guān)推薦

2020-11-04 07:49:04

Select

2021-05-31 06:50:47

SelectPoll系統(tǒng)

2025-01-07 00:07:17

2022-02-22 08:55:29

SelectPoll/ Epoll

2024-11-06 16:38:51

IO網(wǎng)絡(luò)

2022-06-03 10:52:55

selectpolepoll

2025-06-30 00:00:00

2015-10-21 10:24:05

TCPIP網(wǎng)絡(luò)協(xié)議

2017-09-18 17:00:02

前端URLURN

2011-05-20 13:45:30

Java

2011-06-15 10:28:37

Qt QWidget QMainWindo

2025-04-21 03:03:00

2025-02-06 09:47:33

2010-09-26 14:32:34

JDKJREJVM

2024-01-07 12:02:55

URLURIURN

2023-03-01 14:32:31

redisIOEpoll

2021-04-20 13:40:56

Epoll IO

2023-12-06 07:28:47

阻塞IO異步IO

2021-07-14 09:48:15

Linux源碼Epoll

2017-03-08 15:39:11

Linuxselect函數(shù)
點贊
收藏

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

亚洲视频视频在线| 国产精品一区二区视频| 亚洲人成电影网站| 能在线观看的av网站| 国产系列电影在线播放网址| 日韩精品电影在线观看| 最近2019年好看中文字幕视频 | 26uuu国产在线精品一区二区| 午夜精品久久久久久久99热 | 97精品久久久久中文字幕 | 中文字幕国产专区| 成人午夜毛片| 一区二区三区蜜桃网| 国产精品免费一区二区三区四区| 日韩乱码人妻无码中文字幕| 久久不见久久见中文字幕免费| 精品欧美aⅴ在线网站| 日本一区美女| av高清一区二区| 亚洲视频成人| 亚洲无线码在线一区观看| 污污网站免费看| 91精品久久| 91免费在线视频观看| 国产精品视频xxxx| 国产亚洲第一页| 精品国产一区一区二区三亚瑟| 欧美一区二区三区在线观看视频 | 亚洲经典三级| www亚洲精品| 欧美bbbbb性bbbbb视频| 国产精久久一区二区| 欧美性开放视频| 欧美日韩激情四射| 137大胆人体在线观看| 国产精品1区2区| 91国内精品久久| www.5588.com毛片| 免费一区二区| 日韩三级.com| 中文字幕国产免费| 在线天堂资源| 亚洲午夜视频在线观看| 欧美一区二区视频在线| 亚洲欧美另类视频| 狠狠色伊人亚洲综合成人| 欧美资源在线观看| 免费在线一区二区三区| 日韩欧美一区二区三区在线视频 | xxxxwww一片| 久久精品xxxxx| 一本一道综合狠狠老| 久草免费福利在线| av网址在线播放| 国产精品电影一区二区三区| 欧美男人的天堂| 污视频软件在线观看| 福利一区二区在线| 亚洲一区免费网站| 91一区二区视频| 另类的小说在线视频另类成人小视频在线| 2020国产精品视频| 国产无遮挡免费视频| 91精品综合久久久久久久久久久 | 美国黄色一级视频| 久久九九精品视频| 欧美一区二区三区四区五区| 亚洲娇小娇小娇小| 日韩国产91| 欧美精品乱码久久久久久按摩| 99久久国产宗和精品1上映 | 国产精品美女午夜av| 色一情一乱一伦| 久久xxxx精品视频| 青青草国产精品一区二区| 国产成人综合欧美精品久久| 国产精品久久久久久久免费软件 | 午夜影院在线播放| 欧美性xxxxx极品娇小| 国产免费黄色av| 日韩欧美看国产| 色香色香欲天天天影视综合网| 欧美三级一级片| 欧美男女交配| 欧美午夜视频网站| 91女神在线观看| 精品一区二区三区中文字幕视频 | 日韩小视频在线播放| av丝袜在线| 偷拍日韩校园综合在线| 欧美在线观看成人| a屁视频一区二区三区四区| 欧美日韩激情一区二区三区| а 天堂 在线| 国产+成+人+亚洲欧洲在线| 欧美一区二区福利在线| 亚洲视频天天射| 日韩欧美在线精品| 日韩亚洲一区二区| 免费三片在线播放| 国产精品美女久久久浪潮软件| 国产91露脸中文字幕在线| 中国女人真人一级毛片| 美女视频黄久久| 2019国产精品视频| 五月天婷婷激情网| 亚洲国产精品精华液ab| 色爽爽爽爽爽爽爽爽| av成人 com a| 欧美性猛交xxxxxx富婆| 国产精品嫩草影视| 天堂俺去俺来也www久久婷婷| 亚洲最新av在线| 五月婷婷一区二区| 久久亚洲欧洲| 成人在线视频网址| 成人在线观看网站| 夜夜嗨av一区二区三区四季av| a在线视频观看| 久久亚洲人体| 亚洲精品电影网| 亚洲视频重口味| 国产日韩欧美高清免费| 成人免费网站在线| 天堂在线视频免费观看| 中文字幕成人在线观看| 亚洲熟妇无码一区二区三区导航| 精品123区| 亚洲丁香婷深爱综合| 在线视频这里只有精品| 国产视频一区三区| 国产欧美一区二区三区久久| 性xxxx18| 亚洲一区在线视频观看| 日韩一区二区三区不卡视频| 欧美爱爱网站| 欧美成人午夜激情| 中文字幕资源网| www.日本不卡| 免费成人深夜夜行网站视频| 校园春色亚洲色图| 日韩女优制服丝袜电影| 日韩av毛片在线观看| 久久不射网站| 精品一区二区不卡| 精品精品导航| 日韩一区二区在线看| 免费看日本黄色片| 久久久久久黄| 久久综合九色综合网站| xxxx视频在线| 日韩欧美中文一区二区| 小早川怜子一区二区的演员表| 久久亚洲精品伦理| 国产在线精品二区| 欧美xxxx做受欧美88bbw| 欧美丰满少妇xxxxx高潮对白| 岛国精品资源网站| 亚洲大胆av| 国产免费高清一区| 免费av在线播放| 欧美日韩精品欧美日韩精品一综合| 久久精品国产亚洲av久| 亚洲麻豆av| 久久国产一区| 中文在线а√天堂| 亚洲国产成人在线播放| 国产一级视频在线播放| 大白屁股一区二区视频| www.在线观看av| 在这里有精品| 韩国一区二区电影| 人妻妺妺窝人体色www聚色窝 | www.17c.com喷水少妇| 欧美特黄一区| 精品国产免费人成电影在线观...| 电影k8一区二区三区久久| 精品国产乱码久久久久久1区2区 | 国产亚洲欧美一区二区三区| 国产深夜视频在线观看| 亚洲成人av资源网| 日本亚洲色大成网站www久久| 成人激情黄色小说| 能在线观看的av| 成人精品亚洲| 亚洲伊人久久综合| www.久久久久.com| 精品剧情v国产在线观看在线| 国产精品变态另类虐交| 91蜜桃免费观看视频| 熟女少妇精品一区二区| 欧美日韩激情在线一区二区三区| 国产精品自产拍在线观看| 久cao在线| 亚洲第一页中文字幕| 国产黄色免费观看| 久久久久久久久久久久久久久99| 国产高清视频网站| 成人3d动漫在线观看| 91精品久久久久久久久久入口| 中文字幕在线观看播放| 日韩欧美一卡二卡| 91video| 中文字幕一区在线观看视频| 国产麻豆剧传媒精品国产| 亚洲免费一区二区| 无码精品人妻一区二区三区影院| 国产精品嫩草影院com| 日本少妇xxx| 国产伦理一区| 亚洲高清乱码| 99国产精品免费网站| 国产成人综合亚洲| 激情视频在线观看| 亚洲成人黄色网| 神马久久久久久久| 一区二区三区精密机械公司| 亚洲一区二区观看| 国产在线一区二区| 婷婷五月综合缴情在线视频| 全球成人免费直播| 久久久久久国产精品免费免费| 成人亚洲精品| 国产精品劲爆视频| 成年人黄色大片在线| 久热精品视频在线观看| 超碰在线影院| 亚洲欧美在线免费观看| 免费观看黄色一级视频| 日韩欧美国产1| 亚洲天堂免费av| 日本韩国一区二区三区| 日韩精品一区三区| 亚洲精品成人悠悠色影视| 永久免费毛片在线观看| 99国产精品久| 激情图片中文字幕| 免费成人av资源网| 黄色免费视频大全| 亚洲调教视频在线观看| 黄瓜视频免费观看在线观看www| 一区二区三区四区在线看| 国产精品综合久久久久久| 99视频这里有精品| 成人国产亚洲精品a区天堂华泰 | 992tv成人免费视频| 日本免费在线观看| 最新的欧美黄色| 国产主播福利在线| 日韩精品极品视频免费观看| 日韩在线视频第一页| 日韩视频一区二区| 国产又黄又粗又长| 日韩欧美极品在线观看| 欧美 日韩 精品| 欧美性色xo影院| 日本天堂网在线| 色一区在线观看| 无码视频在线观看| 色狠狠综合天天综合综合| 日本视频免费在线| 大伊人狠狠躁夜夜躁av一区| 日韩精品视频播放| 午夜av一区二区| 中文字幕国产在线观看| 欧美日韩综合视频| 无码一区二区三区| 欧美日韩精品一区视频| 伊人网中文字幕| 欧美日韩免费一区二区三区 | 91亚洲精品丁香在线观看| 麻豆精品在线| 国产91视觉| 欧美人与牛zoz0性行为| 日本成人三级| 久久社区一区| 伊人精品久久久久7777| 欧美在线高清| 国产精品www在线观看| 亚洲精品1234| 男人搞女人网站| 免费在线观看一区二区三区| 青青草原国产在线视频| 成人午夜在线视频| free性中国hd国语露脸| 国产三级一区二区| 国产精品一二三区在线观看| 国产精品美女久久久久av爽李琼| 国产色无码精品视频国产| 亚洲宅男天堂在线观看无病毒| yjizz国产| 欧美视频在线播放| 国产一区二区三区中文字幕| 日韩一区二区精品葵司在线| 动漫av一区二区三区| 精品一区二区电影| 日本a在线播放| 色综合视频一区中文字幕| 亚洲精品一区| 95av在线视频| 亚洲69av| 日本精品福利视频| 亚洲在线视频| 亚洲国产日韩欧美在线观看| 国模少妇一区二区三区| 亚洲av无码成人精品国产| 国产精品久久久久婷婷| 久久久久无码国产精品| 在线观看区一区二| 国产内射老熟女aaaa∵| 日韩av中文字幕在线免费观看| 国产大片在线免费观看| 欧美国产欧美亚洲国产日韩mv天天看完整| 国产精品一区二区av影院萌芽| 成人欧美一区二区三区在线| 开心激情综合| 久久观看最新视频| 日韩成人一级片| av av在线| 亚洲另类在线制服丝袜| 日韩三级一区二区| 欧美不卡123| 蜜桃av在线免费观看| 热久久美女精品天天吊色| 中文字幕第4页| 九九九久久久精品| 久久久久麻豆v国产精华液好用吗| 国产精品久久久久久久久快鸭 | 国产精品久久久久久搜索| 欧美a级大片在线| 亚洲aⅴ天堂av在线电影软件| 亚洲精品社区| 天天摸天天舔天天操| 国产欧美精品在线观看| 日本中文字幕免费观看| 在线亚洲精品福利网址导航| 四虎影院在线域名免费观看| 欧美日韩第一视频| 精品久久99| 亚洲精品乱码视频| 亚洲永久字幕| 精品无码人妻少妇久久久久久| 亚洲人成网站色在线观看| 亚洲精品无码久久久久| 亚洲国产精品久久久| a国产在线视频| 91精品国产自产在线| 国产传媒欧美日韩成人精品大片| 欧美一级免费播放| av电影一区二区| 久久精品国产亚洲AV无码男同| 欧美一级搡bbbb搡bbbb| 日韩av激情| 91视频免费在线观看| 欧美成人中文| www.黄色网| 亚洲午夜私人影院| 天堂a√中文在线| 91av在线看| 日韩美脚连裤袜丝袜在线| 国产一级不卡毛片| 538任你躁精品视频网免费| 国产精品视频26uuu| 久久精品一区二区不卡| 热久久久久久久久| 国产精品进线69影院| 中文字幕在线播放日韩| 最近2019中文字幕一页二页| 91精品一久久香蕉国产线看观看| 亚洲砖区区免费| 国产最新精品精品你懂的| 久久国产在线观看| 91精品国产综合久久香蕉的特点 | www国产91| 欧美a级大片在线| www.好吊操| 91美女在线观看| 亚洲国产成人精品激情在线| 亚洲老司机av| 黄色欧美视频| 美女在线免费视频| 国产91精品欧美| 国产综合精品视频| 在线观看欧美视频| 色综合一区二区日本韩国亚洲 | 国产免费的av| 欧美国产欧美亚洲国产日韩mv天天看完整 | 久久成人资源| 久久精品国产99国产| 99精品久久久久| 日韩一区二区三区四区 | 亚洲男同性视频| 丰满肉嫩西川结衣av| 日韩av免费看网站| 中文字幕一区二区三区乱码图片| 国产情侣久久久久aⅴ免费| 亚洲国产精品久久久久秋霞影院| 亚洲 欧美 自拍偷拍| 国产精品久久av| 国产日韩欧美一区|