Maison >interface Web >js tutoriel >Implémentation du protocole websocket basé sur node_node.js
1. Accord
WebSocket est un protocole de communication full-duplex entre client et serveur basé sur TCP. Il est défini en HTML5 et constitue également l'une des spécifications de base de la nouvelle génération d'applications Web.
Il dépasse les limitations précédentes d'AJAX. La clé réside dans les performances en temps réel. Le serveur peut activement transmettre du contenu au client ! Les applications possibles incluent : les jeux multijoueurs en ligne, le chat instantané, la surveillance en temps réel, le bureau à distance, le serveur de nouvelles, etc.
Pour ma part, ce que je veux essayer le plus en ce moment, c'est ce que Canvas+Websocket peuvent faire ensemble.
2.Mise en œuvre
Étant donné que le processus de prise de contact est une requête HTTP standard, il existe deux options pour l'implémentation de websocket : 1) Implémentation sur TCP ; 2) Implémentation sur un logiciel HTTP existant. L'avantage de ce dernier est qu'il peut partager le port du serveur HTTP existant et n'a pas besoin de réimplémenter la fonction d'authentification et la fonction d'analyse des requêtes HTTP.
Module HTTP du nœud utilisé dans cet exemple. (Voir la pièce jointe pour la version TCP et tous les fichiers)
1. Code du serveur du 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 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. Code client du navigateur :
<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. Détails
Le protocole websocket implémenté au-dessus du protocole http ne comporte que deux étapes : la prise de contact et l'envoi de données.
1. Serrez la main
Le processus de prise de contact est appelé défi-réponse. Tout d'abord, le client lance une requête HTTP GET nommée Upgrade. Le serveur vérifie cette requête et donne une réponse 101 pour indiquer l'acceptation de la mise à niveau du protocole.
Message de poignée de main embelli de l'inspecteur Chrome :
Demande URL:ws://192.168.144.131:4400/pub/chat?q=me
Méthode de requête : GET
Code d'état : 101 Prise de contact du protocole WebSocket
En-têtes de requête
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 Z Q ^326 5 9= 7s1 1 7H4
Sec-WebSocket-Protocol::mon-protocole-de-chat-personnalisé
Mise à niveau : WebSocket
(Clé3):7C:44:56:CA:1F:19:D2:0A
En-têtes de réponse
Connexion : Mise à niveau
Emplacement Sec-WebSocket : ws://192.168.144.131:4400/pub/chat?q=me
Sec-WebSocket-Origin : http://localhost:800
Sec-WebSocket-Protocol : mon-protocole-de-chat-personnalisé
Mise à niveau : WebSocket
(Réponse au défi) :52:DF:2C:F4:50:C2:8E:98:14:B7:7D:09:CF:C8:33:40
Partie d'en-tête de demande
Hôte : hôte du serveur websocket
Connexion : type de connexion
Mise à niveau : type de mise à niveau du protocole
Origine : Visiter la source
Sec-WebSocket-Protocol : facultatif, nom du sous-protocole, défini par l'application elle-même, plusieurs protocoles séparés par des espaces. (*La seule option restante est le cookie)
Sec-WebSocket-Key1 : clé d'authentification de sécurité, les requêtes xhr ne peuvent pas falsifier les en-têtes de requête commençant par « sec- ».
Sec-WebSocket-Key2 : Idem que ci-dessus
Clé 3 : contenu du corps de la réponse, 8 octets aléatoires.
Partie d'en-tête de réponse
Sec-WebSocket-Protocol : doit contenir le nom du sous-protocole demandé
Sec-WebSocket-Origin : doit être égal à l'origine de la requête
Sec-WebSocket-Location : doit être égal à l'adresse demandée
Réponse au défi : contenu du corps de la réponse, calculé en fonction des trois clés de la demande, 16 octets.
Pseudocode du processus de calcul de la chaîne de réponse :
part_1 = key1中所有数字 / key1中空格数量 part_2 同上 sum = big_endian(part_1)+big_endian(part_2)+key3 challenge_response = md5_digest(sum);
Stratégie de calcul big_endian pour les entiers 32 bits :
# 很类似于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. Envoyer des données
L'API WebSocket est conçue pour traiter les données à l'aide d'événements. Le client peut obtenir des données complètes tant qu'il est informé de l'événement, sans avoir besoin de traiter manuellement le tampon.
Dans ce cas, chaque élément de données est appelé une trame. Dans la définition de la spécification, son en-tête doit commencer par 0x00 et l'attribut tail doit se terminer par 0xff, afin que chaque transmission de données comporte au moins deux octets.
Dans l'implémentation du serveur, l'en-tête et la queue doivent être tronqués lors de la réception des données, tandis que l'en-tête et la queue doivent être enveloppés lors de l'envoi des données. Le format est le suivant :
# La représentation binaire originale de 'Bonjour', l'en-tête de la requête et voici tous le codage utf8
6efa4fe9dfc3593f66ecc9a1cb850e40
# Représentation binaire enveloppée.
0980ae8760202cd53c77581e245c6b32
Ce qui précède représente l’intégralité du contenu de cet article, j’espère qu’il sera utile à l’étude de chacun.