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

02-Linux-IO多路復(fù)用之select、poll和epoll詳解

這篇具有很好參考價值的文章主要介紹了02-Linux-IO多路復(fù)用之select、poll和epoll詳解。希望對大家有所幫助。如果存在錯誤或未考慮完全的地方,請大家不吝賜教,您也可以點擊"舉報違法"按鈕提交疑問。

前言:

在linux系統(tǒng)中,實際上所有的 I/O 設(shè)備都被抽象為了文件這個概念,一切皆文件,磁盤、網(wǎng)絡(luò)數(shù)據(jù)、終端,甚至進程間通信工具管道 pipe 等都被當做文件對待。

在了解多路復(fù)用 select、poll、epoll 實現(xiàn)之前,我們先簡單回憶復(fù)習(xí)以下兩個概念:

一、什么是多路復(fù)用:

  • 多路:多個 socket 網(wǎng)絡(luò)連接。
  • 復(fù)用:復(fù)用一個線程,使用一個線程來檢查多個文件描述符(socket)的就緒狀態(tài)。
  • 多路復(fù)用主要有三種技術(shù):select、poll、epoll。epoll 是最新的,也是目前最好的多路復(fù)用技術(shù)。

二、五種 I/O 模型

+ blocking I/O      - 阻塞I/O
+ non-blocking I/O  - 非阻塞I/O
+ signal-driven I/O - 信號驅(qū)動I/O
+ asynchronous I/O  - 異步I/O
+ I/O multiplexing  - I/O多路復(fù)用
1. 阻塞 I/O 模型

進程/線程在從調(diào)用 recvfrom() 開始到它返回的整段時間內(nèi)是被阻塞的,recvfrom() 成功返回后,應(yīng)用進程/線程開始處理數(shù)據(jù)報。
02-Linux-IO多路復(fù)用之select、poll和epoll詳解,Linux,linux,服務(wù)器,運維

recvfrom() 是一個系統(tǒng)調(diào)用函數(shù),用于從一個已連接或未連接的套接字(socket)接收數(shù)據(jù)。函數(shù)原型如下:

ssize_t recvfrom(int sockfd,					//套接字文件描述符
				 void *buf,						//指向接收數(shù)據(jù)的緩沖區(qū)
				 size_t len,					//緩沖區(qū)的大小
				 int flags,						//可選的標志參數(shù),用于控制接收操作的行為
				 struct sockaddr *src_addr,		//用于存儲發(fā)送方的地址信息
				 socklen_t *addrlen				//src_addr 的長度
				 );

recvfrom() 在調(diào)用時會阻塞,直到有數(shù)據(jù)到達或發(fā)生錯誤。當有數(shù)據(jù)到達時,它將數(shù)據(jù)讀取到指定的緩沖區(qū)中,并填充發(fā)送方的地址信息到 src_addr 參數(shù)中。如果套接字是已連接的,src_addraddrlen 參數(shù)可以設(shè)置為 NULL。

  • 主要特點
    • 進程阻塞掛起不消耗CPU資源,能及時響應(yīng)每個操作。
    • 實現(xiàn)難度低。
    • 適用并發(fā)量小的網(wǎng)絡(luò)應(yīng)用開發(fā),不適用并發(fā)量大的應(yīng)用。因為一個請求IO會阻塞進程,所以每請求分配一個處理進程(線程)去響應(yīng),系統(tǒng)開銷大。
2. 非阻塞 I/O 模型

進程發(fā)起 I/O 系統(tǒng)調(diào)用后,如果內(nèi)核緩沖區(qū)沒有數(shù)據(jù),需要到 I/O 設(shè)備中讀取,進程返回一個錯誤而不會被阻塞;如果內(nèi)核緩沖區(qū)有數(shù)據(jù),內(nèi)核就會把數(shù)據(jù)返回進程。
02-Linux-IO多路復(fù)用之select、poll和epoll詳解,Linux,linux,服務(wù)器,運維

  • 主要特點
    • 進程輪詢(重復(fù))調(diào)用,消耗CPU的資源。
    • 實現(xiàn)難度低、開發(fā)應(yīng)用相對阻塞IO模式較難。
    • 適用并發(fā)量較小、且不需要及時響應(yīng)的網(wǎng)絡(luò)應(yīng)用開發(fā)。
3. 信號驅(qū)動 I/O 模型

當進程發(fā)起一個 I/O 操作,會向內(nèi)核注冊一個信號處理函數(shù),然后進程返回不阻塞;當內(nèi)核數(shù)據(jù)就緒時會發(fā)送一個信號給進程,進程便在信號處理函數(shù)中調(diào)用 I/O 讀取數(shù)據(jù)。與阻塞式 I/O 或非阻塞式 I/O 模型不同,信號驅(qū)動 I/O 模型允許應(yīng)用程序在進行 I/O 操作時繼續(xù)執(zhí)行其他任務(wù),而不需要顯式地輪詢或阻塞等待 I/O 操作的完成。
02-Linux-IO多路復(fù)用之select、poll和epoll詳解,Linux,linux,服務(wù)器,運維

  • 工作流程

    • 應(yīng)用程序通過調(diào)用 sigaction() 來注冊一個 信號處理函數(shù)(Signal Handler),用于處理特定的 I/O 相關(guān)信號,如 SIGIO。
    • I/O 文件描述符 設(shè)置為信號驅(qū)動模式,通常使用 fcntl() 并設(shè)置 F_SETOWN 標志,將文件描述符的擁有者設(shè)置為當前進程。這樣,當 I/O 事件發(fā)生時,內(nèi)核將向該進程發(fā)送相應(yīng)的信號。
    • I/O 事件(如數(shù)據(jù)到達)發(fā)生時,操作系統(tǒng)將為相應(yīng)的文件描述符生成一個信號(通常是 SIGIO),并將其發(fā)送給擁有者進程。
    • 擁有者進程接收到信號后,會調(diào)用事先注冊的信號處理函數(shù)進行相應(yīng)的處理。在信號處理函數(shù)中,可以執(zhí)行讀取數(shù)據(jù)、寫入數(shù)據(jù)等操作。
    • 信號處理函數(shù)執(zhí)行完畢后,應(yīng)用程序可以繼續(xù)執(zhí)行其他任務(wù),而不需要顯式地等待 I/O 操作的完成。
  • 主要特點

    • 實現(xiàn)、開發(fā)應(yīng)用難度大。需要合理處理信號處理函數(shù)和信號同步的問題。
    • 適用于需要同時處理多個 I/O 事件的情況,如網(wǎng)絡(luò)編程中的異步處理。
4. 異步 I/O 模型

當進程發(fā)起一個 I/O 操作,進程返回(不阻塞),但也不能返回結(jié)果;內(nèi)核把整個 I/O 處理完后,會通知進程結(jié)果。如果 I/O 操作成功則進程直接獲取到數(shù)據(jù)。
02-Linux-IO多路復(fù)用之select、poll和epoll詳解,Linux,linux,服務(wù)器,運維

  • 工作原理

    • 應(yīng)用程序調(diào)用系統(tǒng)調(diào)用函數(shù)(如 aio_read、aio_write 等)發(fā)起異步 I/O 操作。這些函數(shù)通常是系統(tǒng)提供的異步 I/O 接口函數(shù)。
    • 在發(fā)起異步 I/O 操作時,應(yīng)用程序還需要提供一個 回調(diào)函數(shù),該函數(shù)將在 I/O 操作完成時被調(diào)用。
    • 異步 I/O 操作被提交給操作系統(tǒng)或 I/O 子系統(tǒng)進行處理。操作系統(tǒng)將負責(zé)執(zhí)行實際的 I/O 操作,并在操作完成后觸發(fā)相應(yīng)的事件。
    • I/O 操作完成時,操作系統(tǒng)將調(diào)用之前注冊的回調(diào)函數(shù),并將操作的結(jié)果傳遞給回調(diào)函數(shù)。
    • 在回調(diào)函數(shù)中,應(yīng)用程序可以處理操作的結(jié)果,例如讀取已接收的數(shù)據(jù)、處理錯誤情況等。
    • 應(yīng)用程序可以繼續(xù)執(zhí)行其它任務(wù),而無需等待 I/O 操作的完成。
  • 主要特點

    • 不阻塞,數(shù)據(jù)一步到位。
    • 需要操作系統(tǒng)的底層支持,LINUX 2.5 版本內(nèi)核首現(xiàn),2.6 版本產(chǎn)品的內(nèi)核標準特性。
    • 實現(xiàn)、開發(fā)應(yīng)用難度大。
    • 非常適合高性能高并發(fā)應(yīng)用。
5. I/O 復(fù)用模型

大多數(shù)文件系統(tǒng)的默認 I/O 操作都是緩存 I/O。在 Linux 的緩存 I/O 機制中,操作系統(tǒng)會將 I/O 的數(shù)據(jù)緩存在文件系統(tǒng)的頁緩存(page cache)。也就是說,數(shù)據(jù)會先被拷貝到操作系統(tǒng)內(nèi)核的緩沖區(qū)中,然后才會從操作系統(tǒng)內(nèi)核的緩存區(qū)拷貝到應(yīng)用程序的地址空間中。這種做法的缺點就是,需要在應(yīng)用程序地址空間和內(nèi)核進行多次拷貝,這些拷貝動作所帶來的CPU以及內(nèi)存開銷是非常大的。

至于為什么不能直接讓磁盤控制器把數(shù)據(jù)送到應(yīng)用程序的地址空間中呢?最簡單的一個原因就是應(yīng)用程序不能直接操作底層硬件
02-Linux-IO多路復(fù)用之select、poll和epoll詳解,Linux,linux,服務(wù)器,運維
總的來說,IO分兩階段:
1)數(shù)據(jù)準備階段
2)內(nèi)核空間復(fù)制回用戶進程緩沖區(qū)階段。如下圖:
02-Linux-IO多路復(fù)用之select、poll和epoll詳解,Linux,linux,服務(wù)器,運維

  • 工作原理

    • 應(yīng)用程序通過將多個 I/O 流(如套接字)注冊到 I/O 復(fù)用機制中,以便對這些流的狀態(tài)進行監(jiān)視。
    • 當應(yīng)用程序調(diào)用 I/O 復(fù)用機制的函數(shù)(如 selectpollepoll)時,它會被阻塞,直到至少一個注冊的 I/O 滿足指定的條件(如可讀、可寫等)。
    • 當有流滿足指定條件時,I/O 復(fù)用機制并返回,并通知應(yīng)用程序哪些流滿足條件。
    • 應(yīng)用程序可以遍歷返回的流集合,檢查每個流的狀態(tài),并進行相應(yīng)的處理。通常,應(yīng)用程序會使用條件判斷語句來確定流是可讀還是可寫,并執(zhí)行相應(yīng)的讀取或?qū)懭氩僮鳌?/li>
    • 應(yīng)用程序可以繼續(xù)執(zhí)行其他任務(wù)或再次調(diào)用 I/O 復(fù)用機制的函數(shù),以便繼續(xù)監(jiān)視 I/O 流的狀態(tài)變化。
  • 主要特點

    • 使用單個系統(tǒng)調(diào)用來監(jiān)視多個 I/O 流的狀態(tài),而不是針對每個流進行阻塞式的等待。
    • 有效減少系統(tǒng)調(diào)用次數(shù)和上下文切換開銷。
    • 提高應(yīng)用程序的并發(fā)性和響應(yīng)性。

三、I/O 多路復(fù)用之select、poll、epoll詳解

目前支持 I/O 多路復(fù)用的系統(tǒng)調(diào)用有 select,pselect,poll,epoll。與多進程和多線程技術(shù)相比,I/O 多路復(fù)用技術(shù)的最大優(yōu)勢是系統(tǒng)開銷小,系統(tǒng)不必創(chuàng)建進程/線程,也不必維護這些進程/線程,從而大大減小了系統(tǒng)的開銷。

I/O 多路復(fù)用就是通過一種機制,一個進程可以監(jiān)視多個描述符,一旦某個描述符就緒(一般是讀就緒或者寫就緒),能夠通知程序進行相應(yīng)的讀寫操作。但 select,poll,epoll 本質(zhì)上都是 同步I/O,因為他們都需要在讀寫事件就緒后自己負責(zé)進行讀寫,也就是說這個讀寫過程是阻塞的,而 異步I/O 則無需自己負責(zé)進行讀寫,異步I/O的實現(xiàn)會負責(zé)把數(shù)據(jù)從內(nèi)核拷貝到用戶空間。

select

select函數(shù) 監(jiān)視的文件描述符分3類,分別是 writefds、readfdsexceptfds。

  • 當用戶線程調(diào)用 select 的時候,select 將需要監(jiān)控的 readfds集合 拷貝到內(nèi)核空間(假設(shè)監(jiān)控的僅僅是 socket可讀)。
  • 然后遍歷自己監(jiān)控的 skb(SocketBuffer),挨個調(diào)用 skbpoll 邏輯以便檢查該 socket 是否有可讀事件,遍歷完所有的 skb 后。
  • 如果沒有任何一個 socket 可讀,那么 select 會調(diào)用schedule_timeout 進入 schedule 循環(huán),使得線程進入睡眠。如果在 timeout 時間內(nèi)某個 socket 上有數(shù)據(jù)可讀了,或者等待timeout 了,則調(diào)用 select 的線程會被喚醒,接下來 select 就是遍歷監(jiān)控的集合,挨個收集可讀事件并返回給用戶了。

相應(yīng)的偽碼如下:

int select(
			int nfds,					//監(jiān)控的文件描述符集里最大文件描述符+1
			fd_set *readfds,			//監(jiān)控讀數(shù)據(jù)到達文件描述符集合
			fd_set *writefds,			//監(jiān)控寫數(shù)據(jù)到達文件描述符集合
			fd_set *exceptfds,			//監(jiān)控異常發(fā)生到達文件描述符集合
			struct timeval *timeout		//定時阻塞監(jiān)控時間,3種情況:1.NULL,永遠等下去。
										//						 2.設(shè)置timeval,等待固定時間
										//						 3.設(shè)置timeval里時間均為0,檢查描述字后立即返回,輪詢
);

//----------------select服務(wù)端偽碼---------------------
//首先一個線程不斷接受客戶端連接,并把socket文件描述符放到一個list里
while(1){
	connfd = accept(listenfd);
	fcntl(connfd, F_SETFL, O_NONBLOCK);
	fdlist.add(connfd);
}
/*
select函數(shù)還是返回剛剛提交的list,應(yīng)用程序依然列出所有的fd,只不過操作系統(tǒng)會將準備就緒的文件描述符做上標識,
用戶層將不會再有無意義的系統(tǒng)調(diào)用開銷。
*/
struct timeval timeout;
int max = 0;					//用于記錄最大的fd,在輪詢中時刻更新即可
//初始化比特位
FD_ZERO(&read_fd);
while(1){
	//阻塞獲取,每次需要把fd從用戶態(tài)拷貝到內(nèi)核態(tài)
	nfds = select(max + 1, &read_fd, &write_fd, NULL, &timeout);
	//每次需要遍歷所有fd,判斷有無讀寫事件發(fā)生
	for(int i = 0; i <= max && nfds; ++i){
		//只讀已就緒的文件描述符,不用過多遍歷
		if(i == listenfd){
			//這里處理accept事件
			FD_SET(i, &read_fd);	//將客戶端socket加入到集合中
		}
		if(FD_ISSET(i, &read_fd)){
			//這里處理read事件
		}
	}
}

下面是 select 工作原理的動圖:

select工作圖

通過上面的select邏輯過程分析,相信大家都意識到,select存在三個問題:

  • 每次調(diào)用 select,都需要把被監(jiān)控的 fds 集合從用戶態(tài)空間拷貝到內(nèi)核態(tài)空間,高并發(fā)場景下這樣的拷貝會使得消耗的資源是很大的。
  • 能監(jiān)聽端口的數(shù)量有限,單個進程所能打開的最大連接數(shù)由 FD_SETSIZE 宏定義,監(jiān)聽上限就等于 fds_bits 位數(shù)組中所有元素的二進制位總數(shù),其大小是32個整數(shù)的大?。ㄔ?2位的機器上,大小就是32,同理64位機器上為64),當然我們可以對宏 FD_SETSIZE 進行修改,然后重新編譯內(nèi)核,但是性能可能會受到影響,一般該數(shù)和系統(tǒng)內(nèi)存關(guān)系很大,具體數(shù)目可以 cat /proc/sys/fs/file-max 察看。32位機默認1024個,64位默認2048。
  • 被監(jiān)控的 fds集合 中,只要有一個有數(shù)據(jù)可讀,整個 socket集合 就會被遍歷一次調(diào)用 skpoll 函數(shù)收集可讀事件:由于僅關(guān)心是否有數(shù)據(jù)可讀這樣一個事件,數(shù)據(jù)的到來是異步的,于是,只能挨個遍歷每個socket來收集可讀事件了。

poll

poll 的實現(xiàn)和 select 非常相似,只是描述 fd 集合的方式不同。針對 select 遺留的三個問題中(問題(2)是fd限制問題,問題(1)和(3)則是性能問題),poll 使用 pollfd結(jié)構(gòu) 而不是 selectfd_set結(jié)構(gòu),這就解決了 select 的問題(2)fds集合大小限制問題。
pollselect 同樣存在一個性能缺點就是包含大量文件描述符的數(shù)組被整體復(fù)制于用戶態(tài)和內(nèi)核的地址空間之間,而不論這些文件描述符是否就緒,它的開銷隨著文件描述符數(shù)量的增加而線性增大。

下面是 poll 的函數(shù)原型:

int poll(struct pollfd *ufds, unsigned int nfds, int timeout);
struct pollfd{
	int fd;					//文件描述符
	short events;			//監(jiān)控的事件
	short revents;			//監(jiān)控事件中滿足條件返回的事件
};

//-----------------poll服務(wù)端實現(xiàn)偽碼---------------------
struct pollfd fds[POLL_LEN];
unsigned int nfds = 0;
fds[0].fd = server_sockfd;
fds[0].events = POLLIN | POLLPRI;
nfds++;
while(1){
	res = poll(fds, nfds, -1);
	if(fds[0].revents & (POLLIN | POLLPRI)){
		//執(zhí)行accept并加入fds中,nfds++
		if(--res <= 0)	continue;
	}
	//循環(huán)之后的fds
	if(fds[i].revents & (POLLIN | POLLERR)){
		//讀操作或處理異常等
		if(--res <= 0)	continue;
	}
}

poll 相比于 select 的優(yōu)點:使用了 pollfd結(jié)構(gòu),使得 poll 支持的 fds 集合限制遠大于 select 的1024。

由于 poll 基于鏈表存儲,無最大連接數(shù)限制,所以有如下缺點:(1)大量描述符數(shù)組被整體復(fù)制于用戶態(tài)和內(nèi)核態(tài)的地址空間之間,以及個別描述符就緒觸發(fā)整體描述符集合的遍歷的低效問題。(2)poll 隨著監(jiān)控的 socket 集合的增加性能線性下降,使得 poll 也并不適合用于大并發(fā)場景。(3)若報告了 fd 后未被處理,下次 poll 時會再次報告該 fd。

epoll(基于Linux2.4.5)

epoll 模型將主動輪詢改為被動通知,當有事件發(fā)生時,被動接收通知。所以 epoll 模型注冊套接字后,主程序可做其它事情,當事件發(fā)生時,接收到通知后再去處理??衫斫鉃?strong>event poll,epoll 會把哪個流發(fā)生哪種 I/O 事件通知我們。所以 epoll 是事件驅(qū)動(每個事件關(guān)聯(lián) fd),此時我們對這些流的操作都是有意義的,復(fù)雜度也降到 O ( 1 ) O(1) O(1)。

創(chuàng)建一個 epoll 的句柄,size 表明要監(jiān)聽的 fd 數(shù)目。這個參數(shù)不同于 select() 中的第一個參數(shù),給出最大監(jiān)聽的fd+1的值。需要注意的是,當創(chuàng)建好 epoll 句柄后,它就是會占用一個 fd 值,在 linux 下如果查看 /proc/進程id/fd/,是能夠看到這個fd的,所以在使用完 epoll 后,必須調(diào)用 close() 關(guān)閉,否則可能導(dǎo)致 fd 被耗盡。

epoll 的接口非常簡單,一共就三個函數(shù):

  • epoll_create:創(chuàng)建一個 epoll 句柄
  • epoll_ctl:向 epoll 對象中添加/修改/刪除要管理的連接
  • epoll_wait:等待其管理的連接時的 I/O 事件
epoll_create 函數(shù)
int epoll_create(int size);
  • 功能:生成一個 epoll 專用的文件描述符。
  • 參數(shù) size:表明內(nèi)核監(jiān)聽的文件描述符數(shù)目。并不是限制 epoll 所能監(jiān)聽的描述符最大個數(shù),只是對內(nèi)核初始分配內(nèi)部數(shù)據(jù)結(jié)構(gòu)的一個建議。自從 linux 2.6.8 后,size 參數(shù)可以填大于0的任意值。
  • 返回值:成功則返回 epoll 專用的文件描述符,失敗則返回 -1。

epoll_create 的源碼實現(xiàn):

asmlinkage int sys_epoll_create(int maxfds){
	int error = -EINVAL, fd;
	unsigned long addr;
	struct inode *inode;
	struct file *file;
	struct eventpoll *ep;

    //eventpoll接口中不可能存儲超過MAX_FDS_IN_EVENTPOLL的fd
    if (maxfds > MAX_FDS_IN_EVENTPOLL)
    	goto eexit_1;
?
    /*
     * Creates all the items needed to setup an eventpoll file. That is,
     * a file structure, and inode and a free file descriptor.
     */
    error = ep_getfd(&fd, &inode, &file);
    if (error)
    	goto eexit_1;
?
    /*
     * 調(diào)用去初始化eventpoll file. 這和"open" file operation callback一樣,因為 inside
     * ep_getfd() we did what the kernel usually does before invoking
     * corresponding file "open" callback.
     */
    error = open_eventpoll(inode, file);
    if (error)
    	goto eexit_2;
?
    /* "private_data" 由open_eventpoll()設(shè)置 */
    ep = file->private_data;
?
    /* 分配頁給event double buffer */
    error = ep_do_alloc_pages(ep, EP_FDS_PAGES(maxfds + 1));
    if (error)
    	goto eexit_2;
?
    //創(chuàng)建event double buffer的一個用戶空間的映射,以避免當返回events給調(diào)用者時,內(nèi)核到用戶空間的內(nèi)存復(fù)制
    down_write(&current->mm->mmap_sem);
    addr = do_mmap_pgoff(file, 0, EP_MAP_SIZE(maxfds + 1), PROT_READ,
          				 MAP_PRIVATE, 0);
    up_write(&current->mm->mmap_sem);
    error = PTR_ERR((void *) addr);
    if (IS_ERR((void *) addr))
   		goto eexit_2;
?
    return fd;
?
eexit_2:
  sys_close(fd);
eexit_1:
  return error;
}
epoll_ctl

每次注冊新事件到 epoll 句柄中時(在 epoll_ctl 中指定 EPOLL_CTL_ADD),會把所有 fd 拷貝進內(nèi)核,而非在 epoll_wait 時重復(fù)拷貝。epoll 保證每個 fd 在整個過程中只會拷貝一次。

//成功則返回0,失敗則返回-1
int epoll_ctl(int epfd,					//epoll專用的文件描述符,epoll_create的返回值
			  int op,					//表示動作,用三個宏來表示:1.EPOLL_CTL_ADD:注冊新的fd到epfd中
			  							//					    2.EPOLL_CTL_MOD:修改已注冊的fd的監(jiān)聽事件
			  							//                      3.EPOLL_CTL_DEL:從epfd中刪除一個fd
			  int fd,					//需要監(jiān)聽的文件描述符
			  struct epoll_event *event	//內(nèi)核要監(jiān)聽的事件類型
			  );
epoll_wait
//成功則返回要處理的事件數(shù)目,超時返回0,失敗返回-1
int epoll_wait(int epfd, 					//epoll專用的文件描述符,epoll_create的返回值
			   struct epoll_event * events, //內(nèi)核要監(jiān)聽的事件類型
			   int maxevents, 				//事件個數(shù)
			   int timeout); 				//超時時間,為-1時,函數(shù)為阻塞

epoll 不像 select/poll 每次都把當前文件流加入 fd 對應(yīng)的設(shè)備等待隊列,而只在 epoll_ctl 時把當前文件掛一遍(這一遍必不可少),并為每個 fd 指定一個回調(diào)函數(shù)。

當設(shè)備就緒,喚醒等待隊列上的等待者時,就會調(diào)用該回調(diào)函數(shù),而回調(diào)函數(shù)會把就緒 fd 加入一個就緒鏈表。epoll_wait 實際上就是在該就緒鏈表中查看有無就緒 fd

函數(shù)實現(xiàn)偽代碼如下:

const int MAX_EVENT_NUMBER = 10000; //最大事件數(shù)
// 設(shè)置句柄非阻塞
int setnonblocking(int fd)
{
    int old_option = fcntl(fd, F_GETFL);
    int new_option = old_option | O_NONBLOCK;
    fcntl(fd, F_SETFL, new_option);
    return old_option;
}

int main(){

    // 創(chuàng)建套接字
    int nRet=0;
    int m_listenfd = socket(PF_INET, SOCK_STREAM, 0);
    if(m_listenfd<0)
    {
        printf("fail to socket!");
        return -1;
    }
    // 
    struct sockaddr_in address;
    bzero(&address, sizeof(address));
    address.sin_family = AF_INET;
    address.sin_addr.s_addr = htonl(INADDR_ANY);
    address.sin_port = htons(6666);

    int flag = 1;
    // 設(shè)置ip可重用
    setsockopt(m_listenfd, SOL_SOCKET, SO_REUSEADDR, &flag, sizeof(flag));
    // 綁定端口號
    int ret = bind(m_listenfd, (struct sockaddr *)&address, sizeof(address));
    if(ret<0)
    {
        printf("fail to bind!,errno :%d",errno);
        return ret;
    }

    // 監(jiān)聽連接fd
    ret = listen(m_listenfd, 200);
    if(ret<0)
    {
        printf("fail to listen!,errno :%d",errno);
        return ret;
    }

    // 初始化紅黑樹和事件鏈表結(jié)構(gòu)rdlist結(jié)構(gòu)
    epoll_event events[MAX_EVENT_NUMBER];
    // 創(chuàng)建epoll實例
    int m_epollfd = epoll_create(5);
    if(m_epollfd==-1)
    {
        printf("fail to epoll create!");
        return m_epollfd;
    }



    // 創(chuàng)建節(jié)點結(jié)構(gòu)體將監(jiān)聽連接句柄
    epoll_event event;
    event.data.fd = m_listenfd;
    //設(shè)置該句柄為邊緣觸發(fā)(數(shù)據(jù)沒處理完后續(xù)不會再觸發(fā)事件,水平觸發(fā)是不管數(shù)據(jù)有沒有觸發(fā)都返回事件),
    event.events = EPOLLIN | EPOLLET | EPOLLRDHUP;
    // 添加監(jiān)聽連接句柄作為初始節(jié)點進入紅黑樹結(jié)構(gòu)中,該節(jié)點后續(xù)處理連接的句柄
    epoll_ctl(m_epollfd, EPOLL_CTL_ADD, m_listenfd, &event);

    //進入服務(wù)器循環(huán)
    while(1)
    {
        int number = epoll_wait(m_epollfd, events, MAX_EVENT_NUMBER, -1);
        if (number < 0 && errno != EINTR)
        {
            printf( "epoll failure");
            break;
        }
        for (int i = 0; i < number; i++)
        {
            int sockfd = events[i].data.fd;
            // 屬于處理新到的客戶連接
            if (sockfd == m_listenfd)
            {
                struct sockaddr_in client_address;
                socklen_t client_addrlength = sizeof(client_address);
                int connfd = accept(m_listenfd, (struct sockaddr *)&client_address, &client_addrlength);
                if (connfd < 0)
                {
                    printf("errno is:%d accept error", errno);
                    return false;
                }
                epoll_event event;
                event.data.fd = connfd;
                //設(shè)置該句柄為邊緣觸發(fā)(數(shù)據(jù)沒處理完后續(xù)不會再觸發(fā)事件,水平觸發(fā)是不管數(shù)據(jù)有沒有觸發(fā)都返回事件),
                event.events = EPOLLIN | EPOLLRDHUP;
                // 添加監(jiān)聽連接句柄作為初始節(jié)點進入紅黑樹結(jié)構(gòu)中,該節(jié)點后續(xù)處理連接的句柄
                epoll_ctl(m_epollfd, EPOLL_CTL_ADD, connfd, &event);
                setnonblocking(connfd);
            }
            else if (events[i].events & (EPOLLRDHUP | EPOLLHUP | EPOLLERR))
            {
                //服務(wù)器端關(guān)閉連接,
                epoll_ctl(m_epollfd, EPOLL_CTL_DEL, sockfd, 0);
                close(sockfd);
            }
            //處理客戶連接上接收到的數(shù)據(jù)
            else if (events[i].events & EPOLLIN)
            {
                char buf[1024]={0};
                read(sockfd,buf,1024);
                printf("from client :%s");

                // 將事件設(shè)置為寫事件返回數(shù)據(jù)給客戶端
                events[i].data.fd = sockfd;
                events[i].events = EPOLLOUT | EPOLLET | EPOLLONESHOT | EPOLLRDHUP;
                epoll_ctl(m_epollfd, EPOLL_CTL_MOD, sockfd, &events[i]);
            }
            else if (events[i].events & EPOLLOUT)
            {
                std::string response = "server response \n";
                write(sockfd,response.c_str(),response.length());

                // 將事件設(shè)置為讀事件,繼續(xù)監(jiān)聽客戶端
                events[i].data.fd = sockfd;
                events[i].events = EPOLLIN | EPOLLRDHUP;
                epoll_ctl(m_epollfd, EPOLL_CTL_MOD, sockfd, &events[i]);
            }
            //else if 可以加管道,unix套接字等等數(shù)據(jù)
        }
    }


}
觸發(fā)模式

EPOLL LTEPOLL EF 兩種:

  • LT,水平觸發(fā)(默認),只要該 fd 還有數(shù)據(jù)可讀,每次 epoll_wait 都會返回它的事件,提醒用戶程序去處理。
  • ET,邊緣觸發(fā)(高速),無論 fd 中是否還有數(shù)據(jù)都只提示一次,直到下次有數(shù)據(jù)流入前都不會提示。所以 ET 模式下,read 一個 fd 時,一定要把它的 buffer 讀完,即讀到 read 返回值小于請求值或遇到 EAGAIN 錯誤(稍后重試)。

epoll 使用 事件 就緒通知方式,通過 epoll_ctl 注冊 fd,一旦該 fd 就緒,內(nèi)核就會采用類似回調(diào)機制激活該 fd,epoll_wait 便可收到通知。

ET 的意義
若用 LT,系統(tǒng)中一旦有大量無需讀寫的就緒文件描述符,它們每次調(diào)用 epoll_wait 都會返回,這大大降低處理程序檢索自己關(guān)心的就緒文件描述符的效率。
而采用 ET,當被監(jiān)控的文件描述符上有可讀寫事件發(fā)生時,epoll_wait 會通知處理程序去讀寫。若這次沒有把數(shù)據(jù)全部讀寫完(如讀寫緩沖區(qū)太小),則下次調(diào)用 epoll_wait 時,它不會通知你,即只會通知你一次,直到該文件描述符上出現(xiàn)第二次可讀寫事件才通知你。這比水平觸發(fā)效率高,系統(tǒng)不會充斥大量你不關(guān)心的就緒文件描述符。

優(yōu)點
  • 無最大并發(fā)連接的限制,能打開的 fd 上限遠大于1024(1G內(nèi)存能監(jiān)聽約10萬個端口)。
  • 效率提升,不是輪詢,不會隨 fd 數(shù)目增加而效率下降。只有活躍可用的 fd 才會調(diào)用 callback 函數(shù),即 epoll 最大優(yōu)點在于它只關(guān)心“活躍”連接,而跟連接總數(shù)無關(guān)。
  • 內(nèi)存拷貝,利用 mmap() 文件映射內(nèi)存加速與內(nèi)核空間的消息傳遞;即epoll使用mmap減少復(fù)制開銷。
  • epoll 通過內(nèi)核和用戶空間共享一塊內(nèi)存而實現(xiàn)。
缺點
  • 在連接數(shù)少且都十分活躍情況下,selectpoll 性能都可能比 epoll 好,因為 epoll 通知機制需要很多函數(shù)回調(diào)。
  • epollLinux 所特有的,而 selectPOSIX 所規(guī)定,一般 os 均有實現(xiàn)。

四、總結(jié)

select,poll,epoll 都是 I/O 多路復(fù)用機制,即能監(jiān)視多個 fd,一旦某 fd 就緒(讀或?qū)懢途w),能夠通知程序進行相應(yīng)讀寫操作。 但 select,poll,epoll 本質(zhì)都是同步I/O,因為他們都需在讀寫事件就緒后,自己負責(zé)進行讀寫,即該讀寫過程是阻塞的,而異步 I/O 則無需自己負責(zé)進行讀寫,異步 I/O 實現(xiàn)會負責(zé)把數(shù)據(jù)從內(nèi)核拷貝到用戶空間。

select,poll 需自己主動不斷輪詢所有 fd 集合,直到設(shè)備就緒,期間可能要睡眠和喚醒多次交替。而 epoll 其實也需調(diào)用 epoll_wait 不斷輪詢就緒鏈表,期間也可能多次睡眠和喚醒交替,但它是設(shè)備就緒時,調(diào)用回調(diào)函數(shù),把就緒 fd 放入就緒鏈表,并喚醒在 epoll_wait 中進入睡眠的進程。雖然都要睡眠和交替,但 selectpoll 在“醒著”時要遍歷整個 fd 集合,而 epoll 在“醒著”的時候只需判斷就緒鏈表是否為空,節(jié)省大量CPU時間,這就是回調(diào)機制帶來的性能提升。

select,poll 每次調(diào)用都要把 fd 集合從用戶態(tài)往內(nèi)核態(tài)拷貝一次,且要把當前文件往設(shè)備等待隊列中掛一次,而 epoll 只要一次拷貝,且把當前文件往等待隊列上掛也只掛一次(在 epoll_wait 開始,注意這里的等待隊列并不是設(shè)備等待隊列,只是一個 epoll 內(nèi)部定義的等待隊列),這也能節(jié)省不少開銷。

參考:文章來源地址http://www.zghlxwxcb.cn/news/detail-695885.html

  • 一文搞懂select、poll和epoll區(qū)別
  • IO多路復(fù)用——深入淺出理解select、poll、epoll的實現(xiàn)

到了這里,關(guān)于02-Linux-IO多路復(fù)用之select、poll和epoll詳解的文章就介紹完了。如果您還想了解更多內(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īng)查實,立即刪除!

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

相關(guān)文章

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

    多路轉(zhuǎn)接高性能IO服務(wù)器|select|poll|epoll|模型詳細實現(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 去倉庫獲

    2024年02月15日
    瀏覽(26)
  • Linux多路IO復(fù)用技術(shù)——epoll詳解與一對多服務(wù)器實現(xiàn)

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

    本文詳細介紹了Linux中epoll模型的優(yōu)化原理和使用方法,以及如何利用epoll模型實現(xiàn)簡易的一對多服務(wù)器。通過對epoll模型的優(yōu)化和相關(guān)接口的解釋,幫助讀者理解epoll模型的工作原理和優(yōu)缺點,同時附帶代碼實現(xiàn)和圖解說明。

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

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

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

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

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

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

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

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

    2024年02月12日
    瀏覽(26)
  • 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)缺點 2.server.c 3.client.c 二:poll 1.基礎(chǔ)API? poll函數(shù)? poll優(yōu)缺點 read函數(shù)返回值 突破1024 文件描述符限制 2.server.c 3.client.c 三:epoll 1.基礎(chǔ)API epoll_create創(chuàng)建? ?epoll_ctl操作? epoll_wait阻塞 epoll實現(xiàn)多路IO轉(zhuǎn)接思路 epoll優(yōu)缺點

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

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

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

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

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

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

    2024年02月06日
    瀏覽(19)
  • 深入理解網(wǎng)絡(luò) I/O 多路復(fù)用:SELECT、POLL

    深入理解網(wǎng)絡(luò) I/O 多路復(fù)用:SELECT、POLL

    ?? 嗨,您好 ?? 我是 vnjohn,在互聯(lián)網(wǎng)企業(yè)擔(dān)任 Java 開發(fā),CSDN 優(yōu)質(zhì)創(chuàng)作者 ?? 推薦專欄:Spring、MySQL、Nacos、Java,后續(xù)其他專欄會持續(xù)優(yōu)化更新迭代 ??文章所在專欄:網(wǎng)絡(luò) I/O ?? 我當前正在學(xué)習(xí)微服務(wù)領(lǐng)域、云原生領(lǐng)域、消息中間件等架構(gòu)、原理知識 ?? 向我詢問任何您想

    2024年02月04日
    瀏覽(27)
  • select,poll,epoll阻塞IO使用示例介紹

    epoll 打開設(shè)備文件或套接字,并確保設(shè)備或套接字處于可讀或可寫狀態(tài)。 創(chuàng)建一個 epoll 實例,使用 epoll_create 函數(shù)創(chuàng)建一個 epoll 文件描述符。 將設(shè)備文件或套接字的文件描述符添加到 epoll 實例中,使用 epoll_ctl 函數(shù)將設(shè)備文件或套接字的文件描述符添加到 epoll 實例中,并設(shè)

    2024年02月12日
    瀏覽(15)

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

支付寶掃一掃打賞

博客贊助

微信掃一掃打賞

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

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

二維碼1

領(lǐng)取紅包

二維碼2

領(lǐng)紅包