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

Linux多路IO復(fù)用技術(shù)——epoll詳解與一對多服務(wù)器實現(xiàn)

這篇具有很好參考價值的文章主要介紹了Linux多路IO復(fù)用技術(shù)——epoll詳解與一對多服務(wù)器實現(xiàn)。希望對大家有所幫助。如果存在錯誤或未考慮完全的地方,請大家不吝賜教,您也可以點擊"舉報違法"按鈕提交疑問。

在正式閱讀本篇博客之前,建議大家先按順序把下面這兩篇博客看一下,否則直接來看這篇博客的話估計很難搞懂

多路IO復(fù)用技術(shù)①——select詳解&如何使用select模型在本地主機(jī)實現(xiàn)簡易的一對多服務(wù)器多路IO復(fù)用技術(shù)②——poll詳解&如何使用poll模型在本地主機(jī)實現(xiàn)簡易的一對多服務(wù)器

在了解以上兩篇博客講解的內(nèi)容后,我們正式開始本篇博客的相關(guān)內(nèi)容講解,第三種多路套接字監(jiān)聽技術(shù)——epoll模型

目錄

EPOLL模型優(yōu)化的第一部分

1.對拷貝開銷的優(yōu)化

2.對掛載開銷的優(yōu)化

3.對查找開銷的優(yōu)化

EPOLL模型的相關(guān)接口

相關(guān)結(jié)構(gòu)體 struct epoll_event

相關(guān)函數(shù)

EPOLL模型優(yōu)化的第二部分

4.監(jiān)聽方面的優(yōu)化

EPOLL模型的優(yōu)缺點

使用epoll模型實現(xiàn)簡易一對多服務(wù)器的程序?qū)崿F(xiàn)

程序構(gòu)成           結(jié)果圖示

EPOLL的兩種監(jiān)聽模式


epoll模型是最優(yōu)秀的套接字監(jiān)聽技術(shù),他幾乎解決了select和poll模型中的所有缺點,并在其優(yōu)點上進(jìn)行了進(jìn)一步的優(yōu)化,接下來,我們就來了解一下epoll模型的進(jìn)行多路套接字監(jiān)聽的原理吧

EPOLL模型優(yōu)化的第一部分

我們知道,select和poll模型實現(xiàn)的監(jiān)聽原理其實都是按照以下這些步驟:

  1. 系統(tǒng)會將這個用戶層的監(jiān)聽集合拷貝到內(nèi)核層

  2. 系統(tǒng)會將內(nèi)核層的該監(jiān)聽集合中監(jiān)聽的套接字放入IO設(shè)備等待隊列,由IO設(shè)備等待隊列來進(jìn)行一次又一次的遍歷來判斷那些套接字中有數(shù)據(jù)需要處理

  3. 當(dāng)IO設(shè)備等待隊列發(fā)現(xiàn)有套接字處于就緒狀態(tài)時,會傳出就緒集合到內(nèi)核層

  4. 系統(tǒng)通過select模型將該就緒集合由內(nèi)核層拷貝到用戶層,供用戶使用

想要了解下面的內(nèi)容,這里需要為大家簡單講解一種數(shù)據(jù)結(jié)構(gòu)——紅黑樹  ,并希望大家能夠記住紅黑樹的優(yōu)點

紅黑樹的優(yōu)勢就在于非常適合用來存儲有序的數(shù)據(jù),并且增刪查改的時間復(fù)雜度很低,只有O(log2n)

1.對拷貝開銷的優(yōu)化

在之前的博客中,我們講過,隨著select/poll模型的持續(xù)使用,會產(chǎn)生大量的拷貝開銷和掛載開銷

這些拷貝開銷大致由兩部分組成:

  1. 系統(tǒng)從用戶層到內(nèi)核層,內(nèi)核層到用戶層翻來覆去的層級轉(zhuǎn)換

  2. 一旦監(jiān)聽集合更新,就要重新拷貝整個就緒集合,只要服務(wù)器不停,就會面臨無止盡的拷貝

該如何減少第一部分的拷貝開銷呢?

其實很簡單粗暴,epoll模型的開發(fā)人員直接將監(jiān)聽集合定義在內(nèi)核層。所以epoll模型的監(jiān)聽集合都是建立在內(nèi)核層的

PS:這種監(jiān)聽集合實際上是紅黑樹,每一個葉子節(jié)點對應(yīng)一個監(jiān)聽套接字

至于如何減少第二部分的拷貝開銷,就和紅黑樹樹這種數(shù)據(jù)結(jié)構(gòu)息息相關(guān)了。

我們知道,select和poll模型都是采用數(shù)組作為作為監(jiān)聽集合,數(shù)組這種結(jié)構(gòu)非常不便于查找其中哪些數(shù)據(jù)產(chǎn)生了變化,就導(dǎo)致當(dāng)有新的套接字要放入監(jiān)聽集合中時,系統(tǒng)會將整個新的監(jiān)聽集合全部拷貝到內(nèi)核層。

而紅黑樹就完美的避開了這一點,每當(dāng)有新的套接字要放入監(jiān)聽樹中時,我們只需要為這棵監(jiān)聽紅黑樹添加一個葉子節(jié)點就夠了,而不是重建這棵樹,這就導(dǎo)致了這方面的開銷大大減少了

2.對掛載開銷的優(yōu)化

在對拷貝開銷的優(yōu)化方面,epoll模型的開發(fā)人員依舊采用了這種數(shù)據(jù)結(jié)構(gòu),我們來為大家簡單的介紹一下原因:

我們知道,在select與poll模型中,數(shù)組這種結(jié)構(gòu)非常不便于查找其中哪些數(shù)據(jù)產(chǎn)生了變化,所以每當(dāng)有新的套接字放入監(jiān)聽集合中時,系統(tǒng)會將監(jiān)聽的套接字全部重新掛載到IO設(shè)備等待隊列,這就導(dǎo)致掛載開銷方面的開銷非常大。

而紅黑樹則完美的解決了這個問題,由于紅黑樹的特性,每當(dāng)有對應(yīng)新套接字的葉子節(jié)點被放入監(jiān)聽樹中時,系統(tǒng)能夠很輕松的就能找到對應(yīng)新套接字的葉子節(jié)點是哪一個,然后只需要將對應(yīng)的新套接字放入IO設(shè)備等待隊列就可以了,在掛載開銷方面的消耗大大減少

3.對查找開銷的優(yōu)化

我們知道,雖然數(shù)組的下標(biāo)是有序的,方便我們進(jìn)行遍歷,但是數(shù)組中的內(nèi)容并不一定是有序的,這就導(dǎo)致我們無法根據(jù)我們目標(biāo)內(nèi)容來直接找到其在數(shù)組中的哪個位置,而是需要去通過循環(huán)一次次的查找,但紅黑樹就完美地解決了這個問題

我們知道,當(dāng)我們調(diào)用accept函數(shù)讓服務(wù)器與多個客戶端建立TCP鏈接時,其返回的int類型的套接字文件描述符,其實都是有序遞增的,建立一個鏈接返回值就+1。由于紅黑樹在有序數(shù)據(jù)存儲方面的優(yōu)勢,使得服務(wù)器在對于監(jiān)聽就緒和取消監(jiān)聽方面的效率非常之高。

其實epoll模型不止在這些方面進(jìn)行了優(yōu)化,其他部分需要大家了解了EPOLL模型的相關(guān)接口后,才能為大家進(jìn)行講解,所以別著急,我們先來了解下EPOLL模型的相關(guān)接口吧

EPOLL模型的相關(guān)接口

以下接口的頭文件都是 #include <sys/epoll.h>

相關(guān)結(jié)構(gòu)體 struct epoll_event

struct epoll_event 
{
    uint32_t events;  // epoll 事件類型,包括可讀,可寫等
    epoll_data_t data; // 用戶數(shù)據(jù),可以是一個指針或文件描述符等
    callback();  //這個函數(shù)與用戶沒有關(guān)系,我們是看不見的,只有當(dāng)發(fā)現(xiàn)對應(yīng)套接字就緒時才會調(diào)用該函數(shù)進(jìn)行回調(diào)操作
};

typedef union epoll_data 
{
    void *ptr;   //指向任何類型的用戶數(shù)據(jù)
    int fd;   //套接字文件描述符
    uint32_t u32;   //32位的無符號整數(shù)
    uint64_t u64;   //64位的無符號整數(shù)
} epoll_data_t;


其中,events字段表示要監(jiān)聽的事件類型,可以是以下值之一:

  • EPOLLIN:表示對應(yīng)的文件描述符上有數(shù)據(jù)可讀

  • EPOLLOUT:表示對應(yīng)的文件描述符上可以寫入數(shù)據(jù)

  • EPOLLRDHUP:表示對端已經(jīng)關(guān)閉連接,或者關(guān)閉了寫操作端的寫入

  • EPOLLPRI:表示有緊急數(shù)據(jù)可讀

  • EPOLLERR:表示發(fā)生錯誤

  • EPOLLHUP:表示文件描述符被掛起

  • EPOLLET:表示將epoll設(shè)置為邊緣觸發(fā)模式

  • EPOLLONESHOT:表示將事件設(shè)置為一次性事件

這個回調(diào)的原理是什么呢?其實很簡單

其實負(fù)責(zé)監(jiān)聽這些套接字事件的設(shè)備就是網(wǎng)卡,所以最先知道套接字就緒的就是網(wǎng)卡設(shè)備,這時候開發(fā)人員就想了,那我能不能實現(xiàn)一個功能,創(chuàng)建一個結(jié)構(gòu)體隊列,直接將該隊列與網(wǎng)卡進(jìn)行綁定,形成一種回調(diào)關(guān)系。這樣的話,當(dāng)網(wǎng)卡監(jiān)聽到某套接字處于就緒狀態(tài)時,就通過異步通知的方式來告訴服務(wù)器是哪個套接字就緒了,不需要再一次次的遍歷來判斷哪個套接字就緒

所以epoll模型監(jiān)聽套接字的過程就如下所示:

  1. 建立結(jié)構(gòu)體(ep_item)隊列,隊列中的每一個結(jié)構(gòu)體和一個套接字對應(yīng),將該隊列與網(wǎng)卡進(jìn)行綁定

  2. 建立一個就緒鏈表,存放對應(yīng)就緒套接字的結(jié)構(gòu)體epoll_events

  3. 監(jiān)聽到某一套接字就緒時,調(diào)用結(jié)構(gòu)體epoll_events中的callback函數(shù),進(jìn)行拆包,將結(jié)構(gòu)體ep_item中的結(jié)構(gòu)體epoll_events拆分出來,并彈出到就緒鏈表中(雙向鏈表)

  4. 將這個就緒鏈表中的內(nèi)容拷貝到用戶在用戶層定義的結(jié)構(gòu)體(epoll_event)數(shù)組,以便用戶使用

不理解的話可以看下面的這個圖

Linux,多路IO復(fù)用技術(shù),epoll,Linux多路IO復(fù)用技術(shù)③——epoll詳解&如何使用epoll模型實現(xiàn)簡易的一對多服務(wù)器(附圖解與代碼實現(xiàn))

相關(guān)函數(shù)

先來介紹一下一會會用到的參數(shù):

  • int epfd; // 返回的監(jiān)聽紅黑樹文件描述符

  • int epoll_max; //監(jiān)聽套接字的最大數(shù)量

  • int option; //需要執(zhí)行的操作——添加、修改、刪除

  • int sockfd; //需要添加,修改,刪除的socket文件描述符

  • struct epoll_event * node; //掛在紅黑樹上的節(jié)點

  • int array_size; //存放對應(yīng)就緒套接字的結(jié)構(gòu)體的數(shù)組大小

  • struct epoll_event ready_array[array_size]; //存放對應(yīng)就緒套接字的結(jié)構(gòu)體的數(shù)組

  • int ready_max; //允許同時監(jiān)聽的最大套接字?jǐn)?shù)量

  • int timeout; //工作模式

PS:一般情況下,epoll_max = ready_max = array_size

我們來分別介紹一下option和timeout參數(shù)的有效值:

option參數(shù)的有效值為:

  • EPOLL_CTL_ADD:添加與目標(biāo)套接字對應(yīng)的結(jié)構(gòu)體(epoll_event)到epoll監(jiān)聽樹上,開始監(jiān)聽該套接字

  • EPOLL_CTL_MOD:修改目標(biāo)套接字的監(jiān)聽事件

  • EPOLL_CTL_DEL:將與目標(biāo)套接字對應(yīng)的結(jié)構(gòu)體(epoll_event)從epoll監(jiān)聽樹上摘除,也就是不再監(jiān)聽該套接字

timeout參數(shù)的有效值:

  • timeout = 0:表示非阻塞監(jiān)聽套接字相關(guān)事件

  • timeout = -1:表示阻塞監(jiān)聽套接字相關(guān)事件

  • timeout > 0:表示阻塞監(jiān)聽時間為timout(單位:毫秒),如果這段時間內(nèi)沒有監(jiān)聽到套接字相關(guān)事件,后續(xù)變?yōu)榉亲枞O(jiān)聽

函數(shù)功能返回值
int epoll_create(epoll_max);建立監(jiān)聽紅黑樹

1.成功,返回值為epoll監(jiān)聽樹對應(yīng)的文件描述符

2.出錯,返回 -1,并設(shè)置錯誤碼

int epoll_ctl(epfd , option , sockfd , node);添加/刪除節(jié)點或修改監(jiān)聽事件

1.成功,返回0

2.出錯,返回 -1,并設(shè)置錯誤碼

int epoll_wait(epfd , ready_array , ready_max , timeout);監(jiān)聽套接字相關(guān)事件

1.成功,返回值為就緒的套接字?jǐn)?shù)目

2.如果在請求的超時毫秒內(nèi)沒有套接字準(zhǔn)備就緒,返回0

3.出錯,返回 -1,并設(shè)置錯誤碼

epoll_create()的錯誤碼如下所示:

  • EINVAL:epoll_max大小不為正。

  • EMFILE:遇到了每個用戶對/ proc / sys / fs / epoll / max_user_instances施加的epoll實例數(shù)量的限制。

  • ENFILE:超過系統(tǒng)設(shè)定的進(jìn)程最大創(chuàng)建的文件描述符數(shù)量。

  • ENOMEM:沒有足夠的內(nèi)存來創(chuàng)建內(nèi)核對象。

epoll_ctl()的錯誤碼如下所示:

  • EBADF:epfd或fd不是有效的文件描述符。

  • EEXIST:option為EPOLL_CTL_ADD,并且提供的文件描述符fd已在該epoll實例中注冊。

  • EINVAL:epfd不是epoll文件描述符,或者fd與epfd相同,或者此接口不支持請求的操作option。

  • ENOENT:option是EPOLL_CTL_MOD或EPOLL_CTL_DEL,并且fd未在該epoll實例中注冊。

  • ENOMEM:沒有足夠的內(nèi)存來處理請求的操作控制操作。

  • ENOSPC:嘗試在主機(jī)上注冊(EPOLL_CTL_ADD)新文件描述符時遇到了/ proc / sys / fs / epoll / max_user_watches施加的限制。

  • EPERM:目標(biāo)文件fd不支持epoll。

epoll_wait()的錯誤碼如下所示:

  • EBADF:epfd不是有效的文件描述符。

  • EFAULT:具有寫許可權(quán)不能訪問事件指向的存儲區(qū)。

  • EINTR:在任何請求的事件發(fā)生或超時到期之前,信號處理程序中斷了該調(diào)用;參見signal(7)。

  • EINVAL:epfd不是epoll文件描述符,或者epoll_max小于或等于零。

EPOLL模型優(yōu)化的第二部分

4.監(jiān)聽方面的優(yōu)化

由于epoll模型的自定義結(jié)構(gòu)體隊列、異步通知和回調(diào)函數(shù)的使用,導(dǎo)致用戶不再需要遍歷去查找就緒的套接字是哪些,直接對自己在用戶層自定義的結(jié)構(gòu)體數(shù)組進(jìn)行遍歷處理就可以了,大大減少了時間片方面的消耗,使得服務(wù)器的處理能力實現(xiàn)質(zhì)的飛躍

基本了解了epoll在哪些方面做出了優(yōu)化后,我們就可以來聊一聊epoll模型的優(yōu)缺點了

EPOLL模型的優(yōu)缺點

優(yōu)點:

  1. 不存在重復(fù)的拷貝開銷與掛載開銷

  2. 自定義實現(xiàn)監(jiān)聽隊列,采用異步回調(diào)方式,無需輪詢,隊列體積更小

  3. 不止返回就緒的套接字?jǐn)?shù)量,還返回就緒的套接字是哪些

  4. 監(jiān)聽的事件種類豐富

  5. 可以為不同的套接字設(shè)置不同的監(jiān)聽事件,不像select模型只能批量設(shè)置監(jiān)聽事件

  6. 可以監(jiān)聽的socket數(shù)量不受1024的硬限制

缺點:

  1. 僅linux系統(tǒng)支持

  2. 紅黑樹的缺點

使用epoll模型實現(xiàn)簡易一對多服務(wù)器的程序?qū)崿F(xiàn)

程序構(gòu)成

該服務(wù)器與客戶端由以下幾個程序共同組成:

  • func_2th_parcel.h:定義二次包裹的函數(shù)名

  • func_2th_parcel.c:對網(wǎng)絡(luò)初始化相關(guān)的函數(shù)進(jìn)行二次包裹

  • epoll_server.c:使用poll模型的服務(wù)器程序

  • client.c:客戶端程序

/*************************************************************************
        > File Name: func_2th_parcel.h
        > Author: Nan
        > Mail: **@qq.com
        > Created Time: 2023年10月18日 星期三 18時32分22秒
 ************************************************************************/
 
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/stat.h>
#include <signal.h>
#include <pthread.h>
#include <fcntl.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <sys/mman.h>
#include <time.h>
#include <ctype.h>
#include <sys/select.h>
#include <sys/socket.h>
#include <poll.h>
#include <sys/epoll.h>
 
//socket函數(shù)的二次包裹
int SOCKET(int domain , int type , int protocol);
 
//bind函數(shù)的二次包裹
int BIND(int sockfd , const struct sockaddr* addr , socklen_t  addrlen);
 
//listen函數(shù)的二次包裹
int LISTEN(int sockfd , int backlog);
 
//send函數(shù)的二次包裹
ssize_t SEND(int sockfd , const void* buf , size_t len , int flags);
 
//recv函數(shù)的二次包裹
ssize_t RECV(int sockfd , void* buf , size_t len , int flags);
 
//connect函數(shù)的二次包裹
int CONNECT(int sockfd , const struct sockaddr* addr , socklen_t addrlen);
 
//accept函數(shù)的二次包裹
int ACCEPT(int sockfd , struct sockaddr* addr , socklen_t addrlen);
 
//網(wǎng)絡(luò)初始化函數(shù)
int SOCKET_NET_CREATE(const char* ip , int port);
 
//服務(wù)端與客戶端建立連接并返回客戶端套接字文件描述符
int SERVER_ACCEPTING(int server_fd);


/*************************************************************************
        > File Name: func_2th_parcel.c
        > Author: Nan
        > Mail: **@qq.com
        > Created Time: 2023年10月18日 星期三 18時32分42秒
 ************************************************************************/
 
#include <func_2th_parcel.h>
 
int SOCKET(int domain , int type , int protocol){
    int return_value;
    if((return_value = socket(domain , type , protocol)) == -1){
        perror("socket call failed!\n");
        return return_value;
    }
    return return_value;
}
 
int BIND(int sockfd , const struct sockaddr* addr , socklen_t addrlen){
    int return_value;   
    if((return_value = bind(sockfd , addr , addrlen)) == -1){
        perror("bind call failed!\n");
        return return_value;
    }                      
    return return_value;   
}
 
int LISTEN(int sockfd , int backlog){
    int return_value;   
    if((return_value = listen(sockfd , backlog)) == -1){
        perror("listen call failed!\n");
        return return_value;
    }                      
    return return_value;   
}
 
ssize_t SEND(int sockfd , const void* buf , size_t len , int flags){
    ssize_t return_value;
    if((return_value = send(sockfd , buf , len , flags)) == -1){
        perror("send call failed!\n");
        return return_value;
    }
    return return_value;
}
 
ssize_t RECV(int sockfd , void* buf , size_t len , int flags){
    ssize_t return_value;   
    if((return_value = recv(sockfd , buf , len , flags)) == -1){
        perror("recv call failed!\n");
        return return_value;
    }                      
    return return_value;   
}
 
int CONNECT(int sockfd , const struct sockaddr* addr , socklen_t addrlen){
    int return_value;   
    if((return_value = connect(sockfd , addr , addrlen)) == -1){
        perror("connect call failed!\n");
        return return_value;
    }                      
    return return_value;   
}
 
int ACCEPT(int sockfd , struct sockaddr* addr , socklen_t addrlen){
    int return_value;   
    if((return_value = accept(sockfd , addr , &addrlen)) == -1){
        perror("accept call failed!\n");
        return return_value;
    }                      
    return return_value;   
}
 
int SOCKET_NET_CREATE(const char* ip , int port){
    int sockfd;
    struct sockaddr_in addr;
    addr.sin_family = AF_INET;
    addr.sin_port = htons(port);
    inet_pton(AF_INET , ip , &addr.sin_addr.s_addr);
    sockfd = SOCKET(AF_INET , SOCK_STREAM , 0);
    BIND(sockfd , (struct sockaddr*)&addr , sizeof(addr));
    LISTEN(sockfd , 128);
    return sockfd;
}
 
int SERVER_ACCEPTING(int server_fd)
{
    int client_sockfd;
    struct sockaddr_in client_addr;
    char client_ip[16];
    char buffer[1500];
    bzero(buffer , sizeof(buffer));
    bzero(&client_addr , sizeof(client_addr));
    socklen_t addrlen = sizeof(client_addr);
    client_sockfd = ACCEPT(server_fd , (struct sockaddr*)&client_addr , addrlen);
    bzero(client_ip , 16);
    //將客戶端的IP地址轉(zhuǎn)成CPU可以識別的序列并存儲到client_ip數(shù)組中
    inet_ntop(AF_INET , &client_addr.sin_addr.s_addr , client_ip , 16);
    sprintf(buffer , "Hi , %s welcome tcp test server service..\n" , client_ip);
    printf("client %s , %d , connection success , client sockfd is %d\n" , client_ip , ntohs(client_addr.sin_port) , client_sockfd);
    SEND(client_sockfd , buffer , strlen(buffer) , MSG_NOSIGNAL);
    return client_sockfd;
}


/*************************************************************************
        > File Name: epoll_server.c
        > Author: Nan
        > Mail: **@qq.com
        > Created Time: 2023年10月25日 星期三 18時53分30秒
 ************************************************************************/

#include <func_2th_parcel.h>

#define MAX_EPOLL 100000

int main(void)
{
    int server_sockfd;//服務(wù)器套接字文件描述符
    int epfd;//監(jiān)聽樹文件描述符
    struct epoll_event ready_array[MAX_EPOLL];//由于存放epoll返回的就緒結(jié)構(gòu)體
    struct epoll_event node;//向監(jiān)聽樹中放入的節(jié)點
    int ready_num;//處于就緒狀態(tài)的套接字的數(shù)量
    int client_sockfd;//客戶端套接字文件描述符
    char rw_buffer[1500];//讀寫緩沖區(qū)
    int flag;
    int recv_len = 0;//接受到的數(shù)據(jù)的長度
    int i;
    bzero(rw_buffer , sizeof(rw_buffer));//清空讀寫緩沖區(qū)
    server_sockfd = SOCKET_NET_CREATE("192.168.79.128" , 6060);//進(jìn)行網(wǎng)絡(luò)初始化
    //初始化epoll結(jié)構(gòu)體
    node.data.fd = server_sockfd;
    node.events = EPOLLIN;//監(jiān)聽讀事件
    //初始化監(jiān)聽樹,并做錯誤處理
    if((epfd = epoll_create(MAX_EPOLL)) == -1)
    {
    	perror("epoll_create call failed\n");
    	exit(-1);
    }
    //向監(jiān)聽樹中添加服務(wù)器套接字結(jié)構(gòu)體,并做錯誤處理
    if((epoll_ctl(epfd , EPOLL_CTL_ADD , server_sockfd , &node)) == -1)
    {
    	perror("epoll_ctl call failed\n");
    	exit(-1);
    }
    printf("epoll_server wait TCP connect\n");
    while(1)
    {
    	//獲取處于就緒狀態(tài)的套接字?jǐn)?shù)量
        if((ready_num = epoll_wait(epfd , ready_array , MAX_EPOLL , -1)) == -1)
        {
            perror("epoll_wait call failed\n");
            exit(0);
        }
        i = 0;
        while(ready_num)
        {
            //辨別就緒,如果是服務(wù)端套接字就緒
            if(ready_array[i].data.fd == server_sockfd)
            {
                client_sockfd = SERVER_ACCEPTING(ready_array[i].data.fd);//與客戶端建立TCP鏈接
                node.data.fd = client_sockfd;
                if(epoll_ctl(epfd , EPOLL_CTL_ADD , client_sockfd , &node) == -1)
                {
                	perror("epoll_ctl failed\n");
                	exit(-1);
                }
            }
            //如果是客戶端套接字就緒
            else
            {  
                recv_len = RECV(ready_array[i].data.fd , rw_buffer , sizeof(rw_buffer) , 0);
                flag = 0;
                //如果recv_len = 0,就說明與客戶端套接字對應(yīng)的客戶端退出了,將對應(yīng)客戶端套接字移出套接字存儲數(shù)組與監(jiān)聽集合
                if(recv_len == 0)
                {
                    perror("某一客戶端與本服務(wù)器斷開鏈接\n");
                    printf("客戶端%d 與本服務(wù)器斷開鏈接,關(guān)閉其對應(yīng)的套接字并停止監(jiān)聽\n" , ready_array[i].data.fd);
                    //關(guān)閉該套接字
                    close(ready_array[i].data.fd);
                    //將其對應(yīng)的節(jié)點從監(jiān)聽樹上摘下來
                    epoll_ctl(epfd , EPOLL_CTL_DEL , ready_array[i].data.fd , NULL);
                    //注意,這個兩個步驟不能搞反
                }
                //進(jìn)行業(yè)務(wù)處理:小寫字母轉(zhuǎn)大寫字母
                printf("服務(wù)器已接收到客戶端%d 發(fā)來的信息 : %s,現(xiàn)在對其進(jìn)行處理\n" , ready_array[i].data.fd , rw_buffer);
                while(recv_len > flag)
                {
                    rw_buffer[flag] = toupper(rw_buffer[flag]);
                    flag++;
                }
                printf("服務(wù)器已對客戶端%d 發(fā)來的信息完成處理,處理后的數(shù)據(jù)為 : %s\n" , ready_array[i].data.fd , rw_buffer);
                SEND(ready_array[i].data.fd , rw_buffer , recv_len , MSG_NOSIGNAL);
                bzero(rw_buffer , sizeof(rw_buffer));
                recv_len = 0;
            }
            ready_num--;
            i++;
        }
    }
    close(server_sockfd);
    printf("server shutdown\n");
    return 0;
}


/*************************************************************************
        > File Name: client.c
        > Author: Nan
        > Mail: **@qq.com
        > Created Time: 2023年10月19日 星期四 18時29分12秒
 ************************************************************************/
 
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/stat.h>
#include <signal.h>
#include <pthread.h>
#include <fcntl.h>
#include <sys/socket.h>
#include <netdb.h>
#include <arpa/inet.h>
#include <time.h>
 
//服務(wù)器實現(xiàn)大小寫轉(zhuǎn)換業(yè)務(wù)
 
int main()
{
    //1.定義網(wǎng)絡(luò)信息結(jié)構(gòu)體與讀寫緩沖區(qū)并初始化
    struct sockaddr_in dest_addr;
    char buffer[1500];
    bzero(&dest_addr , sizeof(dest_addr));
    bzero(buffer , sizeof(buffer));
    dest_addr.sin_family = AF_INET;
    dest_addr.sin_port = htons(6060);
    //字符串ip轉(zhuǎn)大端序列
    inet_pton(AF_INET , "192.168.79.128" , &dest_addr.sin_addr.s_addr);
    int sockfd = socket(AF_INET , SOCK_STREAM , 0);
    int i;
    //2.判斷連接是否成功
    if((connect(sockfd , (struct sockaddr*) &dest_addr , sizeof(dest_addr))) == -1)
    {
        perror("connect failed!\n");
        exit(0);
    }
    recv(sockfd , buffer , sizeof(buffer) , 0);
    printf("%s" , buffer);
    bzero(buffer , sizeof(buffer));
    //3.循環(huán)讀取終端輸入的數(shù)據(jù)
    while( (fgets(buffer , sizeof(buffer) , stdin) ) != NULL)
    {
        i = strlen(buffer);
        buffer[i-1] = '\0';
        //向服務(wù)端發(fā)送消息
        send(sockfd , buffer , strlen(buffer) , MSG_NOSIGNAL);
        //接收服務(wù)端發(fā)來的消息
        recv(sockfd , buffer , sizeof(buffer) , 0);
        //打印服務(wù)端發(fā)來的信息
        printf("response : %s\n" , buffer);
        //清空讀寫緩沖區(qū),以便下一次放入數(shù)據(jù)
        bzero(buffer , sizeof(buffer));
    }
    //4.關(guān)閉套接字,斷開連接
    close(sockfd);
    return 0;
}


結(jié)果圖示

Linux,多路IO復(fù)用技術(shù),epoll,Linux多路IO復(fù)用技術(shù)③——epoll詳解&如何使用epoll模型實現(xiàn)簡易的一對多服務(wù)器(附圖解與代碼實現(xiàn))

EPOLL的兩種監(jiān)聽模式

1.EPOLLLT水平觸發(fā)模式,也被稱之為負(fù)責(zé)模式,如果不進(jìn)行特殊設(shè)置,默認(rèn)情況下對套接字都是以此模式監(jiān)聽。

在此模式下,當(dāng)epoll監(jiān)聽到某一套接字就緒時,就會先發(fā)送處理通知給上層。之后每隔一段時間就來查看該套接字緩沖區(qū)中的數(shù)據(jù)有沒有被處理完。

如果沒有處理完,就會繼續(xù)發(fā)送處理通知給上層,一天不處理完就發(fā)一天,一周處理不完就發(fā)一周,直到緩沖區(qū)中的數(shù)據(jù)被全部處理完畢。這就是所說的負(fù)責(zé)模式

PS:在此模式下,如果緩沖區(qū)中的數(shù)據(jù)沒有被處理完,epoll_wait()無法開啟下一輪監(jiān)聽。意思就是如果數(shù)據(jù)沒處理完,就去調(diào)用epoll_wait()函數(shù)的話,會立即返回,返回值為未處理的就緒事件數(shù)量

優(yōu)點:能夠保證套接字緩沖區(qū)中的所有數(shù)據(jù)被處理完畢

缺點:開銷比較大

適用場景:該模式適合處理有效期較短、或需要被緊急處理的數(shù)據(jù)

2.EPOLLET,邊緣觸發(fā)模式,也被稱之為不負(fù)責(zé)模式,使用該模式需要進(jìn)行手動設(shè)置

在此模式下,當(dāng)epoll監(jiān)聽到某一套接字就緒時,指揮發(fā)送一次處理通知給上層。之后無論該套接字緩沖區(qū)中的數(shù)據(jù)有沒有被處理完,都與epoll無關(guān),epoll可以立即進(jìn)入新一輪的監(jiān)聽

優(yōu)點:開銷比較小

缺點:存在隱患,不能夠保證套接字緩沖區(qū)中的所有數(shù)據(jù)被處理(完畢),用戶要自行保證數(shù)據(jù)讀取完畢

PS:邊緣觸發(fā)模式一般結(jié)合非阻塞讀取套接字緩沖區(qū)的數(shù)據(jù)

想要設(shè)置邊緣觸發(fā)模式也很簡單,按照下面的步驟來就可以了

int sockfd;//套接字文件描述符

struct epoll_event node;
node.data.fd = sockfd;
node.events = EPOLLIN|EPOLLET;//監(jiān)聽讀事件,監(jiān)聽模式切換為邊緣觸發(fā)模式


以上就是本篇博客的全部內(nèi)容了,大家有什么地方?jīng)]有看懂的話,可以在評論區(qū)留言給我,咱要力所能及的話就幫大家解答解答

今天的學(xué)習(xí)記錄到此結(jié)束啦,咱們下篇文章見,ByeBye!

Linux,多路IO復(fù)用技術(shù),epoll,Linux多路IO復(fù)用技術(shù)③——epoll詳解&如何使用epoll模型實現(xiàn)簡易的一對多服務(wù)器(附圖解與代碼實現(xiàn))文章來源地址http://www.zghlxwxcb.cn/news/detail-744045.html

到了這里,關(guān)于Linux多路IO復(fù)用技術(shù)——epoll詳解與一對多服務(wù)器實現(xiàn)的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!

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

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

相關(guān)文章

  • 驅(qū)動開發(fā),IO多路復(fù)用實現(xiàn)過程,epoll方式

    驅(qū)動開發(fā),IO多路復(fù)用實現(xiàn)過程,epoll方式

    被稱為當(dāng)前時代最好用的io多路復(fù)用方式; 核心操作:一棵樹(紅黑樹)、一張表(內(nèi)核鏈表)以及三個接口; ?思想:(fd代表文件描述符) ????????epoll要把檢測的事件fd掛載到內(nèi)核空間紅黑樹上,遍歷紅黑樹,調(diào)用每個fd對應(yīng)的操作方法,找到發(fā)生事件的fd,如果沒有發(fā)

    2024年02月07日
    瀏覽(30)
  • 【Linux】IO多路轉(zhuǎn)接技術(shù)Epoll的使用

    【Linux】IO多路轉(zhuǎn)接技術(shù)Epoll的使用

    ? 在學(xué)習(xí) epoll 之前,我們首先了解一下Linux中的多路復(fù)用技術(shù): 在Linux系統(tǒng)中, IO多路復(fù)用 是一種重要的技術(shù),它允許一個進(jìn)程同時監(jiān)視多個文件描述符,一旦某個描述符準(zhǔn)備好進(jìn)行讀取(通常是讀就緒或?qū)懢途w),內(nèi)核會通知該進(jìn)程進(jìn)行相應(yīng)的讀寫操作。這樣,我們可以

    2024年04月27日
    瀏覽(15)
  • linux并發(fā)服務(wù)器 —— IO多路復(fù)用(八)

    linux并發(fā)服務(wù)器 —— IO多路復(fù)用(八)

    半關(guān)閉只能實現(xiàn)數(shù)據(jù)單方向的傳輸;當(dāng)TCP 接中A向 B 發(fā)送 FIN 請求關(guān)閉,另一端 B 回應(yīng)ACK 之后 (A 端進(jìn)入 FIN_WAIT_2 狀態(tài)),并沒有立即發(fā)送 FIN 給 A,A 方處于半連接狀態(tài) (半開關(guān)),此時 A 可以接收 B 發(fā)送的數(shù)據(jù),但是 A 已經(jīng)不能再向 B 發(fā)送數(shù)據(jù) close不會影響到其他進(jìn)程,shutdown會

    2024年02月09日
    瀏覽(19)
  • 網(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? ? ?客戶端 通過IO多路復(fù)用實現(xiàn)服務(wù)器在單進(jìn)程單線程下可以與多個客戶端交互 ?API epoll函數(shù) ?head.h TcpGrpSer.c TcpGrpUsr.c ?

    2024年02月11日
    瀏覽(23)
  • epoll多路復(fù)用_并發(fā)服務(wù)器

    應(yīng)用程序: 驅(qū)動程序:

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

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

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

    2024年02月03日
    瀏覽(98)
  • 【高并發(fā)網(wǎng)絡(luò)通信架構(gòu)】引入IO多路復(fù)用(select,poll,epoll)實現(xiàn)高并發(fā)tcp服務(wù)端

    【高并發(fā)網(wǎng)絡(luò)通信架構(gòu)】引入IO多路復(fù)用(select,poll,epoll)實現(xiàn)高并發(fā)tcp服務(wù)端

    目錄 一,往期文章 二,基本概念 IO多路復(fù)用 select 模型 poll 模型 epoll 模型 select,poll,epoll 三者對比 三,函數(shù)清單 1.select 方法 2.fd_set 結(jié)構(gòu)體 3.poll 方法 4.struct pollfd 結(jié)構(gòu)體 5.epoll_create 方法 6.epoll_ctl 方法 7.epoll_wait 方法 8.struct epoll_event 結(jié)構(gòu)體 四,代碼實現(xiàn) select 操作流程 s

    2024年02月12日
    瀏覽(31)
  • 【高并發(fā)網(wǎng)絡(luò)通信架構(gòu)】3.引入IO多路復(fù)用(select,poll,epoll)實現(xiàn)高并發(fā)tcp服務(wù)端

    【高并發(fā)網(wǎng)絡(luò)通信架構(gòu)】3.引入IO多路復(fù)用(select,poll,epoll)實現(xiàn)高并發(fā)tcp服務(wù)端

    目錄 一,往期文章 二,基本概念 IO多路復(fù)用 select 模型 poll 模型 epoll 模型 select,poll,epoll 三者對比 三,函數(shù)清單 1.select 方法 2.fd_set 結(jié)構(gòu)體 3.poll 方法 4.struct pollfd 結(jié)構(gòu)體 5.epoll_create 方法 6.epoll_ctl 方法 7.epoll_wait 方法 8.struct epoll_event 結(jié)構(gòu)體 四,代碼實現(xiàn) select 操作流程 s

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

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

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

    2024年02月06日
    瀏覽(17)
  • IO多路復(fù)用詳解

    IO多路復(fù)用詳解

    在IO多路復(fù)用模型中,引入了一種新的系統(tǒng)調(diào)用,查詢IO的就緒狀態(tài)。在Linux系統(tǒng)中,對應(yīng)的系統(tǒng)調(diào)用為select/poll/epoll系統(tǒng)調(diào)用。通過該系統(tǒng)調(diào)用,一個進(jìn)程可以監(jiān)視多個文件描述符,一旦某個描述符就緒(一般是內(nèi)核緩沖區(qū)可讀/可寫),內(nèi)核能夠?qū)⒕途w的狀態(tài)返回給應(yīng)用程序

    2024年02月08日
    瀏覽(19)

覺得文章有用就打賞一下文章作者

支付寶掃一掃打賞

博客贊助

微信掃一掃打賞

請作者喝杯咖啡吧~博客贊助

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

二維碼1

領(lǐng)取紅包

二維碼2

領(lǐng)紅包