- net常用API解析以及應用
- 手動解析HTTP請求頭
- 基於網絡模塊net與文件模塊fs搭建簡易的node服務
- net模塊部分API參數詳細解析
一、net常用API解析以及簡單的應用
net模塊的組成部分:
net.connect()實際上是net.createConnection()的別名,還有一個基於net模塊子類的new net.socket()構造方法也具備相同的功能,都是用來創建一個新的客戶端連接對象。
net.createServer()同樣有一個基於net模塊子類的new net.server()構造方法與其功能相同,都是用來創建一個新的服務器端網絡連接的對象。
net.server類:
net.server事件:
- close事件:當 server 關閉的時候觸發。 如果有連接存在,直到所有的連接結束才會觸發這個事件。
- listening事件:啟動這個服務監聽時觸發。
- error事件:當錯誤出現的時候觸發。 不同於 net.Socket,'close' 事件不會在這個事件觸發后繼續觸發,除非 server.close() 是手動調用。
- connection事件:當服務被客戶端成功連接時觸發,該事件函數可以接受一個服務產生的socket對象作為參數,用於與客戶端連接通訊。
net.server屬性:
- server.listening:<boolean:布爾類型>表明 server 是否正在監聽連接。
- server.maxConnections:<integer:整數類型>設置該屬性使得當 server 連接數過多時拒絕連接。
net.server方法:
- new net.server():net.server對象構造方法,net.createServer()。
- server.address():返回當前連接服務的客戶端地址信息(返回值為一個對象,address:IP地址;familey:協議族;port:端口號)
- server.close():當 server 被關閉時調用,並且返回當前net.server對象。
- server.getConnections(callback):異步獲取服務器的當前並發連接數,當 socket 被傳遞給子進程時工作。回調函數callback的兩個參數是
err
和count
。 - server.listen():啟動一個服務來監聽連接。該方法有多種參數形式,后面單獨解析。
- server.ref():這個方法是用來解除server.unref()方法的關閉server的操作,並且server.ref()被調用后再調用server.unref()方法不生效。
- server.unref():在server(服務)事件系統中,如果當前server是唯一開啟中的服務,如果這個server調用過server.unref()方法,該server在事件系統中僅存最后一個事件執行完以后,當前server會被關閉。
net.socket類:
net.socket事件:
- close事件:當socket.end()方法被調用后並且正確的關閉了連接,會觸發這個關閉連接的監聽事件。這個事件發生在end事件之后。socket.end()方法正確關閉了連接后連接的兩端close監聽事件都會被觸發。
- connect事件:當通過socket.connect()方法連接上服務后,觸發這個連接成功監聽事件。
- data事件:用來接收消息,並且會將接收到的消息數據data傳入回調函數。該事件與發送消息的write方法相對應,data接收的就是write發送的消息。
- end事件:當socket.end()方法被調用表示執行關閉連接,當前end監聽事件會被觸發。這個事件發生在close事件之前。socket.end()方法會觸發當前端的end事件,並且連接的另一端接收到FIN請求關閉連接包后也會觸發這個事件。
- error事件
- lookup事件:在找到主機之后創建連接之前觸發。不可用於 Unix socket。
- ready事件:套接字准備好使用時觸發,即socket.
connect()
后立即觸發。 - timeout事件:當socket.setTimeout()啟動了一個超時監聽,如果在指定的事件內沒有收到連接對面的回復,會觸發這個事件,但不會關閉連接,關閉連接需要手動調用end或者destroy方法。
net.socket方法:
- socket.address():返回(操作系統報告)當前連接到的服務器地址信息(返回值為一個對象,address:IP地址;familey:協議族;port:端口號)
- socket.connect():用來與服務建立連接,該方法異步執行,連接建立成功后觸發connect事件,這個方法有三種參數形式,后面單獨拿出來解析。
- socket.destroy():在發生錯誤時一般使用在try.catch捕獲錯誤中,通過該方法強制關閉該連接。該方法會強制清除socket上的所有io操作,並關閉該socket鏈接,但不會關閉server服務。該方法調用前socket.destroyed屬性值為false,調用后為true。相比end關閉連接只會關閉連接,而不會清除socket正在執行的的io操作。調用該方法是可以將try.catch捕獲到的錯誤對象傳入該方法,然后會觸發socket的error事件,如果不傳入error對象則不會觸發error事件。
- socket.end():關閉當前連接,當連接的另一端調用了socket.end()方法后會向服務端發送一個FIN包,這個方法會先觸發end事件,連接關閉后會觸發close事件。
- socket.pause():暫停讀寫數據,比如通過socket.write()來發送一個大文件時,計算機的處理速度或網絡速度可能跟不上socket的讀寫速度,可以通過socket.pause()中途暫停,暫停后不會觸發data事件,並且可以通過socket.resume()重新開始數據的讀寫操作。
- socket.ref():這個方法是用來解除socket.unref()方法的關閉socket的操作,並且socket.ref()被調用后再調用server.unref()方法不生效。
- socket.resume():恢復數據的讀寫操作,這個方法與socket.pause()配合使用。
- socket.setEncoding():設置作為可讀流的編碼。在 readable.setEncoding() 查看更多詳情。默認情況下將字符串轉換成‘utf-8’格式,這個方法可以設置一個參數來指定socket通訊的數據編碼,比如:‘hex’。
- socket.setKeepAlive(enable[,initialDelay]):禁止或者啟用長連接,enable默認為false表示禁止啟用長連接,initialDelay用來設置接收最后一個數據包與發送一個長連接探針之間的延遲事件。
- socket.setNoDelay([noDelay]):禁止 Nagle 算法。默認情況下 TCP 連接使用 Nagle 算法,在發送之前緩沖數據。當調用socketl.setNoDelay()方法就會禁止Nagle算法,發送數據之前不會緩沖數據,noDelay默認值為true也就是禁止Nagle算法的參數設置。
- socket.setTimeout():啟動連接超時監聽事件,該方法第一個參數為超時時間(毫秒)必須傳值,第二個是回調函數可選,這個回調函數是在超時時間回調函數之后被調用。
- socket.unref():當前socket是程序中最后一個活躍的服務(連接),執行到最后一個socket事件后關閉連接。該方法只關閉socket的連接,並不會關閉server服務,server服務有對應的server.unref()方法來管理此類操作。
- socket.write(data [,encoding] [,callback]):向服務發送一條數據(data:<string> | <Buffer> | <Uint8Array>),encoding用來設置數據編碼格式(默認utf-8),當數據成功發送出去以后調用callback。
net.socket屬性:
- socket.bufferSize:設置連接通訊的緩沖字符數,配合socket.write使用,這個屬性值關系到服務的網絡性能,詳細參考:http://blog.chinaunix.net/uid-20726500-id-4949695.html
- socket.bytesRead:記錄接收字節數量。
- socket.bytesWritten:記錄發送字節數量。
- socket.connecting:<boolean>當socket.connect()被調用並正確建立連接之前為false,未建立連接或連接建立成功之前為true。
- socket.destroyed:<boolean>該屬性配合socket.destroy()方法使用,默認參數為false,當socket.destroy()調用並成功關閉了soket上所有io操作后,該屬性值被修改為true。
- socket.localAddress:本地的地址
- socket.localPort:本地的端口
- socket.pending:如果 socket 尚未連接,則為
true
,因為尚未調用.connect()
或者因為它仍處於連接過程中。用來判斷連接是否連接成功。 - socket.remoteAddress:服務端的地址
- socket.remoteFamily:服務的協議族
- socket.remotePort:服務的端口
net模塊API測試代碼一:(基本應用)
1 //server.js 2 let net = require("net"); 3 // var server = net.createServer(); 4 let server = new net.Server(); //創建一個net服務對象 5 server.listen(12306, "127.0.0.1"); //serever.listen()啟動net服務監聽 6 server.on("listening", function() { //listening事件,啟動net服務監聽時觸發 7 console.log("服務已啟動"); 8 }); 9 server.on("connection", function(sockte){ //connection事件,當服務器被客戶端成功連接時觸發,並且會將連接到的(sockte)客戶端對象傳入事件回調函數 10 server.unref(); 11 // server.ref(); 12 let address = server.address(); 13 //server.address()返回當前連接服務的客戶端地址信息(返回值為一個對象,address:IP地址;familey:協議族;port:端口號) 14 console.log("有新的連接,當前連接服務器客戶端:\n" + 15 "IP地址:" + address.address + 16 "; \n協議族:" + address.family + 17 "; \n端口:" + address.port +";"); 18 19 server.getConnections(function(err,conten){ //獲取當前並發連接數,也就是用來查看當前這個服務端口被多少個客戶端連接 20 try{ 21 console.log(conten); 22 }catch (e) { 23 console.log(err); 24 } 25 }); 26 sockte.on("data",function (data) { //data事件,用來接收消息,並且會將接收到的消息數據data傳入回調函數 27 console.log("client:" + data.toString()); 28 sockte.write("hello,client,我已經收到了你的消息了。"); //向服務發送會話,詳細sockte參考方法解析 29 }); 30 sockte.on("end",function () { //當連接的另一端調用了sockte.end()方法后會向服務端發送一個FIN包,表示客戶端正在關閉連接 31 console.log("接收到FIN,客戶端請求關閉了連接。"); 32 }); 33 sockte.on("close",function () { //當客戶端的sockte.end()方法觸發關閉后,服務端的close事件也能監聽到並觸發回調函數。 34 console.log("客戶端已關閉。"); 35 }); 36 }); 37 //client.js 38 var net = require("net"); 39 var socket = net.connect(12306, "127.0.0.1");//啟動客戶端網絡連接請求 40 socket.on("connect",function(){ 41 console.log("連接的服務地址:"+ socket.remoteAddress); 42 console.log("連接的服務協議族:" + socket.remoteFamily); 43 console.log("連接的服務端口:" + socket.remotePort); 44 console.log("本地的IP地址:" + socket.localAddress); 45 console.log("本地的端口:" + socket.localPort); 46 }); 47 socket.write("hello,server")//向服務發送會話,詳細sockte參考方法解析 48 socket.on("data",function (data) {//data事件,用來接收消息,並且會將接收到的消息數據data傳入回調函數 49 console.log("server:" + data.toString()); 50 socket.end();//關閉連接 51 }); 52 socket.on("end",function () { 53 console.log("客戶端調用了end方法,已經向服務端發送了FIN報文"); 54 }) 55 socket.on("close",function () { //當socket.end()觸發連接關閉后,觸發close事件 56 console.log("連接以關閉"); 57 });
net模塊API測試代碼二:(測試:server.unref()、server.ref()、socket.unref()、socket.ref())

1 //server.js 2 let net = require("net"); 3 let server1 = net.createServer(); 4 let server2 = new net.Server(); 5 6 server1.listen(12306, "127.0.0.1"); //serever.listen()啟動net服務監聽 7 // server2.listen(12307, "127.0.0.1"); //serever.listen()啟動net服務監聽 8 9 server1.on("listening",function () { 10 console.log("服務server1已啟動"); 11 }); 12 // server2.on("listening",function () { 13 // console.log("服務server2已啟動"); 14 // }); 15 16 server1.on("connection",function (socket) { 17 server1.unref(); 18 server1.ref(); 19 server1.unref();//這個無效了 20 console.log("服務server1有新的連接。"); 21 socket.on("data",function (data) { 22 console.log("client1:" + data.toString()); 23 socket.write("hello,client1,我已經收到你的消息了"); 24 }); 25 socket.on("end",function () { //當連接的另一端調用了sockte.end()方法后會向服務端發送一個FIN包,表示客戶端正在關閉連接 26 console.log("接收到FIN,客戶端請求關閉了連接。"); 27 }); 28 socket.on("close",function () { //當客戶端的sockte.end()方法觸發關閉后,服務端的close事件也能監聽到並觸發回調函數。 29 console.log("客戶端已關閉。"); 30 }); 31 }); 32 // server2.on("connection",function (socket) { 33 // console.log("服務server2有新的連接。"); 34 // }); 35 36 //client.js 37 var net = require("net"); 38 var socket1 = net.connect(12306, "127.0.0.1");//啟動客戶端網絡連接請求 39 // var socket2 = net.connect(12307, "127.0.0.1"); 40 41 socket1.write("hello,server1"); 42 // socket2.write("hello,server2"); 43 // 44 socket1.on("data",function (data) { 45 console.log("server1:" + data.toString()); 46 // socket1.end(); 47 socket1.unref(); 48 }); 49 socket1.on("end",function () { 50 console.log("客戶端調用了end方法,已經向服務端發送了FIN報文"); 51 }) 52 socket1.on("close",function () { //當socket.end()觸發連接關閉后,觸發close事件 53 console.log("連接以關閉"); 54 });
net模塊API測試代碼三:(測試socket.end()、socket.destroy()、socket.destroyed)

1 //server.js 2 let net = require("net"); 3 let server = net.createServer(); 4 server.listen(12306, "127.0.0.1"); 5 server.on("listening",function () { 6 console.log("服務已啟動"); 7 }); 8 server.on("connection",function (socket) { 9 socket.on("data",function(data){ 10 try{ 11 console.log("client:" + data.toString()); 12 throw new Error("測試server的Error事件"); 13 }catch (e) { 14 console.log("錯誤處理后關閉客戶端的連接"); 15 console.log(socket.destroyed);//false 16 socket.destroy();//這里可以關閉客戶端的連接,但不會關閉server服務。相比end,destroy會清除該socket上的所有io操作。 17 console.log(socket.destroyed);//true 18 // socket.end();//end也可以關閉客戶端連接,且不會關閉server服務。 19 //try、catch處理錯誤,但不會關閉客戶端連接,在服務端使用 20 // console.log(socket.) 21 } 22 }); 23 }); 24 //client.js 25 let net = require("net"); 26 let socket = net.connect(12306, "127.0.0.1"); 27 socket.on("connect",function () { 28 socket.write("hello,server!"); 29 }); 30 socket.on("end",function () { 31 console.log("end與destroy方法調用都會觸發這個方法。"); 32 }) 33 socket.on("close",function () { 34 console.log("連接以關閉"); 35 })
net模塊API測試代碼四:(測試setTimeout()、timeout事件)

1 //server.js 2 let net = require("net"); 3 let server = net.createServer(); 4 server.listen(12306, "127.0.0.1"); 5 server.on("listening",function () { 6 console.log("服務已啟動"); 7 }); 8 server.on("connection",function (socket) { 9 socket.on("data",function(data){ 10 console.log("client:" + data.toString()); 11 }); 12 }); 13 14 //client.js 15 let net = require("net"); 16 let socket = net.connect(12306, "127.0.0.1"); 17 socket.on("connect",function () { 18 socket.write("hello,server!");//向服務器發送消息 19 socket.setTimeout(5000,function () { 20 console.log("我在什么時候調用呢?") 21 });//發送消息后啟動超時監聽事件,如果在指定的事件內沒有收到服務端的消息,就會觸發回調函數及超時事件。(這里沒有使用超時回調函數) 22 }); 23 socket.on("timeout",function () { 24 console.log("連接超時,執行end關閉連接"); 25 socket.end();//socket 將會收到一個 'timeout' 事件,但連接不會被斷開。用戶必須手動調用 socket.end() 或 socket.destroy() 來斷開連接。 26 }) 27 socket.on("end",function () { 28 console.log("end與destroy方法調用都會觸發這個方法。"); 29 }) 30 socket.on("close",function () { 31 console.log("連接以關閉"); 32 })
二、手動解析HTTP請求頭
前面只針對net的API做了解析,並未就net的實現基礎做任何闡述,net作為nodejs中重要的網絡應用底層模塊實際是基於TCP/IP協議的API。所以如果net.server如果接收到一個http請求的話,他會解析出一個http報文。這是因為net模塊底層只負責解析TCP/IP協議的套字節,並不解析http的套字節,比如啟動下面這個服務程序,然后通過瀏覽器訪問這個服務,服務接收到請求后會打印出一個完整的http請求報文頭:
1 let net = require("net"); 2 let server = net.createServer(); 3 server.listen(12306, "127.0.0.1"); 4 server.on("listening",function () { 5 console.log("服務已啟動"); 6 }); 7 server.on("connection",function (socket) { 8 socket.on("data",function(data){ 9 console.log(data.toString()); 10 }); 11 });
在瀏覽器通過http協議發起請求:
http://127.0.0.1:12306
然后控制台會打印出一個完整的http請求報文:
net啟動的網絡服務能解析出http報文,也就意味着可以通過net服務向瀏覽器響應一個http報文,並且可以被瀏覽器解析:

1 let net = require("net"); 2 let server = net.createServer(); 3 server.listen(12306, "127.0.0.1"); 4 server.on("listening",function () { 5 console.log("服務已啟動"); 6 }); 7 server.on("connection",function (socket) { 8 socket.on("data",function(data){ 9 console.log(data.toString()); 10 //手寫一個http響應報文,報文內容為一個html頁面 11 socket.write("HTTP/1.1 200OK\r\nContent-Type: text/html\r\n\r\n<html><h1>hello, browser!</h1></html>") 12 }); 13 });
通過瀏覽器可以通過請求http://127.0.0.1:12306可以獲得響應,服務還可以獲得完整的http報文也就意味着可以手動解析報文,來分析http請求的內容是什么,沿着這樣的思路就可以通過解析http請求來給瀏覽器響應對應的請求資源,用下面這個請求index.html包含一個圖片的頁面作為示例:
//服務端文件 index.html server.js image.jpg
html代碼:
<h1>
hello,browser!
</h1>
<img src="./image.jpg"/>
server.js
1 let net = require("net"); 2 let fs = require("fs"); 3 4 let server = net.createServer(); 5 server.listen(12306, "127.0.0.1"); 6 server.on("listening",function () { 7 console.log("服務已啟動"); 8 }); 9 10 server.on("connection",function (socket) { 11 try{ 12 socket.on("data",function(data){ 13 let request = data.toString().split("\r\n"); 14 let url = request[0].split(" ")[1]; 15 let fileData = fs.readFileSync(__dirname + url); 16 //手寫一個http響應報文,報文內容為一個html頁面 17 // let writeState = socket.write("HTTP/1.1 200OK\r\nContent-Type: text/html\r\n\r\n<html><h1>hello, browser!</h1></html>"); 18 // let writeState = socket.write("HTTP/1.1 200OK\r\nContent-Type: text/html\r\n\r\n" + fileData.toString()); 19 //當需要發送文件時,特別是html文件,其內部會包含很多其他文件的引用,如果采用拼接到報文頭部后面無法被socket正常解析內部文件引用 20 //可是依靠TCP協議傳輸特性,每次先將獨立的HTTP頭部發送出去,然后再通過socket.write發送文件,當socket.write發送文件時會自動解析文件內容 21 socket.write("HTTP1.1 200OK\r\nContent-Type: text/html\r\n\r\n"); 22 let writeState = socket.write(fileData); 23 if(writeState){ 24 socket.end();//當成功發送數據以后關閉連接 25 }else{ 26 socket.write("HTTP/1.1 200OK\r\nContent-Type: text/html\r\n\r\n<html><h1>404</h1></html>"); 27 socket.end(); 28 } 29 }); 30 }catch(e){ 31 socket.destroy(e);//強制關閉socket所有io操作,並將傳入的e:Error對象傳遞給socket的Error監聽事件 32 } 33 socket.on("error",function (e) { 34 console.log(e); 35 }) 36 });
三、基於網絡模塊net與文件模塊fs搭建簡易的node服務
通常情況下我們不會把前端代碼與后端代碼放到一起,特別在開發的時候,為了更方便前端代碼連接服務測試,這時候我們可以在服務端配置前端代碼的路徑以及測試端口:
//假設將前端文件放到桌面 web //根目錄 html //html文件 index.html css //css文件 js //js文件 image //圖片文件 image.jpg json //json文件

1 <!DOCTYPE html> 2 <head> 3 <title>Node-Net-Fs-Server</title> 4 <link rel="stylesheet" href=""> 5 </head> 6 <body> 7 <h3>hello,browser!</h3> 8 <img src="./image/image.jpg"/> 9 </body> 10 </html>
服務端:
server.config
server.js
serverConfig.js
可以通過server.config配置不同的端口與前端文件路徑:
port=12306
path= 桌面的路徑 + \web

1 //serverConfig.js 用來解析配置文件 2 let fs = require("fs"); 3 4 function analysisConfig(configFile){ 5 let obj = {}; 6 let arr = configFile.toString().split("\r\n"); 7 for(let i = 0; i < arr.length; i++){ 8 let item = arr[i].split("="); 9 obj[item[0]] = item[1]; 10 } 11 return obj; 12 } 13 // 文件模型, 文件數據對象 14 let configFile, configData; 15 try{ 16 configFile = fs.readFileSync(__dirname + "/server.config"); 17 configData = analysisConfig(configFile); 18 }catch (e) { 19 console.log("解析server.config文件出錯!",e); 20 } 21 22 module.exports = configData;
1 //srever.js 2 let net = require("net"); 3 let fs = require("fs"); 4 let config = require("./serverConfig.js"); 5 let server = net.createServer(); 6 server.listen(parseInt(config["port"]),"127.0.0.1"); 7 server.on("listening",function () { 8 console.log("服務已啟動"); 9 }); 10 server.on("connection",function (socket) { 11 socket.on("data",function (data) { 12 try{ 13 let request = data.toString().split("\r\n"); 14 let url = request[0].split(" ")[1]; 15 let urlType = url.split("/")[1]; 16 if(url === "/" || url === "/index.html"){//這里響應首頁 17 socket.write("HTTP1.1 200OK\r\nContent-Type: text/html\r\n\r\n"); 18 socket.write(fs.readFileSync(config["HTMLPath"] + "/index.html")); 19 }else if(urlType === 'image' || urlType === 'html' || urlType === 'css' || urlType === 'js' || urlType==='json'){ 20 //這里主要用來響應html、css、js內部的引用文件 21 socket.write("HTTP1.1 200OK\r\nContent-Type: text/html\r\n\r\n"); 22 socket.write(fs.readFileSync(config["path"] + url)); 23 }else{ //如果有請求除html、css、js、image以外的其他文件就在服務器相對路徑下查找 24 socket.write("HTTP1.1 200OK\r\nContent-Type: text/html\r\n\r\n"); 25 socket.write(fs.readFileSync(__dirname + url)); 26 } 27 socket.end(); 28 }catch(e){//如果出現請求不存在的數據fs在解析文件時會拋出錯誤,這里返回404頁面,然后關閉當前會話。 29 //這里你可能會想到如果請求頁面內部引用文件不存在呢? 30 //這種情況可以在響應頁面內部文件之前先判斷,如果文件存在再讀寫文件,然而這種情況HTTP模塊已經提供了非常完善的機制。 31 socket.write("HTTP1.1 200OK\r\nContent-Type: text/html\r\n\r\n<html><h1>404!</h1></html>"); 32 socket.destroy(e); 33 } 34 }); 35 socket.on("error",function (e) { 36 console.log(e); 37 }) 38 });
四、net模塊部分API參數詳細解析