[Node.js] 01 - How to learn node.js

基本概念javascript

連接:https://www.zhihu.com/question/47244505/answer/105026648php

連接:How to decide when to use Node.js?html

JavaScript 是一種(最好的)編程語言, 主要做爲前端開發中用來增長網頁的動態功能,好比操做DOM, 讀取用戶輸入, 動畫效果, 提 交服務器請求(Ajax). JavaScript 是什麼? - 前端開發前端

NodeJs 是基於JavaScript的,能夠作爲後臺開發的語言. 提供了不少系統級的API,如文件操做、網絡編程等. 用事件驅動, 異步編程,主要是爲後臺網絡服務設計. Node.js是用來作什麼的? - 編程vue

ReactJS 爲Facebook開發的,更多的像一個JS的庫.主要是在前端Web開發中, 對MVC中的V進行操做. AngularJS和ReactJS分別是幹什麼的?不會Javascript是否能夠學習 - 前端開發java

React Native 它基於開源框架ReacJS,並可用來開發iOS和Android原生應用, 主要爲移動端服務. 深刻淺出 React Native:使用 JavaScript 構建原生應用 - 前端外刊評論 - 知乎專欄
 
 

學習指導node

The ideal candidate will have:git

  • Expereienced in web application developement using node.js
  • Experience using Chinese API, i.e. WeChat Open API

The following skills are an advantage:github

  • Experience in React

Experience:web

  • node.js: 1 year (Required)

 


做者:廠長

連接:https://www.zhihu.com/question/33578075/answer/56951771
原文地址 Node.js is the New Black
 
經過JavaScript語言開發web服務端的東西」。
node.js有非阻塞,事件驅動I/O等特性,從而讓高併發(high concurrency)在的輪詢(Polling)和comet構建的應用中成爲可能。
 
傳統方式:

瀏覽器給網站發請求的過程一直沒怎麼變過:

  1. 當瀏覽器給網站發了請求。
  2. 服務器收到了請求,而後開始搜尋被請求的資源。若是有須要,服務器還會查詢一下數據庫
  3. 最後把響應結果傳回瀏覽器。

不過,在傳統的web服務器中(好比Apache),每個請求都會讓服務器建立一個新的進程來處理這個請求

 

部分頁面請求:

後來有了Ajax。有了Ajax,咱們就不用每次都請求一個完整的新頁面了,取而代之的是,每次只請求須要的部分頁面信息就能夠了。這顯然是一個進步。

 

案例:

你的好友會隨時的推送新的狀態,而後你的新鮮事會實時自動刷新。要達成這個需求,咱們須要讓用戶一直與服務器保持一個有效鏈接。目前最簡單的實現方法,就是讓用戶和服務器之間保持長輪詢(long polling)

  長輪詢

HTTP請求不是持續的鏈接,你請求一次,服務器響應一次,而後就完了。

長輪訓是一種利用HTTP模擬持續鏈接技巧

具體來講,只要頁面載入了,無論你需不須要服務器給你響應信息,你都會給服務器發一個Ajax請求。【客戶端先發了再說】

這個請求不一樣於通常的Ajax請求,服務器不會直接給你返回信息,而是它要等着,直到服務器以爲該給你發信息了,它纔會響應。

好比,你的好友發了一條新鮮事,服務器就會把這個新鮮事當作響應發給你的瀏覽器,而後你的瀏覽器就刷新頁面了。

瀏覽器收到響應刷新完以後,再發送一條新的請求給服務器,這個請求依然不會當即被響應。

因而就開始重複以上步驟。利用這個方法,可讓瀏覽器始終保持等待響應的狀態。雖然以上過程依然只有非持續的Http參與,可是咱們模擬出了一個看似持續的鏈接狀態

傳統的長鏈接方式很差

咱們再看傳統的服務器(好比Apache)。每次一個新用戶連到你的網站上,你的服務器就得開一個鏈接。每一個鏈接都須要佔一個進程,這些進程大部分時間都是閒着的(好比等着你好友發新鮮事,等好友發完纔給用戶響應信息。或者等着數據庫返回查詢結果什麼的)。雖然這些進程閒着,可是照樣佔用內存。這意味着,若是用戶鏈接數的增加到必定規模,你服務器沒準就要耗光內存直接癱了。

非阻塞事件驅動

這種狀況怎麼解決?解決方法就是剛纔上邊說的:非阻塞事件驅動

你把非阻塞的服務器想象成一個loop循環,這個loop會一直跑下去。一個新請求來了,這個loop就接了這個請求,把這個請求傳給其餘的進程(好比傳給一個搞數據庫查詢的進程),而後響應一個回調(callback)。完事了這loop就接着跑,接其餘的請求。

這樣下來。服務器就不會像以前那樣傻等着數據庫返回結果了。

若是數據庫把結果返回來了,loop就把結果傳回用戶的瀏覽器,接着繼續跑。在這種方式下,你的服務器的進程就不會閒着等着。

從而在理論上說,同一時刻的數據庫查詢數量,以及用戶的請求數量就沒有限制了。服務器只在用戶那邊有事件發生的時候才響應,這就是事件驅動。


FriendFeed是用基於Python的非阻塞框架Tornado (知乎也用了這個框架) 來實現上面說的新鮮事功能的。

不過,Node.js就比前者更妙了。Node.js的應用是經過javascript開發的,而後直接在Google的變態V8引擎上跑。用了Node.js,你就不用擔憂用戶端的請求會在服務器裏跑了一段可以形成阻塞的代碼了。

由於javascript自己就是事件驅動的腳本語言。你回想一下,在給前端寫javascript的時候,更多時候你都是在搞事件處理和回調函數。javascript自己就是給事件處理量身定製的語言。

Node.js仍是處於初期階段。若是你想開發一個基於Node.js的應用,你應該會須要寫一些很底層代碼。可是下一代瀏覽器很快就要採用WebSocket技術了,從而長輪詢也會消失。在Web開發裏,Node.js這種類型的技術只會變得愈來愈重要。

 

讀罷,引出若干問題。

 

Ref: WEB通信技術之短輪詢、長輪詢(comet)、長鏈接(SSE)、WebSocket

實現Web端即時通信的方法:實現即時通信主要有四種方式,它們分別是短輪詢長輪詢(comet)長鏈接(SSE)WebSocket

①短輪詢

服務器端在收到請求後,不管是否有數據更新,都直接進行響應。

實現簡單;但連接頻繁,洪水攻擊的危險。

②comet 長輪詢

當服務器收到客戶端發來的請求後,不會直接進行響應,而是先將這個請求掛起,而後判斷服務器端數據是否有更新。

若是有更新,則進行響應,若是一直沒有數據,則到達必定的時間限制(服務器端設置)後關閉鏈接。

明顯減小了不少沒必要要的http請求次數,相比之下節約了資源。However,鏈接掛起也會致使資源的浪費。

③SSE

SSE是HTML5新增的功能,全稱爲Server-Sent Events。容許服務推送數據到客戶端。

SSE在本質上就與以前的長輪詢、短輪詢不一樣,雖然都是基於http協議的,可是輪詢須要客戶端先發送請求。而SSE最大的特色就是不須要客戶端發送請求

④WebSocket

WebSocket是Html5定義的一個新協議,與傳統的http協議不一樣,該協議能夠實現服務器與客戶端之間全雙工通訊

簡單來講,首先須要在客戶端和服務器端創建起一個鏈接,這部分須要http。鏈接一旦創建,客戶端和服務器端就處於平等的地位,能夠相互發送數據,不存在請求和響應的區別。

四種Web即時通訊技術比較

從兼容性角度考慮,短輪詢 > 長輪詢 > 長鏈接SSE > WebSocket;

從性能方面考慮,WebSocket > 長鏈接SSE > 長輪詢 > 短輪詢。

 

 

Ref: 七種WebSocket框架的性能比較【實驗數據比較】

測試結果分析

  • Netty, Node.js, Undertow, Vert.x都能正常創建百萬鏈接。 Jetty, Grizzly 和 Spray未能完成百萬鏈接
  • Netty表現最好。內存佔用很是的少, CPU使用率也不高。 尤爲內存佔用,遠遠小於其它框架
  • Jetty, Grizzly和Spray會產生大量的中間對象,致使垃圾回收頻繁。Jetty表現最差
  • Node.js表現很是好。 尤爲是測試中使用單實例單線程,創建速度很是快,消息的latency也很好。 內存佔用也不錯
  • Undertow表現也不錯,內存佔用比Netty高一些,其它差很少
  • 這裏還未測到Spray另外一個很差的地方。 在大量鏈接的狀況小,即便沒有消息發送,Spray也會佔用40% CPU 時間
  • 來自:http://www.tuicool.com/articles/veIvue

 

 

Ref: HTML5:WebSocket是否會取代HTTP

「下一代瀏覽器很快就要採用WebSocket技術,長輪詢(long polling)也就會消失掉」

  • 什麼是WebSocket?
  • WebSocket跟普通的HTTP有什麼不同?
  • WebSocket是否會取代HTTP?

(1).

關於第一個問題,簡單來講,WebSocket主要提供了一個全雙工(Full-Duplex)和持久化(Long lived)的鏈接,它跟HTTP同樣工做在TCP層上,它主要解決了長輪詢的問題,那什麼是長輪詢問題呢?

首先看一個應用場景:瀏覽器(客戶端)想從服務器端接收實時數據。

在之前,客戶端就會採起長輪詢(long polling)機制:客戶端發送請求之後,若是沒有等到服務器的響應,該請求就阻塞等待服務器返回消息,直到服務器返回響應之後,客戶端纔會發起新的HTTP輪詢請求 (注意:若是是HTTP1.1,那麼新的HTTP請求會複用前一個HTTP請求的TCP連接),因爲是阻塞等待模式,若是這時候,客戶端還須要再一次發送消息,就須要創建新的TCP鏈接到服務器,從而形成資源的浪費;

另外一方面,若是服務器想要再次推送新的響應消息給客戶端,它須要等待客戶端發起新的請求,才能給予。。。(HTTP的Request只能有一個Response,服務器不是想給就能給,不便於"推送"

 

實際上HTTP Streaming技術是能夠知足服務器發送多個消息到客戶端的:

HTTP streaming: a variety of techniques (multipart/chunked response) that allow the server to send more than one response to a single client request. The W3C is standardizing this as Server-Sent Events using a text/event-stream MIME type. The browser API (which is fairly similar to the WebSocket API) is called the EventSource API.

可是,並不能解決客戶端阻塞等待或者創建新鏈接的問題。

而WebSocket協議,容許客戶端和服務器端能夠在任什麼時候間任意發送消息,而且異步處理接收到的消息,很好的解決了長輪詢這種相似hacks服務器的簡單粗暴行爲。

 

(2).

關於第二個問題,WebSocket在第一次跟服務器握手的時候,會用到HTTP協議,在Upgrade裏指明該次握手是用於創建WebSocket (Upgrade: WebSocket),握手以後,客戶端和服務器之間的通訊,將會按照WebSocket的協議報文來通訊。

實際上,HTTP和WebSocket這兩種協議在初次跟服務器握手時,都採用同樣的建連方式,可是WebSocket只須要創建一次TCP鏈接,隨後只須要發送較小的WebSocket報文(該報文只有6個字節的開銷,2個字節header,4個字節的mask value)。

順便插一句,很喜歡stackoverflow上面的一段話:

The latency overhead is not so much from the size of the headers, but form the logic to parse/handle/store those headers. In addtion, the TCP connection setup latency is probably a bigger factor than the size or processing time for each request.

這裏講TCP建連的開銷比處理header大小須要的開銷更多,其實,這個也適用於其餘方面,消息大小長短可能不是形成開銷的主要緣由,更爲重要的是咱們用什麼方式(邏輯)去解析/處理這些消息?

 

(3). 

關於第三個問題,WebSocket並不會取代HTTP協議,它只是HTTP的一個擴展,它主要應用的場景是,當javascript應用運行的瀏覽器(客戶端)想從服務器端接收實時數據,服務器能夠隨時推送消息到客戶端,客戶端也不須要阻塞或者從新創建鏈接到服務器。

最後說下,爲何WebSocket不直接採用HTTP,那是由於WebSocket須要異步通訊模式,若是採用普通HTTP,會讓服務器感到困惑,因此,採用了與HTTP徹底不同的報文格式。

 

另外,我我的很喜歡的一篇頗有意思的解讀:

WebSocket 是什麼原理?爲何能夠實現持久鏈接?

 

HTTP的弊端

請記住 Request = Response , 在HTTP中永遠是這樣,也就是說一個request只能有一個response。並且這個response也是被動的,不能主動發起。

 

Client發送請求

GET /chat HTTP/1.1 Host: server.example.com Upgrade: websocket Connection: Upgrade Sec-WebSocket-Key: x3JJHMbDL1EzLkh9GBhXDw== Sec-WebSocket-Protocol: chat, superchat Sec-WebSocket-Version: 13 Origin: http://example.com
首先, Sec-WebSocket-Key 是一個Base64 encode的值,這個是瀏覽器隨機生成的,告訴服務器: 泥煤,不要忽悠窩,我要驗證尼是否是真的是Websocket助理。
而後, Sec_WebSocket-Protocol 是一個用戶定義的字符串,用來區分同URL下,不一樣的服務所須要的協議。簡單理解: 今晚我要服務A,別搞錯啦~
最後, Sec-WebSocket-Version 是告訴服務器所使用的Websocket Draft(協議版本)
 
Server返回請求
HTTP/1.1 101 Switching Protocols Upgrade: websocket Connection: Upgrade Sec-WebSocket-Accept: HSmrc0sMlYUkAGmm5OPpG2HaGWk= Sec-WebSocket-Protocol: chat

 

同時由客戶主動詢問,轉換爲服務器(推送)有信息的時候就發送(固然客戶端仍是等主動發送信息過來的。。),
沒有信息的時候就交給接線員(Nginx),不須要佔用自己速度就慢的客服(Handler)了

【Nginx :一個高性能的HTTP和反向代理服務器】
 
至於怎麼在不支持Websocket的客戶端上使用Websocket。。答案是:不能
可是能夠經過上面說的 long poll 和 ajax 輪詢來 模擬出相似的效果

 

Node.js 建立第一個應用

 Node.js 應用是由哪幾部分組成的:

  1. 引入 required 模塊:咱們可使用 require 指令來載入 Node.js 模塊。

  2. 建立服務器:服務器能夠監聽客戶端的請求,相似於 Apache 、Nginx 等 HTTP 服務器。

  3. 接收請求與響應請求 服務器很容易建立,客戶端可使用瀏覽器或終端發送 HTTP 請求,服務器接收請求後返回響應數據。

 
一個能夠工做的 HTTP 服務器,簡單示範:
var http = require('http'); http.createServer( function (request, response) { // 發送 HTTP 頭部 
    // HTTP 狀態值: 200 : OK
    // 內容類型: text/plain
    response.writeHead (200, {'Content-Type': 'text/plain'}); // 發送響應數據 "Hello World"
    response.end ('Hello World\n'); }).listen(8888); // 終端打印以下信息
console.log('Server running at http://127.0.0.1:8888/');

 

NPM 使用介紹

From: http://www.runoob.com/nodejs/nodejs-npm.html

隨同NodeJS一塊兒安裝的包管理工具,能解決NodeJS代碼部署上的不少問題,常見的使用場景有如下幾種:

  • 容許用戶從NPM服務器下載別人編寫的第三方包到本地使用。【借用他人代碼】
  • 容許用戶從NPM服務器下載並安裝別人編寫的命令行程序到本地使用。【借用他人命令行程序】
  • 容許用戶將本身編寫的包或命令行程序上傳到NPM服務器供別人使用。【分享本身的代碼或命令行程序】

 

unsw@unsw-UX303UB$ node -v v8.9.4
unsw@unsw-UX303UB$ npm -v 5.6.0 

╭─────────────────────────────────────╮
│                                     │
│ Update available 5.6.0 → 5.8.0      │
│ Run npm i -g npm to update          │
│                                     │
╰─────────────────────────────────────╯

 

$ npm list -g  // 查看全部全局安裝的模塊

 

package.json文件

From: package.json文件 - from ruanyifeng 

  1. node,npm 都要用。
    1.1  node在調用require的時候去查找模塊,會按照一個次序去查找,package.json會是查找中的一個環節。【見阮一峯的require分析 http://www.ruanyifeng.com/blog/2015/05/require.html
    1.2  npm用的就比較多,其中的 "dependencies" 字段就是本模塊的依賴的模塊清單。每次npm update的時候,npm會自動的把依賴到的模塊也下載下來。當npm install 本模塊的時候,會把這裏提到的模塊都一塊兒下載下來。經過package.json,就能夠管理好模塊的依賴關係。

  2. 若是是應用,沒必要編寫package.json

 

(1). 每一個項目的根目錄下面,通常都有一個package.json文件,定義了這個項目所須要的各類模塊,以及項目的配置信息(好比名稱、版本、許可證等元數據)。

$ npm init
// 以使用命令自動生成。npm init

(2). 有了package.json文件,直接使用npm install命令,就會在當前目錄中安裝所須要的模塊。

// 根據這個配置文件,自動下載所需的模塊,也就是配置項目所需的運行和開發環境。$ npm install

(3). 若是一個模塊不在package.json文件之中,能夠單獨安裝這個模塊,並使用相應的參數,將其寫入package.json文件之中。

$ npm install express --save $ npm install express --save-dev

 

一個更完整的package.json文件,以下所示:

{ "name": "Hello World", "version": "0.0.1", "author": "張三", "description": "第一個node.js程序", "keywords":["node.js","javascript"], "repository": { "type": "git", "url": "https://path/to/url" },
"license":"MIT", "engines": {"node": "0.10.x"}, "bugs":{"url":"http://path/to/bug","email":"bug@example.com"}, "contributors":[{"name":"李四","email":"lisi@example.com"}], "scripts": { "start": "node index.js"    // npm run start },
"dependencies": {          // 運行 所依賴模塊 "express": "latest", "mongoose": "~3.8.3", "handlebars-runtime": "~1.0.12", "express3-handlebars": "~0.5.0", "MD5": "~1.2.0" },
"devDependencies": {        // 開發 所須要的模塊 "bower": "~1.2.8", "grunt": "~0.4.1", "grunt-contrib-concat": "~0.3.0", "grunt-contrib-jshint": "~0.7.2", "grunt-contrib-uglify": "~0.2.7", "grunt-contrib-clean": "~0.5.0", "browserify": "2.36.1", "grunt-browserify": "~1.3.0", } }
 

peerDependencies

須要一種機制,在模板安裝的時候提醒用戶,若是A和B一塊兒安裝,那麼B必須是2.0模塊。

從npm 3.0版開始,peerDependencies再也不會默認安裝了。

{
  "name": "chai-as-promised",    // 安裝模塊時 "peerDependencies": {         // 主程序必須一塊兒安裝
"chai": "1.x"    // 並且的版本必須是
}
}chai-as-promisedchaichai1.x

bin字段

指定各個內部命令對應的可執行文件的位置

"bin": { "someTool": "./bin/someTool.js" }

main字段

指定了加載的入口文件,require('moduleName')就會加載這個文件。這個字段的默認值是模塊根目錄下面的index.js

"main": "index.js",

config 字段

{ "name" : "foo", "config" : { "port" : "8080" }, "scripts" : { "start" : "node server.js" } }

get(): 用戶執行npm run start命令時,腳本就能夠獲得該引用config值,以下:

http .createServer(...) .listen(process.env.npm_package_config_port)

set(): 用戶也能夠改變這個值。

$ npm config set foo:port 80

 

其餘字段,詳見連接。

package.json的加載與解析。

  

require 的源碼:在 Node 的 lib/module.js 文件。 

模塊的加載實質上就是,

    1. 注入exports、require、module三個全局變量,
    2. 而後執行模塊的源碼,
    3. 而後將模塊的 exports 變量的值輸出。

 

require執行邏輯:

當 Node 遇到 require(X) 時,按下面的順序處理。

(1)若是 X 是內置模塊(好比 require('http')) 
  a. 返回該模塊。 
  b. 再也不繼續執行。

 

(2)若是 X 以 "./" 或者 "/" 或者 "../" 開頭 
  a. 根據 X 所在的父模塊,肯定 X 的絕對路徑。 
  b. 將 X 當成文件,依次查找下面文件,只要其中有一個存在,就返回該文件,再也不繼續執行。

  • X
  • X.js
  • X.json
  • X.node

  c. 將 X 當成目錄,依次查找下面文件,只要其中有一個存在,就返回該文件,再也不繼續執行。

  • X/package.json(main字段)
  • X/index.js
  • X/index.json
  • X/index.node

 

(3)若是 X 不帶路徑 
  a. 根據 X 所在的父模塊,肯定 X 可能的安裝目錄。 
  b. 依次在每一個目錄中,將 X 當成文件名或目錄名加載。

 

例子:require('bar') 

首先,肯定 x 的絕對路徑多是下面這些位置,依次搜索每個目錄。

/home/ry/projects/node_modules/bar /home/ry/node_modules/bar /home/node_modules/bar /node_modules/bar 

搜索時,Node 先將 bar 當成文件名,依次嘗試加載下面這些文件,只要有一個成功就返回。

bar
bar.js bar.json bar.node 

若是都不成功,說明 bar 多是目錄名,因而依次嘗試加載下面這些文件。

bar/package.json(main字段) bar/index.js bar/index.json bar/index.node 

若是在全部目錄中,都沒法找到 bar 對應的文件或目錄,就拋出一個錯誤。

 

(4) 拋出 "not found"

 


以上即是基本的背景和配置內容。
相關文章
相關標籤/搜索