摘要:調(diào)用一次獲得就緒文件描述符時(shí),返回的并不是實(shí)際的描述符,而是一個(gè)代表就緒描述符數(shù)量的值,拿到這些值去指定的一個(gè)數(shù)組中依次取得相應(yīng)數(shù)量的文件描述符即可,這里使用內(nèi)存映射技術(shù),避免了復(fù)制大量文件描述符帶來(lái)的開銷。
Nodejs定義
什么是IONode.js uses an event-driven, non-blocking I/O model that makes it lightweight and efficient.
IO(Input & Output),顧名思義,輸入輸出即是IO。磁盤,網(wǎng)絡(luò),鼠標(biāo),鍵盤等都算IO;而大家通常說(shuō)的IO,大部分指磁盤和網(wǎng)絡(luò)的數(shù)據(jù)操作。
對(duì)于磁盤,IO=讀寫;對(duì)于網(wǎng)絡(luò),IO=收發(fā)。
學(xué)習(xí)C語(yǔ)言時(shí),有個(gè)作業(yè),大意是寫一個(gè)server程序和client程序,實(shí)現(xiàn)TCP/UDP通信??雌饋?lái)代碼如下:
Client端int ClientSend(SOCKET s, char* msg) { char buf[BUF_SIZE] = {0}; if (s && msg) { int len = send(s, msg, strlen(msg), 0); if (len > 0) { println("Client send OK!"); len = recv(s, buf, BUF_SIZE); if (len > 0) { println("Client receive: %s", buf); } // else socket recv error } // else socket send error } // else } int main(char* argc, char* argv[]) { // 初始化socket SOCKET s = InitSocket(); if (s != -1) { ClientSend(s, "Hi, I am Client"); } // else socket init error return 0; }Server端
int main(char* argc, char* argv[]) { char buf[BUF_SIZE] = {0}; const char* msg = "Roger that, I am Server"; // 初始化socket,略 SOCKET s = InitSocket(); SOCKET cs; sockaddr_in addr; int nAddrLen = sizeof(addr); while ((cs = accept(s, &addr, &nAddrLen)) != -1) { int len = recv(cs, buf, BUF_SIZE, 0); if (len > 0) { len = send(cs, msg, strlen(msg), 0); if (len > 0) { println("Serve one client"); } // else socket send error } } return 0; }
在這個(gè)例子中,如果一個(gè)Client通信沒有結(jié)束,其它的Client是無(wú)法和Server通信的。原因就是代碼里面使用的是Blocking I/O,即同步IO。因?yàn)樵诖a中的recv或者send,都會(huì)阻塞住當(dāng)前代碼的執(zhí)行。單靠這種模型,是無(wú)法實(shí)現(xiàn)一個(gè)完善的服務(wù)器的。
Blocking I/O,多線程(多進(jìn)程)為了讓Server能服務(wù)更多的Client,基于Blocking I/O,可以采用多線程(進(jìn)程)來(lái)處理,實(shí)現(xiàn)1對(duì)多的服務(wù)。
Server端int ThreadProc(void* pParam) { char buf[BUF_SIZE] = {0}; const char* msg = "Roger that, I am Server"; if (pParam) { int len = recv(cs, buf, BUF_SIZE, 0); if (len > 0) { len = send(cs, msg, strlen(msg), 0); if (len > 0) { println("Serve one client"); } // else socket send error } // else socket recv error } // else param error return 0; } int main(char* argc, char* argv[]) { // 初始化socket,略 SOCKET s = InitSocket(); SOCKET cs; sockaddr_in addr; int nAddrLen = sizeof(addr); while ((cs = accept(s, &addr, &nAddrLen)) != -1) { int pThread = CreateThread(NULL, 0, ThreadProc, cs); // serve on client } return 0; }
這樣的方案,的確能同時(shí)處理多個(gè)Client請(qǐng)求,實(shí)現(xiàn)并發(fā)。但由于創(chuàng)建線程的成本很高(需要分配內(nèi)存,調(diào)度CPU等),受Server硬件條件的限制,這種方案不能服務(wù)很多Client,即服務(wù)器性能很低下。
另外,如果把ThreadProc里面的代碼增加邏輯:
// recive data from buf setenv(buf); CreateProcess(NULL, 0 ...); // parse env in child process
這就是一個(gè)簡(jiǎn)單的CGI模型了。
在一些簡(jiǎn)單的http服務(wù)器代碼中,見到過(guò)這樣的模型。(比如一些嵌入式系統(tǒng)服務(wù)器)。
因?yàn)锽locking I/O的特點(diǎn),所以系統(tǒng)提供了另外的方法,Non-blocking I/O,即調(diào)用send,recv等接口時(shí),不會(huì)阻塞線程,但調(diào)用者需要自己去輪訓(xùn)IO的狀態(tài)來(lái)判定操作;就像一個(gè)監(jiān)工不停的問(wèn)工人,你完事兒沒有。
int main(char * argc, char * argv[]) { // 初始化socket,略 SOCKET s = InitSocket(); SOCKET cs; sockaddr_in addr; int fd; int nAddrLen = sizeof(addr); SetNonblocking(s); while (running) { int ret = select(FD_SETSIZE, ...); if (ret == -1) break; if (ret == 0) continue; for (fd = 0; fd < FD_SETSIZE; fd++) { if (FD_ISSET(fd, ...) { // 有新的client進(jìn)來(lái) if (fd == s) { cs = accept(s, & addr, & nAddrLen, 0); FD_SET(cs, ...); } else // cs中的一個(gè)里面有變化 { ioctl(fd, FIONREAD, & nread); // 處理完畢 if (nread == 0) { close(fd); FD_CLR(fd, ...); } else { // 處理Client邏輯,這里可能會(huì)創(chuàng)建線程。 ...... } } } // serve on client } } return 0; }
在這種模型中,while和for循環(huán)不停的檢查fd_set的狀態(tài),并做相應(yīng)的處理,類似Apache的解決方案。
但是,這個(gè)模型里面還有一個(gè)block,就是select,當(dāng)有fd發(fā)生變化時(shí),select才會(huì)返回。
還有,select中的FD_SETSIZE有限制(一般是2048),就表明單進(jìn)程還是不能支持更大量級(jí)的并發(fā)。Apache采用多進(jìn)程的方式來(lái)解決這個(gè)問(wèn)題。
后期有了epoll,這個(gè)限制放的更寬,很多http服務(wù)器是用epoll來(lái)實(shí)現(xiàn)的(Nginx)。
epoll主要有兩個(gè)優(yōu)點(diǎn):
基于事件的就緒通知方式 ,select/poll方式,進(jìn)程只有在調(diào)用一定的方法后,內(nèi)核才會(huì)對(duì)所有監(jiān)視的文件描述符進(jìn)行掃描,而epoll事件通過(guò)epoll_ctl()注冊(cè)一個(gè)文件描述符,一旦某個(gè)文件描述符就緒時(shí),內(nèi)核會(huì)采用類似call back的回調(diào)機(jī)制,迅速激活這個(gè)文件描述符,epoll_wait()便會(huì)得到通知。
調(diào)用一次epoll_wait()獲得就緒文件描述符時(shí),返回的并不是實(shí)際的描述符,而是一個(gè)代表就緒描述符數(shù)量的值,拿到這些值去epoll指定的一個(gè)數(shù)組中依次取得相應(yīng)數(shù)量的文件描述符即可,這里使用內(nèi)存映射(mmap)技術(shù), 避免了復(fù)制大量文件描述符帶來(lái)的開銷。
Nodejs,也采用了和Nginx類似的思路,可以再深入了解下libuv。
Asynchronous I/O有些人說(shuō)Nodejs是Asynchronous I/O,其實(shí)不然。Asynchronous I/O是說(shuō)用戶發(fā)起read等IO操作后,去做其它的事情了,而系統(tǒng)在完成IO操作后,用signal的方式通知用戶完成。目前使用此模型的http服務(wù)器有asyncio等。
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/64571.html
原文 先說(shuō)1.1總攬: Reactor模式 Reactor模式中的協(xié)調(diào)機(jī)制Event Loop Reactor模式中的事件分離器Event Demultiplexer 一些Event Demultiplexer處理不了的復(fù)雜I/O接口比如File I/O、DNS等 復(fù)雜I/O的解決方案 未完待續(xù) 前言 nodejs和其他編程平臺(tái)的區(qū)別在于如何去處理I/O接口,我們聽一個(gè)人介紹nodejs,總是...
摘要:異步和事件驅(qū)動(dòng)注本文是對(duì)眾多博客的學(xué)習(xí)和總結(jié),可能存在理解錯(cuò)誤。接觸有兩個(gè)月,對(duì)的兩大特性一直有點(diǎn)模糊,即異步和事件驅(qū)動(dòng)。 nodejs 異步I/O和事件驅(qū)動(dòng) 注:本文是對(duì)眾多博客的學(xué)習(xí)和總結(jié),可能存在理解錯(cuò)誤。請(qǐng)帶著懷疑的眼光,同時(shí)如果有錯(cuò)誤希望能指出。 接觸nodejs有兩個(gè)月,對(duì)nodejs的兩大特性一直有點(diǎn)模糊,即異步IO和事件驅(qū)動(dòng)。通過(guò)對(duì)《深入淺出nodejs》和幾篇博客的閱...
摘要:對(duì)于而言,單線程指的是它的執(zhí)行線程是單線程。對(duì)于來(lái)說(shuō),單線程不僅不是劣勢(shì),它對(duì)于降低編程復(fù)雜度還有很重要的作用,單線程避免了多線程編程模型多線程死鎖狀態(tài)同步等問(wèn)題。單線程的應(yīng)用是脆弱了,但群體的力量是強(qiáng)大的。 我們常聽說(shuō) JavaScript 是單線程的,那這個(gè)單線程是什么意思呢?單線程是否意味 JavaScript 存在性能缺陷呢? 在瀏覽器端,JavaScript 單線程指的是 J...
摘要:事件驅(qū)動(dòng)在中,當(dāng)某個(gè)執(zhí)行完畢后,會(huì)以事件的形式通知執(zhí)行操作的線程而線程去執(zhí)行對(duì)應(yīng)事件的回調(diào)函數(shù)。為了處理異步,線程必須要有事件循環(huán),不斷的檢查有沒有事件要處理,并依次處理。其實(shí)在底層中,有一半的代碼,都是在處理事件隊(duì)列回調(diào)函數(shù)。 事件驅(qū)動(dòng) 上一節(jié)中,我們提到異步I/O;當(dāng)I/O處理完畢后,nodejs是怎樣知道I/O已經(jīng)完成了呢?又是怎樣去處理的呢?答案是:事件驅(qū)動(dòng)(事件循環(huán))機(jī)制。 ...
閱讀 3550·2023-04-26 00:16
閱讀 1367·2021-11-25 09:43
閱讀 3836·2021-11-23 09:51
閱讀 2975·2021-09-24 09:55
閱讀 726·2021-09-22 15:45
閱讀 1402·2021-07-30 15:30
閱讀 3072·2019-08-30 14:04
閱讀 2254·2019-08-26 13:46