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/>       <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基本工作原理及流程