前端點滴(Node.js)(四)網絡編程 ---- 側重(上)

Node 網絡編程

前言

利用Node可以十分方便地搭建網絡服務器,在WEB領域,大多數編程語言需要專門的web服務器作爲容器,比如ASP,ASP.NET需要IIS作爲服務器,PHP需要搭載在Apache或者Nignx環境等,JSP需要Tomcat服務器等。當對於Node而言,只需要幾行代碼就可以構建一個服務器,無需額外的容器。
Node提供了net、http、https、dgram這四個模塊,分別用於處理TCP、HTTP、HTTPS、UDP,適用於服務器與客戶端。

一、構建 TCP 服務器

1. 七層模型與TCP協議

TCP全名傳輸控制協議,在OSI模型中有以下七層,被稱爲七層網絡協議。許多應用層議都是基於TCP構建,典型的有HTTP、SMTP、IMAP等協議。
在這裏插入圖片描述
TCP是面向連接的協議,其顯著特徵爲3次握手後才形成會話。
在這裏插入圖片描述
注意:只有在會話形成之後,服務器端和客戶端之間才能互相發送數據,在創建會話的過程中,服務器端和客戶端分別提供一個套接字,這兩個套接字共同形成了一個鏈接,服務器端與客戶端則通過套接字實現兩者之間連接的操作。
具體流程:請移步到
https://blog.csdn.net/Errrl/article/details/103662867

2. 創建TCP服務器

在基本瞭解TCP工作原理後,接下來就可以開始創建TCP服務器端來接受請求:

server.js

/* 引入net核心模塊 */
var net = require('net');
/* 創建一個TCP服務器 */
var server = net.createServer(function(socket){
	socket.on('data',function(data){
		socket.write('hello');
	});
	socket.on('end',function(){
		socket.write('end');
	});
	socket.write('welcome to node tcp');
});
/* 監聽端口號 */
server.listen(8000,function(){
	console.log('server is done');
})

利用win10自帶的telnet客戶端對上述的見到服務器進行會話
在這裏插入圖片描述
在這裏插入圖片描述
通過net模塊構造客戶端進行會話,測試上述構建的TCP服務器:

client.js

var net = require('net');

var client = net.connect({port:8000},function(){
    console.log('client is connect');
    client.write('world!\r\n');
});

client.on('data',function(data){
    console.log(data.toString());
    client.end();
})

client.on('end',function(){
    console.log('client is disconnect');
})

在這裏插入圖片描述

3. TCP服務器事件

(1)服務器事件

對於通過net.createServer()創建的服務器而言,他是一個EventEmitter實例,他自定義事件有以下幾種:

  • listening:在調用server.listen()綁定端口,簡介寫法爲server.listen(port,listeningListener),通過listen()方法的的第二個參數傳入。
  • connection:每個客戶端套接字連接到服務端時觸發,簡介寫法爲通過net.createServer(),最後一個參數傳入。
  • close:當服務器關閉時觸發,在調用server.close()後,服務器將停止接受新的套接字連接,但保持當前的連接,對待所有連接都斷開後會觸發該事件。
  • error:當服務器發生異常時,將會觸發該事件。比如監聽一個使用中的端口,將會觸發一個異常,如果不偵聽error事件,服務器將會拋出異常。

(2)連接事件

服務器可以同時與多個客戶端保持連接,對於每個連接而言是典型的可寫可讀Stream對象。Stream對象可以用於服務器與客戶端之間的通信,既可以通過data事件從一端讀取另一端發來的數據,也可以通過write()方法從一端向另一端發送數據,它具有如下自定義事件:

  • data:當一端調用write()發送數據時,另一端觸發data事件,事件傳遞的數據即是write()發送的數據。
  • end:當連接中的任意一端發送FIN數據時,將會觸發該事件。
  • connect:改時間用於客戶端,當套接字與服務器端連接成功時被觸發。
  • drain:當任意一端調用write()發送數據時,當前這端會觸發該事件。
  • error:當發生異常時觸發該事件。
  • close:當套接字完全關閉時觸發事件。
  • timout:當一定時間後連續不在活躍時,該事件會被觸發,通知用戶當前該連接已經被閒置了。

二、構建 UDP服務器

1. 與 TCP 協議的區別

請移步到:
https://blog.csdn.net/Errrl/article/details/103662867

2. 創建 UDP 套接字

創建UDP套接字十分簡單,UDP套接字一旦創建,既可以作爲客戶端發送數據,也可以作爲服務器端接收數據,創建一個UDP套接字:

var dgram = require('dgram');
var socket = dgram.createSocket ('udp4')

3. 創建 UDP 服務器端

如果要想UDP套接字接受網路消息,只要調用dgram.bind(port,[address])進行綁定即可。

server.js

var dgram = require('dgram');
var server = dgram.createSocket ('udp4');

server.on('message',function(msg,rinfo){
	console.log("server got:"+msg+"from"+rinfo.address+":"+rinfo.port);
})

server.on('listening',function(){
	var address = server.address(); 
	console.log("server listening"+address.address+":"+address.port);
})

server.bind(41234)

該套接字將接收所有網卡上41234端口信息上的消息。在綁定完成後,將會觸發listening事件。

4. 創建 UDP 客戶端

創建一個客戶端與服務器端進行對話:

client.js

var dgram = require('dgram');

var message = new Buffer('hello node udp');
var client = dgram .createSocket('udp4');
client.send(message,0,message.length,41234,"localhost",function(err,bytes){
	client.close();
})

在這裏插入圖片描述
當套接字對象用在客戶端時,可以調用send()方法發送消息到網絡中。send()方法的參數如下:

socket.send(buf,offset,length,port,address,[callback])
  • buf:buffer
  • offset:buffer偏移量
  • length:buffer的長度
  • port:目標端口
  • address:目標地址
  • callback:發送完成後的回調

5. UDP 套接字事件

UDP套接字相對於TCP套接字使用起來更加簡單,它只是一個EventEmitter的實例,它具有如下自定義事件:

  • message:當UDP套接字偵聽網卡端口後,接收到消息觸發該事件,出發攜帶的數據爲消息buffer對象和一個遠程地址信息。
  • listening:當UDP套接字開始偵聽時觸發該事件。
  • close:調用close()方法時觸發該事件,並不再觸發message事件。如需再次觸發message事件,重新綁定即可。
  • error:當異常發生時出發該事件,如果不偵聽,異常將直接拋出,使進程退出。

三、構建 HTTP 服務器端、客戶端

在Node中構建HTTP服務極其容易,Node官網上的經典例子就展示瞭如何用幾行代碼實現一個HTTP服務器:

var http = require('http');
/* 創建服務器 */
http.createServer(function(req,res){
	res.writeHead(200,{'content-type':'text/plain'});
	res.end('hello world');
}).listen(8000,'127.0.0.1');
console.log('server run at http://127.0.0.1:8000/');

在這裏插入圖片描述

1. HTTP

(1)HTTP 報文

在啓動上述代碼後,我們對經典的示例代碼進行了一次報文的獲取,這裏使用的工具是curl,通過 -v選項,可以顯示這次網絡通訊所有的報文信息。
在這裏插入圖片描述
請求報文的四部分:

  • TCP三次握手。
  • 請求報文。(請求頭、請求體)
  • 響應報文(包括響應頭、響應體)。
  • 會話結束信息。

在這裏插入圖片描述
從上述可以看出HTTP的特點,它是基於請求響應式的,基於TCP協議。

2. http 模塊、HTTP服務器端

Node的http模塊包含對HTTP處理的封裝,在Node中,HTTP服務繼承自TCP服務器(net模塊),它能夠與多個客戶端保持連接,由於採用事件驅動的形式,並不爲每一個連接創建額外的線程或進程,並保持很低的內存佔有率,所以能實現高校併發。

(1)HTTP 請求

請求頭

> GET / HTTP/1.1
> Host: 127.0.0.1:8000
> User-Agent: curl/7.55.1
> Accept: */*
>

請求頭第一行GET / HTTP/1.1解析之後會分解成如下屬性:

  • request.method:值是GET,一種請求方法,常用的請求方法還有:POST、DELETE、PUT、CONNECT等請求方法。
  • request.url:值爲/,這就可以解釋爲什麼在項目中查詢request.url會返回/,原因就是取決於報文。
  • request.httpVersion:值爲1.1,表示版本(規則)。

其餘的包頭就以簡單、規律的key:value的格式,被解析後放置在request.headers屬性上傳遞給業務邏輯以供調用。

(2)HTTP 響應

響應頭

< HTTP/1.1 200 OK
< content-type: text/plain

在項目中經常要寫入響應頭,用於獲取符合類型的數據。
除此之外,http模塊會自動設置一些頭信息:(用於處理緩存

< Date: Fri, 07 Feb 2020 14:30:32 GMT
< Connection: keep-alive             
< Transfer-Encoding: chunked         
<                                    

響應體
調用respone.write()或者調用respone.end()傳入的內容稱爲響應體:

hello world

調用respone.write()或者調用respone.end()的區別在於:
前者只發送,不結束響應,會造成客戶端處於等待狀態。
而後者就會先調用write()發送完後調用end()結束響應。

(3)HTTP 服務的事件

同TCP服務一樣,HTTP服務器也抽象了一些事件,以供應用層使用,同樣典型的是,服務器也是一個EventEmitter實例:

  • connection 事件:在開始HTTP請求和響應之前,客戶端與服務器需要建立底層的TCP連接,這個連接可能因爲開啓了keep-alive的原因,可以在多次請求與響應之間使用;當這個連接建立時,服務器會觸發一次connection事件。
  • request 事件:建立TCP連接後,HTTP模塊底層將在數據流中抽出HTTP請求和HTTP響應,當請求數據發送到服務器端,在解析出HTTP請求頭後,將會觸發該事件;在res.end()後,TCP連接可能將用於下一次請求響應。
  • close 事件:與TCP服務器行爲一致,調用server.close()方法停止接受新的連接,當已有的連接都斷開時,觸發該事件;可以給server.close()傳遞一個回調函數來快速註冊該事件。
  • checkContinue 事件:某些客戶端在發送較大的數據時,並不會之間將數據發送,而是先發送一個頭部帶有Expect:100-continue的請求到服務器,服務器將會觸發checkContinue事件;如果沒有爲服務器監聽這個事件,服務器將會自動響應客戶端100 Continue的狀態碼,表示可以接受數據上傳;如果不接受或者數據確實超出承載時響應客戶端400 Bad Request拒絕客戶端繼續發送數據即可。需要注意的是:當該事件發生時不會觸發request事件,兩個事件互斥。當客戶端收到100 Continue後重新發送請求時纔會觸發request事件。與預檢(Preflighted)的跨域請求類似
  • connect 事件:當客戶端發起CONNECT請求時觸發,二發起CONNECT請求通常在HTTP代理時出現;如果不監聽該事件,發起該事件的連接就會中斷。
  • upgrade 事件:當客戶端要求升級連接的協議時,需要和服務器端協商,客戶端會在請求頭中帶上Upgrade字段,服務器端會在接收到這樣的請求時觸發該事件。者在後面的WebSoket中會有詳細的流程介紹。如果不監聽該事件,發起該請求的連接就會中斷。
  • clientError 事件:連接的客戶端觸發error事件時,這個錯誤會傳遞到服務器端,此時觸發該事件。

擴展

keep-alive
在http早期,每個http請求都要求打開一個tpc socket連接,並且使用一次之後就斷開這個tcp連接。

使用keep-alive可以改善這種狀態,即在一次TCP連接中可以持續發送多份數據而不會斷開連接。通過使用keep-alive機制,可以減少tcp連接建立次數,也意味着可以減少TIME_WAIT狀態連接,以此提高性能和提高httpd服務器的吞吐率(更少的tcp連接意味着更少的系統內核調用,socket的accept()和close()的調用)

3. HTTP 客戶端

http模塊提供了一個底層的API:http.request(options,connect),用於構建HTTP客戶端。

var http = require('http');
/* 請求報文 */
var options = {
	hostname:'127.0.0.1',
	port:8000,
	path:'/',
	method:'GET'
}
/* 發送報文 */
var req = http.request(options,function(res){
	/* 獲取狀態碼 */
	console.log('status:'+res.statusCode);
	/* 獲取響應頭 */
	console.log('headers:'+res.headers);
	res.setEncoding('utf8');
	/* 獲取響應體 */
	res.on('data',function(datas){
		console.log(datas);
	})
})
/* 發送後斷開連接,緩解服務器壓力 */
req.end();

在這裏插入圖片描述
修改:

/* 獲取響應頭 */
	console.log('headers:'+JSON.stringify(res.headers));

在這裏插入圖片描述
其中options的參數配置:

  • host:服務器的域名或者ip地址,默認爲localhost。
  • hostname:服務器名稱。
  • port:端口號。默認80。
  • method:HTTP請求方法,默認GET。
  • path:請求路徑,默認/
  • headers:請求頭對象。
  • auth:Basic認證,這個值將會被計算成請求頭中的Authorization部分。

報文體的內容由請求對象的write()和end()方式實現:通過write()方法向連接中寫入數據,通過end()方法告知報文結束。他與前端中的Ajax調用非常相似,Ajax的實質就是一個異步的網絡HTTP請求。

(1)HTTP 響應

HTTP客戶端的響應對象與服務器端類似,在客戶端請求對象中,它的事件名叫做response。客戶端請求後(也就是解析報文完成後)響應頭就會觸發response事件,同時傳遞一個響應對象以供客戶端進行響應操作。對於上述代碼而言,res就是response,datas就是響應對象。

(2)HTTP 代理

如服務器端的實現一般http模塊提供的客戶端請求對象也是基於TCP層實現的,在keep-alive機制下,一個底層會話連接可以多次用於請求。爲了重複使用TCP連接,http模塊包含一個默認的客戶端代理對象http.globalAgent。它對每一個服務端(host+port)創建連接進行了管理,默認情況下,通過客戶端請求對象對同一服務器端發起的HTTP請求最多可以創建5個連接。實際上它就是一個連接池(循環代理)
在這裏插入圖片描述
那麼如何進行代理,很簡單:
重構options

/* 設置代理 */
var agent = new http.Agent({
    maxSochets: 10,
    keepAlive: true,
})
var options = {
    hostname: '127.0.0.1',
    port: 8000,
    path: '/',
    method: 'GET',
    agent: agent
}
/* 發送報文 */
var req = http.request(options, function (res) {
    /* 獲取狀態碼 */
    console.log('status:' + res.statusCode);
    /* 獲取響應頭 */
    console.log('headers:' + JSON.stringify(res.headers));
    res.setEncoding('utf8');
    /* 獲取響應體 */
    res.on('data', function (datas) {
        console.log(datas);
    })
})

/* 發送後斷開連接,緩解服務器壓力 */
req.end();

相關鏈接:
http://nodejs.cn/api/http.html#http_class_http_agent

(3)HTTP 客戶端事件

  • response 事件:與服務器端的request事件對應的客戶端在請求發送後得到服務器的響應時會觸發該事件。
  • socket 事件:在底層連接池中建立的連接分配給當前請求對象時,觸發該事件。
  • connect 事件:當客戶端向服務器端發送CONNECT請求時,如果服務器響應了200狀態碼,客戶端將會觸發該事件。
  • upgrade 事件:客戶端向服務器端發起Upgrade請求時,如果服務器端響應了101 Switching Protocols狀態,客戶端將會觸發該事件。
  • continue 事件:客戶端向服務器端發起Expect:100-continue頭信息,以試圖發送叫大數據量,如果服務器響應了100 Continue狀態,客戶端將觸發該事件。

四、構建 webSocket 服務端

1. 客戶端下的 webSocket

HTML:(client.html)

<input id="content" type="text">
<button id="send">send</button>

以一個webSocket聊天室進行實例操作:

/* client.html */
var websocket = new WebSocket("ws://localhost:8000/");
        websocket.onopen = function() {
            console.log("webSocket open");
            // 發送消息放在這裏
            document.getElementById("send").onclick = function() {
                var txt = document.getElementById("content").value;
                if (txt) {
                	/* 發送數據 */
                    websocket.send(txt);
                }
            }
        }
        websocket.onclose = function() {
            console.log("websocket close");
        }
        /* 接收響應數據 */
        websocket.onmessage = function(e) {
            console.log(e.data);
            var mes = JSON.parse(e.data);
            showMessage(mes.data, mes.type);
        }
/* server.js */
var ws = require("nodejs-websocket");
/* 端口號 */
const PORT = 8000;
// 每進來一個客戶端就記錄一下
var clientCount = 0;

var server = ws.createServer(function (conn) {
    console.log("New connection")
    clientCount++;
    conn.nickname = 'user' + clientCount;
    let mes = {};
    mes.type = "enter";
    mes.data = conn.nickname + ' comes in'
    broadcast(JSON.stringify(mes));
    /* 收到 text 文本觸發 */
    conn.on("text", function (str) {
        console.log("Received " + str);
        let mes = {};
        mes.type = "message";
        mes.data = conn.nickname + ' says: ' + str;
        broadcast(JSON.stringify(mes));
    })
    /* 當任一側關閉連接時發出 */
    conn.on("close", function (code, reason) {
        console.log("Connection closed");
        let mes = {};
        mes.type = "leave";
        mes.data = conn.nickname + ' left'
        broadcast(JSON.stringify(mes));
    })
    /* 發生錯誤時發出(例如嘗試在仍然發送二進制數據的同時發送文本數據)。如果握手無效,也會發出響應。 */
    conn.on("error", function (err) {
        console.log("handle err");
        console.log(err);
    })
}).listen(PORT);//監聽端口號

console.log("websocket server running on port: " + PORT);
/* 響應數據 */
function broadcast(str) {
    server.connections.forEach(function (connection) {
        connection.sendText(str);
    })
}

上述代碼中,瀏覽器與服務器端創建webSocket協議請求,onopen在請求完成後持續執行,通過事件綁定的方法綁定一個發送按鈕發送數據,同時還可以通過onmessage()方法接收服務器端相應的數據的數據。這種行爲與TCP客戶端很相似,相較於HTTP,它能夠雙向通信。
並且相比於HTTP,webSocket更接近於傳輸層協議,它並沒有在HTTP的基礎上模擬服務器端的推送,而是在TCP上定義獨立的協議,但是疑惑的是webSocket的握手部分由HTTP完成,這就是人們感覺webSocket是基於HTTP實現的原因。
webSocket協議主要分爲兩個部分:握手和數據傳輸。

2. webSocket 握手

客戶端建立連接時,通過HTTP發起的請求報文:
在這裏插入圖片描述
上面的報文告知客戶端正在更換協議(協議升級),更新應用層協議爲webSocket協議,並在當前的套接字連接上應用新的協議。剩餘的字段分別表示服務器端基於Sec-WebSocket-Key生成的字符串和選中的子協議。客戶端將會校驗Sec-WebSocket-Key的值,如果成功,將開始接下來的數據傳輸。
簡而言之就是websocket複用了http的握手通道,客戶端通過http請求與服務端進行協商,升級協議。協議升級完後校驗Sec-WebSocket-Key的值,若成功後面的數據交換則遵照websocket協議,若否反之。

流程:
1、客戶端申請協議升級

Request URL: ws://localhost:8888/
Request Method: GET
Connection: Upgrade
Upgrade: websocket
Sec-WebSocket-Version: 13
Sec-WebSocket-Key: uR5YP/BMO6M24tAFcmHeXw==
Sec-WebSocket-Extensions: permessage-deflate; client_max_window_bits
  • Connection: Upgrade 表示要升級協議

  • Upgrade: websocket 表示升級到websocket協議

  • Sec-WebSocket-Version: 13 表示websocket的版本

  • Sec-WebSocket-Key 表示websocket的驗證,防止惡意的連接,與服務端響應的Sec-WebSocket-Accept是配套。

2、服務端響應協議升級

Status Code: 101 Switching Protocols
Connection: Upgrade
Sec-WebSocket-Accept: eS92kXpBNI6fWsCkj6WxH6QeoHs=
Upgrade: websocket
  • Status Code:101 表示狀態碼,協議切換。
  • Sec-WebSocket-Accept 表示服務端響應的校驗,與客戶端的Sec-WebSocket-Key是配套的。

3、Sec-WebSocket-Accept是如何計算的

將 Sec-WebSocket-Key 的值與 258EAFA5-E914-47DA-95CA-C5AB0DC85B11 拼接。

然後通過sha1計算,再轉成base64。

const crypto = require('crypto');
 
function getSecWebSocketAccept(key) {
    return crypto.createHash('sha1')
        .update(key + '258EAFA5-E914-47DA-95CA-C5AB0DC85B11')
        .digest('base64');
}
 
console.log(getSecWebSocketAccept('uR5YP/BMO6M24tAFcmHeXw=='));

4、協議升級完後,後續的數據傳輸就需要按websocket協議來走。(瞭解即可)

websocket客戶端與服務端通信的最小單位是 幀,由1個或多個幀組成完整的消息。

客戶端:將消息切割成多個幀,發送給服務端。

服務端:接收到消息幀,將幀重新組裝成完整的消息。

數據幀的格式

單位是1個比特位,FIN,PSV1,PSV2,PSV3 佔1個比特位,opcode佔4個比特位。

0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7
+-+-+-+-+-------+-+-------------+-------------------------------+
|F|R|R|R| opcode|M| Payload len |    Extended payload length    |
|I|S|S|S|  (4)  |A|     (7)     |             (16/64)           |
|N|V|V|V|       |S|             |   (if payload len==126/127)   |
| |1|2|3|       |K|             |                               |
+-+-+-+-+-------+-+-------------+-------------------------------+
|     Extended payload length continued, if payload len == 127  |
+-------------------------------+-------------------------------+
|                               |Masking-key, if MASK set to 1  |
+-------------------------------+-------------------------------+
| Masking-key (continued)       |          Payload Data         |
+-------------------------------+-------------------------------+
|                     Payload Data continued ...                |
+---------------------------------------------------------------+
|                     Payload Data continued ...                |
+---------------------------------------------------------------+

6、掩碼的算法

Masking-key掩碼鍵是由客戶端生成的32位隨機數,掩碼操作不會影響數據載荷的長度。

function unmask(buffer, mask) {
    const length = buffer.length;
    for (var i = 0; i < length; i++) {
        buffer[i] ^= mask[i & 3];
    }
}

7、實現websocket的握手,數據傳輸
JavaScript:(up.js)

const crypto = require('crypto');
const net = require('net');
 
//計算websocket校驗
function getSecWebSocketAccept(key) {
    return crypto.createHash('sha1')
        .update(key + '258EAFA5-E914-47DA-95CA-C5AB0DC85B11')
        .digest('base64');
}
 
//掩碼操作
function unmask(buffer, mask) {
    const length = buffer.length;
    for (var i = 0; i < length; i++) {
        buffer[i] ^= mask[i & 3];
    }
}
 
//創建一個tcp服務器
let server = net.createServer(function (socket) {
 
    socket.once('data', function (data) {
        data = data.toString();
 
        //查看請求頭中是否有升級websocket協議的頭信息
        if (data.match(/Upgrade: websocket/)) {
            let rows = data.split('\r\n');
            //去掉第一行的請求行
            //去掉請求頭的尾部兩個空行
            rows = rows.slice(1, -2);
            let headers = {};
            rows.forEach(function (value) {
                let [k, v] = value.split(': ');
                headers[k] = v;
            });
            //判斷websocket的版本
            if (headers['Sec-WebSocket-Version'] == 13) {
                let secWebSocketKey = headers['Sec-WebSocket-Key'];
                //計算websocket校驗
                let secWebSocketAccept = getSecWebSocketAccept(secWebSocketKey);
                //服務端響應的內容
                let res = [
                    'HTTP/1.1 101 Switching Protocols',
                    'Upgrade: websocket',
                    `Sec-WebSocket-Accept: ${secWebSocketAccept}`,
                    'Connection: Upgrade',
                    '\r\n'
                ].join('\r\n');
                //給客戶端發送響應內容
                socket.write(res);
 
                //注意這裏不要斷開連接,繼續監聽'data'事件
                socket.on('data', function (buffer) {
                    //注意buffer的最小單位是一個字節
                    //取第一個字節的第一位,判斷是否是結束位
                    let fin = (buffer[0] & 0b10000000) === 0b10000000;
                    //取第一個字節的後四位,得到的一個是十進制數
                    let opcode = buffer[0] & 0b00001111;
                    //取第二個字節的第一位是否是1,判斷是否掩碼操作
                    let mask = buffer[1] & 0b100000000 === 0b100000000;
                    //載荷數據的長度
                    let payloadLength = buffer[1] & 0b01111111;
                    //掩碼鍵,佔4個字節
                    let maskingKey = buffer.slice(2, 6);
                    //載荷數據,就是客戶端發送的實際數據
                    let payloadData = buffer.slice(6);
 
                    //對數據進行解碼處理
                    unmask(payloadData, maskingKey);
 
                    //向客戶端響應數據
                    let send = Buffer.alloc(2 + payloadData.length);
                    //0b10000000表示發送結束
                    send[0] = opcode | 0b10000000;
                    //載荷數據的長度
                    send[1] = payloadData.length;
                    payloadData.copy(send, 2);
                    socket.write(send);
                });
            }
        }
    });
 
    socket.on('error', function (err) {
        console.log(err);
    });
 
    socket.on('end', function () {
        console.log('連接結束');
    });
 
    socket.on('close', function () {
        console.log('連接關閉');
    });
});
 
//監聽8000端口
server.listen(8000);

html:(up.html)

<!doctype html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <title>Document</title>
</head>
<body>
<script>
    var ws = new WebSocket('ws://localhost:8888');
    ws.onopen = function () {
        console.log('連接成功');
        ws.send('你好服務端');
    };
    ws.onmessage = function (ev) {
        console.log('接收數據', ev.data);
    };
    ws.onclose = function () {
        console.log('連接斷開');
    };
</script>
</body>
</html>

8、結束

發佈了37 篇原創文章 · 獲贊 6 · 訪問量 2209
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章