Maison > interface Web > js tutoriel > Implémentation du protocole websocket basé sur node_node.js

Implémentation du protocole websocket basé sur node_node.js

WBOY
Libérer: 2016-05-16 15:04:11
original
1918 Les gens l'ont consulté

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

Copier après la connexion

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>

Copier après la connexion

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);
Copier après la connexion

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]
Copier après la connexion

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

# Représentation binaire enveloppée.

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.

Étiquettes associées:
source:php.cn
Déclaration de ce site Web
Le contenu de cet article est volontairement contribué par les internautes et les droits d'auteur appartiennent à l'auteur original. Ce site n'assume aucune responsabilité légale correspondante. Si vous trouvez un contenu suspecté de plagiat ou de contrefaçon, veuillez contacter admin@php.cn
Tutoriels populaires
Plus>
Derniers téléchargements
Plus>
effets Web
Code source du site Web
Matériel du site Web
Modèle frontal