1. 程式人生 > >Node.js (一)

Node.js (一)

node.js簡介

1.Node.js 不是一種獨立的語言 2.Node.js 使用JavaScript 進行程式設計 3.Node.js 跳過了 Apache Naginx IIs 等HTTP伺服器,它自己不用建設在任何伺服器軟體之上 4.Node.js 是花費最小的硬體成本,追求更高的併發,更高的處理效能. . . .

Node.js的特點

1.單執行緒 2.非阻塞I/O 3.事件驅動

單執行緒

在Java、PHP或者.net等伺服器端語言中,會為每一個客戶端連線建立一個新的執行緒。而每個執行緒需要耗費大約2MB記憶體。也就是說,理論上,一個8GB記憶體的伺服器可以同時連線的最大使用者數為4000個左右。要讓Web應用程式支援更多的使用者,就需要增加伺服器的數量,而Web應用程式的硬體成本當然就上升了。
Node.js不為每個客戶連線建立一個新的執行緒,而僅僅使用一個執行緒。當有使用者連線了,就觸發一個內部事件,通過非阻塞I/O、事件驅動機制,讓Node.js程式巨集觀上也是並行的。使用Node.js,一個8GB記憶體的伺服器,可以同時處理超過4萬用戶的連線。
另外,帶執行緒的帶來的好處,還有作業系統完全不再有執行緒建立、銷燬的時間開銷。
壞處,就是一個使用者造成了執行緒的崩潰,整個服務都崩潰了,其他人也崩潰了。


在這裡插入圖片描述

在這裡插入圖片描述

也就是說,單執行緒也能造成巨集觀上的“併發”多執行緒、單執行緒的一個對比。

非阻塞I/O non-blocking I/O

例如,當在訪問資料庫取得資料的時候,需要一段時間。在傳統的單執行緒處理機制中,在執行了訪問資料庫程式碼之後,整個執行緒都將暫停下來,等待資料庫返回結果,才能執行後面的程式碼。也就是說,I/O阻塞了程式碼的執行,極大地降低了程式的執行效率。
由於Node.js中採用了非阻塞型I/O機制,因此在執行了訪問資料庫的程式碼之後,將立即轉而執行其後面的程式碼,把資料庫返回結果的處理程式碼放在回撥函式中,從而提高了程式的執行效率。
當某個I/O執行完畢時,將以事件的形式通知執行I/O操作的執行緒,執行緒執行這個事件的回撥函式。為了處理非同步I/O,執行緒必須有事件迴圈,不斷的檢查有沒有未處理的事件,依次予以處理。
阻塞模式下,一個執行緒只能處理一項任務,要想提高吞吐量必須通過多執行緒。而非阻塞模式下,一個執行緒永遠在執行計算操作,這個執行緒的CPU核心利用率永遠是100%。所以,這是一種特別有哲理的解決方案:與其人多,但是好多人閒著;還不如一個人玩命,往死裡幹活兒。


事件驅動event-driven

在Node中,客戶端請求建立連線,提交資料等行為,會觸發相應的事件。在Node中,在一個時刻,只能執行一個事件回撥函式,但是在執行一個事件回撥函式的中途,可以轉而處理其他事件(比如,又有新使用者連線了),然後返回繼續執行原事件的回撥函式,這種處理機制,稱為“事件環”機制。
Node.js底層是C++(V8也是C++寫的)。底層程式碼中,近半數都用於事件佇列、回撥函式佇列的構建。用事件驅動來完成伺服器的任務排程,這是鬼才才能想到的。針尖上的舞蹈,用一個執行緒,擔負起了處理非常多的任務的使命。


在這裡插入圖片描述

Nodejs特點的總結

單執行緒:
		單執行緒的好處,減少了記憶體開銷,作業系統的記憶體換頁。
		如果某一個事情,進入了,但是被I/O阻塞了,所以這個執行緒就阻塞了。
		
非阻塞I/O: 
		不會傻等I/O語句結束,而會執行後面的語句。
		非阻塞就能解決問題了麼?比如執行著小紅的業務,執行過程中,小剛的I/O回撥完成了,此時怎麼辦??
		
事件機制:
		事件環,不管是新使用者的請求,還是老使用者的I/O完成,都將以事件方式加入事件環,等待排程。


Node.js中所有的I/O都是非同步的,回撥函式,套回撥函式。


Nodejs適合開發什麼

Node.js適合用來開發什麼樣的應用程式呢?
善於I/O,不善於計算。因為Node.js最擅長的就是任務排程,如果你的業務有很多的CPU計算,實際上也相當於這個計算阻塞了這個單執行緒,就不適合Node開發。
當應用程式需要處理大量併發的I/O,而在向客戶端發出響應之前,應用程式內部並不需要進行非常複雜的處理的時候,Node.js非常適合。Node.js也非常適合與web socket配合,開發長連線的實時互動應用程式。
比如:
● 使用者表單收集
● 考試系統
● 聊天室
● 圖文直播
● 提供JSON的API(為前臺Angular使用)

Node.js無法挑戰老牌3P(JSP ASP PHP)

在這裡插入圖片描述

Nodejs簡單的伺服器搭建

Node.js是伺服器的程式,寫的js語句,都將執行在伺服器上。返回給客戶的,都是已經處理好的純html。

1.HelloWorld.js


// require 表示引包,引包就是引用自己的一個特殊技能
var http = require('http');
//建立伺服器,引數是一個回撥函式,表示如果有請求進來,要做什麼
var server = http.createServer(function(req,res){
    //req表示請求,request; res表示響應,response
    //設定HTTP頭部,狀態碼是200,檔案型別是html,字符集是utf-8
    res.writeHead(200,{'Content-type':'text/html;charset=UTF-8'});
    res.end('This is my first Nodejs page!');

});

//執行伺服器 監聽8000埠(埠號可以任改)
server.listen(8000,"127.0.0.1");



2.開啟命令列cmd 找到js檔案下的目錄

此時伺服器已經建立成功並執行中

在這裡插入圖片描述

任何一個js檔案,都可以通過node來執行。也就是說,node就是一個js的執行環境。 我們現在,要跑起來一個伺服器,這個伺服器的指令碼,要以.js儲存。是一個js檔案。用node命令執行這個js檔案罷了。

3.瀏覽器訪問伺服器

在這裡插入圖片描述

Nodejs 的路由設計功能

Node.js沒有根目錄的概念,因為它根本沒有任何的web容器! 讓node.js提供一個靜態服務,都非常難! 也就是說,node.js中,如果看見一個網址是 127.0.0.1:3000/fang 別再去想,一定有一個資料夾,叫做fang了。可能/fang的物理檔案,是同目錄的test.html URL和真實物理檔案,是沒有關係的。URL是通過了Node的頂層路由設計,呈遞某一個靜態檔案的。

Node 有優秀的路由設計功能,可以將需要訪問的頁面設計成自定義路由

127.0.0.1:8000/yuan 訪問了同目錄下的haha.html

在這裡插入圖片描述

在這裡插入圖片描述

在這裡插入圖片描述

程式碼塊

StaticRender.js


//require表示引包,引包就是引用自己的一個特殊功能
var http = require("http");
// 引用一個可以讀檔案的fs
var fs = require("fs");

//建立伺服器,引數是一個回撥函式,表示如果有請求進來,要做什麼
var server = http.createServer(function(req,res){
    if(req.url == "/fang"){
        fs.readFile("./test/xixi.html",function(err,data){
            //req表示請求,request;  res表示響應,response
            //設定HTTP頭部,狀態碼是200,檔案型別是html,字符集是utf8
            res.writeHead(200,{"Content-type":"text/html;charset=UTF-8"});
            res.end(data);
        });
    }else if(req.url == "/yuan"){
        fs.readFile("./test/haha.html",function(err,data){
            //req表示請求,request;  res表示響應,response
            //設定HTTP頭部,狀態碼是200,檔案型別是html,字符集是utf8
            res.writeHead(200,{"Content-type":"text/html;charset=UTF-8"});
            res.end(data);
        });
    }else if(req.url == "/0.jpg"){
        fs.readFile("./test/0.jpg",function(err,data){
            //req表示請求,request;  res表示響應,response
            //設定HTTP頭部,狀態碼是200,檔案型別是image,字符集是utf8
            res.writeHead(200,{"Content-type":"image/jpg"});
            res.end(data);
        });
    }else if(req.url == "/bbbbbb.css"){
        fs.readFile("./test/aaaaaa.css",function(err,data){
            //req表示請求,request;  res表示響應,response
            //設定HTTP頭部,狀態碼是200,檔案型別是html,字符集是utf8
            res.writeHead(200,{"Content-type":"text/css"});
            res.end(data);
        });
    }else{
        res.writeHead(404,{"Content-type":"text/html;charset=UTF-8"});
        res.end("嘻嘻,沒有這個頁面呦");
    }
});

//執行伺服器,監聽3000埠(埠號可以任改)
server.listen(8000,"127.0.0.1");



在這裡插入圖片描述

在這裡插入圖片描述

http模組的簡單運用

Node.js中,將很多的功能,劃分為了一個個mudule,大陸的書翻譯為模組;臺灣的書,翻譯為模組。 這是因為,有一些程式需要使用fs功能(檔案讀取功能),有一些不用的,所以為了效率,你用啥,你就require啥。

在這裡插入圖片描述

所以Nodejs沒有Web容器

. . . 設定一個響應頭: res.writeHead(200,{“Content-Type”:“text/plain;charset=UTF8”});

在這裡插入圖片描述

http.js



// 案例,簡單講解http模組
//引用模組
var http = require('http');

//建立一個伺服器,回撥函式表示接收到請求之後做的事情
var server = http.createServer(function(req,res){
    //req 引數表示請求,res 引數表示響應
    console.log('伺服器接收到請求: ' + req.url);
    // 每一個請求必須對應一個響應(res.end),否則伺服器一直掛起
    res.end();
});

// 伺服器監聽
server.listen(8000,'127.0.0.1');



在這裡插入圖片描述

http.js



// 案例,簡單講解http模組
//引用模組
var http = require('http');

//建立一個伺服器,回撥函式表示接收到請求之後做的事情
var server = http.createServer(function(req,res){
    //req 引數表示請求,res 引數表示響應
    console.log('伺服器接收到請求: ' + req.url);

	//設定一個響應頭:
    res.writeHead(200,{"Content-Type":"text/html;charset=UTF-8"});
    res.write('<h1>我是h1標籤</h1>');
    res.write('<h2>我是h2標籤</h2>');
    res.write('<h3>我是h3標籤</h3>');
    res.write('<h4>我是h4標籤</h4>');

    // 每一個請求必須對應一個響應(res.end),否則伺服器一直掛起
    res.end();
});

// 伺服器監聽
server.listen(8000,'127.0.0.1');





在這裡插入圖片描述

url模組的介紹

最關鍵的就是req.url屬性,表示使用者的請求URL地址。所有的路由設計,都是通過req.url來實現的。 我們比較關心的不是拿到URL,而是識別這個URL。

識別URL,用到兩個新模組,第一個就是url模組,第二個就是querystring模組

url.js




// 引用http 模組和 url 模組
var http = require('http');
var url = require('url');

var server = http.createServer(function(req,res){
    //設定響應頭
    res.writeHead(200,{"Content-Type":"text/html;charset=UTF-8"});

    // 獲取路徑
    var pathname = url.parse(req.url).pathname;

    // 獲取查詢結果(路徑後面的資料)
    // 如果url.parse()第二個引數是true,那麼獲取得到的是一個物件集合
    var query = url.parse(req.url,true).query;
    var age = query.age // 此時query為集合

    console.log('pathname --> ' + pathname);
    console.log('query --> ' + query.sex);
    console.log('age -->' + age);

    res.end('查詢成功!!!');

});

server.listen(8000,'127.0.0.1');




在這裡插入圖片描述

案例之表單提交至本地伺服器

任意目錄下建立的html表單

1.demo表單提交.html


<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<title>Examples</title>
<meta name="description" content="">
<meta name="keywords" content="">
<link href="" rel="stylesheet">
</head>
<body>

    <!-- 提交到http://127.0.0.1本地伺服器上 -->
    <form action="http://127.0.0.1:8000/" method="GET">
        <p><span>姓名  :</span><input type="text" name="name"></p>
        <p><span>使用者名稱:</span><input type="text" name="user"></p>
        <p><span>密碼  :</span><input type="password" name="pwd"></p>
        <p><span>性別:</span><input type="radio" name='sex' value="男">男<input type="radio" name='sex' value="女">女</p>
        <p><input type="submit" value="提交"></p>
    </form>

</body>
</html>



2.demo.js



// 案例 表單資料提交到本地伺服器並列印
var http = require('http');
var url = require('url');

var server = http.createServer(function(req,res){
    //設定響應頭
    res.writeHead(200,{"Content-Type":"text/html;charset=UTF-8"});

    //獲取查詢部分的物件結果集(GET方式將資料顯示在url位址列上)
    var queryObj = url.parse(req.url,true).query;
    var name = queryObj.name;
    var user = queryObj.user;
    var pwd = queryObj.pwd;
    var sex = queryObj.sex;

    console.log('伺服器接收到資料\n' + 'name --> ' + name  + '\nuser --> ' + user  +
        '\npwd --> ' + pwd  + '\nsex --> ' + sex);
    res.end('提交成功!!!');


});

// 此處伺服器監聽的埠號與IP地址應該與表單提交的地址相一致
server.listen(8000,'127.0.0.1');




3.cmd命令列下找到demo.js 所在目錄 在這裡插入圖片描述

4.進入表單html 輸入註冊資訊並提交

在這裡插入圖片描述

案例之頂層路由設計

適用於使用者通過學生id或者老師id查詢具體資訊

demo1.js



var http = require('http');
var url = require('url');

var server = http.createServer(function(req,res){
    //獲取url
    var userurl = req.url;

    //設定響應頭
    res.writeHead(200,{'Content-Type':'text/html;charset=UTF-8'});

    //substr函式判斷此時的開頭,第0位到第8位
    if( userurl.substr(0,9) == '/student/' ){
        var stuId =userurl.substr(9);
        if( /^\d{10}$/.test(stuId) ){
            res.end('您要查詢的學生id為: ' + stuId);
        }else{
            res.end('您要查詢的學生不存在!');
        }

    }else if( userurl.substr(0,9) == '/teacher/' ){
        var teacherId = userurl.substr(9);
        if( /^\d{6}$/.test(teacherId) ){
            res.end('您要查詢的老師id為: ' + teacherId);
        }else{
            res.end('您要查詢的老師不存在!');
        }
    }else{
        res.end('輸入的id不存在!');

    }

});


server.listen(8000,'127.0.0.1');







在這裡插入圖片描述

在這裡插入圖片描述

在這裡插入圖片描述

案例之探求事件環機制

單執行緒多事件,非阻塞I/O,事件驅動迴圈 特點 Node 處理多客戶端訪問是單執行緒多事件形式 當多客戶端訪問時不會I/O阻塞, 如果回撥函式內容事件處理時間較長而多使用者同時訪問會併發進行並將使用者回撥函式放入事件迴圈機制中再執行

fs.js



var http = require('http');
var fs = require('fs');

var server = http.createServer(function(req,res){
    var userId = parseInt(Math.random() * 89999 ) +10000;

    console.log('歡迎' + userId);

    //設定響應頭
    res.writeHead(200,{"Content-Type":"text/html;charset=UTF-8"});
    // 兩個引數,第一個是完整路徑,當前目錄寫./  因為node是跨平臺的, ./可以適用linux和window,這樣寫通用
    // 第二個引數是回撥函式,表示檔案讀取成功後做的事情
    fs.readFile('./test/1.txt',function(err,data){
        if(err)throw err;
        console.log(userId + '檔案讀取完畢!!!');
        res.end(data);
    });

});

server.listen(8000,'127.0.0.1');



此處多客戶端訪問node 將回調函式事件重新放入迴圈機制再執行(多使用者併發執行時候出現的現象)

在這裡插入圖片描述

正常的先執行事件在執行回撥函式內容(因為回撥函式事件執行過快,在下一個使用者訪問之前已經處理完回撥函式所以沒有出現併發執行的狀態)

在這裡插入圖片描述

案例之fs模組的功能

fsDemo.js


var http = require('http');
var fs = require('fs');

var server = http.createServer(function(req,res){
    res.writeHead(200,{'Content-Type':'text/html;charset=UTF-8'});

    //不處理小圖示
    if(req.url == '/favicon.ico'){
        return;
    }

    fs.mkdir('./album/ooo',function(){
        res.end('建立成功');
    });
    fs.rmdir('./album/ooo',function(){
        res.end('刪除資料夾成功!!!');
    });


    stat檢測狀態
    fs.stat('./album/ha',function(err,data){
        //檢測這個路徑,是不是一個資料夾
        console.log(data.isDirectory());
    });

    fs.readdir('./album',function(err,files){
        //files是個陣列,表示 ./album這個資料夾中的所有東西
        //包括檔案丶資料夾
        console.log(files);
    });

});

server.listen(8000,'127.0.0.1');



在這裡插入圖片描述

在這裡插入圖片描述

在這裡插入圖片描述 在這裡插入圖片描述

在這裡插入圖片描述

案例之查詢album資料夾下的所有檔案,將非同步變同步

由於for 迴圈下有太多非同步語句,所以遍歷出來的值可能為空

迭代器將非同步變成同步

for.js



var http = require('http');
var fs = require('fs');

var server = http.createServer(function(req,res){
    //不處理收藏夾小圖示
    if(req.url == '/favicon.ico'){
        return;
    }

    //遍歷album裡面的所有資料夾丶檔案
    fs.readdir('./album/',function(err,files){
        //files 是一個存放檔案或資料夾名的陣列
        //存放資料夾的陣列
        var wenjianjia = [];

        //定義一個迭代器,從0開始
        //迭代器就是強行把非同步的函式變成同步的函式
        (function iterator(i){
            //遍歷結束
            if(i == files.length){
                console.log(wenjianjia);
                return;
            }
            fs.stat('./album/' + files[i],function(err,stats){
                //檢測成功之後做的事
                if(stats.isDirectory()){
                    wenjianjia.push(files[i]);
                }

                iterator(i+1);
            });
        })(0);
    });
    res.end();
});

server.listen(8000,'127.0.0.1');





在這裡插入圖片描述

案例之處理靜態檔案管理

Nodejs可以單執行緒多事件非同步處理併發,這是php javaee jsp等沒有的功能,但是Nodejs也有缺點, Apache伺服器環境有預設的靜態檔案管理,即url位址列輸入網址對應了本地檔案的目錄路徑 而Nodejs沒有這項功能,此案例即自己寫出靜態檔案管理的功能,實現url位址列輸入檔案目錄來訪問實際目錄下的檔案

statics.js


var http = require('http');
var url = require('url');
var fs = require('fs');
var path = require('path');

http.createServer(function(req,res){
    //得到使用者的路徑
    var pathname = url.parse(req.url).pathname;
    //預設首頁
    if(pathname == '/'){
        pathname = 'index.html';
    }
    //拓展名
    var extname = path.extname(pathname);
    console.log(extname);

    //真的讀取這個檔案
    fs.readFile('./static/' + pathname,function(err,data){
        if(err){
            //如果此檔案不存在,就應該用404返回
            fs.readFile('./static/404.html',function(err,data){
                res.writeHead(404,{'Content-Type':'text/html;charset=UTF-8'});
                res.end(data);
            })
            //退出伺服器掛起
            return;

        };
        //MIME型別:
        //  網頁檔案: text/html
        //  jpg檔案: image/jpg
        var mime = getMime(extname);
        res.writeHead(200,{'Content-Type':mime});
        res.end(data);
    });

}).listen(8000,'127.0.0.1');


function getMime(extname){
    switch (extname) {
        case '.html':
            return 'text/html';
            break;
        case '.jpg' :
            return 'image/jpg';
            break;
        case '.css':
            return 'text/css';
            break;
    }

}



static目錄下 在這裡插入圖片描述

在這裡插入圖片描述

此時通過自定義函式將響應頭型別Content-Type 設定為text/html 在這裡插入圖片描述

總結


Node.js開發伺服器,資料、路由。本地關心的效果,互動;
Node.js實際上是極客開發出的一個小玩具,不是銀彈。有著別人不具備的怪異特點:
單執行緒、Non-blocking I/O、Event Driven。 實際上是一個特點。
首先,Node不為每個使用者開闢一個執行緒,所以非常極端的選擇了單執行緒。單執行緒,要照顧所有的使用者,那麼就必須有非阻塞I/O,否則一個人的I/O就把別人、自己都阻塞了。一旦有非阻塞I/O,一個人如果I/O去了,就會放棄CPU的使用權,換成另一個人使用CPU(或者執行此人後面的語句)。所以CPU的利用率100%。第一個人I/O結束了,就要用事件來通知執行緒,執行回撥函式。此時必須有事件環,就有一個排隊排程機制。Node中有超過半數的C++程式碼,在搭建事件環。

Node.js和別的老牌3P不一樣: 
1) 沒有自己的語法,使用V8引擎,所以就是JS。V8引擎解析JS的,效率非常高,並且V8中很多東西都是非同步的。Node就是將V8中的一些功能自己沒有重寫(別人做了,自己就站在巨人肩膀上),移植到了伺服器上。
2) 沒有web容器,就是安裝配置完成之後,沒有一個根目錄。


系統中,80埠,就是預設http埠。所以當沒有埠號的時候,就是80埠。
server.listen(80,"127.0.0.1");