Heim > Artikel > Web-Frontend > Implementierung des Websocket-Protokolls basierend auf node_node.js
1. Vereinbarung
WebSocket ist ein Protokoll für die Vollduplex-Kommunikation zwischen Client und Server auf Basis von TCP. Es ist in HTML5 definiert und gehört auch zu den Grundspezifikationen der neuen Generation von Webapps.
Es durchbricht die bisherigen Einschränkungen von AJAX. Der Schlüssel liegt in der Echtzeitleistung. Der Server kann Inhalte aktiv an den Client weiterleiten. Zu den möglichen Anwendungen gehören: Multiplayer-Onlinespiele, Instant Chat, Echtzeitüberwachung, Remotedesktop, Newsserver usw.
Für mich selbst möchte ich im Moment am liebsten ausprobieren, was Canvas+Websocket zusammen leisten können.
2. Umsetzung
Da es sich beim Handshake-Prozess um eine Standard-HTTP-Anfrage handelt, gibt es zwei Optionen für die Websocket-Implementierung: 1) Implementierung auf TCP; 2) Implementierung auf bestehender HTTP-Software. Der Vorteil von Letzterem besteht darin, dass der vorhandene HTTP-Server-Port gemeinsam genutzt werden kann und die Authentifizierungsfunktion und die Funktion zum Parsen von HTTP-Anforderungen nicht neu implementiert werden müssen.
Das in diesem Beispiel verwendete HTTP-Modul des Knotens. (Siehe den Anhang für die TCP-Version und alle Dateien)
1. Knotenservercode:
var http = require('http'); var url = require('url'); // var mime = require('mime'); var crypto = require('crypto'); var port = 4400; var server = http.createServer(); server.listen(port,function() { console.log('server is running on localhost:',port); server .on('connection',function(s) { console.log('on connection ',s); }) .on('request',onrequest) .on('upgrade',onupgrade); }); var onrequest = function(req,res) { console.log( Object.keys(req) ,req.url,req['upgrade']); if( !req.upgrade ){ // 非upgrade请求选择:中断或提供普通网页 res.writeHead(200, { 'content-type': 'text/plain' }); res.write( 'WebSocket server works!' ); } res.end(); return; }; var onupgrade = function (req,sock,head) { // console.log('方法:',Object.keys(sock)); if(req.headers.upgrade !== 'WebSocket'){ console.warn('非法连接'); sock.end(); return; } bind_sock_event(sock); try{ handshake(req,sock,head); }catch(e){ console.error(e); sock.end(); } }; // 包装将要发送的帧 var wrap = function(data) { var fa = 0x00, fe = 0xff, data = data.toString() len = 2+Buffer.byteLength(data), buff = new Buffer(len); buff[0] = fa; buff.write(data,1); buff[len-1] = fe; return buff; } // 解开接收到的帧 var unwrap = function(data) { return data.slice(1,data.length-1); } var bind_sock_event = function(sock) { sock .on('data',function(buffer) { var data = unwrap(buffer); console.log('socket receive data : ',buffer,data,'\n>>> '+data); // send('hello html5,'+Date.now()) sock.emit('send',data); }) .on('close',function() { console.log('socket close'); }) .on('end',function() { console.log('socket end'); }) .on('send',function(data) { //自定义事件 sock.write(wrap(data),'binary'); }) }; var get_part = function(key) { var empty = '', spaces = key.replace(/\S/g,empty).length, part = key.replace(/\D/g,empty); if(!spaces) throw {message:'Wrong key: '+key,name:'HandshakeError'} return get_big_endian(part / spaces); } var get_big_endian = function(n) { return String.fromCharCode.apply(null,[3,2,1,0].map(function(i) { return n >> 8*i & 0xff })) } var challenge = function(key1,key2,head) { var sum = get_part(key1) + get_part(key2) + head.toString('binary'); return crypto.createHash('md5').update(sum).digest('binary'); } var handshake = function(req,sock,head) { var output = [],h = req.headers, br = '\r\n'; // header output.push( 'HTTP/1.1 101 WebSocket Protocol Handshake','Upgrade: WebSocket','Connection: Upgrade', 'Sec-WebSocket-Origin: ' + h.origin, 'Sec-WebSocket-Location: ws://' + h.host + req.url, 'Sec-WebSocket-Protocol: my-custom-chat-protocol'+br ); // body var c = challenge(h['sec-websocket-key1'],h['sec-websocket-key2'],head); output.push(c); sock.write(output.join(br),'binary'); }
2. Browser-Client-Code:
<html> <head> <title>WebSocket Demo</title> </head> <style type="text/css"> textarea{width:400px;height:150px;display:block;overflow-y:scroll;} #output{width:600px;height:400px;background:whiteSmoke;padding:1em .5em;color:#000;border:none;} button{padding:.2em 1em;} </style> <link href="layout.css" rel="stylesheet" type="text/css" /> <body> <textarea id="output" readonly="readonly"></textarea> <br> <textarea id="input"></textarea> <button id="send">send</button> <script type="text/javascript"> // localhost var socket = new WebSocket('ws://192.168.144.131:4400/') socket.onopen = function(e) { log(e.type); socket.send('hello node'); } socket.onclose = function(e) { log(e.type); } socket.onmessage = function(e) { log('receive @ '+ new Date().toLocaleTimeString() +'\n'+e.data); output.scrollTop = output.scrollHeight } socket.onclose = function(e) { log(e.type); } socket.addEventListener('close',function() { log('a another close event handler..'); },false); // dom var id = function(id) { return document.getElementById(id); } var output = id('output'), input = id('input'), send = id('send'); var log = function(msg) { output.textContent += '> '+msg + '\n' } send.addEventListener('click',function() { socket.send(input.value); },false); </script> </body> </html>
3. Details
Das auf dem http-Protokoll implementierte Websocket-Protokoll besteht nur aus zwei Schritten: Handshake und Senden von Daten.
1. Hände schütteln
Der Handshake-Prozess wird Challenge-Response genannt. Zunächst initiiert der Client eine HTTP-GET-Anfrage mit dem Namen „Upgrade“. Der Server überprüft diese Anfrage und gibt eine 101-Antwort aus, um die Annahme des Protokoll-Upgrades anzuzeigen.
Verschönerte Handshake-Nachricht des Chrome-Inspektors:
Anfrage URL:ws://192.168.144.131:4400/pub/chat?q=me
Anforderungsmethode:GET
Statuscode: 101 WebSocket Protocol Handshake
Header anfordern
Verbindung:Upgrade
Host:192.168.144.131:4400
Herkunft:http://localhost:800
Sec-WebSocket-Key1:p2 G 947T 80 661 jAf2
Sec-WebSocket-Key2:z Z Q ^326 5 9= 7s1 1 7H4
Sec-WebSocket-Protocol::my-custom-chat-protocol
Upgrade:WebSocket
(Schlüssel3):7C:44:56:CA:1F:19:D2:0A
Antwortheader
Verbindung:Upgrade
Sec-WebSocket-Location:ws://192.168.144.131:4400/pub/chat?q=me
Sec-WebSocket-Origin:http://localhost:800
Sec-WebSocket-Protocol:my-custom-chat-protocol
Upgrade:WebSocket
(Herausforderungsantwort):52:DF:2C:F4:50:C2:8E:98:14:B7:7D:09:CF:C8:33:40
Header-Teil anfordern
Host: WebSocket-Server-Host
Verbindung: Verbindungstyp
Upgrade: Protokoll-Upgrade-Typ
Herkunft: Quelle besuchen
Sec-WebSocket-Protocol: Optionaler Unterprotokollname, der von der Anwendung selbst definiert wird, mehrere Protokolle durch Leerzeichen getrennt. (*Die einzige verbleibende Option ist Cookie)
Sec-WebSocket-Key1: Sicherheitsauthentifizierungsschlüssel, xhr-Anfragen können keine Anforderungsheader fälschen, die mit „sec-“ beginnen.
Sec-WebSocket-Key2: Wie oben
Key3: Inhalt des Antworttextes, 8 Bytes zufällig.
Antwort-Header-Teil
Sec-WebSocket-Protokoll: Muss den angeforderten Unterprotokollnamen enthalten
Sec-WebSocket-Origin: muss dem Ursprung der Anfrage entsprechen
Sec-WebSocket-Location: muss mit der angeforderten Adresse
übereinstimmen
Challenge Response: Inhalt des Antworttextes, berechnet auf Grundlage der drei Schlüssel in der Anfrage, 16 Byte.
Pseudocode des Prozesses zur Berechnung der Antwortzeichenfolge:
part_1 = key1中所有数字 / key1中空格数量 part_2 同上 sum = big_endian(part_1)+big_endian(part_2)+key3 challenge_response = md5_digest(sum);
Big_endian-Berechnungsstrategie für 32-Bit-Ganzzahlen:
# 很类似于rgba颜色计算,从下面的函数可以看出计算过程 var big_endian = function(n) { return [3,2,1,0].map(function(i) { return n >> 8*i & 0xff }); } big_endian(0xcc77aaff); // -> [204, 119, 170, 255]
2. Daten senden
Die WebSocket-API dient zur Verarbeitung von Daten mithilfe von Ereignissen. Der Client kann vollständige Daten erhalten, solange er über das Ereignis benachrichtigt wird, ohne dass der Puffer manuell verarbeitet werden muss.
In diesem Fall wird jedes Datenelement als Frame bezeichnet. Bei der Definition der Spezifikation muss deren Header mit 0x00 beginnen und das Tail-Attribut mit 0xff enden, sodass jede Datenübertragung mindestens zwei Bytes umfasst.
In der Serverimplementierung müssen Header und Tail beim Empfangen von Daten abgeschnitten werden, während Header und Tail beim Senden von Daten umbrochen werden müssen. Das Format ist wie folgt:
# Die ursprüngliche binäre Darstellung von „Hallo“, der Anforderungsheader und hier sind alle UTF8-Kodierung
9ff83d3ef972e65a2e1d9ef95b68e70d
# Umschlossene binäre Darstellung.
f3838b625e6cab6c8cb4df3f79fd4313
Das Obige ist der gesamte Inhalt dieses Artikels. Ich hoffe, er wird für das Studium aller hilfreich sein.