深入理解node.js非同步編程

來源:互聯網
上載者:User

標籤:log   有一個   message   current   計算過程   運營   self   老婆   html   

  1. 概述目前開源社區最火熱的技術當屬Node.js莫屬了,作為使用Javascript為主要開發語言的伺服器端編程技術和平台,一開始就註定會引人矚目。 當然能夠吸引眾人的目光,肯定不是三教九流之輩,必然擁有獨特的優勢和魅力,才能引起群猿追逐。其中當屬非同步IO和事件編程模型,本文據Node.js的非同步IO和事件編程做深入分析。 ##2. 什麼是非同步同步和非同步是一個比較早的概念,大抵在作業系統發明時應該就出現了。舉一個最簡單的生活中的例子,比如發簡訊的情況會比較好說明他們的區別:同步:正在處於苦逼工作狀態中的我,但狗屎運的交到了女朋友並正處於處於熱戀期,因此傳送簡訊給她詢問那個餐廳吃飯,急不可耐的看著手機等待簡訊回覆,收到資訊看完是否加班或者下班;非同步:正處於公司運營決策關鍵工作狀態中的你,不可以被打斷太久,隨便發送了一條詢問老婆什麼時候做好晚飯然後吃飯的簡訊後立馬返回工作,一邊工作一邊等待簡訊回覆通知,根據通知決定是否再工作和下班。由此可以看出,同步和非同步特點是:

  至少在兩個對象之間需要協作(男朋友和女朋友,老公和老婆);

  兩個對象都需要處理一系列的事情(工作和吃飯)。 另一個類似的關於CPU計算和磁碟操作編的例子: 同步:CPU需要計算10個資料,每計算一個結果後,將其寫入磁碟,等待寫入成功後,再計算下一個資料,直到完成。 非同步:CPU需要計算10個資料,每計算一個結果後,將其寫入磁碟,不等待寫入成功與否的結果,立刻返回繼續計算下一個資料,計算過程中可以收到之前寫入是否成功的通知,直到完成。

  ##3. 為什麼需要非同步知其然,還要知其所以然,讀者可能會問,為什麼存在非同步?根據上面發簡訊和磁碟操作的例子,答案很明顯,為了提高辦事的效率,CPU計算速度和磁碟的讀寫速度差太遠了,磁碟供不應求,因此有了電腦的儲存系統的分層設計,平衡了效率和成本。可以說懶惰推動人類的進步,任何可以降低花費時間而達到同等功效的方法肯定會被優先採用。傳送簡訊時等待對方回複的時間純粹的浪費掉了,CPU寫入磁碟等待返回的結果的等待時間也被無情的消耗了,這是一個講究效率的時代完全不能忍受的,因此讓員工一直處於忙碌狀態,最大限度的榨取員工價值是老闆追求的,讓CPU和磁碟都不停的滿負荷處理事務也是效率需要的。因此,非同步處理出現了。

  ##4. Node.js非同步IO與事件初次接觸Node.js,恐怕任何人都會被先先灌輸的第一條Node.js就與眾不同的地方:非同步IO和事件驅動。毫無疑問,這確實是Node.js最令人津津樂道的特色之處,也是本文重點分析的地方。 ###4.1 Node.js非同步機制由於非同步高效性,node.js設計之初就考慮做為一個高效的web伺服器,作者理所當然地使用了非同步機制,並貫穿於整個node.js的編程模型中,新手在使用node.js編程時,往往會羈絆於由於其他程式設計語言的習慣,比如C/C++,覺得無所適從。我們可以從以下一段簡單的睡眠程式碼窺視出他們的區別,下面是摘自《linux程式設計》列印10個時間的C代碼:

  #include

  #include

  #include

  int main()

  {

  int i;

  time_t the_time;

  for(i = 1; i <= 10; i++) {

  the_time = time((time_t *)0);

  printf("The time is %ld\n", the_time);

  sleep(2);

  }

  exit(0);

  }

  編譯後列印結果如下: The time is 1396492137 The time is 1396492139 The time is 1396492141 The time is 1396492143 The time is 1396492145 The time is 1396492147 The time is 1396492149 The time is 1396492151 The time is 1396492153 The time is 1396492155 從C語言的列印結果可以發現,是隔2秒列印一次,按照C程式該有的邏輯,代碼逐行執行。以下Node.js代碼本意如同上述C代碼,使用目的隔2秒列印一次時間,共列印10條(初次從C/C++轉來接觸Node.js的程式員可能會寫出下面的代碼):

  function test() {

  for (var i = 0; i < 10; i++) {

  console.log(new Date);

  setTimeout(function(){}, 2000); //睡眠2秒,然後再進行一下次for迴圈列印

  }

  };

  test();

  列印結果: Tue Apr 01 2014 14:53:22 GMT+0800 (中國標準時間)

  Tue Apr 01 2014 14:53:22 GMT+0800 (中國標準時間)

  Tue Apr 01 2014 14:53:22 GMT+0800 (中國標準時間)

  Tue Apr 01 2014 14:53:22 GMT+0800 (中國標準時間)

  Tue Apr 01 2014 14:53:22 GMT+0800 (中國標準時間)

  Tue Apr 01 2014 14:53:22 GMT+0800 (中國標準時間)

  Tue Apr 01 2014 14:53:22 GMT+0800 (中國標準時間)

  Tue Apr 01 2014 14:53:22 GMT+0800 (中國標準時間)

  Tue Apr 01 2014 14:53:22 GMT+0800 (中國標準時間)

  Tue Apr 01 2014 14:53:22 GMT+0800 (中國標準時間)

  觀察結果發現都是在14:53:22同一個時間點列印的,根本就沒有睡眠2秒後再執行下一輪迴圈列印!這是為什麼?從官方的文檔我們看出setTimeout是第二個參數表示逝去時間之後在執行第一個參數表示的callback函數,因此我們可以分析, 由於Node.js的非同步機制,setTimeout每個for迴圈到此之後,都註冊了一個2秒後執行的回呼函數然後立即返回馬上執行console.log(new Date),導致了所有列印的時間都是同一個點,因此我們修改for迴圈的代碼如下:

  for (var i = 0; i < 10; i++) {

  setTimeout(function(){

  console.log(new Date);

  }, 2000);

  }

  執行結果如下所示: Thu Apr 03 2014 09:30:35 GMT+0800 (中國標準時間)

  Thu Apr 03 2014 09:30:35 GMT+0800 (中國標準時間)

  Thu Apr 03 2014 09:30:35 GMT+0800 (中國標準時間)

  Thu Apr 03 2014 09:30:35 GMT+0800 (中國標準時間)

  Thu Apr 03 2014 09:30:35 GMT+0800 (中國標準時間)

  Thu Apr 03 2014 09:30:35 GMT+0800 (中國標準時間)

  Thu Apr 03 2014 09:30:35 GMT+0800 (中國標準時間)

  Thu Apr 03 2014 09:30:35 GMT+0800 (中國標準時間)

  Thu Apr 03 2014 09:30:35 GMT+0800 (中國標準時間)

  Thu Apr 03 2014 09:30:35 GMT+0800 (中國標準時間) 神奇,仍然是同一個時間點,見鬼!冷靜下來分析,時刻考慮非同步,for迴圈裡每次setTimeout註冊了2秒之後執行的一個列印時間的回呼函數,然後立即返回,再執行setTimeout,如此反覆直到for迴圈結束,因為執行速度太快,導致同一個時間點註冊了10個2秒後執行的回呼函數,因此導致了2秒後所有回呼函數的立即執行。 我們在for迴圈之前添加console.log("before FOR: " + new Date)和之後console.log("after FOR: " + new Date),來驗證我們的推測,列印結果如下(後面省略8條相同的列印行): before FOR: Thu Apr 03 2014 09:42:43 GMT+0800 (中國標準時間)

  after FOR: Thu Apr 03 2014 09:42:43 GMT+0800 (中國標準時間)

  Thu Apr 03 2014 09:42:45 GMT+0800 (中國標準時間)

  Thu Apr 03 2014 09:42:45 GMT+0800 (中國標準時間) …… (省略與上一行8條相同的列印行)由此可以窺視出Node.js非同步機制的端倪了,在for迴圈中的代碼於其後的代碼幾乎在一個單位秒內完成,而定時器中的回呼函數則按要求的2秒之後執行,也是同一秒內執行完畢。那麼如何?最初C語言每隔2秒列印一個系統時間的需求函數呢,我實現了如下一個wsleep函數,放在for迴圈中,可以達到該目的:

  function wsleep(milliSecond) {

  var startTime = new Date().getTime();

  while(new Date().getTime() <= milliSecond + startTime) {

  }

  }

  但是該函數有一個令他無法在項目中使用的缺陷,請問為什麼? ###4.2 Node.js事件編程事件編程並不是一個新的概念,做過介面UI編程的程式猿們可以覺得事件再熟悉不過了,特別是用戶端開發和web開發的感觸頗深吧,如Android、ios、或是javascript前端編程的工程師們,一個按鈕、一個清單項目、一個長按操作等等,每次按下都會由作業系統或者瀏覽器產生一個事件,你需要做的工作就是編寫和註冊這個事件的回呼函數(可能各自領域內不稱為回呼函數,但是從作業系統的角度考慮其實就是一個回呼函數),當這個事件發生時,執行你的回呼函數。Node.js與眾不同的時,它基因裡就是由事件和非同步組成的。請看用於生產環境中的真實項目代碼的一個片段(略去了一些不相關的代碼),我加上一段關於事件資訊的注釋,讓讀者更清晰:

  self.sio.sockets.on(‘connection‘, function(socket) { //監聽socket串連事件

  var addr = socket.handshake.address;

  var limiter = new RateLimiter(constant.RL_MAXREQRATELIMIT, constant.RL_RATELIMITUNIT, true);

  var connect = new Connection(socket);

  then(function(defer) {

  if (ipLimit) {

  throttle.throttleHandle(connect, null, defer); //結果回調處理事件

  } else {

  defer(null); //發送處理結果事件

  }

  }).all(function(defer) { //收到處理結果事件

  socket.on(‘message‘, function(data) { //監聽資料轉送事件

  cloudKeyMain(connect, 1, data, cloudKeyApi);

  });

  });

  socket.on("disconnect", function(data) { //監聽socket離線事件

  var currentSockClient = connect.client;

  if (currentSockClient) {

  currentSockClient.signalOffline(); //發送用戶端離線事件

  }

  });

  });

  從上面的代碼,我們可以看出Node.js無所不在的事件機制,事件機制讓我們專註與代碼業務的處理流程,提高了軟體開發的效率,降低了http://www.chinamaofa.com/代碼之間的耦合,讓人不被瑣事纏繞,編程更有趣。如何開始一個簡單的Node.js事件編程呢,答案是使用Node.js的javascript API核心模組events的events.EventEmitter類即可完成,下面以一個QQ的線上和離線來說明,事件機制的使用主要包括3個方面的內容:

  繼承events.EventEmitter事件類別,主要是屏蔽事件機制的實現(其實原理很簡單),讓我們直接使用;

  事件的註冊;

  事件的發布。

  var events = require(‘events‘);

  var util = require(‘util‘);

  function MyQQ() {

  events.EventEmitter.call(this);

  //……

  }

  util.inherits(MyQQ, events.EventEmitter);

  OK,上述代碼就完成了事件機制的添加,此時,我們的工作為QQ添加事件註冊函數進行事件的註冊,事件註冊主要是使用EventEmitter的http://www.sansewa.com/zhifa_anli/toufa/2018-08-24/816.htmlon()完成,因為我們繼承了EventEmitter,可以直接使用on函數,我們在on函數的第二個參數callback函數中自訂處理業務,並註冊自己的上線事件,以下是一個QQ上線時簡單的處理業務:

  function onlineHandle(QQNumber) {

  //擷取和QQNumber的連絡人清單

  //擷取離線訊息

  //……

  }

  var myQQ = new MyQQ();

  myQQ.on(“onLine”, onlineHandle);

  上述程式碼完成了事件的處理,下面輪到在什麼時候發布這個事件,下述的一個業務情境中可能是需要發布該事件的,發布事件用emit()函數:

  function main() {

  //串連伺服器

  //檢測登入狀態

  //登入伺服器成功後發布事件

  myQQ.emit(“onLine”,123655245);

  }

  上述myQQ.emit()函數執行後發布了onLine事件後,會立即執行onlineHandle()函數,處理我們註冊的商務邏輯,需要注意的是,事件發布函數emit第二個參數後的參數個數需要和我們註冊時的處理函數參數個數相同並且順序一致才能正確處理,為什麼有這樣的要求?這需要從Node.js事件的原理說起。

深入理解node.js非同步編程

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.