摘要:調用一次獲得就緒文件描述符時,返回的并不是實際的描述符,而是一個代表就緒描述符數量的值,拿到這些值去指定的一個數組中依次取得相應數量的文件描述符即可,這里使用內存映射技術,避免了復制大量文件描述符帶來的開銷。
Nodejs定義
什么是IONode.js uses an event-driven, non-blocking I/O model that makes it lightweight and efficient.
IO(Input & Output),顧名思義,輸入輸出即是IO。磁盤,網絡,鼠標,鍵盤等都算IO;而大家通常說的IO,大部分指磁盤和網絡的數據操作。
對于磁盤,IO=讀寫;對于網絡,IO=收發。
學習C語言時,有個作業,大意是寫一個server程序和client程序,實現TCP/UDP通信。看起來代碼如下:
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; }
在這個例子中,如果一個Client通信沒有結束,其它的Client是無法和Server通信的。原因就是代碼里面使用的是Blocking I/O,即同步IO。因為在代碼中的recv或者send,都會阻塞住當前代碼的執行。單靠這種模型,是無法實現一個完善的服務器的。
Blocking I/O,多線程(多進程)為了讓Server能服務更多的Client,基于Blocking I/O,可以采用多線程(進程)來處理,實現1對多的服務。
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; }
這樣的方案,的確能同時處理多個Client請求,實現并發。但由于創建線程的成本很高(需要分配內存,調度CPU等),受Server硬件條件的限制,這種方案不能服務很多Client,即服務器性能很低下。
另外,如果把ThreadProc里面的代碼增加邏輯:
// recive data from buf setenv(buf); CreateProcess(NULL, 0 ...); // parse env in child process
這就是一個簡單的CGI模型了。
在一些簡單的http服務器代碼中,見到過這樣的模型。(比如一些嵌入式系統服務器)。
因為Blocking I/O的特點,所以系統提供了另外的方法,Non-blocking I/O,即調用send,recv等接口時,不會阻塞線程,但調用者需要自己去輪訓IO的狀態來判定操作;就像一個監工不停的問工人,你完事兒沒有。
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進來 if (fd == s) { cs = accept(s, & addr, & nAddrLen, 0); FD_SET(cs, ...); } else // cs中的一個里面有變化 { ioctl(fd, FIONREAD, & nread); // 處理完畢 if (nread == 0) { close(fd); FD_CLR(fd, ...); } else { // 處理Client邏輯,這里可能會創建線程。 ...... } } } // serve on client } } return 0; }
在這種模型中,while和for循環不停的檢查fd_set的狀態,并做相應的處理,類似Apache的解決方案。
但是,這個模型里面還有一個block,就是select,當有fd發生變化時,select才會返回。
還有,select中的FD_SETSIZE有限制(一般是2048),就表明單進程還是不能支持更大量級的并發。Apache采用多進程的方式來解決這個問題。
后期有了epoll,這個限制放的更寬,很多http服務器是用epoll來實現的(Nginx)。
epoll主要有兩個優點:
基于事件的就緒通知方式 ,select/poll方式,進程只有在調用一定的方法后,內核才會對所有監視的文件描述符進行掃描,而epoll事件通過epoll_ctl()注冊一個文件描述符,一旦某個文件描述符就緒時,內核會采用類似call back的回調機制,迅速激活這個文件描述符,epoll_wait()便會得到通知。
調用一次epoll_wait()獲得就緒文件描述符時,返回的并不是實際的描述符,而是一個代表就緒描述符數量的值,拿到這些值去epoll指定的一個數組中依次取得相應數量的文件描述符即可,這里使用內存映射(mmap)技術, 避免了復制大量文件描述符帶來的開銷。
Nodejs,也采用了和Nginx類似的思路,可以再深入了解下libuv。
Asynchronous I/O有些人說Nodejs是Asynchronous I/O,其實不然。Asynchronous I/O是說用戶發起read等IO操作后,去做其它的事情了,而系統在完成IO操作后,用signal的方式通知用戶完成。目前使用此模型的http服務器有asyncio等。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/64571.html
原文 先說1.1總攬: Reactor模式 Reactor模式中的協調機制Event Loop Reactor模式中的事件分離器Event Demultiplexer 一些Event Demultiplexer處理不了的復雜I/O接口比如File I/O、DNS等 復雜I/O的解決方案 未完待續 前言 nodejs和其他編程平臺的區別在于如何去處理I/O接口,我們聽一個人介紹nodejs,總是...
摘要:異步和事件驅動注本文是對眾多博客的學習和總結,可能存在理解錯誤。接觸有兩個月,對的兩大特性一直有點模糊,即異步和事件驅動。 nodejs 異步I/O和事件驅動 注:本文是對眾多博客的學習和總結,可能存在理解錯誤。請帶著懷疑的眼光,同時如果有錯誤希望能指出。 接觸nodejs有兩個月,對nodejs的兩大特性一直有點模糊,即異步IO和事件驅動。通過對《深入淺出nodejs》和幾篇博客的閱...
摘要:對于而言,單線程指的是它的執行線程是單線程。對于來說,單線程不僅不是劣勢,它對于降低編程復雜度還有很重要的作用,單線程避免了多線程編程模型多線程死鎖狀態同步等問題。單線程的應用是脆弱了,但群體的力量是強大的。 我們常聽說 JavaScript 是單線程的,那這個單線程是什么意思呢?單線程是否意味 JavaScript 存在性能缺陷呢? 在瀏覽器端,JavaScript 單線程指的是 J...
摘要:事件驅動在中,當某個執行完畢后,會以事件的形式通知執行操作的線程而線程去執行對應事件的回調函數。為了處理異步,線程必須要有事件循環,不斷的檢查有沒有事件要處理,并依次處理。其實在底層中,有一半的代碼,都是在處理事件隊列回調函數。 事件驅動 上一節中,我們提到異步I/O;當I/O處理完畢后,nodejs是怎樣知道I/O已經完成了呢?又是怎樣去處理的呢?答案是:事件驅動(事件循環)機制。 ...
閱讀 3525·2023-04-26 00:16
閱讀 1361·2021-11-25 09:43
閱讀 3824·2021-11-23 09:51
閱讀 2964·2021-09-24 09:55
閱讀 713·2021-09-22 15:45
閱讀 1387·2021-07-30 15:30
閱讀 3064·2019-08-30 14:04
閱讀 2237·2019-08-26 13:46