構(gòu)建websocket服務(wù)
websocket的優(yōu)勢(shì):
- 客戶端與服務(wù)器只需要一個(gè)tcp連接
- 服務(wù)器可以推送到客戶端
- 輕量化的協(xié)議頭养涮,提高傳輸效率
node使用websocket的優(yōu)勢(shì):
- WebSocket客戶端基于事件的編程模式和node的自定義事件類似
- websocket需要客戶端與服務(wù)器之間的長(zhǎng)連接眯娱,node事件驅(qū)動(dòng)的方式擅長(zhǎng)與量大的客戶端保持高并發(fā)連接
WebSocket握手
客戶端發(fā)起升級(jí)協(xié)議請(qǐng)求:
GET / chat HTTP / 1.1
Host: server.example.com
Upgrade: websocket //升級(jí)協(xié)議為websocket
Connection: Upgrade
Sec - WebSocket - Key: dGhlIHNhbXBsZSBub25jZQ ==
Sec - WebSocket - Protocol: chat, superchat //子協(xié)議
Sec - WebSocket - Version: 13 //版本號(hào)
Sec-WebSocket-Key用于安全校驗(yàn)蕾殴,值是隨機(jī)生成的Base64編碼的字符串宇攻。服務(wù)端將其與字符串258EAFA5-E914-47DA-95CA-C5AB0DC85B11拼接八毯,然后再用sha1計(jì)算再Base64編碼
var crypto = require('crypto');
var val = crypto.createHash('sha1').update(key).digest('base64');
//服務(wù)端響應(yīng)b報(bào)文
HTTP / 1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec - WebSocket - Accept: s3pPLMBiTxaQ9kYGzzhZRbK + xOo =
Sec - WebSocket - Protocol: chat
客戶端校驗(yàn)Sec-WebSocket-Accept翠霍,正確的話就開(kāi)始數(shù)據(jù)傳輸
WebSocket數(shù)據(jù)傳輸
在握手后就開(kāi)始websocket數(shù)據(jù)幀協(xié)議本谜, 握手完成客戶端onopen()被觸發(fā)
socket.onopen = function() {
// TODO: opened()
};
服務(wù)端沒(méi)有onopen()方法献酗,想完成tcp套接字事件到websocket事件的封裝寝受,需要在收發(fā)數(shù)據(jù)時(shí)處理,Websocket的數(shù)據(jù)幀是在底層data事件上封裝的
//接收
WebSocket.prototype.setSocket = function(socket) {
this.socket = socket;
this.socket.on('data', this.receiver);
};
//發(fā)送
WebSocket.prototype.send = function(data) {
this._send(data);
};
當(dāng)一端調(diào)用send()發(fā)送時(shí)罕偎,另一端會(huì)觸發(fā)onmessage,協(xié)議可能將數(shù)據(jù)封裝為多幀發(fā)送很澄。客戶端需要對(duì)發(fā)送的數(shù)據(jù)幀做掩碼處理颜及,服務(wù)端收到無(wú)掩碼幀會(huì)斷開(kāi)連接甩苛,而服務(wù)端發(fā)送時(shí)不需要
websocket數(shù)據(jù)幀定義
![Smaller icon](../picture/websocket%E6%95%B0%E6%8D%AE%E5%B8%A7%E5%AE%9A%E4%B9%89.png)
- fin 如果這數(shù)據(jù)幀是最后一幀時(shí)為1(如果數(shù)據(jù)就一幀,它也是1)俏站,其余為0
- rsv1讯蒲、rsv2、rsv3:1位長(zhǎng) 用于標(biāo)識(shí)拓展肄扎,當(dāng)有拓展時(shí)為1
- opcode: 4位(0~15) 0:附加數(shù)據(jù)幀 ,1:文本數(shù)據(jù)幀 ,2:二進(jìn)制數(shù)據(jù)幀,8:發(fā)送一個(gè)連接關(guān)閉幀,9:ping數(shù)據(jù)幀 ,10:pong數(shù)據(jù)幀 ping,pong用于心跳檢測(cè)墨林,一端發(fā)ping、一端發(fā)pong
- masked 是否進(jìn)行掩碼處理 客戶端發(fā)送時(shí)是1 服務(wù)端是0
- payload 標(biāo)識(shí)數(shù)據(jù)長(zhǎng)度
- masking key 當(dāng)masked為1時(shí)存在 長(zhǎng)度32位 用于解密
- payload data 目標(biāo)數(shù)據(jù) 位數(shù)為8的倍數(shù)
網(wǎng)絡(luò)服務(wù)和安全
- ssl(Secure Sockets Layer,安全套接層)犯祠,應(yīng)用在傳輸層
- TLS(Transport Layer Security,安全傳輸層協(xié)議)萌丈,由IETF標(biāo)準(zhǔn)化
node提供crypto,tls,https雷则。crypto用于加解密辆雾,tls與net功能類似,區(qū)別是它建立在TLS/SSL加密的tcp.https和http接口也一致月劈,也是區(qū)別在建立于安全的連接
TLS/SSL
- 非對(duì)稱加密度迂,公鑰用于加密傳輸數(shù)據(jù)藤乙,私鑰解密
![Smaller icon](../picture/ssl%E4%BA%A4%E6%8D%A2%E7%A7%98%E9%92%A5.png)
node的tls/ssl是用openssl實(shí)現(xiàn)的,公惭墓、私鑰生成參照:
// 生成服務(wù)器端私
$ openssl genrsa -out server.key 1024 // 生成客戶端私
$ openssl genrsa -out client.key 1024
//利用上面的1024位長(zhǎng)的RSA私鑰生成公鑰
$ openssl rsa -in server.key -pubout -out server.pem
$ openssl rsa -in client.key -pubout -out client.pem
數(shù)字證書
- 由CA頒發(fā)坛梁,并提供驗(yàn)證
- 防止中間人攻擊
中間人攻擊
:在服務(wù)端和客戶端交換密鑰時(shí),偽裝成其中一方發(fā)送公鑰腊凶,如對(duì)客戶端就偽裝成服務(wù)端划咐。所以需要對(duì)公鑰認(rèn)證,確認(rèn)來(lái)自目標(biāo)服務(wù)器
服務(wù)端通過(guò)私鑰生成CSR(Certificate Signing Request钧萍,證書簽名請(qǐng)求)褐缠,ca通過(guò)它頒發(fā)屬于該服務(wù)器的簽名證書
自簽名證書流程:
\\ca生成私鑰,csr文件风瘦,和自簽名的證書
$ openssl genrsa -out ca.key 1024
$ openssl req -new -key ca.key -out ca.csr
$ openssl x509 -req -in ca.csr -signkey ca.key -out ca.crt
\\服務(wù)器生成csr,向ca申請(qǐng)簽名队魏,獲取證書
$ openssl req -new -key server.key -out server.csr
$ openssl x509 -req -CA ca.crt -CAkey ca.key -CAcreateserial -in server.csr -out server.crt
客戶端發(fā)起安全連接會(huì)獲取服務(wù)端證書,然后用ca的證書驗(yàn)證服務(wù)器證書万搔,包括真?zhèn)魏啊⒎?wù)器名稱、ip等瞬雹。對(duì)于知名ca,它的證書一般預(yù)裝在瀏覽器昧谊,自簽的ca需要客戶端安裝才能驗(yàn)證
創(chuàng)建tcl服務(wù)
- 通過(guò)node的tls創(chuàng)建安全的tcp服務(wù)
//服務(wù)端
var tls = require('tls');
var fs = require('fs');
var options = {
key: fs.readFileSync('./keys/server.key'),
cert: fs.readFileSync('./keys/server.crt'),
requestCert: true,
ca: [fs.readFileSync('./keys/ca.crt')]
};
var server = tls.createServer(options, function(stream) {
console.log('server connected', stream.authorized ? 'authorized' : 'unauthorized');
stream.write("welcome!\n");
stream.setEncoding('utf8');
stream.pipe(stream);
});
server.listen(8000, function() {
console.log('server bound');
});
//測(cè)試: $ openssl s_client -connect 127.0.0.1:8000
//客戶端
$ openssl genrsa - out client.key 1024
$ openssl req - new - key client.key - out client.csr
$ openssl x509 - req - CA ca.crt - CAkey ca.key - CAcreateserial - in client.csr - out client.crt
var fs = require('fs');
var tls = require('tls');
var options = {
key: fs.readFileSync('./keys/client.key'),
cert: fs.readFileSync('./keys/client.crt'),
ca: [fs.readFileSync('./keys/ca.crt')]
};
var stream = tls.connect(8000, options, function() {
console.log('client connected', stream.authorized ? 'authorized' : 'unauthorized');
process.stdin.pipe(stream);
});
stream.setEncoding('utf8');
stream.on('data', function(data) {
console.log(data);
});
stream.on('end', function() {
server.close();
});
//和tcp相比只是多了證書配置
https服務(wù)
- 使用node的https,比http多了一個(gè)配置
var https = require('https');
var fs = require('fs');
var options = {
key: fs.readFileSync('./keys/server.key'),
cert: fs.readFileSync('./keys/server.crt')
};
https.createServer(options, function(req, res) {
res.writeHead(200);
res.end("hello world\n");
}).listen(8000);
//驗(yàn)證 $ curl https://localhost:8000/ -k, 忽略證書驗(yàn)證 -carcert ca證書地址
//客戶端
var https = require('https');
var fs = require('fs');
var options = {
hostname: 'localhost',
port: 8000,
path: '/',
method: 'GET',
key: fs.readFileSync('./keys/client.key'),
cert: fs.readFileSync('./keys/client.crt'),
ca: [fs.readFileSync('./keys/ca.crt')]
};
options.agent = new https.Agent(options);//https代理另設(shè)
var req = https.request(options, function(res) {
res.setEncoding('utf-8');
res.on('data', function(d) {
console.log(d);
});
});
req.end();
req.on('error', function(e) {
console.log(e);
});