Heim > Web-Frontend > js-Tutorial > Hauptteil

Implementierung des Websocket-Protokolls basierend auf node_node.js

WBOY
Freigeben: 2016-05-16 15:04:11
Original
1834 Leute haben es durchsucht

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');
}

Nach dem Login kopieren

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>

Nach dem Login kopieren

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);
Nach dem Login kopieren

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]
Nach dem Login kopieren

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

# Umschlossene binäre Darstellung.

Das Obige ist der gesamte Inhalt dieses Artikels. Ich hoffe, er wird für das Studium aller hilfreich sein.

Verwandte Etiketten:
Quelle:php.cn
Erklärung dieser Website
Der Inhalt dieses Artikels wird freiwillig von Internetnutzern beigesteuert und das Urheberrecht liegt beim ursprünglichen Autor. Diese Website übernimmt keine entsprechende rechtliche Verantwortung. Wenn Sie Inhalte finden, bei denen der Verdacht eines Plagiats oder einer Rechtsverletzung besteht, wenden Sie sich bitte an admin@php.cn
Beliebte Tutorials
Mehr>
Neueste Downloads
Mehr>
Web-Effekte
Quellcode der Website
Website-Materialien
Frontend-Vorlage
Über uns Haftungsausschluss Sitemap
Chinesische PHP-Website:Online-PHP-Schulung für das Gemeinwohl,Helfen Sie PHP-Lernenden, sich schnell weiterzuentwickeln!