典型IO模型總結
- 1.典型IO模型
-
- 1.1 什麼是IO模型
- 1.2 阻塞IO模型
- 1.3 非阻塞IO模型
- 1.4 信号驅動IO模型
- 1.5 異步IO模型
- 1.6 多路轉接IO模型
-
- 1.6.1 select
-
- 1.6.1.1 select優缺點總結
- 1.6.2 poll
-
- 1.6.2.1 函數接口
- 1.6.2.2 程式設計執行個體
- 1.6.2.3 poll的優缺點
- 1.6.3 epoll(目前公認的在Linux系統下監控性能最高)
-
- 1.6.3.1 epoll(建立句柄)接口
- 1.6.3.2 epoll(添加/删除/修改事件結構)接口
- 1.6.3.3 epoll監控接口
- 1.6.3.4 epoll的工作原理
- 1.6.3.5 epoll模拟代碼
- 1.6.3.6 工作方式:水準觸發 LT模式(EPOLLLT)
- 1.6.3.7 工作方式:邊緣觸發 ET模式(EPOLLET)
1.典型IO模型
1.1 什麼是IO模型
- 我們在執行一個程式時,這個程式要求外設進行輸入或者輸出,核心在處理這個資料時,都在做什麼?
- 第一步:等待IO就緒,已經準備好需要的資源了,可以開始操作
典型I/O模型——阻塞IO,非阻塞IO,信号驅動IO,異步IO,IO多路轉接(select&poll&epoll)1.典型IO模型 - 第二步:将資料拷貝到緩沖區當中(接受緩沖區&發送緩沖區)
1.2 阻塞IO模型
- 資源不可用的情況下,IO請求一直被阻塞,直到資源可以用
典型I/O模型——阻塞IO,非阻塞IO,信号驅動IO,異步IO,IO多路轉接(select&poll&epoll)1.典型IO模型 - 以釣魚舉例子:釣魚的時候,将魚鈎抛入水中
,一直盯着魚漂(發起IO調用)
,魚兒咬鈎(等待IO就緒)
,将魚兒釣上來(拷貝資料到緩沖區)
(IO調用傳回)
- 阻塞IO的特點:
1.發起IO調用之後,等待的時間取決于核心
2.在等待的過程中,執行流是被挂起的(對CPU的使用率低)
3.在IO就緒到拷貝資料之間,實時性很高(響應快)
1.3 非阻塞IO模型
- 我們在使用程式是輪詢的方式,一直詢問核心資料有沒有準備好
- 資源不可用的時候,IO請求不會被阻塞,而是直接傳回,傳回目前資源不可用(EBUSY)
- 以釣魚舉例子:釣魚的時候,将魚鈎抛入水中,看一眼魚漂,魚漂如果沒動,則看一會手機,再看一眼魚漂,魚漂沒動,再看一會手機,如此往複,直到魚兒咬鈎,将魚釣上來
- 非阻塞IO的特點:
1.非阻塞IO對CPU的使用率比阻塞IO高 ``2.代碼結構相對複雜
3.需要搭配循環使用,直到IO請求完成
4.IO準備就緒到資料拷貝之間,實時性不高(因為可能在你看手機的過程中,魚兒咬鈎了)
差別: 在資源不可用的情況下,就看系統的調用是否立即傳回
- 立即傳回:非阻塞IO
- 沒有立即傳回:阻塞IO
1.4 信号驅動IO模型
- 信号驅動IO:核心态中把資料準備完成之後,使用之前定義的SIGIO信号通知應用程式進行IO操作
流程:
1.自定義一個IO信号(SIGIO)的處理函數,再處理函數當中發起IO調用
2.程式收到一個IO信号(SIGIO),核心就會調用自定義的處理函數,在自定義的處理函數中發起IO調用
![]()
典型I/O模型——阻塞IO,非阻塞IO,信号驅動IO,異步IO,IO多路轉接(select&poll&epoll)1.典型IO模型
- 以釣魚舉例子:先在魚杆上綁一個鈴铛,将魚鈎抛入水中(接下來玩手機),魚兒咬鈎之後,鈴铛就會響,将魚兒釣上來
- 信号驅動IO的特點:
1.IO準備就緒到拷貝資料之間,實時性更強
2.代碼更加複雜,流程控制更加困難(引入了信号)
3.不需要重複發起IO調用,但是需要在代碼當中增加自定義信号的邏輯
1.5 異步IO模型
- 異步IO:當核心中把程式拷貝完成後,再通知程式(信号驅動就是告訴應用程式何時開始拷貝資料)
流程:
1.自定義信号(SIGIO)處理函數,用來通知資料拷貝完成
2.發起一個異步IO調用,并且異步IO調用直接傳回
(由作業系統核心來等待IO就緒和資料拷貝)
3.異步IO調用傳回之後,執行流可以執行使用者代碼
4.當資料拷貝完成之後,核心通過信号來告知調用者
![]()
典型I/O模型——阻塞IO,非阻塞IO,信号驅動IO,異步IO,IO多路轉接(select&poll&epoll)1.典型IO模型
- 異步IO調用:異步IO函數接口當中一般會設定一個函數指針,來儲存IO調用完成之後,通知調用者的回調函數
小結:當程式發出一個調用的時候,在沒有得到結果之前,這個調用就不會傳回。當調用傳回的時候,就一定是得到了一個結果
1.同步IO:
當程式發出一個調用,這個調用就直接傳回了,是以沒有傳回結果。等到核心中完成了拷貝的操作,再通過一些方式來通知調用者(信号),或者通過回調函數來處理這個調用
2.異步IO:
3.異步IO最大的特點:使用者不需要拷貝資料了,拷貝資料由作業系統核心來完成,完成拷貝之後,通知調用
1.6 多路轉接IO模型
- IO多路轉接:可以完成大量檔案描述符的監控,監控的事件:
,可讀事件
,可寫事件
異常事件
- 監控檔案描述符:哪個檔案描述符準備就緒,就處理哪一個檔案描述符
- 好處:避免了其他程序對沒有就緒的檔案描述符進行操作,進而陷入阻塞狀态
1.6.1 select
- 作用:用程式來對多個檔案描述符的狀态進行監控,如果有描述符準備就緒,就傳回該描述符,讓使用者對這個描述符進行操作
- 流程:
.将用于關心的檔案描述符拷貝到核心當中,核心來進行監控Ⅰ
.如何核心監控到某個檔案描述符就緒,則傳回該描述符Ⅱ
.使用者對傳回的描述符進行操作Ⅲ
- 函數接口
典型I/O模型——阻塞IO,非阻塞IO,信号驅動IO,異步IO,IO多路轉接(select&poll&epoll)1.典型IO模型
1.
nfds
:取值為監控最大的檔案描述符數值+1(最大的數值為1024),輪詢的範圍由
nfds
來決定
2.
fd_set
:本質是一個結構體,結構體内部是一個
fds_bits
數組,可以了解為一個1024位的位圖,對應1024個檔案描述符
_FD_SIZE:#define _FD_SETSIZE 1024
_NFDBITS:#define _NFDBITS (8*(int)sizeof(_fd_mask))
數組的元素個數:1024 / 8*(int)sizeof(_fd_mask)
_fd_mask:typedef long int_fd mask
數組當中比特位的個數:
(1024/8 * (int)sizeof(_fd_mask)) * 8 *(int)sizeof(_fd_mask)) = 1024
總結:select的事件集合當中總共有1024個比特位,取決于宏 _FD_SETSIZE 這個宏的大小
3.
舉個例子
4.
fd_set集合中提供了4個函數
//從事件集合當中删除檔案描述符fd,描述符對應的比特位置0
void FD_CLR(int fd, fd_set *set);
//判斷fd描述符是否在set集合當中;
//傳回值:0表示沒有在集合當中,非0表示在集合當中
int FD_ISSET(int fd, fd_set *set);
//将檔案描述符fd設定到set集合當中,描述符對應的比特位置1
void FD_SET(int fd, fd_set *set);
//清空事件集合,将所有的比特位置0
void FD_ZERO(fd_set *set);
5.
readfds:
可讀檔案描述符的結合 ;
writefds:
可寫檔案描述符的集合;
exceptfds:
異常檔案描述符的集合
6.
timeout:
逾時時間;
tv_sec:秒
tv_usec:微秒
- timeout == NULL
阻塞監控
- timeout == 0
非阻塞監控
- timeout > 0
等待逾時時間監控
7.
函數傳回值
1.6.1.1 select優缺點總結
- 優點
1.select遵循的是POSIX标準,可以2.select的
跨平台移植
可以精确到微秒
逾時時間
- 缺點
1.select采用的是輪詢周遊,監控的效率會随着檔案描述符的增多而下降
2.select所能監控的檔案描述符是有上限的(
1024
),取決于核心FD_SETSIZE宏的值
3.select監控檔案描述符的時候,需要将集合拷貝到核心當中,select發現有事件就緒之後,同時需要将事件集合從核心拷貝到使用者空間,效率也會受影響
4.select在傳回的時候,
,導緻下一次監控的時候,如果還需要監控去除掉的檔案描述符,就得
會将未就緒的檔案描述符從集合當中去除掉
5.select
重新添加
,需要手
無法直接檢視那個檔案描述符已經就緒
6.select的
動通過傳回事件的集合去判斷
,如果在循環判斷的情況下,每次調用之前都需要更新一下時間。因為在計時的時候,這個結構體中的時間是會變的
逾時機制
1.6.2 poll
poll和select相比,跨平台移植性不如select,poll函數隻能在Linux環境下使用,也采用輪詢周遊
與select相比,改進的點:
1.不限制監控的檔案描述符的個數
2.select使用的是事件集合方式,poll采用的是事件結構
(檔案描述符對應一個
,這個結構中
事件結構
,一個
有兩個事件
,另一個
是要監控的檔案描述符
)
是這個檔案描述符所對應的事件
1.6.2.1 函數接口
事件結構數組
fds:
事件結構數組中有效的元素個數
nfds:
逾時時間
timeout:
fd:
關心的檔案描述符
是什麼
events:關心的檔案描述符産生的事件是什麼,如果關心多個事件,可以将多個事件
按照按位或的方式連接配接起來
![]()
典型I/O模型——阻塞IO,非阻塞IO,信号驅動IO,異步IO,IO多路轉接(select&poll&epoll)1.典型IO模型
revents:當關心的檔案描述符産生對應的關心的事件時,傳回給調用者發生的事件(每次監控的時候,就會被初始化為空)
1.6.2.2 程式設計執行個體
#include<stdio.h>
#include<unistd.h>
#include<poll.h>
#include<iostream>
int main()
{
//建立一個struct pollfd結構體,關心0号檔案描述符,标準輸入
//監視可讀事件
struct pollfd fd_arr[10];
fd_arr[0].fd = 0;
fd_arr[0].events = POLLIN;
//輪詢周遊
while(1)
{
int ret = poll(fd_arr, 1 , 1000);
if(ret < 0) //poll出錯
{
perror("poll error");
return -1;
}
else if(ret == 0)//監控逾時
{
printf("poll timeout\n");
continue;
}
for(int i = 0; i < ret; i++)
{
if(fd_arr[i].revents == POLLIN)//當發生的事件可讀
{
//讀取資料
char buf[1024] = {0};
read(fd_arr[i].fd, buf, sizeof(buf) - 1);
printf("buf: %s\n",buf);
}
}
}
return 0;
}
運作結果:
1.6.2.3 poll的優缺點
優點:
- poll采用了事件結構的方式,簡化了代碼的編寫
- poll不限制檔案描述符的個數
- 不需要再二次監控的時候重新添加檔案描述符
缺點:
- poll采用輪詢周遊事件結構數組的方式,随着檔案描述符增多,性能下降
- poll不支援平台
- poll也沒有告訴使用者哪一個具體的檔案描述符就緒了,需要自己判斷
- poll也需要将事件結構拷貝到核心,從核心再拷貝到使用者空間
1.6.3 epoll(目前公認的在Linux系統下監控性能最高)
1.6.3.1 epoll(建立句柄)接口
- 建立
的操作句柄epoll
#include <sys/epoll.h>
int epoll_create(int size);
size:本來的含義是定義epoll最大能夠監控檔案描述符的個數,核心在2.6.8之後就啟棄用了,現在采用動态記憶體開辟的方式,來進行擴容,size的值大于0,在使用完之後,必須使用close進行關閉
傳回值:傳回epoll的操作句柄
epoll的操作句柄其實就是用來找到struct eventpoll結構體,進而對結構體當中的變量進行操作
核心觀點:在核心當中就是建立一個struct eventpoll結構體,在這個結構體當中有兩個重要的變量,一個是紅黑樹,一個是雙向連結清單
1.6.3.2 epoll(添加/删除/修改事件結構)接口
向核心維護的紅黑樹當中添加/删除/修改事件結構
#include <sys/epoll.h>
int epoll_ctl(int epfd, int op, int fd,
struct epoll_event *event);
epfd:epoll_create的傳回值,epoll的操作句柄
op:想讓epoll_ctl做的事
![]()
典型I/O模型——阻塞IO,非阻塞IO,信号驅動IO,異步IO,IO多路轉接(select&poll&epoll)1.典型IO模型 fd:告訴epoll函數,我們關心的檔案描述符
event:epoll的事件結構,類型是 struct epoll_event結構體
typedef union epoll_data
{
*ptr和fd兩者隻能選一個*
void *ptr; --->如果使用ptr,需要包含fd的内容
int fd; --->使用者關心的檔案描述符,可以當做檔案描述符中的事件就緒之後,傳回給我們時檢視;其取值為檔案描述符的數值
uint32_t u32;
uint64_t u64;
} epoll_data_t;
struct epoll_event
{
想讓檔案描述符關心的事件集合:EPOLLIN可讀事件 EPOLLOUT可寫事件
uint32_t events; /* Epoll events */
epoll_data類型的聯合結構體
epoll_data_t data; /* User data variable */
} __EPOLL_PACKED;
1.6.3.3 epoll監控接口
#include <sys/epoll.h>
int epoll_wait(int epfd, struct epoll_event *events,
int maxevents, int timeout);
epfd:epoll操作句柄
events:事件結構數組,作為出參,傳回就緒的事件結構,一個檔案描述符對應一個事件結構
maxevents:表示最大能接受多少個事件結構
timeout:逾時時間
![]()
典型I/O模型——阻塞IO,非阻塞IO,信号驅動IO,異步IO,IO多路轉接(select&poll&epoll)1.典型IO模型
1.6.3.4 epoll的工作原理
1.6.3.5 epoll模拟代碼
#include<iostream>
#include<stdio.h>
#include<unistd.h>
#include<sys/epoll.h>
#include<string.h>
#include<stdlib.h>
/*
* 1.建立epoll操作句柄
* 2.添加事件結構
* 2.1準備事件結構
* 2.2關心的事件及檔案描述符
* 3.監控
* 3.1阻塞
* 3.2非阻塞
* 3.3帶有逾時事件
* 4.判斷epoll_wait的傳回值
* 5.執行相應的操作
* */
int main()
{
int epfd = epoll_create(5);
if(epfd < 0)
{
perror("create error");
return 0;
}
struct epoll_event ee;
ee.events = EPOLLIN;
ee.data.fd = 0;
epoll_ctl(epfd, EPOLL_CTL_ADD, 0, &ee);
while(1)
{
struct epoll_event arr[10];
memset(arr, '\0', sizeof(struct epoll_event) * 10);
int ret = epoll_wait(epfd, arr, 10, 0);
if(ret < 0)
{
perror("epoll_wait error");
return 0;
}
else if(ret == 0)
{
sleep(1);
printf("timeout..\n");
continue;
}
for(int i = 0; i < 10; i++)
{
if(arr[i].events == EPOLLIN)
{
char buf[1024] = {0};
read(arr[i].data.fd, buf,sizeof(buf) - 1);
printf("buf:%s", buf);
}
}
}
return 0;
}
1.6.3.6 工作方式:水準觸發 LT模式(EPOLLLT)
- epoll的預設工作方式,
都是水準觸發方式select和poll
- 在LT模式當中,當epoll中檢測到了等待觸發事件就緒後,可以不立即進行處理,而是隻處理一部分。等到第二次調用epoll_wait函數的時候,可以接着剛才沒有處理完的資料進行操作(
支援阻塞讀寫和非阻塞讀寫)
- 可讀事件:隻要發送緩沖區當中資料大于低水位标記(1位元組),就會一直觸發可讀事件就緒,直到接收緩沖區當中沒有資料可讀(接收緩沖區當中的資料低于低水位标記)
- 可寫事件:隻要發送緩沖區當中的空間大小大于低水位标記(1位元組),就會一直觸發可寫事件就緒,直到緩沖區當中沒有空間可寫
1.6.3.7 工作方式:邊緣觸發 ET模式(EPOLLET)
- EPOLLET隻有epoll才有
- 可讀事件:隻有新的資料到來的時候,才會觸發可讀,否則通知一次就不通知了(
)每次到來一個新的資料,隻會通知一次,如果應用程式沒有将接收緩沖區當中的資料讀走或者讀完,也不會通知,直到新的資料到來,才會觸發可讀事件。如果出發可讀事件,盡量将資料讀完
- 對于ET模式而言,如果就緒事件産生,一定要把握好機會,對于可讀事件,将資料讀完,對于可寫事件,将資料寫完
如何在代碼中展現ET
設定檔案描述符對應的事件結構的時候,隻需要在事件結構當中關心的事件變量按位或上 EPOLLET 就可以了
struct epoll_enent ev;
ev.events = EPOLLIN | EPOLLET;