1. Contrat
WebSocket est un protocole basé sur la communication duplex complète entre les clients et les serveurs sur TCP. Il est défini dans HTML5 et est également l'une des spécifications de base de la nouvelle génération de WebApps.
Il franchit les limites de l'Ajax précédent, la clé est en temps réel et le serveur peut activement pousser le contenu vers le client! Les applications possibles incluent: les jeux en ligne multijoueur, le chat en direct, la surveillance en temps réel, le bureau à distance, le serveur d'actualités, etc.
Pour moi, ce que je veux essayer le plus pour le moment, c'est ce qui peut être fait avec la combinaison Canvas + WebSocket.
2. Réalisation
Étant donné que le processus de poignée de main est une demande HTTP standard, il existe deux options pour implémenter WebSocket: 1) implémentation sur TCP; 2) Implémentation sur le logiciel HTTP existant. Ce dernier avantage est qu'il peut partager des ports de serveur HTTP existants et n'a pas besoin de réimplémenter la fonction d'authentification et d'analyser les demandes HTTP.
Le module HTTP du nœud est utilisé dans cet exemple. (Voir la pièce jointe pour la version TCP et tous les fichiers)
1. Code côté serveur de nœud:
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 s'exécute sur localhost:', port); serveur .on ('connexion', fonction (s) {console.log ('on connection', s);}) .on ('request', onrequest) .on ('upgrade', onupgrade);}); var onrequest = fonction (req, res) {console); Object.Keys (req), req.url, req ['mise à niveau']); if (! req.upgrade) {// Sélection de demande non-mise à niveau: interrompre ou fournir une page Web normale res.writehead (200, {'contenu-type': 'text / plain'}); res.write ('WebSocket Server Works!'); } res.end (); return;}; var onupgrade = function (req, sock, head) {// console.log ('méthode:', object.keys (sock)); if (req.heders.upgrade! == 'WebSocket') {console.warn ('connexion illégale'); sock.end (); retour; } bind_sock_event (sock); essayez {Handshake (req, chaussette, tête); } catch (e) {Console.Error (e); sock.end (); }}; // Enveloppez le cadre à envoyé var wrap = fonction (data) {var fa = 0x00, fe = 0xff, data = data.toString () len = 2 + buffer.ByteLength (data), buff = nouveau tampon (len); buff [0] = fa; buff.write (données, 1); buff [Len-1] = Fe; return buff;} // un peu le cadre reçu var un peu de fonction = fonction (data) {return data.slice (1, data.length-1);} var bind_sock_event = function (sock) {sock. Envoyer ('Hello Html5,' + Date.Now ()) sock.emit ('send', data);}) .on ('close', function () {console.log ('socket close');}) .on (end ', function () {console.log) sock.write (wrap (data), 'binary');})}; var get_part = function (key) {var vide = '', spaces = key.replace (// s / g, vide) .length, part = key.replace (// d / g, vide); if (! Spaces) Throw {Message: 'Mauvaise clé:' + clé, nom: 'HandshakeError'} return get_big_endian (part / espaces);} var get_big_endian = function (n) {return string.fromcharcode.apply (null, [3,2,1,0] .map (function (i) {return n >> 8 * i & 0xff}) 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.heders, 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'], tête); Output.push (C); sock.write (output.join (br), 'binary');}2. Code client du navigateur:
<html> <éad- head> <ititle> Demo WebSocket </Title> </ Head> <style type = "Text / CSS"> TextArea {Width: 400px; Height: 150px; Affichage: Block; Overflow-y: Scroll;} #Output {With: 600px; High Button {Padding: .2em 1em;} </ style> <link href = "Layout.css" rel = "Stylesheet" Type = "Text / CSS" /> <Body> <TextArea id = "outwing" ReadOnly = "ReadOnly"> </ TextArea> <br> <TextArea id = "Input"> </ textarea> <Button ID = "Envoyer" ENVOYAG Type = "Text / JavaScript"> // LocalHostVar 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 ('recevoir @' + new Date (). TolocaleTimestring () + '/ n' + e.data); output.scrollTop = output.ScrollHeight} socket.OnClose = function (e) {log (e.type);} socket.addeventListener ('close', function () {log ('un autre gestionnaire d'événements de fermeture ..');}, false); // domvar id = function (id) {return document.getelementById (id);} VaRput = id (id) id ('input'), send = id ('send'); var log = function (msg) {output.textContent + = '>' + msg + '/ n'} send.addeventListener ('click', function () {socket.send (input.value);}, false); </cript> </odody> </html>3. Détails
L'implémentation du protocole WebSocket au-dessus du protocole HTTP n'a que deux étapes: la poignée de main et l'envoi de données.
1. Se serrer la main
Le processus de poignée de main est appelé défi-réponse. Tout d'abord, le client initie une demande de requête HTTP GET nommée, le serveur vérifie la demande, donne une réponse 101 pour indiquer que la mise à niveau du protocole est acceptée et la poignée de main est terminée.
Les informations de poignée de main embellies par l'inspecteur de Chrome:
URL de demande : ws: //192.168.144.131: 4400 / pub / chat? Q = moi
Méthode de demande: obtenir
Code d'état: 101 Protocole WebSocket Protocole
Demander des en-têtes
Connexion: mise à niveau
Hôte: 192.168.144.131: 4400
Origine: http: // localhost: 800
SEC-WebSocket-Key1: P2 G 947T 80 661 JAF2
Sec-websocket-key2: z zq ^ 326 5 9 = 7S1 1 7H4
Sec-websocket-protocol :: my-custom-chat-protocol
Mise à niveau: WebSocket
(Key3): 7c: 44: 56: CA: 1F: 19: D2: 0A
En-têtes de réponse
Connexion: mise à niveau
Sec-websocket-location: ws: //192.168.144.131: 4400 / pub / chat? Q = moi
Sec-websocket-original: http: // localhost: 800
Sec-websocket-protocol: my-custom-chat-protocol
Mise à niveau: WebSocket
(Réponse du défi): 52: DF: 2C: F4: 50: C2: 8E: 98: 14: B7: 7D: 09: CF: C8: 33: 40
En-tête de demande
Hôte: Hôte de serveur WebSocket
Connexion: type de connexion
Mise à niveau: type de mise à niveau du protocole
Origine: Visitez la source
SEC-WEBSOCKET-PROTOCOL: Facultatif, nom de sous-protocole, défini par l'application elle-même, et plusieurs protocoles sont divisés par des espaces. (* Une autre option reste des cookies)
SEC-WEBSOCKET-KEY1: Clé d'authentification de sécurité, la demande XHR ne peut pas forger les en-têtes de demande en commençant par «Sec-».
Sec-websocket-key2: Identique à ci-dessus
Key3: Réponse du contenu du corps, 8 octets aléatoires.
En-tête de réponse
SEC-WebSocket-Protocol: doit contenir le nom de sous-protocole demandé
SEC-WebSocket-Origin: doit être égal à la source de la demande
Sec-websocket-location: doit être égal à l'adresse demandée
Réponse du défi: la teneur en matière de réponse du corps, calculée sur la base des trois clés de la demande, 16 octets.
Processus de calcul de la chaîne de réponse pseudocode:
part_1 = tous les nombres dans key1 / nombre d'espaces dans key1 part_2 identique à sum = big_endian (part_1) + big_endian (part_2) + key3challenge_response = md5_digest (sum);
Stratégie de calcul Big_endian pour les entiers 32 bits:
# Il est très similaire au calcul des couleurs RGBA. À partir de la fonction suivante, nous pouvons voir que le processus de calcul var big_endian = fonction (n) {return [3,2,1,0] .map (fonction (i) {return n >> 8 * i & 0xff});} big_endian (0xcc77aaff); // -> [204, 119, 170, 255]2. Envoyer des données
L'API WebSocket est conçue pour traiter les données à l'aide d'événements. Les clients peuvent obtenir des données complètes tant qu'ils obtiennent des notifications d'événements sans traiter manuellement le tampon.
Dans ce cas, chaque données est appelée cadre. Dans la définition de spécification, sa tête doit commencer avec 0x00 et l'attribut de queue se termine par 0xFF, de sorte que chaque données d'envoi a au moins deux octets.
Dans l'implémentation du serveur, la tête et la queue doivent être coupées lors de la réception de données; tandis que la tête et la queue doivent être emballées lors de l'envoi de données. Le format est le suivant:
# Représentation binaire d'origine 'Hello', l'en-tête de demande et voici les encodages UTF8
<Buffer E4 BD A0 E5 A5 BD>
# La représentation binaire enveloppée.
<Buffer 00 E4 BD A0 E5 A5 BD FF>
Ce qui précède concerne cet article, j'espère qu'il sera utile à l'apprentissage de tout le monde.