国产 无码 综合区,色欲AV无码国产永久播放,无码天堂亚洲国产AV,国产日韩欧美女同一区二区

IO多路復(fù)用之select/poll/epoll

這篇具有很好參考價(jià)值的文章主要介紹了IO多路復(fù)用之select/poll/epoll。希望對(duì)大家有所幫助。如果存在錯(cuò)誤或未考慮完全的地方,請(qǐng)大家不吝賜教,您也可以點(diǎn)擊"舉報(bào)違法"按鈕提交疑問(wèn)。


前言

  • 掌握select編程模型,能夠?qū)崿F(xiàn)select版本的TCP服務(wù)器.
  • 掌握poll編程模型,能夠?qū)崿F(xiàn)poll版本的TCP服務(wù)器.
  • 掌握epoll的編程模型,能夠?qū)崿F(xiàn)epoll版本的TCP服務(wù)器.
  • epoll的LT模式和ET模式.
  • 理解select和epoll的優(yōu)缺點(diǎn)對(duì)比.

提示:以下是本篇文章正文內(nèi)容,下面案例可供參考

一、IO多路轉(zhuǎn)接select

多路轉(zhuǎn)接天然的是讓我們可以依次等待多個(gè)文件描述符.

什么叫做文件描述符狀態(tài)的變化 —>1.可讀 2.可寫 3.異常

初始select

系統(tǒng)提供select函數(shù)來(lái)實(shí)現(xiàn)多路復(fù)用輸入/輸出模型.

  • select系統(tǒng)調(diào)用是用來(lái)讓我們的程序堅(jiān)實(shí)多個(gè)文件描述符的狀態(tài)變化的;
  • 程序會(huì)停在select這里等待,直到被監(jiān)視的文件描述符有一個(gè)或者多個(gè)發(fā)生了狀態(tài)變化;

select函數(shù)原型

如下所示:

IO多路復(fù)用之select/poll/epoll
參數(shù)解釋:

  • 參數(shù)ndfs是需要監(jiān)視的最大的文件描述符值+1;
  • rdset,wrset,exset分別對(duì)應(yīng)與需要檢測(cè)的可讀文件描述符的集合,可寫文件描述符的集合和異常文件描述符的集合;
  • 參數(shù)timeout為結(jié)構(gòu)timeval,用來(lái)設(shè)置select()的等待時(shí)間.

參數(shù)timeout取值:

  • NULL:表示select()沒(méi)有timeout,select將一直被阻塞,直到某個(gè)文件描述符上發(fā)生了事件;
  • 0:僅檢測(cè)描述符集合的狀態(tài),然后立即返回,并不等待外部事件的發(fā)生;
  • 特定的時(shí)間值:如果在指定的時(shí)間段里沒(méi)有事件發(fā)生,select將超時(shí)返回.

關(guān)于fd_set結(jié)構(gòu)

IO多路復(fù)用之select/poll/epoll

fd_set是一種位圖結(jié)構(gòu).**比特位的位置代表fd的編號(hào),比特位的內(nèi)容代表(就緒/未就緒)**的概念.

提供了一組fd_set的接口,來(lái)比較方便的操作位圖.

void FD_CLR(int fd, fd_set *set); // 用來(lái)清除描述詞組set中相關(guān)fd 的位
 int FD_ISSET(int fd, fd_set *set); // 用來(lái)測(cè)試描述詞組set中相關(guān)fd 的位是否為真
 void FD_SET(int fd, fd_set *set); // 用來(lái)設(shè)置描述詞組set中相關(guān)fd的位
 void FD_ZERO(fd_set *set); // 用來(lái)清除描述詞組set的全部位

查看fd_set的大小
IO多路復(fù)用之select/poll/epoll
因?yàn)閒d_set是位圖結(jié)構(gòu),求出來(lái)的結(jié)果是128字節(jié),所以對(duì)于bit為就是1024.

關(guān)于timeval結(jié)構(gòu)

timeval結(jié)構(gòu)用于描述一段時(shí)間長(zhǎng)度,如果在這個(gè)時(shí)間內(nèi),需要監(jiān)視的描述符沒(méi)有事件發(fā)生則函數(shù)返回,返回值為0.

IO多路復(fù)用之select/poll/epoll
函數(shù)返回值

  • 執(zhí)行成功則返回文件描述詞狀態(tài)已改變的個(gè)數(shù)
  • 如果返回0代表在描述詞狀態(tài)改變前已超過(guò)timeout時(shí)間,沒(méi)有返回
  • 當(dāng)有錯(cuò)誤發(fā)生時(shí)則返回-1,錯(cuò)誤原因存于errno,此時(shí)參數(shù)readfds,writefds,exceptfds和timeout的值變成不可預(yù)測(cè).

錯(cuò)誤值可能為:

  • EBADF文件描述詞為無(wú)效的或該文件已關(guān)閉
  • EINTR此調(diào)用被信號(hào)所中斷
  • EINVAL參數(shù)n為負(fù)值.
  • ENOMEM核心內(nèi)存不足,

select使用示例

makefile

SelectServer:SelectServer.cc
	g++ -o $@ $^ -std=c++11
.PHONY:clean
clean:
	rm SelectServer

Sock.hpp

#include <iostream>
#include <string>
#include <cassert>
#include <unistd.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/socket.h>
#include <string.h>
#include <arpa/inet.h>
#include <fstream>
using namespace std;

class Sock
{
public:
    static const int gBackLog = 20;
    static int Socket()
    {
        // 1.創(chuàng)建socket
        int _listenSock = socket(AF_INET, SOCK_STREAM, 0);
        if (_listenSock < 0)
        {
            exit(1);
        }
        int opt = 1;
        setsockopt(_listenSock,SOL_SOCKET,SO_REUSEADDR | SO_REUSEPORT, &opt,sizeof opt);
    }
    static void Bind(int socket, uint16_t _port)
    {
        // 2.bind綁定
        // 2.1填充服務(wù)器
        struct sockaddr_in local; // 用戶棧
        memset(&local, 0, sizeof local);
        local.sin_family = AF_INET;
        local.sin_port = htons(_port);
        local.sin_addr.s_addr = INADDR_ANY;
        // 2.2本地socket信息,寫入_sock對(duì)應(yīng)的內(nèi)核區(qū)域
        if (bind(socket, (const sockaddr *)&local, sizeof local) < 0)
        {
            exit(2);
        }
    }
    static void Listen(int socket)
    {
        // 3.監(jiān)聽(tīng)socket,為何要監(jiān)聽(tīng)呢?tcp是面向連接的!
        if (listen(socket, gBackLog) < 0)
        {
            exit(3);
        }
        // 允許別人來(lái)連接你了
    }
    static int Accept(int socket,string* clientip,uint16_t* clientport)
    {
        struct sockaddr_in peer;
        socklen_t len = sizeof(peer);
        int serviceSock = accept(socket, (struct sockaddr *)&peer, &len);
        if (serviceSock < 0)
        {
            return -1;
        }
        if(clientport) *clientport = ntohs(peer.sin_port);
        if(clientip) *clientip = inet_ntoa(peer.sin_addr);
        return serviceSock;
    }
};

SelectServer.cc

#include <iostream>
#include <sys/select.h>
#include <string>
#include "Sock.hpp"

using namespace std;

// 保存歷史上所有的合法fd
#define NUM (sizeof(fd_set) * 8)
int fdsArray[NUM] = {0};
#define DFL -1

static void Usage(string process)
{
    cerr << "\nUsage: " << process << "port\n"
         << endl;
}

static void showArray(int arr[], int num)
{
    cout << "當(dāng)前合法sock list # ";
    for (int i = 0; i < num; ++i)
    {
        if (arr[i] == DFL)
            continue;
        else
            cout << arr[i] << " ";
    }
    cout << endl;
}

// redfds : 現(xiàn)在包含的就是已經(jīng)就緒的sock
static void HandlerEvent(int listensock, fd_set &readfds)
{
    for (int i = 0; i < NUM; ++i)
    {
        if (fdsArray[i] == DFL)
            continue;
        if (i == 0 && fdsArray[i] == listensock)
        {
            // 如何得知那些fd上面的事件就緒了呢?
            if (FD_ISSET(listensock, &readfds))
            {
                // 具有了一個(gè)新鏈接
                cout << "已經(jīng)有一個(gè)新鏈接到來(lái)了,需要進(jìn)行獲取了..." << endl;
                string ip;
                uint16_t port;
                int sock = Sock::Accept(listensock, &ip, &port); // 這里不會(huì)阻塞
                if (sock < 0)
                    return;
                cout << "獲取新鏈接成功 : " << ip << " : " << port << " sock: " << sock << endl;
                // read/wirte --- 不能調(diào)用,因?yàn)槟鉹ead不知道底層數(shù)據(jù)是否就緒!!!
                // secelt知道! 想辦法把新的fd托管給select
                int i = 0;
                for (; i < NUM; ++i)
                {
                    if (fdsArray[i] == DFL)
                        break;
                }
                if (i == NUM)
                {
                    cerr << "我的服務(wù)器已經(jīng)到了最大的上限了,無(wú)法在承載更多的連接了..." << endl;
                    close(sock);
                }
                else
                {
                    // 將sock添加到select中,進(jìn)一步的監(jiān)聽(tīng)就緒事件了!
                    fdsArray[i] = sock;
                    showArray(fdsArray, NUM);
                }
            }
        }
        else
        {
            // 處理普通sock的IO事件!
            if (FD_ISSET(fdsArray[i], &readfds))
            {
                // 一定是一個(gè)合法的普通的IO類sock就緒了
                // read/recv讀取即可
                char buffer[1024];
                ssize_t s = recv(fdsArray[i], buffer, sizeof buffer, 0);
                if (s > 0)
                {
                    buffer[s] = 0;
                    cout << "client[" << fdsArray[i] << "]# " << buffer << endl;
                }
                else if (s == 0)
                {
                    cout << "client[" << fdsArray[i] << "] quit,server close: " << fdsArray[i] << endl;
                    close(fdsArray[i]);
                    fdsArray[i] = DFL; // 取出對(duì)該文件描述符的select的事件監(jiān)聽(tīng)
                }
                else
                {
                    cout << "client[" << fdsArray[i] << "] quit,server close: " << fdsArray[i] << endl;
                    close(fdsArray[i]);
                    fdsArray[i] = DFL; // 取出對(duì)該文件描述符的select的事件監(jiān)聽(tīng)
                    showArray(fdsArray, NUM);
                }
            }
        }
    }
}

// ./SelectServer 8080
// 只關(guān)心讀事件
int main(int argc, char *argv[])
{
    if (argc != 2)
    {
        Usage(argv[0]);
        exit(1);
    }
    // fd_set fds; // fa_set是用位圖表示多個(gè)fd的.
    // cout<<sizeof(fd_set)<<endl;

    int listensock = Sock::Socket();
    Sock::Bind(listensock, 8080);
    Sock::Listen(listensock);

    for (int i = 0; i < NUM; ++i)
        fdsArray[i] = DFL;
    fdsArray[0] = listensock;

    while (true)
    {
        int maxFd = -1;
        fd_set readfds;
        FD_ZERO(&readfds);
        for (int i = 0; i < NUM; ++i)
        {
            if (fdsArray[i] == DFL) // 過(guò)濾不合法的fd
                continue;
            FD_SET(fdsArray[i], &readfds); // 添加所有的合法的fd到readfds中,方便select統(tǒng)一進(jìn)行就緒監(jiān)聽(tīng)
            if (maxFd < fdsArray[i])       // 更新最大值
                maxFd = fdsArray[i];
        }

        struct timeval timeout = {5, 0};
        // 如何看待監(jiān)聽(tīng)socket, 獲取新鏈接的, 本質(zhì)是需要先三次握手!
        // 前提是給我發(fā)送syn -? 建立連接的本質(zhì),其實(shí)也是IO,一個(gè)建立好的連接,我們成為讀時(shí)間就緒!
        // accept: 等+"數(shù)據(jù)拷貝"
        // string ip;
        // uint16_t port;
        // int sock = Sock::Accept(listensock,&ip,&port);

        // 編寫多路轉(zhuǎn)接代碼時(shí),必須先保證條件就緒了,才能調(diào)用IO類函數(shù)!
        int n = select(maxFd + 1, &readfds, nullptr,
                       nullptr, &timeout);

        switch (n)
        {
        case 0:
            cout << "time out ..." << (unsigned long long)time(nullptr) << endl;
            break;
        case -1:
            cerr << errno << " : " << strerror(errno) << endl;
            break;
        default:
            // 等待成功
            // 1.剛啟動(dòng)的時(shí)候,只有一個(gè)fd,listenscok
            // 2.server 運(yùn)行的時(shí)候,sock才會(huì)慢慢變多
            // 3.select 使用位圖,采用輸入輸出型參數(shù)的方式,來(lái)進(jìn)行 內(nèi)核<->用戶 信息的傳遞.
            // 每一次調(diào)用select,都需要對(duì)歷史數(shù)據(jù)和sock進(jìn)行重新設(shè)置!
            // 4.listensock, 永遠(yuǎn)都要被設(shè)置進(jìn)readfds中!
            // 5.select就緒的時(shí)候,可能是listen就緒,也可能是普通的IO sock就緒!!
            HandlerEvent(listensock, readfds);
            break;
        }
    }

    return 0;
}

select編碼特征

  1. select之前要進(jìn)行所有參數(shù)的重置.之后要遍歷所有合法fd進(jìn)行事件監(jiān)測(cè).
  2. select需要用戶自己維護(hù)第三方數(shù)組,來(lái)保存所有的合法fd,方便select批量處理.
  3. 一旦特點(diǎn)的fd事件就緒,本次的讀取或者寫入不會(huì)被阻塞.

select優(yōu)缺點(diǎn)

優(yōu)點(diǎn):占用資源少,并且高效.對(duì)比之前的多線程,多進(jìn)程.

缺點(diǎn):每一次都要進(jìn)行大量的重置工作,效率比較低.
每一次能夠檢測(cè)的fd數(shù)量是有上限的.
每一次都需要內(nèi)核到用戶,用戶到內(nèi)核傳遞位圖參數(shù),有較為大量的數(shù)據(jù)拷貝
select編碼特別不方便,需要用戶自己維護(hù)數(shù)組
select底層需要遍歷的方式,檢測(cè)所需要檢測(cè)的fd

二、IO多路轉(zhuǎn)接poll

poll函數(shù)接口

IO多路復(fù)用之select/poll/epoll

參數(shù)說(shuō)明:

  • fds是一個(gè)poll函數(shù)監(jiān)聽(tīng)的結(jié)構(gòu)列表.每一個(gè)元素中,包含了三部分內(nèi)容:文件描述符,監(jiān)聽(tīng)事件集合,返回的事件集合.
  • nfds表示fds數(shù)組的長(zhǎng)度.
  • timeout表示poll函數(shù)的超時(shí)時(shí)間,單位是毫秒(ms).

events和revents的取值

IO多路復(fù)用之select/poll/epoll
返回結(jié)果

  • 返回值小于0,表示出錯(cuò).
  • 返回值等于0,表示poll函數(shù)等待超時(shí).
  • 返回值大于0,表示poll由于監(jiān)聽(tīng)的文件描述符就緒而返回.

socket就緒的條件

和select相同.

poll的優(yōu)點(diǎn)

不同于select使用三個(gè)位圖來(lái)表示三個(gè)fdset的方式,poll使用一個(gè)pollfd的指針實(shí)現(xiàn).

  • pollfd結(jié)構(gòu)包含了要監(jiān)視的event和發(fā)生的event,不再使用select"參數(shù)-值"傳遞的方式.接口使用比select更方便.
  • poll并沒(méi)有最大文件描述符數(shù)量限制(但是數(shù)量過(guò)大后性能還是會(huì)下降).

poll的缺點(diǎn)

poll監(jiān)聽(tīng)的文件描述符數(shù)目增多時(shí)

  • 和select函數(shù)一樣,poll返回后,需要輪詢pollfd來(lái)獲取就緒的文件描述符.
  • 每次調(diào)用poll都需要把大量的pollfd結(jié)構(gòu)從用戶態(tài)拷貝到內(nèi)核中.
  • 同時(shí)連接的大量客戶端在同一時(shí)刻可能只有很少的處于就緒狀態(tài),因此隨著監(jiān)視的描述符數(shù)量的增長(zhǎng),其效率也會(huì)線性下降.

poll示例

makefile

PollServer:PollServer.cc
	g++ -o $@ $^ -std=c++11
.PHONY:clean
clean:
	rm PollServer
#include <iostream>
#include <poll.h>
#include <string>
#include "Sock.hpp"

using namespace std;

// 保存歷史上所有的合法fd
#define NUM 1024
struct pollfd fdsArray[NUM] = {0};
#define DFL -1


// 意義:不光是網(wǎng)絡(luò)sock,本地的文件描述符也可以被托管給多路轉(zhuǎn)接,那么后面的文件操作,管道等也可以直接對(duì)接到多路轉(zhuǎn)接!!!


static void Usage(string process)
{
    cerr << "\nUsage: " << process << "port\n"
         << endl;
}

static void showArray(int arr[], int num)
{
    cout << "當(dāng)前合法sock list # ";
    for (int i = 0; i < num; ++i)
    {
        if (arr[i] == DFL)
            continue;
        else
            cout << arr[i] << " ";
    }
    cout << endl;
}

// redfds : 現(xiàn)在包含的就是已經(jīng)就緒的sock
static void HandlerEvent(int listensock)
{
    for (int i = 0; i < NUM; ++i)
    {
        if (fdsArray[i].fd == DFL)
            continue;
        if (i == 0 && fdsArray[i].fd == listensock)
        {
            // 如何得知那些fd上面的事件就緒了呢?
            if (fdsArray[i].revents & POLLIN)
            {
                // 具有了一個(gè)新鏈接
                cout << "已經(jīng)有一個(gè)新鏈接到來(lái)了,需要進(jìn)行獲取了..." << endl;
                string ip;
                uint16_t port;
                int sock = Sock::Accept(listensock, &ip, &port); // 這里不會(huì)阻塞
                if (sock < 0)
                    return;
                cout << "獲取新鏈接成功 : " << ip << " : " << port << " sock: " << sock << endl;
                // read/wirte --- 不能調(diào)用,因?yàn)槟鉹ead不知道底層數(shù)據(jù)是否就緒!!!
                // secelt知道! 想辦法把新的fd托管給select
                int i = 0;
                for (; i < NUM; ++i)
                {
                    if (fdsArray[i].fd == DFL)
                        break;
                }
                if (i == NUM)
                {
                    cerr << "我的服務(wù)器已經(jīng)到了最大的上限了,無(wú)法在承載更多的連接了..." << endl;
                    close(sock);
                }
                else
                {
                    // 將sock添加到select中,進(jìn)一步的監(jiān)聽(tīng)就緒事件了!
                    fdsArray[i].fd = sock;
                    fdsArray[i].events = POLLIN;
                    fdsArray[i].revents = 0;
                }
            }
        }
        else
        {
            // 處理普通sock的IO事件!
            if (fdsArray[i].revents & POLLIN)
            {
                // 一定是一個(gè)合法的普通的IO類sock就緒了
                // read/recv讀取即可
                char buffer[1024];
                ssize_t s = recv(fdsArray[i].fd, buffer, sizeof buffer, 0);
                if (s > 0)
                {
                    buffer[s] = 0;
                    cout << "client[" << fdsArray[i].fd << "]# " << buffer << endl;
                }
                else if (s == 0)
                {
                    cout << "client[" << fdsArray[i].fd << "] quit,server close: " << fdsArray[i].fd << endl;
                    close(fdsArray[i].fd);
                    fdsArray[i].fd = DFL; // 取出對(duì)該文件描述符的select的事件監(jiān)聽(tīng)
                }
                else
                {
                    cout << "client[" << fdsArray[i].fd << "] quit,server error: " << fdsArray[i].fd << endl;
                    close(fdsArray[i].fd);
                    fdsArray[i].fd = DFL; // 取出對(duì)該文件描述符的select的事件監(jiān)聽(tīng)
                }
            }
        }
    }
}

// ./SelectServer 8080
// 只關(guān)心讀事件
int main(int argc, char *argv[])
{
    if (argc != 2)
    {
        Usage(argv[0]);
        exit(1);
    }
    // fd_set fds; // fa_set是用位圖表示多個(gè)fd的.
    // cout<<sizeof(fd_set)<<endl;

    int listensock = Sock::Socket();
    Sock::Bind(listensock, 8080);
    Sock::Listen(listensock);

    for (int i = 0; i < NUM; ++i)
    {
        fdsArray[i].fd = DFL;
        fdsArray[i].events = 0;
        fdsArray[i].revents = 0;
    }
    fdsArray[0].fd = listensock;
    fdsArray[0].events = POLLIN; // 只關(guān)心讀事件
    int timeout = 1000;
    while (true)
    {
        int n = poll(fdsArray,NUM,timeout);

        switch (n)
        {
        case 0:
            cout << "time out ..." << (unsigned long long)time(nullptr) << endl;
            break;
        case -1:
            cerr << errno << " : " << strerror(errno) << endl;
            break;
        default:
            HandlerEvent(listensock);
            break;
        }
    }

    return 0;
}

Sock.hpp的代碼和select當(dāng)時(shí)的代碼相同!!!

那么現(xiàn)在的多路轉(zhuǎn)接還有什么問(wèn)題?

  1. 鏈接多的時(shí)候,select和poll都是基于對(duì)多個(gè)fd進(jìn)行遍歷檢測(cè),來(lái)識(shí)別事件的,鏈接多的時(shí)候,一定會(huì)引起遍歷周期的增加.
  2. 對(duì)于事件(用戶告訴內(nèi)核,內(nèi)核通知用戶),需要使用的數(shù)據(jù)結(jié)構(gòu)(數(shù)組),需要由程序員自己維護(hù).

三、IO多路轉(zhuǎn)接之epoll

epoll初始

按照man手冊(cè)的說(shuō)法:是為處理大批量句柄而作了改進(jìn)的poll.

他是在2.5.44內(nèi)核中引進(jìn)的(epoll(4) is a nwe API introduced in Linux kernel 2.5.44).

他幾乎具備了之前所說(shuō)的一切優(yōu)點(diǎn),被公認(rèn)為L(zhǎng)inux2.6下性能最好的多路IO就緒通知方法.

epoll的相關(guān)系統(tǒng)調(diào)用

epoll有三個(gè)相關(guān)的系統(tǒng)調(diào)用.
無(wú)論有多個(gè)接口,核心工作:只負(fù)責(zé)等!

epoll_create

IO多路復(fù)用之select/poll/epoll
創(chuàng)建一個(gè)epoll的句柄.

  • 自從linux2.6.8以后,size參數(shù)是被忽略的,參數(shù)只要大于0即可.
  • 用完之后,必須調(diào)用close關(guān)閉.

epoll_ctl

IO多路復(fù)用之select/poll/epoll
epoll的事件注冊(cè)函數(shù).

  • 他不同于select()是在監(jiān)聽(tīng)事件時(shí)告訴內(nèi)核要監(jiān)聽(tīng)什么類型的事件,而是在這里先注冊(cè)要監(jiān)聽(tīng)的事件類型.
  • 第一個(gè)參數(shù)是epoll_create()的返回值(epoll的句柄).
  • 第二個(gè)參數(shù)表示動(dòng)作,用三個(gè)宏來(lái)表示.
  • 第三個(gè)參數(shù)是需要監(jiān)聽(tīng)的fd.
  • 第四個(gè)參數(shù)是告訴內(nèi)核需要監(jiān)聽(tīng)什么事.

第二個(gè)參數(shù)的取值:

  • EPOLL_CTL_ADD:注冊(cè)新的fd到epfd中;
  • EPOLL_CTL_MOD:修改已經(jīng)注冊(cè)的fd的監(jiān)聽(tīng)事件;
  • EPOLL_CTL_DEL:從epfd中刪除一個(gè)fd;

struct epoll_event的結(jié)構(gòu)如下:

IO多路復(fù)用之select/poll/epoll

epoll_data_t的結(jié)構(gòu)如下:

IO多路復(fù)用之select/poll/epoll

events可以是以下幾個(gè)宏的集合:

  • EPOLLIN : 表示對(duì)應(yīng)的文件描述符可以讀(包括對(duì)端SOCKET正常關(guān)閉);
  • EPOLLOUT : 表示對(duì)應(yīng)的文件描述符可以寫;
  • EPOLLPRI : 表示對(duì)應(yīng)的文件描述符有緊急的數(shù)據(jù)可讀(這里應(yīng)該表示有外帶數(shù)據(jù)的到來(lái));
  • EPOLLERR : 表示對(duì)應(yīng)的文件描述符發(fā)生錯(cuò)誤;
  • EPOLLLHUP : 表示對(duì)應(yīng)的文件描述符被掛斷;
  • EPOLLET : 將EPOLL設(shè)為邊緣觸發(fā)(Edge Triggered)模式,這是相對(duì)于水平觸發(fā)(Level Triggered)來(lái)說(shuō)的.
  • EPOLLONESHOT : 只監(jiān)聽(tīng)一次事件,當(dāng)監(jiān)聽(tīng)完這次事件事件之后,如果還需要監(jiān)聽(tīng)這個(gè)socket的話,需要再次把這個(gè)socket加入到EPOLL隊(duì)列中.

epoll_wait

IO多路復(fù)用之select/poll/epoll

收集在epoll監(jiān)控的事件中已經(jīng)發(fā)送的事件:

  • 參數(shù)events是分配好的epoll_event結(jié)構(gòu)體數(shù)組.
  • epoll將會(huì)把發(fā)生的事件賦值到events數(shù)組中(events不可以是空指針,內(nèi)核只負(fù)責(zé)把數(shù)據(jù)復(fù)制到這個(gè)events數(shù)組中,不會(huì)取幫助我們?cè)谟脩魬B(tài)中分配內(nèi)存).
  • maxevents告知內(nèi)核這個(gè)even ts有多大,這個(gè)maxevents的值不能大于創(chuàng)建epoll_create()時(shí)的size.
  • 參數(shù)timeout是超時(shí)時(shí)間(毫秒,0會(huì)立即返回,-1是永久阻塞).
  • 如果函數(shù)調(diào)用成功,返回對(duì)應(yīng)IO上已準(zhǔn)備好的文件描述符數(shù)目,如返回0表示已超時(shí),返回小于0表示函數(shù)調(diào)用失敗.

epoll工作原理

操作系統(tǒng)如何得知,網(wǎng)絡(luò)中的數(shù)據(jù)到來(lái)了呢?

網(wǎng)卡先得到數(shù)據(jù)會(huì)向CPU發(fā)送硬件中斷,調(diào)用OS預(yù)設(shè)的中斷函數(shù),負(fù)責(zé)從外設(shè)進(jìn)行數(shù)據(jù)拷貝.

epoll函數(shù)針對(duì)特定的一個(gè)或者多個(gè)fd,設(shè)定對(duì)應(yīng)的回調(diào)機(jī)制;當(dāng)fd緩沖區(qū)有數(shù)據(jù)的時(shí)候,進(jìn)行回調(diào).

當(dāng)某一進(jìn)程調(diào)用epoll_create方法時(shí),Linux內(nèi)核會(huì)創(chuàng)建一個(gè)eventpoll結(jié)構(gòu)體,這個(gè)結(jié)構(gòu)體中有兩個(gè)成員與epoll的使用方式密切相關(guān).

....
/* 紅黑樹(shù)的根節(jié)點(diǎn),這棵樹(shù)中存儲(chǔ)著所有添加到epoll中的需要監(jiān)控的事件*/
struct rb_root rbr;
/* 雙鏈表中則存放著將要通過(guò)epoll_wait返回給用戶的滿足條件的事件*/
struct list_head rdlist;
....
  • 每一個(gè)epoll對(duì)象都有一個(gè)獨(dú)立的eventpoll結(jié)構(gòu)體,用于存放通過(guò)epoll_ctl方法向epoll對(duì)象中添加進(jìn)來(lái)的事件.
  • 這些事件都會(huì)掛載在紅黑樹(shù)中,如此重復(fù)添加的事件就可以通過(guò)紅黑樹(shù)而高效的識(shí)別出來(lái)(紅黑樹(shù)的插入時(shí)間效率是O(lgn),其中n為樹(shù)的高度).
  • 而所有添加到epoll的事件都會(huì)與設(shè)備(網(wǎng)卡)驅(qū)動(dòng)程序建立回調(diào)關(guān)系,也就是說(shuō),當(dāng)響應(yīng)的事件發(fā)生時(shí)會(huì)調(diào)用這個(gè)回調(diào)方法.
  • 這個(gè)回調(diào)方法在內(nèi)核中叫ep_poll_callback,他會(huì)將發(fā)生的時(shí)間添加到rdlist雙鏈表中.
  • 在epoll中,對(duì)于每一個(gè)事件,都會(huì)建立一個(gè)epitem的結(jié)構(gòu)體.
struct epitem{ 
 struct rb_node rbn;//紅黑樹(shù)節(jié)點(diǎn) 
 struct list_head rdllink;//雙向鏈表節(jié)點(diǎn) 
 struct epoll_filefd ffd; //事件句柄信息 
 struct eventpoll *ep; //指向其所屬的eventpoll對(duì)象 
 struct epoll_event event; //期待發(fā)生的事件類型 
}
  • 當(dāng)調(diào)用epoll_wait檢查是否有事件發(fā)生時(shí),只需要檢查eventpoll對(duì)象中的rdlist雙鏈表中是否有epitem元素即可.
  • 如果rdlist不為空,則把發(fā)生的時(shí)間復(fù)制到用戶態(tài),同時(shí)將事件數(shù)量返回給用戶,這個(gè)操作的時(shí)間復(fù)雜度是O(1).

總結(jié)一下,epoll的使用過(guò)程就是三部曲:

  • 調(diào)用epoll_create創(chuàng)建一個(gè)epoll句柄.
  • 調(diào)用epoll__ctl,將監(jiān)控的文件描述符進(jìn)行注冊(cè).
  • 調(diào)用epoll_wait,等待文件描述符就緒.

epoll的優(yōu)點(diǎn)(對(duì)比于select)

  • 接口使用方便:雖然拆分成三個(gè)函數(shù),但是反而使用起來(lái)更方便和更高效.不需要每次循環(huán)都設(shè)置關(guān)注的文件描述符,也做到了輸入輸出參數(shù)分離開(kāi).
  • 數(shù)據(jù)拷貝輕量:只在合適的時(shí)候調(diào)用EPOLL_CTL_ADD將文件描述符結(jié)構(gòu)拷貝到內(nèi)核中,這個(gè)操作并不頻繁(而select/poll都是每次循環(huán)都要進(jìn)行拷貝).
  • 事件回調(diào)機(jī)制:避免使用遍歷,而是使用回調(diào)函數(shù)的方式,將就緒的文件描述符結(jié)構(gòu)加入到就緒隊(duì)列中,epoll_wait返回直接訪問(wèn)就緒隊(duì)列就知道哪些文件描述符就緒.這個(gè)操作時(shí)間復(fù)雜度O(1).即使文件描述符數(shù)目很多,效率也不會(huì)受到影響.
  • 沒(méi)有數(shù)量限制:文件描述符數(shù)目無(wú)上限.

注意!!!
網(wǎng)上有些博客說(shuō),epoll中使用了內(nèi)存映射機(jī)制

  • 內(nèi)存映射機(jī)制:內(nèi)核直接將就緒隊(duì)列通過(guò)mmap的方式映射到用戶態(tài),避免了拷貝內(nèi)存這樣的額外性能開(kāi)銷.

這種說(shuō)法是不準(zhǔn)確的.我們定義的struct epoll_event是我們?cè)谟脩艨臻g中分配好的內(nèi)存.勢(shì)必還是需要將內(nèi)核的數(shù)據(jù)拷貝到這個(gè)用戶空間的內(nèi)存中的.

epoll的工作方式

epoll有兩種工作方式-水平觸發(fā)(LT)和邊緣觸發(fā)(ET).

加入有這樣一個(gè)例子

  • 我們已經(jīng)把一個(gè)tcp socket添加到epoll描述符.
  • 這個(gè)時(shí)候socket的另一端被寫入了2KB的數(shù)據(jù).
  • 調(diào)用epoll_wait,并且他會(huì)返回,說(shuō)明他已經(jīng)準(zhǔn)備好讀取操作.
  • 然后調(diào)用read,制度去了1KB數(shù)據(jù).
  • 繼續(xù)調(diào)用epoll_wait.

水平觸發(fā)Level Triggered 工作模式

epoll默認(rèn)狀態(tài)下就是LT工作模式.

  • 當(dāng)epoll檢測(cè)到socket上事件就緒的時(shí)候,可以不立刻進(jìn)行處理,或者只處理一部分.
  • 如上面的例子,由于只讀了1K數(shù)據(jù),緩沖區(qū)中還剩1K數(shù)據(jù),在第二次調(diào)用epoll_wait時(shí),epoll_wait仍會(huì)立刻返回并通知socket讀事件就緒.
  • 直到緩沖區(qū)上的所有數(shù)據(jù)都被處理完,epoll_wait才不會(huì)立刻返回.
  • 支持阻塞讀寫和非阻塞讀寫.

邊緣觸發(fā)Edge Triggered工作模式

如果我們?cè)诘谝徊綄ocket添加到epoll描述符的時(shí)候使用了EPOLLET表示,epoll進(jìn)入ET工作模式.

  • 當(dāng)epoll檢測(cè)到socket上事件就緒時(shí),必須立刻處理.
  • 如上面的例子,雖然只讀了1K的數(shù)據(jù),緩沖區(qū)還剩1K的數(shù)據(jù),在第二次調(diào)用epoll_wait的時(shí)候,epoll_wait不會(huì)再返回了.
  • 也就是說(shuō)ET模式下,文件描述符上的事件就緒后,只有一次處理機(jī)會(huì).
  • ET的性能比LT性能更高(epoll_Wait返回的次數(shù)少了很多).Nginx默認(rèn)采用ET模式使用epoll.
  • 只支持非阻塞的讀寫

select和poll其實(shí)也是工作在LT模式下,epoll既可以支持LT,也可以支持ET.

對(duì)比LT和ET

LT是epoll的默認(rèn)行為.使用ET能夠減少epoll觸發(fā)的次數(shù).但是代價(jià)就是強(qiáng)逼著程序猿一次響應(yīng)就緒過(guò)程中就把所有的數(shù)據(jù)都處理完.

相當(dāng)于一個(gè)文件描述符就緒以后,不會(huì)反復(fù)被提示就緒,看起來(lái)就比LT更高效一些.但是在LT情況下如果也能做到每次就緒的文件描述符都立即處理,不讓這個(gè)就緒被重復(fù)提示的話,其實(shí)性能也是一樣的.

另一方面,ET的代碼復(fù)雜程度更高了!

理解ET模式和非阻塞文件描述符

使用ET模式的epoll,需要將文件描述設(shè)置為非阻塞.這個(gè)不是接口上的要求,而是"工程實(shí)踐"上的要求.

假設(shè)這樣的場(chǎng)景:服務(wù)器接收到一個(gè)10K的請(qǐng)求,會(huì)向客戶端返回一個(gè)應(yīng)答數(shù)據(jù).如果客戶端收不到應(yīng)答,不會(huì)發(fā)送第二個(gè)10K請(qǐng)求.

IO多路復(fù)用之select/poll/epoll
如果服務(wù)器寫的代碼是阻塞式的read,并且一次只read1K的數(shù)據(jù)的話(read不能保證一次就把所有的數(shù)據(jù)都讀出來(lái),參考man手冊(cè)的說(shuō)明,可能信號(hào)被打斷),剩下的9K數(shù)據(jù)就會(huì)待在緩沖區(qū)中.

IO多路復(fù)用之select/poll/epoll

此時(shí)由于epoll是ET模式,并不會(huì)認(rèn)為文件描述符就緒.epoll_wait就不會(huì)再次返回.剩下的9K數(shù)據(jù)會(huì)一直在緩沖區(qū)中.直到下一次客戶端再給服務(wù)器寫數(shù)據(jù).epoll_wait才能返回.

但是問(wèn)題來(lái)了.

  • 服務(wù)器只讀到1K數(shù)據(jù),要10K讀完才會(huì)給客戶端返回響應(yīng)數(shù)據(jù).
  • 客戶端要讀到服務(wù)器的響應(yīng),才會(huì)發(fā)送下一個(gè)請(qǐng)求.
  • 客戶端發(fā)送下一個(gè)請(qǐng)求,epoll_wait才會(huì)返回,才能去讀緩沖區(qū)中剩余的數(shù)據(jù).

IO多路復(fù)用之select/poll/epoll
所以為了解決上述問(wèn)題(阻塞read不一定能一下把完整的請(qǐng)求讀完),于是就可以使用非阻塞輪詢的方式來(lái)讀緩沖區(qū),保證一定能把完整的請(qǐng)求都讀出來(lái).

如果是LT沒(méi)這個(gè)問(wèn)題.只要緩沖區(qū)中的數(shù)據(jù)沒(méi)讀完,就能夠讓epoll_wait返回文件描述符讀就緒.

epoll的使用場(chǎng)景

epoll的高性能,是有一定的特定場(chǎng)景的.如果場(chǎng)景選擇不適宜的話,epoll的性能可能適得其反.

  • 對(duì)于多連接,且多個(gè)連接中只有一部分連接比較活躍時(shí),比較適合epoll.

例如,典型的一個(gè)需要處理上萬(wàn)個(gè)客戶端的服務(wù)器,例如各種互聯(lián)網(wǎng)APP的入口服務(wù)器,這樣的服務(wù)器就很適合epoll.

如果只是系統(tǒng)內(nèi)部,服務(wù)器和服務(wù)器之間進(jìn)行通信,只有少數(shù)的幾個(gè)連接,這種情況下用epoll就并不合適.具體要根據(jù)需求和場(chǎng)景特點(diǎn)來(lái)決定使用哪種IO模型.

epoll示例: epoll服務(wù)器(LT模式)

makefile

main:main.cc
	g++ -o $@ $^ -std=c++11
.PHONY:clean
clean:
	rm main

log.hpp

/*
 * @Author: hulu 2367598978@qq.com
 * @Date: 2022-11-28 16:18:12
 * @LastEditors: hulu 2367598978@qq.com
 * @LastEditTime: 2022-12-05 11:47:11
 * @FilePath: /udp/log.hpp
 * @Description: 這是默認(rèn)設(shè)置,請(qǐng)?jiān)O(shè)置`customMade`, 打開(kāi)koroFileHeader查看配置 進(jìn)行設(shè)置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
 */
#pragma once
#include<cstdio>
#include<ctime>
#include<cstdarg>
#include<cassert>
#include<cstdlib>
#include<cstring>
#include<cerrno>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#define DEBUG     0
#define NOTICE    1
#define WARINING  2
#define FATAL     3

const char* log_level[]={"DEBUG","NOTICE","WARINING","FATAL"};

#define LOGFIFE "ServerTcp.log"

class Log
{
public:
    Log():_logFd((-1))
    {}
    ~Log()
    {
        if(_logFd!=-1)
        {
            fsync(_logFd);//將操作系統(tǒng)中的數(shù)據(jù)盡快刷盤
            close(_logFd);
        }

    }
    void enable()
    {
        _logFd=open(LOGFIFE,O_WRONLY|O_APPEND|O_CREAT,0666);
        assert(_logFd!=-1);
        dup2(_logFd,0);
        dup2(_logFd,1);
        dup2(_logFd,2);
    }
private:
    int _logFd;
};

//logMessage(DEBUG,"%d",10);
void logMessage(int level,const char* format,...)
{
    assert(level>=DEBUG);
    assert(level<=FATAL);
    char logInfo[1024];
    char* name=getenv("USER");
    va_list ap; //ap--->char*
    va_start(ap,format);

    vsnprintf(logInfo,sizeof(logInfo)-1,format,ap);

    va_end(ap); //ap=NULL

    FILE* out=(level==FATAL)?stderr:stdout;
    fprintf(out,"%s | %u | %s | %s\n",\
    log_level[level],(unsigned int)time(nullptr),\
    name==nullptr?"unknow":name,logInfo);
    fflush(out);//將C緩沖區(qū)中的數(shù)據(jù)刷新到OS
    fsync(fileno(out)); // 將OS中的數(shù)據(jù)盡快刷盤
}

Sock.hpp

#include <iostream>
#include <string>
#include <cassert>
#include <unistd.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/socket.h>
#include <string.h>
#include <arpa/inet.h>
#include <fstream>
using namespace std;

class Sock
{
public:
    static const int gBackLog = 20;
    static int Socket()
    {
        // 1.創(chuàng)建socket
        int _listenSock = socket(AF_INET, SOCK_STREAM, 0);
        if (_listenSock < 0)
        {
            exit(1);
        }
        int opt = 1;
        setsockopt(_listenSock,SOL_SOCKET,SO_REUSEADDR | SO_REUSEPORT, &opt,sizeof opt);
        return _listenSock;
    }
    static void Bind(int socket, uint16_t _port)
    {
        struct sockaddr_in local; // 用戶棧
        memset(&local, 0, sizeof local);
        local.sin_family = AF_INET;
        local.sin_port = htons(_port);
        local.sin_addr.s_addr = INADDR_ANY;
        // 2.2本地socket信息,寫入_sock對(duì)應(yīng)的內(nèi)核區(qū)域
        if (bind(socket, (const sockaddr *)&local, sizeof local) < 0)
        {
            exit(2);
        }
    }
    static void Listen(int socket)
    {
        // 3.監(jiān)聽(tīng)socket,為何要監(jiān)聽(tīng)呢?tcp是面向連接的!
        if (listen(socket, gBackLog) < 0)
        {
            exit(3);
        }
        // 允許別人來(lái)連接你了
    }
    static int Accept(int socket,string* clientip,uint16_t* clientport)
    {
        struct sockaddr_in peer;
        socklen_t len = sizeof(peer);
        int serviceSock = accept(socket, (struct sockaddr *)&peer, &len);
        if (serviceSock < 0)
        {
            return -1;
        }
        if(clientport) *clientport = ntohs(peer.sin_port);
        if(clientip) *clientip = inet_ntoa(peer.sin_addr);
        return serviceSock;
    }
};

EpollServer.hpp

#pragma once
#include <iostream>
#include <sys/epoll.h>
#include <string>
#include<functional>
#include "log.hpp"
#include "Sock.hpp"
using namespace std;

#define NUM 1024

class EpollServer
{
public:
    using func_t = function<int(int)>;
    EpollServer(uint16_t port,func_t func)
        : _port(port),_func(func)
    {
    }
    ~EpollServer()
    {
        if (_listenSock != -1)
            close(_listenSock);
        if (_epFd != -1)
            close(_epFd);
    }
    void InitEpollServer()
    {
        _listenSock = Sock::Socket();

        Sock::Bind(_listenSock, _port);

        Sock::Listen(_listenSock);

        // 這里直接使用原生接口
        _epFd = epoll_create(NUM);
        if (_epFd < 0)
        {
            logMessage(FATAL, "%d:%s", errno, strerror(errno));
            exit(4);
        }

        logMessage(DEBUG, "創(chuàng)建監(jiān)聽(tīng)套接字成功:%d", _listenSock);
        logMessage(DEBUG, "創(chuàng)建epoll成功:%d", _epFd);
    }
    void Run()
    {
        // 1.先添加_listenSock
        struct epoll_event ev;
        ev.events = EPOLLIN;
        ev.data.fd = _listenSock;
        int n = epoll_ctl(_epFd, EPOLL_CTL_ADD, _listenSock, &ev);
        assert(n == 0);
        (void)n;

        struct epoll_event revs[NUM];
        int timeout = 1000;
        while (true)
        {
            // 關(guān)于n: 就緒的fd的個(gè)數(shù),只需要進(jìn)行將底層的就緒隊(duì)列中節(jié)點(diǎn),一次從0下標(biāo)放入到revs中即可
            int n = epoll_wait(_epFd, revs, NUM, timeout);
            switch (n)
            {
            case 0:
                cout << "time out ..." << (unsigned long long)time(nullptr) << endl;
                break;
            case -1:
                cerr << errno << " : " << strerror(errno) << endl;
                break;
            default:
                HandlerEvents(revs, n);
                break;
            }
        }
    }
    void HandlerEvents(struct epoll_event revs[], int n)
    {
        for (int i = 0; i < n; ++i)
        {
            int sock = revs[i].data.fd;
            uint32_t event = revs[i].events;
            if (event & EPOLLIN) // 讀就緒就緒
            {
                if (sock == _listenSock) // 監(jiān)聽(tīng)socket就緒,獲取新鏈接
                {
                    string ip;
                    uint16_t port;
                    int sockfd = Sock::Accept(_listenSock, &ip, &port);
                    if (sockfd < 0)
                    {
                        logMessage(WARINING, "%d:%s", errno, strerror(errno));
                        continue;
                    }
                    // 托管給Epoll
                    struct epoll_event ev;
                    ev.events = EPOLLIN;
                    ev.data.fd = sockfd;
                    int n = epoll_ctl(_epFd, EPOLL_CTL_ADD, sockfd, &ev);
                    assert(n == 0);
                    (void)n;
                }
                else // 普通socket就緒,進(jìn)行數(shù)據(jù)INPUT
                {
                    int n = _func(sock);
                    if(n == 0 || n<0)
                    {
                        // 文件描述符先移除在關(guān)閉
                        int x =epoll_ctl(_epFd,EPOLL_CTL_DEL,sock,nullptr);
                        assert(x == 0);
                        (void)x;
                        logMessage(DEBUG,"client quit: %d",sock);
                        close(sock);
                    }


                }
            }
            else
            {
            }
        }
    }

private:
    int _listenSock = -1;
    int _epFd = -1;
    uint16_t _port;
    func_t _func;
};

main.cc

#include "EpollServer.hpp"
#include <memory>

static void Usage(string process)
{
    cerr << "\nUsage: " << process << "\tport\n"
         << endl;
}

int myfunc(int sock)
{
    char buffer[NUM];
    ssize_t s = recv(sock, buffer,sizeof(buffer)-1,0); // 不會(huì)被阻塞
    if(s > 0)
    {
        buffer[s] = 0;
        logMessage(DEBUG,"client[%d] #:%s",sock,buffer);
    }
    return s;
}

// ./SelectServer 8080
// 只關(guān)心讀事件
int main(int argc, char *argv[])
{
    if (argc != 2)
    {
        Usage(argv[0]);
        exit(1);
    }
    unique_ptr<EpollServer> epollServer(new EpollServer(atoi(argv[1]), myfunc));
    epollServer->InitEpollServer();
    epollServer->Run();
}

總結(jié)

對(duì)于IO多路復(fù)用的三個(gè)函數(shù)就介紹到這里了,下一篇博客我們基于ET模式下的epoll服務(wù)器,也加Reactor模式.
(本章完!)文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-406973.html

到了這里,關(guān)于IO多路復(fù)用之select/poll/epoll的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!

本文來(lái)自互聯(lián)網(wǎng)用戶投稿,該文觀點(diǎn)僅代表作者本人,不代表本站立場(chǎng)。本站僅提供信息存儲(chǔ)空間服務(wù),不擁有所有權(quán),不承擔(dān)相關(guān)法律責(zé)任。如若轉(zhuǎn)載,請(qǐng)注明出處: 如若內(nèi)容造成侵權(quán)/違法違規(guī)/事實(shí)不符,請(qǐng)點(diǎn)擊違法舉報(bào)進(jìn)行投訴反饋,一經(jīng)查實(shí),立即刪除!

領(lǐng)支付寶紅包贊助服務(wù)器費(fèi)用

相關(guān)文章

  • 多路轉(zhuǎn)接高性能IO服務(wù)器|select|poll|epoll|模型詳細(xì)實(shí)現(xiàn)

    多路轉(zhuǎn)接高性能IO服務(wù)器|select|poll|epoll|模型詳細(xì)實(shí)現(xiàn)

    那么這里博主先安利一下一些干貨滿滿的專欄啦! Linux專欄 https://blog.csdn.net/yu_cblog/category_11786077.html?spm=1001.2014.3001.5482 操作系統(tǒng)專欄 https://blog.csdn.net/yu_cblog/category_12165502.html?spm=1001.2014.3001.5482 手撕數(shù)據(jù)結(jié)構(gòu) https://blog.csdn.net/yu_cblog/category_11490888.html?spm=1001.2014.3001.5482 去倉(cāng)庫(kù)獲

    2024年02月15日
    瀏覽(25)
  • IO多路復(fù)用中select的TCP服務(wù)器模型和poll服務(wù)模型

    服務(wù)器端 客戶端 poll客戶端

    2024年02月12日
    瀏覽(32)
  • Linux網(wǎng)絡(luò)編程:多路I/O轉(zhuǎn)接服務(wù)器(select poll epoll)

    Linux網(wǎng)絡(luò)編程:多路I/O轉(zhuǎn)接服務(wù)器(select poll epoll)

    文章目錄: 一:select 1.基礎(chǔ)API? select函數(shù) 思路分析 select優(yōu)缺點(diǎn) 2.server.c 3.client.c 二:poll 1.基礎(chǔ)API? poll函數(shù)? poll優(yōu)缺點(diǎn) read函數(shù)返回值 突破1024 文件描述符限制 2.server.c 3.client.c 三:epoll 1.基礎(chǔ)API epoll_create創(chuàng)建? ?epoll_ctl操作? epoll_wait阻塞 epoll實(shí)現(xiàn)多路IO轉(zhuǎn)接思路 epoll優(yōu)缺點(diǎn)

    2024年02月11日
    瀏覽(23)
  • IO多路復(fù)用(poll:與select類似,當(dāng)監(jiān)測(cè)的文件描述符有一個(gè)或多個(gè)就緒時(shí),執(zhí)行對(duì)應(yīng)的IO操作

    IO多路復(fù)用(poll:與select類似,當(dāng)監(jiān)測(cè)的文件描述符有一個(gè)或多個(gè)就緒時(shí),執(zhí)行對(duì)應(yīng)的IO操作

    使用poll實(shí)現(xiàn)TCP循環(huán)服務(wù)器接收客戶端消息并打印 服務(wù)器 ? ? 客戶端 ? ? 寫一個(gè)makefile方便使用 ? 結(jié)果 ? 筆記 ? ?

    2024年02月12日
    瀏覽(26)
  • 網(wǎng)絡(luò)編程 IO多路復(fù)用 [epoll版] (TCP網(wǎng)絡(luò)聊天室)

    網(wǎng)絡(luò)編程 IO多路復(fù)用 [epoll版] (TCP網(wǎng)絡(luò)聊天室)

    //head.h? ? ? ? ? ? 頭文件 //TcpGrpSer.c? ? ?服務(wù)器端 //TcpGrpUsr.c? ? ?客戶端 通過(guò)IO多路復(fù)用實(shí)現(xiàn)服務(wù)器在單進(jìn)程單線程下可以與多個(gè)客戶端交互 ?API epoll函數(shù) ?head.h TcpGrpSer.c TcpGrpUsr.c ?

    2024年02月11日
    瀏覽(24)
  • 多路轉(zhuǎn)接方案:select poll epoll 介紹和對(duì)比

    多路轉(zhuǎn)接方案:select poll epoll 介紹和對(duì)比

    內(nèi)存和外設(shè)的交互叫做IO,網(wǎng)絡(luò)IO就是將數(shù)據(jù)在內(nèi)存和網(wǎng)卡間拷貝。 IO本質(zhì)就是等待和拷貝,一般等待耗時(shí)往往遠(yuǎn)高于拷貝耗時(shí)。所以提高IO效率就是盡可能減少等待時(shí)間的比重。 IO模型 簡(jiǎn)單對(duì)比解釋 阻塞IO 阻塞等待數(shù)據(jù)到來(lái) 非阻塞IO 輪詢等待數(shù)據(jù)到來(lái) 信號(hào)驅(qū)動(dòng) 信號(hào)遞達(dá)時(shí)

    2024年02月08日
    瀏覽(17)
  • 網(wǎng)絡(luò)編程 IO多路復(fù)用 [select版] (TCP網(wǎng)絡(luò)聊天室)

    網(wǎng)絡(luò)編程 IO多路復(fù)用 [select版] (TCP網(wǎng)絡(luò)聊天室)

    //head.h? ? ? ? ? ? ? ? ?頭文件 //TcpGrpSer.c? ? ? ? 服務(wù)器端 //TcpGrpUsr.c? ? ? ? 客戶端 select函數(shù)? 功能:阻塞函數(shù),讓內(nèi)核去監(jiān)測(cè)集合中的文件描述符是否準(zhǔn)備就緒,若準(zhǔn)備就緒則解除阻塞。 原型: head.h TcpGrpSer.c TcpGrpUsr.c ? ?

    2024年02月14日
    瀏覽(25)
  • 【Linux網(wǎng)絡(luò)編程】TCP并發(fā)服務(wù)器的實(shí)現(xiàn)(IO多路復(fù)用select)

    【Linux網(wǎng)絡(luò)編程】TCP并發(fā)服務(wù)器的實(shí)現(xiàn)(IO多路復(fù)用select)

    服務(wù)器模型主要分為兩種, 循環(huán)服務(wù)器 和 并發(fā)服務(wù)器 。 循環(huán)服務(wù)器 : 在同一時(shí)間只能處理一個(gè)客戶端的請(qǐng)求。 并發(fā)服務(wù)器 : 在同一時(shí)間內(nèi)能同時(shí)處理多個(gè)客戶端的請(qǐng)求。 TCP的服務(wù)器默認(rèn)的就是一個(gè)循環(huán)服務(wù)器,原因是有兩個(gè)阻塞 accept函數(shù) 和recv函數(shù) 之間會(huì)相互影響。

    2024年02月03日
    瀏覽(100)
  • 多路IO—POll函數(shù),epoll服務(wù)器開(kāi)發(fā)流程

    多路IO—POll函數(shù),epoll服務(wù)器開(kāi)發(fā)流程

    \\\"在計(jì)算機(jī)網(wǎng)絡(luò)編程中,多路IO技術(shù)是非常常見(jiàn)的一種技術(shù)。其中,Poll函數(shù)和Epoll函數(shù)是最為常用的兩種多路IO技術(shù)。這兩種技術(shù)可以幫助服務(wù)器端處理多個(gè)客戶端的并發(fā)請(qǐng)求,提高了服務(wù)器的性能。本文將介紹Poll和Epoll函數(shù)的使用方法,并探討了在服務(wù)器開(kāi)發(fā)中使用這兩種技

    2024年02月06日
    瀏覽(19)
  • Linux多路IO復(fù)用:epoll

    Linux多路IO復(fù)用:epoll

    ? ? ? ? epoll是為克服select、poll每次監(jiān)聽(tīng)都需要在用戶、內(nèi)核空間反復(fù)拷貝,以及需要用戶程序自己遍歷發(fā)現(xiàn)有變化的文件描述符的缺點(diǎn)的多路IO復(fù)用技術(shù)。 epoll原理 創(chuàng)建內(nèi)核空間的紅黑樹(shù); 將需要監(jiān)聽(tīng)的文件描述符上樹(shù); 內(nèi)核監(jiān)聽(tīng)紅黑樹(shù)上文件描述符的變化; 返回有變化

    2024年02月04日
    瀏覽(22)

覺(jué)得文章有用就打賞一下文章作者

支付寶掃一掃打賞

博客贊助

微信掃一掃打賞

請(qǐng)作者喝杯咖啡吧~博客贊助

支付寶掃一掃領(lǐng)取紅包,優(yōu)惠每天領(lǐng)

二維碼1

領(lǐng)取紅包

二維碼2

領(lǐng)紅包