HTTP概念進階

1.什麼是回調?javascript

在Java中,就是類A調用類B中的某個方法b,而後類B又在某個時候反過來調用類A中的某個方法a,對於A來講,這個a方法便叫作回調方法

pubilc interface CallBack{

public void callbackMethod();

}

public class A implements CallBack{ // A實現接口CallBack

B b = new B();

public void do(){

b.doSomething(this); // A運行時調用B中doSomething方法,以自身傳入參數,B已取得A,能夠隨時回調A所實現的CallBack接口中的方法

}

public void callbackMethod(){ // 對A來講,該方法就是回調方法

System.out.println("callbackMethod is executing!");

}

}

public class B{

public void doSomething(CallBack cb){ // B擁有一個參數爲CallBack接口類型的方法

System.out.println(「I am processing my affairs… 」);

System.out.println(「then, I need invoke callbackMethod…」);

cb.callbackMethod();

}

}


2.什麼是同步/異步

進程同步用來實現程序併發執行時候的可再現性。html

一.進程同步及異步的概念java

1.進程同步:就是在發出一個功能調用時,在沒有獲得結果以前,該調用就不返回。也就是必須一件一件事作,等前一件作完了才能作下一件事.就像早上起牀後,先洗涮,而後才能吃飯,不能在洗涮沒有完成時,就開始吃飯.按照這個定義,其實絕大多數函數都是同步調用(例如sin,isdigit等)。可是通常而言,咱們在說同步、異步的時候,特指那些須要其餘部件協做或者須要必定時間完成的任務。最多見的例子就是node

sendmessage。該函數發送一個消息給某個窗口,在對方處理完消息以前,這個函數不返回。當對方處理完畢之後,該函數才把消息處理函數所返回的lresult值返回給調用者。git

2.異步程序員

異步的概念和同步相對。當一個異步過程調用發出後,調用者不能馬上獲得結果。實際處理這個調用的部件在完成後,經過狀態、通知和回調來通知調用者。github

以casycsocket類爲例(注意,csocket從casyncsocket派生,可是其功能已經由異步轉化爲同步),當一個客戶端經過調用connect函數發出一個鏈接請求後,調用者線程馬上能夠朝下運行。當鏈接真正創建起來之後,socket底層會發送一個消息通知該對象。web

這裏提到執行部件和調用者經過三種途徑返回結果:狀態、通知和回調。可使用哪種依賴於執行部件的實現,除非執行部件提供多種選擇,不然不受調用者控制。若是執行部件用狀態來通知,那麼調用者就須要每隔必定時間檢查一次,效率就很低(有些初學多線程編程的人,總喜歡用一個循環去檢查某個變量的值,這實際上是一種很嚴重的錯誤)。若是是使用通知的方式,效率則很高,由於執行部件幾乎不須要作額外的操做。至於回調函數,其實和通知沒太多區別。數據庫

進程同步的基本概念編程

在計算機系統中,因爲資源有限而致使了進程之間的資源競爭和共享,所以,進程的併發執行不只僅是用戶程序的執行開始時間的隨機性和提升資源利用率的結果,也是資源有限性致使資源的競爭與共享對進程的執行過程進行制約所形成的。那麼,在進程的併發執行過程當中存在哪些制約呢?

二.同步與異步傳輸:

1.異步傳輸

一般,異步傳輸是以字符爲傳輸單位,每一個字符都要附加 1 位起始位和 1 位中止位,以標記一個字符的開始和結束,並以此實現數據傳輸同步。所謂異步傳輸是指字符與字符(一個字符結束到下一個字符開始)之間的時間間隔是可變的,並不須要嚴格地限制它們的時間關係。起始位對應於二進制值 0,以低電平表示,佔用 1 位寬度。中止位對應於二進制值 1,以高電平表示,佔用 1~2 位寬度。一個字符佔用 5~8位,具體取決於數據所採用的字符集。例如,電報碼字符爲 5 位、ASCII碼字符爲 7 位、漢字碼則爲8 位。此外,還要附加 1 位奇偶校驗位,能夠選擇奇校驗或偶校驗方式對該字符實施簡單的差錯控制。發送端與接收端除了採用相同的數據格式(字符的位數、中止位的位數、有無校驗位及校驗方式等)外,還應當採用相同的傳輸速率。典型的速率有:9 600 b/s、19.2kb/s、56kb/s等。

異步傳輸又稱爲起止式異步通訊方式,其優勢是簡單、可靠,適用於面向字符的、低速的異步通訊場合。例如,計算機與Modem之間的通訊就是採用這種方式。它的缺點是通訊開銷大,每傳輸一個字符都要額外附加2~3位,通訊效率比較低。例如,在使用Modem上網時,廣泛感受速度很慢,除了傳輸速率低以外,與通訊開銷大、通訊效率低也密切相關。

2. 同步傳輸

一般,同步傳輸是以數據塊爲傳輸單位。每一個數據塊的頭部和尾部都要附加一個特殊的字符或比特序列,標記一個數據塊的開始和結束,通常還要附加一個校驗序列 (如16位或32位CRC校驗碼),以便對數據塊進行差錯控制。所謂同步傳輸是指數據塊與數據塊之間的時間間隔是固定的,必須嚴格地規定它們的時間關係。

三.同步阻塞與異步阻塞:

同步是阻塞模式,異步是非阻塞模式。 

個人理解:同步是指兩個線程的運行是相關的,其中一個線程要阻塞等待另一個線程的運行。異步的意思是兩個線程毫無相關,本身運行本身的。 

同步是指:發送方發出數據後,等接收方發回響應之後才發下一個數據包的通信方式。 

異步是指:發送方發出數據後,不等接收方發回響應,接着發送下個數據包的通信方式。 

舉個不太恰當的例子,就像: 

SendMessage(...) 

TRACE0("just  like  send"); 

PostMessage(...) 

TRACE0("just  like  WSASend  using  overlapped"); 

 SendMessage是調用的時候不返回,等消息響應後才執行TRACE0,這就是同步. 

PostMessage是調用後立刻返回,不用消息響應就執行TRACE0,這就是異步.

四.其它解釋:

 同步和異步的區別

 舉個例子:普通B/S模式(同步)AJAX技術(異步)

同步:提交請求->等待服務器處理->處理完畢返回 這個期間客戶端瀏覽器不能幹任何事

異步: 請求經過事件觸發->服務器處理(這是瀏覽器仍然能夠做其餘事情)->處理完畢

同步就是你叫我去吃飯,我聽到了就和你去吃飯;若是沒有聽到,你就不停的叫,直到我告訴你聽到了,才一塊兒去吃飯。

異步就是你叫我,而後本身去吃飯,我獲得消息後可能當即走,也可能等到下班纔去吃飯。

因此,要我請你吃飯就用同步的方法,要請我吃飯就用異步的方法,這樣你能夠省錢。

舉個例子 打電話時同步 發消息是異步


3.什麼是I/O?
I/O是 input/output的縮寫,即輸入輸出端口。每一個設備都會有一個專用的I/O地址,用來處理本身的輸入輸出信息。CPU與外部設備、存儲器的鏈接和數據交換都須要經過接口設備來實現,。,習慣上說到接口只是指I/O接口。


4.什麼是單線程/多線程?
打個比方,單線程就是你去廚房有燒飯又燒菜,一我的來回跑;多線程就是兩我的,一個單作飯,一個單作菜。這樣的解釋應該比純理論的好理解一點吧?
再補充一下,多線程就是一個CPU虛擬了幾個CPU,而雙核就是實際上就有兩個線程了,固然,還能夠每一個核再去虛擬多個線程(也能夠理解成多個流水線吧)
 
 
5.什麼是阻塞/非阻塞?
阻塞
    阻塞調用是指調用結果返回以前,當前線程會被掛起。函數只有在獲得結果以後纔會返回。有人也許會把阻塞調用和同步調用等同起來,實際上它們是不一樣的。對於同步調用來講,不少時候當前線程仍是激活的,只是從邏輯上當前函數沒有返回而已。例如,咱們在CSocket中調用Receive函數,若是緩衝區中沒有數據,這個函數就會一直等待,直到有數據才返回。而此時,當前線程還會繼續處理各類各樣的消息。若是主窗口和調用函數在同一個線程中,除非你在特殊的界面操做函數中調用,其實主界面仍是應該能夠刷新。socket接收數據的另一個函數recv則是一個阻塞調用的例子。當socket工做在阻塞模式的時候, 若是沒有數據的狀況下調用該函數,則當前線程就會被掛起,直到有數據爲止。
 
非阻塞
    非阻塞和阻塞的概念相對應,指在不能馬上獲得結果以前,該函數不會阻塞當前線程,而會馬上返回。
 
對象的阻塞模式和阻塞函數調用
 
    對象是否處於阻塞模式和函數是否是阻塞調用有很強的相關性,可是並非一一對應的。阻塞對象上能夠有非阻塞的調用方式,咱們能夠經過必定的API去輪詢狀態,在適當的時候調用阻塞函數,就能夠避免阻塞。而對於非阻塞對象,調用特殊的函數也能夠進入阻塞調用。函數select就是這樣的一個例子。
 
 
6.什麼是事件?
事件:樣本空間的一些子集稍爲隨機事件,簡稱事件. 
= =本身理解:大概就是進行了操做產生了變化對應的這個事

7.什麼是事件驅動?
早期程序使用輸入-操做-輸出的機制,整個流程徹底由程序員事先設定好。
面向對象程序設計當中採用的就是事件驅動機制。好比說鼠標左擊、雙擊都是具體事件,根據這些事件啓用預先設置的相應動做就是事件驅動機制。



8.什麼是基於事件驅動的回調?

這一切都歸結於「Node.js是事件驅動的」這一事實。好吧,其實我也不是特別確切的瞭解這句話的意思。不過我會試着解釋,爲何它對咱們用Node.js寫網絡應用(Web based application)是有意義的。

當咱們使用 http.createServer 方法的時候,咱們固然不僅是想要一個偵聽某個端口的服務器,咱們還想要它在服務器收到一個HTTP請求的時候作點什麼。

問題是,這是異步的:請求任什麼時候候均可能到達,可是咱們的服務器卻跑在一個單進程中。

寫PHP應用的時候,咱們一點也不爲此擔憂:任什麼時候候當有請求進入的時候,網頁服務器(一般是Apache)就爲這一請求新建一個進程,而且開始從頭至尾執行相應的PHP腳本。

那麼在咱們的Node.js程序中,當一個新的請求到達8888端口的時候,咱們怎麼控制流程呢?

嗯,這就是Node.js/JavaScript的事件驅動設計可以真正幫上忙的地方了——雖然咱們還得學一些新概念才能掌握它。讓咱們來看看這些概念是怎麼應用在咱們的服務器代碼裏的。

咱們建立了服務器,而且向建立它的方法傳遞了一個函數。不管什麼時候咱們的服務器收到一個請求,這個函數就會被調用。

咱們不知道這件事情何時會發生,可是咱們如今有了一個處理請求的地方:它就是咱們傳遞過去的那個函數。至於它是被預先定義的函數仍是匿名函數,就可有可無了。

這個就是傳說中的 回調 。咱們給某個方法傳遞了一個函數,這個方法在有相應事件發生時調用這個函數來進行 回調 。

至少對我來講,須要一些功夫才能弄懂它。你若是仍是不太肯定的話就再去讀讀Felix的博客文章。

讓咱們再來琢磨琢磨這個新概念。咱們怎麼證實,在建立完服務器以後,即便沒有HTTP請求進來、咱們的回調函數也沒有被調用的狀況下,咱們的代碼還繼續有效呢?咱們試試這個:

var http = require("http"); function onRequest(request, response) {   console.log("Request received.");   response.writeHead(200, {"Content-Type": "text/plain"});   response.write("Hello World");   response.end(); } http.createServer(onRequest).listen(8888); console.log("Server has started.");

注意:在 onRequest (咱們的回調函數)觸發的地方,我用 console.log 輸出了一段文本。在HTTP服務器開始工做以後,也輸出一段文本。

當咱們與往常同樣,運行它node server.js時,它會立刻在命令行上輸出「Server has started.」。當咱們向服務器發出請求(在瀏覽器訪問http://localhost:8888/),「Request received.」這條消息就會在命令行中出現。

這就是事件驅動的異步服務器端JavaScript和它的回調啦!

(請注意,當咱們在服務器訪問網頁時,咱們的服務器可能會輸出兩次「Request received.」。那是由於大部分服務器都會在你訪問 http://localhost:8888 /時嘗試讀取 http://localhost:8888/favicon.ico )


9.什麼是時間循環?

在瞭解node.js以前你首先須要瞭解的一個基本的論點是:I/O是「昂貴」的。

 

所以對於當前的編程技術而言,最大的浪費來自於等待I/O的完成。下面列出了改善該問題的幾種方式,其中的某個能夠幫助你提升性能:

 

  • 同步:在某一時刻,一次只處理一個請求。但這種狀況下,任何一個請求都會「耽誤」(阻塞)全部其餘的請求。
  • fork一個新進程:對於每一個請求,你啓動一個新的進程來處理。這種狀況下,沒法達到很好的擴展,上百個鏈接就意味着上百個進程的存在。fork()函數是Unix程序員的錘子,由於使用它很方便,因此每一個程序都看起來像個釘子同樣(都喜歡用錘子拿來敲敲它)。因此,常常形成過分使用,而有些過往矯正。
  • 線程:開啓一個新的線程來處理每一個請求。這種方式很簡單,而且對於內核來說使用線程也比fork進程來得「親切」,由於一般線程花費比進程更少的開銷。缺點:你的機子可能不支持基於線程編程,而且基於線程的程序,其複雜度增加得很是快,同時你還會有對訪問共享資源的擔心。

 

你須要瞭解的第二個論點是:被線程處理的每一個鏈接都是「內存昂貴的」。

Apache是採用多線程處理請求的。它對於每一個請求「孵化」出一個線程(或者進程,這取決於配置)來處理。你將會看到隨着併發鏈接數的增加以及更多的線程須要服務多個客戶端時,那些開銷有多消耗內存。Nginx跟Node.js都不是基於多線程模型的,由於線程跟進程都須要很是大的內存開銷。他們都是單線程的,可是基於事件的。這種基於單線程的模型消除了爲了處理不少請求而建立成百上千個線程或進程帶來的開銷。

 

Node.js爲你的代碼保持單線程的運行環境

 

它確實是基於單線程運行的,你沒法編寫任何代碼來執行併發;例如執行一個"sleep"操做將阻塞整個服務器1秒鐘。

 

[javascript]  view plain copy print ?
 
  1. while(new Date().getTime() < now + 1000) {  
  2.    // do nothing  
  3. }  
 

 

所以,當代碼運行的時候,node.js將不會響應來自客戶端的其餘請求,由於它只有一個線程來執行你的代碼。或者,若是你有某些CPU密集型的操做,好比說,重置圖片的尺寸,那也將阻塞全部其餘的請求。

 

...然而,除了你的代碼以外,其餘的一切都是併發執行

 

 

在一個單獨的請求裏,沒有辦法可使得代碼並行執行。然而,全部的I/O都是基於時間的而且是異步的,因此接下來的代碼將不會阻塞服務器:

[javascript]  view plain copy print ?
 
  1. c.query(  
  2.    'SELECT SLEEP(20);',  
  3.    function (err, results, fields) {  
  4.      if (err) {  
  5.        throw err;  
  6.      }  
  7.      res.writeHead(200, {'Content-Type': 'text/html'});  
  8.      res.end('<html><head><title>Hello</title></head><body><h1>Return from async DB query</h1></body></html>');  
  9.      c.end();  
  10.     }  
  11. );  


若是你在一個請求中這麼作,其餘請求可以很好得被執行。

 

爲何這是更好的方式?何時咱們須要從同步轉向異步/併發執行?

 

採用同步執行是個不錯的方式,由於它使得編碼變得容易(對比線程而言,併發問題經常讓你陷入萬劫不復)。

在node.js中,你不須要去擔憂你的代碼在後端會發生。你只須要在你作I/O操做的時候使用回調就能夠了。你會獲得保證:你的代碼不會被中斷,而且I/O操做也不會阻塞其餘請求(由於沒有了那些線程/進程須要花費的開銷,好比在Apache中會發生的內存太高等)。


採用異步I/O也很好,由於I/O比那些執行其餘操做更昂貴,咱們應該作一些更有意義的事情而不是去等待I/O。

 

一個事件循環指的是——一個實體,它能夠處理外部事件而且將它們轉化爲回調的執行。所以,I/O調用變成了node.js能夠從一個請求切換到另一個請求的「點」,你的代碼保存了回調並返回控制權給node.js運行時環境。而回調在最終得到了數據以後被執行。

固然,在node.js內部,仍然是依靠線程和進程來進行數據訪問、處理其餘任務執行。然而,這些都沒有明確地對你的代碼暴露出來,因此你不須要額外擔憂內部如何處理I/O之間的交互。對比Apache的模型,它少去了不少線程以及線程開銷,由於對每一個鏈接來說單獨的線程不是必須的。僅僅是當你絕對須要讓某個操做併發執行纔會須要線程,但即使如此線程也是node.js本身管理的。


除了I/O調用以外,node.js期待全部的請求最好快速返回。好比,那些CPU密集型的工做應該被隔離到另外一個進程上去執行(經過與事件交互或者使用像WebWorker同樣的抽象)。這很明顯意味着當你與事件交互的時候,若是沒有另外一個線程在後端(node.js運行時),那麼你是沒法並行化執行代碼的。基本上,全部能夠emit事件的對象(例如EventEmitter的實例)都支持基於事件的異步交互而且你也能夠與「blocking code」交互(例如使用文件、sockets或者在node.js中是EventEmitter的子進程)。使用這種方案的話,就可以很好得利用多核的優點了,能夠看看:node-http-proxy。

 

內部實現

 

內部,node.js依賴於libev提供的事件循環,libeio是對於libev的補充,node.js使用池化的線程來提供對於異步I/O的支持。若是你想了解更多細節,你能夠看一下libev的文檔

 

如何在Node.js中實現異步

 

 

Tim Caswell在其PPT中描述了整個模式:

 

  • First-classfunction:例如咱們將function做爲數據傳遞,包裹他們以在須要的時候執行。
  • Function組裝:就像你瞭解的關於異步函數或者閉包同樣,在觸發了I/O事件以後執行。
  • 回調計數器:對於基於事件的回調,你沒法保證對於任何特殊的命令,I/O事件都會被執行。因此,一旦你須要屢次查詢來完成某個處理,一般你僅須要對任何的併發I/O操做進行計數,而後在你確實須要最後的結果的時候檢查是否必要的操做都已所有完成(其中的一個例子是在事件回調中,經過對返回的數據庫查詢進行計數)。查詢會被併發執行,而且I/O也對此提供支持(例如能夠經過鏈接池的方式實現併發查詢)。
  • 事件循環:上面已經提到過,你能夠將blockingcode包裹進一個基於事件的抽象中去(好比經過運行一個子進程,而後當它執行完成以後再返回)。

再次申明原文出處:http://blog.mixu.net/2011/02/01/understanding-the-node-js-event-loop/

相關文章
相關標籤/搜索