天天看點

node.js基本工作原理及流程

概述

Node.js是什麼

Node 是一個伺服器端 JavaScript 解釋器,用于友善地搭建響應速度快、易于擴充的網絡應用。Node.js 使用事件驅動, 非阻塞I/O 模型而得以輕量和高效,非常适合在分布式裝置上運作資料密集型的實時應用。 

Node.js 是一個可以讓 JavaScript 運作在浏覽器之外的平台。它實作了諸如檔案系統、子產品、包、作業系統 API、網絡通信等 Core JavaScript 沒有或者不完善的功能。曆史上将 JavaScript移植到浏覽器外的計劃不止一個,但Node.js 是最出色的一個。

什麼是v8引擎

V8 JavaScript 引擎是 Google 用于其 Chrome 浏覽器的底層 JavaScript 引擎。很少有人考慮 JavaScript 在客戶機上實際做了些什麼?實際上,JavaScript 引擎負責解釋并執行代碼。Google 使用 V8 建立了一個用 C++ 編寫的超快解釋器,該解釋器擁有另一個獨特特征;您可以下載下傳該引擎并将其嵌入任何 應用程式。V8 JavaScript 引擎并不僅限于在一個浏覽器中運作。是以,Node 實際上會使用 Google 編寫的 V8 JavaScript 引擎,并将其重建為可在伺服器上使用。

Node.js的作用

Node 公開宣稱的目标是 “旨在提供一種簡單的建構可伸縮網絡程式的方法”。我們來看一個簡單的例子,在 Java™ 和 PHP 這類語言中,每個連接配接都會生成一個新線程,每個新線程可能需要 2 MB 的配套記憶體。在一個擁有 8 GB RAM 的系統上,理論上最大的并發連接配接數量是 4,000 個使用者。随着您的客戶群的增長,如果希望您的 Web 應用程式支援更多使用者,那麼,您必須添加更多伺服器。是以在傳統的背景開發中,整個 Web 應用程式架構(包括流量、處理器速度和記憶體速度)中的瓶頸是:伺服器能夠處理的并發連接配接的最大數量。這個不同的架構承載的并發數量是不一緻的。 

而Node的出現就是為了解決這個問題:更改連接配接到伺服器的方式。在Node 聲稱它不允許使用鎖,它不會直接阻塞 I/O 調用。Node在每個連接配接發射一個在 Node 引擎的程序中運作的事件,而不是為每個連接配接生成一個新的 OS 線程(并為其配置設定一些配套記憶體)。

Node.js能做什麼

借用一句經典的描述Node.js的話:正如 JavaScript 為用戶端而生,Node.js 為網絡而生。 

使用Node.js,你可以輕易的實作:

  • 具有複雜邏輯的網站;
  • 基于社交網絡的大規模 Web 應用;
  • Web Socket 伺服器;
  • TCP/UDP 套接字應用程式;
  • 指令行工具;
  • 互動式終端程式;
  • 帶有圖形使用者界面的本地應用程式;
  • 單元測試工具;
  • 用戶端 JavaScript 編譯器。

什麼是事件驅動程式設計

在我們使用Java,PHP等語言實作程式設計的時候,我們面向對象程式設計是完美的程式設計設計,這使得他們對其他程式設計方法不屑一顧。卻不知大名鼎鼎Node使用的卻是事件驅動程式設計的思想。那什麼是事件驅動程式設計。 

事件驅動程式設計,為需要處理的事件編寫相應的事件處理程式。代碼在事件發生時執行。 

為需要處理的事件編寫相應的事件處理程式。要了解事件驅動和程式,就需要與非事件驅動的程式進行比較。實際上,現代的程式大多是事件驅動的,比如多線程的程式,肯定是事件驅動的。早期則存在許多非事件驅動的程式,這樣的程式,在需要等待某個條件觸發時,會不斷地檢查這個條件,直到條件滿足,這是很浪費cpu時間的。而事件驅動的程式,則有機會釋放cpu進而進入睡眠态(注意是有機會,當然程式也可自行決定不釋放cpu),當事件觸發時被作業系統喚醒,這樣就能更加有效地使用cpu。 

來看一張簡單的事件驅動模型(uml):

事件驅動模型主要包含3個對象:事件源、事件和事件處理程式。

  • 事件源:産生事件的地方(html元素)
  • 事件:點選/滑鼠操作/鍵盤操作等等
  • 事件對象:當某個事件發生時,可能會産生一個事件對象,該時間對象會封裝好該時間的資訊,傳遞給事件處理程式
  • 事件處理程式:響應使用者事件的代碼 

    其實我們使用的window系統也算得上是事件驅動了。我們來看一個簡單的事例:監聽滑鼠點選事件,并能夠顯示滑鼠點選的位置x,y。

    <html> 
       <head> 
       <script> 
       function test1(e){ 
         window.alert("x="+e.clientX+"y="+e.clientY); 
       } 
       </script> 
       </head> 
       <body onmousedown="test1(event)"> 
       </body> 
    </html>      

Node.js運作原理分析

當我們搜尋Node.js時,奪眶而出的關鍵字就是 “單線程,異步I/O,事件驅動”,應用程式的請求過程可以分為倆個部分:CPU運算和I/O讀寫,CPU計算速度通常遠高于磁盤讀寫速度,這就導緻CPU運算已經完成,但是不得不等待磁盤I/O任務完成之後再繼續接下來的業務。 

是以I/O才是應用程式的瓶頸所在,在I/O密集型業務中,假設請求需要100ms來完成,其中99ms化在I/O上。如果需要優化應用程式,讓他能同時處理更多的請求,我們會采用多線程,同時開啟100個、1000個線程來提高我們請求處理,當然這也是一種可觀的方案。 

但是由于一個CPU核心在一個時刻隻能做一件事情,作業系統隻能通過将CPU切分為時間片的方法,讓線程可以較為均勻的使用CPU資源。但作業系統在核心切換線程的同時也要切換線程的上線文,當線程數量過多時,時間将會被消耗在上下文切換中。是以在大并發時,多線程結構還是無法做到強大的伸縮性。 

那麼是否可以另辟蹊徑呢?!我們先來看看單線程,《深入淺出Node》一書提到 “單線程的最大好處,是不用像多線程程式設計那樣處處在意狀态的同步問題,這裡沒有死鎖的存在,也沒有線程上下文切換所帶來的性能上的開銷”,那麼一個線程一次隻能處理一個請求豈不是無稽之談,先讓我們看張圖: 

Node.js的單線程并不是真正的單線程,隻是開啟了單個線程進行業務處理(cpu的運算),同時開啟了其他線程專門處理I/O。當一個指令到達主線程,主線程發現有I/O之後,直接把這個事件傳給I/O線程,不會等待I/O結束後,再去處理下面的業務,而是拿到一個狀态後立即往下走,這就是“單線程”、“異步I/O”。 

I/O操作完之後呢?Node.js的I/O 處理完之後會有一個回調事件,這個事件會放在一個事件處理隊列裡頭,在程序啟動時node會建立一個類似于While(true)的循環,它的每一次輪詢都會去檢視是否有事件需要處理,是否有事件關聯的回調函數需要處理,如果有就處理,然後加入下一個輪詢,如果沒有就退出程序,這就是所謂的“事件驅動”。這也從Node的角度解釋了什麼是”事件驅動”。 

在node.js中,事件主要來源于網絡請求,檔案I/O等,根據事件的不同對觀察者進行了分類,有檔案I/O觀察者,網絡I/O觀察者。事件驅動是一個典型的生産者/消費者模型,請求到達觀察者那裡,事件循環從觀察者進行消費,主線程就可以馬不停蹄的隻關注業務不用再去進行I/O等待。

Node.js的簡單實踐

關于node的環境搭建這裡就不說明了node入門。這裡為了友善大家了解,我們寫一個簡單的登入執行個體。 

這裡為了友善前端小白的了解,新增一個小節,如何使用Node搭建一個新的項目。 

## 使用Node建立項目 

安裝Express

npm install -g express  
 npm install -g express-generator      

建立項目

express -t ejs newsproject       

按照提示進入項目目錄,運作npm安裝

cd newsprojec
 npm install        

運作項目

node app.js        

浏覽器通路:http://127.0.0.1:3000/即可見nodejs站點頁面即可。 

接下來我們寫一個簡單的例子,來看一下效果圖: 

整個目錄如下:

根目錄--------------
    |-package.json
    |-test.js
    |-public
        |-main.html
        |-next.html      

整個目錄包含三個檔案,test.js(作為控制檔案)、main.html和next.html作為頁面的顯示檔案。 

來看一下代碼: 

test.js(作為控制檔案)

// file name :test.js
var express             = require('express');
var app                 = express();
var bodyParse           = require('body-parser')
var cookieParser        = require('cookie-parser') ;
app.use(cookieParser()) ;
app.use(bodyParse.urlencoded({extended:false})) ;

// 處理根目錄的get請求
app.get('/',function(req,res){
    res.sendfile('public/main.html') ;
    console.log('main page is required ');
}) ;

// 處理/login的get請求
app.get('/add', function (req,res) {
    res.sendfile('public/add.html') ;
    console.log('add page is required ') ;
}) ;

// 處理/login的post請求
app.post('/login',function(req,res){
    name=req.body.name ;
    pwd=req.body.pwd   ;
    console.log(name+'--'+pwd) ;
    res.status(200).send(name+'--'+pwd) ;
});

// 監聽3000端口
var server=app.listen(3000) ;      

 main.html的代碼

<html>

    <link rel="stylesheet" type="text/css" href="http://fonts.useso.com/css?family=Tangerine|Inconsolata|Droid+Sans">

    <style>
        div#test{
            font-family: 'Tangerine',serif;
            font-size: 48px;
        }
        p#link1{
            font-family: 'Tangerine',serif;
        }

    </style>

    <script src="//cdn.bootcss.com/jquery/2.2.1/jquery.min.js"></script>

</head>
<body>

<div id="test">
    <h1>Main Page</h1>
</div>
<p>Register & Login</p>
<form action="test.jsp" method="post">
    賬号 :
    <input type="text" id="name" />
    <br/><br/>
    密碼 :
    <input type="text" id="pwd" />
    <br/><br/>
    &nbsp&nbsp&nbsp&nbsp&nbsp
    <div><a href="/add" id="add">EXTRA</a></div>
    <input type="button" value="Submit" id="x">
</form>

</body>

    <script type="text/javascript">

        var after_login=function(data,status){
            if (status=='success'){
                alert(data+'--'+status) ;
            }
            else alert('login refused') ;

        }

        $(document).ready(function(){
            $("#x").click(function(){
                var name    =   $("#name").val() ;
                var pwd     =   $("#pwd").val() ;
                $.post('http://127.0.0.1:3000/login',
                {
                    name    :   name ,
                    pwd     :   pwd
                },
//                        function(data,status){
//                            alert(data+'--'+status) ;
//                        }
                        after_login
                );
//                $.get('add',function(data,status){
//                    document.write(data) ;
//                }) ;
            });
        });
    </script>
</html>      

 next.html的代碼

<!DOCTYPE html>
<html>
<head lang="en">
    <meta charset="UTF-8">
    <title>第二頁面</title>
</head>
<body>
<h1>This is an additional web page</h1>
<p>just for test</p>
</body>
</html>