一、相關(guān)概念
1.1 同步與異步胞皱、阻塞與非阻塞
同步和異步:用戶程序通過recv()進(jìn)行系統(tǒng)調(diào)用后,如果最后還需要調(diào)用read()骨望,則是同步的牵囤;否則就是異步的爸黄。
阻塞和非阻塞:用戶程序通過recv()進(jìn)行系統(tǒng)調(diào)用后滞伟,如果還可以做其它事情,則是非阻塞的炕贵,反之則是阻塞的梆奈。
概念有些抽象,可以通過以下的例子來理解一下這組概念:
假設(shè)小明需要在網(wǎng)上下載一個(gè)軟件:
- 同步阻塞:如果小明點(diǎn)擊下載按鈕之后称开,就一直干瞪著進(jìn)度條不做其他任何事情直到軟件下載完成鉴裹;
- 異步阻塞(比較罕見):如果小明點(diǎn)擊下載按鈕之后,不用瞪著進(jìn)度條但也不做其他任何事情直到軟件下載完成钥弯,軟件下載完成是會(huì)「叮」的一聲通知的督禽。
- 同步非阻塞:如果小明點(diǎn)擊下載按鈕之后脆霎,就去做其他事情了,不過他總需要時(shí)不時(shí)瞄一眼屏幕看軟件是不是下載完成了
- 異步非阻塞:如果小明點(diǎn)擊下載按鈕之后狈惫,就去做其他事情了睛蛛,軟件下載完之后「叮」的一聲通知小明胧谈,小明再回來繼續(xù)處理下載完的軟件
1.2 用戶態(tài)和內(nèi)核態(tài)
1.2.1 用戶態(tài)和內(nèi)核態(tài)的基本概念
- 用戶態(tài):就是提供應(yīng)用程序運(yùn)行的空間忆肾,為了使應(yīng)用程序訪問到內(nèi)核管理的資源例如CPU,內(nèi)存菱肖,I/O客冈。
- 內(nèi)核態(tài):控制計(jì)算機(jī)的硬件資源,例如協(xié)調(diào)CPU資源稳强,分配內(nèi)存資源场仲,并且提供穩(wěn)定的環(huán)境供應(yīng)用程序運(yùn)行。
1.2.2 用戶態(tài)切換到內(nèi)核態(tài)的方式
- 中斷:當(dāng)外設(shè)完成用戶的請求時(shí)退疫,會(huì)向CPU發(fā)送中斷信號(hào)渠缕。
- 異常:如果當(dāng)前進(jìn)程運(yùn)行在用戶態(tài),如果這個(gè)時(shí)候發(fā)生了異常事件褒繁,就會(huì)觸發(fā)切換亦鳞。例如:缺頁異常。
- 系統(tǒng)調(diào)用:本質(zhì)上也是中斷棒坏,屬于軟中斷的一種燕差。
1.3 文件描述符
內(nèi)核(kernel)利用文件描述符(file descriptor)來訪問文件。文件描述符是非負(fù)整數(shù)俊抵。打開現(xiàn)存文件或新建文件時(shí)谁不,內(nèi)核會(huì)返回一個(gè)文件描述符。讀寫文件也需要使用文件描述符來指定待讀寫的文件徽诲。
二刹帕、IO模型
2.1 同步阻塞IO
應(yīng)用進(jìn)程向內(nèi)核發(fā)起recfrom讀取數(shù)據(jù)吵血。
準(zhǔn)備數(shù)據(jù)報(bào)(應(yīng)用進(jìn)程阻塞)。
將數(shù)據(jù)從內(nèi)核負(fù)責(zé)到應(yīng)用空間偷溺。
復(fù)制完成后蹋辅,返回成功提示。
2.2 同步非阻塞IO
- 應(yīng)用進(jìn)程向內(nèi)核發(fā)起recvfrom讀取數(shù)據(jù)挫掏。
- 沒有數(shù)據(jù)報(bào)準(zhǔn)備好侦另,即刻返回EWOULDBLOCK錯(cuò)誤碼。
- 應(yīng)用進(jìn)程向內(nèi)核發(fā)起recvfrom讀取數(shù)據(jù)尉共。
- 已有數(shù)據(jù)包準(zhǔn)備好就進(jìn)行一下 步驟褒傅,否則還是返回錯(cuò)誤碼。
- 將數(shù)據(jù)從內(nèi)核拷貝到用戶空間袄友。
- 完成后殿托,返回成功提示。
2.3 IO多路復(fù)用
定義:單線程或單進(jìn)程同時(shí)監(jiān)測若干個(gè)文件描述符是否可以執(zhí)行IO操作的能力剧蚣,linux有三種實(shí)現(xiàn)方式支竹,分別為select,poll和epoll。
2.3.1 select
- 源碼
/* According to POSIX.1-2001, POSIX.1-2008 */
#include <sys/select.h>
/* According to earlier standards */
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
int select(int nfds, fd_set *readfds, fd_set *writefds,
fd_set *exceptfds, struct timeval *timeout);
int pselect(int nfds, fd_set *readfds, fd_set *writefds,
fd_set *exceptfds, const struct timespec *timeout,
const sigset_t *sigmask);
void FD_CLR(int fd, fd_set *set);
int FD_ISSET(int fd, fd_set *set);
void FD_SET(int fd, fd_set *set);
void FD_ZERO(fd_set *set);
select的調(diào)用會(huì)阻塞到有文件描述符可以進(jìn)行IO操作或被信號(hào)打斷或者超時(shí)才會(huì)返回鸠按。
select將監(jiān)聽的文件描述符分為三組礼搁,每一組監(jiān)聽不同的需要進(jìn)行的IO操作。readfds是需要進(jìn)行讀操作的文件描述符目尖,writefds是需要進(jìn)行寫操作的文件描述符馒吴,exceptfds是需要進(jìn)行異常事件處理的文件描述符。這三個(gè)參數(shù)可以用NULL來表示對應(yīng)的事件不需要監(jiān)聽瑟曲。
當(dāng)select返回時(shí)募书,每組文件描述符會(huì)被select過濾,只留下可以進(jìn)行對應(yīng)IO操作的文件描述符测蹲。
FD_xx系列的函數(shù)是用來操作文件描述符組和文件描述符的關(guān)系莹捡。
FD_ZERO用來清空文件描述符組。每次調(diào)用select前都需要清空一次扣甲。
select可同時(shí)監(jiān)聽的文件描述符數(shù)量是通過FS_SETSIZE來限制的篮赢,在Linux系統(tǒng)中,該值為1024琉挖。
2.3.2 poll
- 源碼
#include <poll.h>
int poll(struct pollfd *fds, nfds_t nfds, int timeout);
#include <signal.h>
#include <poll.h>
int ppoll(struct pollfd *fds, nfds_t nfds,
const struct timespec *tmo_p, const sigset_t *sigmask);
struct pollfd {
int fd; /* file descriptor */
short events; /* requested events to watch */
short revents; /* returned events witnessed */
};
poll和select用三組文件描述符不同的是启泣,poll只有一個(gè)pollfd數(shù)組,數(shù)組中的每個(gè)元素都表示一個(gè)需要監(jiān)聽IO操作事件的文件描述符示辈。events參數(shù)是我們需要關(guān)心的事件寥茫,revents是所有內(nèi)核監(jiān)測到的事件。
poll在linux下沒有監(jiān)聽的文件描述符數(shù)量限制矾麻,但是如果過多的話纱耻,速度也會(huì)變慢芭梯。
2.3.3 epoll
- 源碼
#include <sys/epoll.h>
int epoll_create(int size);
int epoll_create1(int flags);
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
int epoll_wait(int epfd, struct epoll_event *events,
int maxevents, int timeout);
int epoll_pwait(int epfd, struct epoll_event *events,
int maxevents, int timeout,
const sigset_t *sigmask);
epoll_create用于創(chuàng)建一個(gè)epoll實(shí)例,而epoll_ctl用于往epoll實(shí)例中增刪改要監(jiān)測的文件描述符弄喘,epoll_wait則用于阻塞的等待可以執(zhí)行IO操作的文件描述符直到超時(shí)玖喘。
epoll優(yōu)于select&poll在下面幾點(diǎn):
- 在需要同時(shí)監(jiān)聽的文件描述符數(shù)量增加時(shí),select&poll是O(N)的復(fù)雜度蘑志,epoll是O(1)累奈,在N很小的情況下,差距不會(huì)特別大急但,但如果N很大的前提下澎媒,一次O(N)的循環(huán)可要比O(1)慢很多,所以高性能的網(wǎng)絡(luò)服務(wù)器都會(huì)選擇epoll進(jìn)行IO多路復(fù)用波桩。
- epoll內(nèi)部用一個(gè)文件描述符掛載需要監(jiān)聽的文件描述符旱幼,這個(gè)epoll的文件描述符可以在多個(gè)線程/進(jìn)程共享,所以epoll的使用場景要比select&poll要多突委。
2.4 異步IO
進(jìn)程發(fā)起read操作之后,立刻就可以開始去做其它的事冬三。而另一方面匀油,從kernel的角度,當(dāng)它受到一個(gè)asynchronous read之后勾笆,首先它會(huì)立刻返回敌蚜,所以不會(huì)對用戶進(jìn)程產(chǎn)生任何block。然后窝爪,kernel會(huì)等待數(shù)據(jù)準(zhǔn)備完成弛车,然后將數(shù)據(jù)拷貝到用戶內(nèi)存,當(dāng)這一切都完成之后蒲每,kernel會(huì)給用戶進(jìn)程發(fā)送一個(gè)signal纷跛,告訴它read操作完成了⊙樱
這個(gè)模型工作機(jī)制是:告訴內(nèi)核啟動(dòng)某個(gè)操作贫奠,并讓內(nèi)核在整個(gè)操作(包括第二階段,即將數(shù)據(jù)從內(nèi)核拷貝到進(jìn)程緩沖區(qū)中)完成后通知我們望蜡。