1. 程式人生 > >node.js基本工作原理及流程

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

實例 I/O 同步問題 code 本地 https 事情 sans ready

概述

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>

  

本文摘自:https://blog.csdn.net/xiangzhihong8/article/details/53954600

原創作者:xiangzhihong8

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