久久国产成人av_抖音国产毛片_a片网站免费观看_A片无码播放手机在线观看,色五月在线观看,亚洲精品m在线观看,女人自慰的免费网址,悠悠在线观看精品视频,一级日本片免费的,亚洲精品久,国产精品成人久久久久久久

分享

細(xì)說WebSocket - Node篇

 icecity1306 2015-09-17
本文為歸檔內(nèi)容,原始地址在 博客園.

在上一篇提高到了 web 通信的各種方式,,包括 輪詢、長連接 以及各種 HTML5 中提到的手段,。本文將詳細(xì)描述 WebSocket協(xié)議 在 web通訊 中的實(shí)現(xiàn),。

一,、WebSocket 協(xié)議

1. 概述

websocket協(xié)議允許不受信用的客戶端代碼在可控的網(wǎng)絡(luò)環(huán)境中控制遠(yuǎn)程主機(jī),。該協(xié)議包含一個握手和一個基本消息分幀,、分層通過TCP。簡單點(diǎn)說,,通過握手應(yīng)答之后,,建立安全的信息管道,這種方式明顯優(yōu)于前文所說的基于 XMLHttpRequest 的 iframe 數(shù)據(jù)流和長輪詢,。該協(xié)議包括兩個方面,,握手鏈接(handshake)和數(shù)據(jù)傳輸(data transfer)。

2. 握手連接

這部分比較簡單,,就像路上遇到熟人問好,。

Client:嘿,大哥,,有火沒,?(煙遞了過去)
Server:哈,,有啊,,來~ (點(diǎn)上)
Client:火柴啊,也行?。燑c(diǎn)上,,驗(yàn)證完畢)

握手連接中,client 先主動伸手:

GET /chat HTTP/1.1
Host: server.
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
Origin: http://
Sec-WebSocket-Protocol: chat, superchat
Sec-WebSocket-Version: 13

客戶端發(fā)了一串 Base64 加密的密鑰過去,,也就是上面你看到的 Sec-WebSocket-Key,。 Server 看到 Client 打招呼之后,悄悄地告訴 Client 他已經(jīng)知道了,,順便也打個招呼,。

HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
Sec-WebSocket-Protocol: chat

Server 返回了 Sec-WebSocket-Accept 這個應(yīng)答,這個應(yīng)答內(nèi)容是通過一定的方式生成的,。生成算法是:

mask  = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";  // 這是算法中要用到的固定字符串
accept = base64( sha1( key + mask ) );

key 和 mask 串接之后經(jīng)過 SHA-1 處理,,處理后的數(shù)據(jù)再經(jīng)過一次 Base64 加密。分解動作:

1. t = "GhlIHNhbXBsZSBub25jZQ==" + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"
-> "GhlIHNhbXBsZSBub25jZQ==258EAFA5-E914-47DA-95CA-C5AB0DC85B11"
2. s = sha1(t)
-> 0xb3 0x7a 0x4f 0x2c 0xc0 0x62 0x4f 0x16 0x90 0xf6
0x46 0x06 0xcf 0x38 0x59 0x45 0xb2 0xbe 0xc4 0xea
3. base64(s)
-> "s3pPLMBiTxaQ9kYGzzhZRbK+xOo="

上面 Server 端返回的 HTTP 狀態(tài)碼是 101,,如果不是 101 ,,那就說明握手一開始就失敗了~

下面就來個 demo,,跟服務(wù)器握個手:

var crypto = require('crypto');

require('net').createServer(function(o){
var key;
o.on('data',function(e){
if(!key){
// 握手
// 應(yīng)答部分,代碼先省略
console.log(e.toString());
}else{

};
});
}).listen(8000);

客戶端代碼:

var ws=new WebSocket("ws://127.0.0.1:8000");
ws.onerror=function(e){
console.log(e);
};

上面當(dāng)然是一串不完整的代碼,,目的是演示握手過程中,,客戶端給服務(wù)端打招呼。在控制臺我們可以看到:

看起來很熟悉吧,,其實(shí)就是發(fā)送了一個 HTTP 請求,,這個我們在瀏覽器的 Network 中也可以看到:

但是 WebSocket協(xié)議 并不是 HTTP 協(xié)議,剛開始驗(yàn)證的時(shí)候借用了 HTTP 的頭,,連接成功之后的通信就不是 HTTP 了,,不信你用 fiddler2 抓包試試,肯定是拿不到的,,后面的通信部分是基于 TCP 的連接,。

服務(wù)器要成功的進(jìn)行通信,必須有應(yīng)答,,往下看:

//服務(wù)器程序
var crypto = require('crypto');
var WS = '258EAFA5-E914-47DA-95CA-C5AB0DC85B11';
require('net').createServer(function(o){
var key;
o.on('data',function(e){
if(!key){
//握手
key = e.toString().match(/Sec-WebSocket-Key: (.+)/)[1];
key = crypto.createHash('sha1').update(key + WS).digest('base64');
o.write('HTTP/1.1 101 Switching Protocols\r\n');
o.write('Upgrade: websocket\r\n');
o.write('Connection: Upgrade\r\n');
o.write('Sec-WebSocket-Accept: ' + key + '\r\n');
o.write('\r\n');
}else{
console.log(e);
};
});
}).listen(8000);

關(guān)于crypto模塊,,可以看看官方文檔,上面的代碼應(yīng)該是很好理解的,,服務(wù)器應(yīng)答之后,,Client 拿到 Sec-WebSocket-Accept ,然后本地做一次驗(yàn)證,,如果驗(yàn)證通過了,,就會觸發(fā) onopen 函數(shù)。

//客戶端程序
var ws=new WebSocket("ws://127.0.0.1:8000/");
ws.onopen=function(e){
console.log("握手成功");
};

可以看到

3. 數(shù)據(jù)幀格式

官方文檔提供了一個結(jié)構(gòu)圖

 0                   1                   2                   3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-------+-+-------------+-------------------------------+
|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 ... |
+---------------------------------------------------------------+

第一眼瞟到這張圖恐怕是要吐血,,如果大學(xué)修改計(jì)算機(jī)網(wǎng)絡(luò)這門課應(yīng)該不會對這東西陌生,,數(shù)據(jù)傳輸協(xié)議嘛,是需要定義字節(jié)長度及相關(guān)含義的,。

FIN      1bit 表示信息的最后一幀,,flag,也就是標(biāo)記符
RSV 1-3 1bit each 以后備用的 默認(rèn)都為 0
Opcode 4bit 幀類型,,稍后細(xì)說
Mask 1bit 掩碼,,是否加密數(shù)據(jù),默認(rèn)必須置為1 (這里很蛋疼)
Payload 7bit 數(shù)據(jù)的長度
Masking-key 1 or 4 bit 掩碼
Payload data (x + y) bytes 數(shù)據(jù)
Extension data x bytes 擴(kuò)展數(shù)據(jù)
Application data y bytes 程序數(shù)據(jù)

每一幀的傳輸都是遵從這個協(xié)議規(guī)則的,,知道了這個協(xié)議,,那么解析就不會太難了,下面我就直接拿了次碳酸鈷同學(xué)的代碼,。

4. 數(shù)據(jù)幀的解析和編碼

數(shù)據(jù)幀的解析代碼:

function decodeDataFrame(e){
var i=0,j,s,frame={
//解析前兩個字節(jié)的基本數(shù)據(jù)
FIN:e[i]>>7,Opcode:e[i++]&15,Mask:e[i]>>7,
PayloadLength:e[i++]&0x7F
};
//處理特殊長度126和127
if(frame.PayloadLength==126)
frame.length=(e[i++]<<8)+e[i++];
if(frame.PayloadLength==127)
i+=4, //長度一般用四字節(jié)的整型,,前四個字節(jié)通常為長整形留空的
frame.length=(e[i++]<<24)+(e[i++]<<16)+(e[i++]<<8)+e[i++];
//判斷是否使用掩碼
if(frame.Mask){
//獲取掩碼實(shí)體
frame.MaskingKey=[e[i++],e[i++],e[i++],e[i++]];
//對數(shù)據(jù)和掩碼做異或運(yùn)算
for(j=0,s=[];j<frame.PayloadLength;j++)
s.push(e[i+j]^frame.MaskingKey[j%4]);
}else s=e.slice(i,frame.PayloadLength); //否則直接使用數(shù)據(jù)
//數(shù)組轉(zhuǎn)換成緩沖區(qū)來使用
s=new Buffer(s);
//如果有必要則把緩沖區(qū)轉(zhuǎn)換成字符串來使用
if(frame.Opcode==1)s=s.toString();
//設(shè)置上數(shù)據(jù)部分
frame.PayloadData=s;
//返回?cái)?shù)據(jù)幀
return frame;
}

數(shù)據(jù)幀的編碼:

//NodeJS
function encodeDataFrame(e){
var s=[],o=new Buffer(e.PayloadData),l=o.length;
//輸入第一個字節(jié)
s.push((e.FIN<<7)+e.Opcode);
//輸入第二個字節(jié),判斷它的長度并放入相應(yīng)的后續(xù)長度消息
//永遠(yuǎn)不使用掩碼
if(l<126)s.push(l);
else if(l<0x10000)s.push(126,(l&0xFF00)>>2,l&0xFF);
else s.push(
127, 0,0,0,0, //8字節(jié)數(shù)據(jù),前4字節(jié)一般沒用留空
(l&0xFF000000)>>6,(l&0xFF0000)>>4,(l&0xFF00)>>2,l&0xFF
);
//返回頭部分和數(shù)據(jù)部分的合并緩沖區(qū)
return Buffer.concat([new Buffer(s),o]);
}

有些童鞋可能沒有明白,,應(yīng)該解析哪些數(shù)據(jù),。這的解析任務(wù)主要是服務(wù)端處理,客戶端送過去的數(shù)據(jù)是二進(jìn)制流形式的,,比如: 

var ws = new WebSocket("ws://127.0.0.1:8000/"); ws.onopen = function(){   ws.send("握手成功"); };

Server 收到的信息是這樣的:

一個放在Buffer格式的二進(jìn)制流,。而當(dāng)我們輸出的時(shí)候解析這個二進(jìn)制流:

//服務(wù)器程序
var crypto = require('crypto');
var WS = '258EAFA5-E914-47DA-95CA-C5AB0DC85B11';
require('net').createServer(function(o){
var key;
o.on('data',function(e){
if(!key){
//握手
key = e.toString().match(/Sec-WebSocket-Key: (.+)/)[1];
key = crypto.createHash('sha1').update(key + WS).digest('base64');
o.write('HTTP/1.1 101 Switching Protocols\r\n');
o.write('Upgrade: websocket\r\n');
o.write('Connection: Upgrade\r\n');
o.write('Sec-WebSocket-Accept: ' + key + '\r\n');
o.write('\r\n');
}else{
// 輸出之前解析幀
console.log(decodeDataFrame(e));
};
});
}).listen(8000);

那輸出的就是一個幀信息十分清晰的對象了:

5. 連接的控制

上面我買了個關(guān)子,提到的Opcode,,沒有詳細(xì)說明,,官方文檔也給了一張表:

 |Opcode  | Meaning                             | Reference |
-+--------+-------------------------------------+-----------|
| 0 | Continuation Frame | RFC 6455 |
-+--------+-------------------------------------+-----------|
| 1 | Text Frame | RFC 6455 |
-+--------+-------------------------------------+-----------|
| 2 | Binary Frame | RFC 6455 |
-+--------+-------------------------------------+-----------|
| 8 | Connection Close Frame | RFC 6455 |
-+--------+-------------------------------------+-----------|
| 9 | Ping Frame | RFC 6455 |
-+--------+-------------------------------------+-----------|
| 10 | Pong Frame | RFC 6455 |
-+--------+-------------------------------------+-----------|

decodeDataFrame 解析數(shù)據(jù),得到的數(shù)據(jù)格式是:

{
FIN: 1,
Opcode: 1,
Mask: 1,
PayloadLength: 4,
MaskingKey: [ 159, 18, 207, 93 ],
PayLoadData: '握手成功'
}

那么可以對應(yīng)上面查看,,此幀的作用就是發(fā)送文本,,為文本幀。因?yàn)檫B接是基于 TCP 的,,直接關(guān)閉 TCP 連接,,這個通道就關(guān)閉了,不過 WebSocket 設(shè)計(jì)的還比較人性化,,關(guān)閉之前還跟你打一聲招呼,,在服務(wù)器端,可以判斷frame的Opcode:

var frame=decodeDataFrame(e);
console.log(frame);
if(frame.Opcode==8){
o.end(); //斷開連接
}

客戶端和服務(wù)端交互的數(shù)據(jù)(幀)格式都是一樣的,,只要客戶端發(fā)送 ws.close(),, 服務(wù)器就會執(zhí)行上面的操作。相反,,如果服務(wù)器給客戶端也發(fā)送同樣的關(guān)閉幀(close frame):

o.write(encodeDataFrame({
FIN:1,
Opcode:8,
PayloadData:buf
}));

客戶端就會相應(yīng) onclose 函數(shù),,這樣的交互還算是有規(guī)有矩,不容易出錯,。

二,、注意事項(xiàng)

1. WebSocket URIs

很多人可能只知道 ws://text.com:8888,但事實(shí)上 websocket 協(xié)議地址是可以加 path 和 query 的,。

ws-URI = "ws:" "http://" host [ ":" port ] path [ "?" query ]
wss-URI = "wss:" "http://" host [ ":" port ] path [ "?" query ]

如果使用的是 wss 協(xié)議,,那么 URI 將會以安全方式連接。 這里的 wss 大小寫不敏感,。

2. 協(xié)議中”多余”的部分(吐槽)

握手請求中包含Sec-WebSocket-Key字段,明眼人一下就能看出來是websocket連接,,而且這個字段的加密方式在服務(wù)器也是固定的,,如果別人想黑你,不會太難,。

再就是那個 MaskingKey 掩碼,,既然強(qiáng)制加密了(Mask為1表示加密,加密方式就是 MaskingKey 與 PayLoadData 進(jìn)行異或處理),還有必要讓開發(fā)者處理這個東西么,?直接封裝到內(nèi)部不就行了,?

3. 與 TCP 和 HTTP 之間的關(guān)系

WebSocket協(xié)議是一個基于TCP的協(xié)議,就是握手鏈接的時(shí)候跟HTTP相關(guān)(發(fā)了一個HTTP請求),,這個請求被Server切換到(Upgrade)websocket協(xié)議了,。websocket把 80 端口作為默認(rèn)websocket連接端口,而websocket的運(yùn)行使用的是443端口,。

三,、參考資料

四、特別感謝

再次感謝 次碳酸鈷 跟我交流了幾個小時(shí) : ),,本文部分 node 代碼參考自他的博客,。

下次將以php作為后臺,講解websocket的相關(guān)知識,。

本文鏈接:http://www./blog/2013/12/20/cb-websocket-with-node/

    本站是提供個人知識管理的網(wǎng)絡(luò)存儲空間,,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點(diǎn),。請注意甄別內(nèi)容中的聯(lián)系方式,、誘導(dǎo)購買等信息,謹(jǐn)防詐騙,。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,,請點(diǎn)擊一鍵舉報(bào)。
    轉(zhuǎn)藏 分享 獻(xiàn)花(0

    0條評論

    發(fā)表

    請遵守用戶 評論公約

    類似文章 更多