Rumah  >  Artikel  >  hujung hadapan web  >  Melaksanakan protokol websocket berdasarkan node_node.js

Melaksanakan protokol websocket berdasarkan node_node.js

WBOY
WBOYasal
2016-05-16 15:04:111882semak imbas

1. Perjanjian
WebSocket ialah protokol untuk komunikasi dupleks penuh antara klien dan pelayan berdasarkan TCP Ia ditakrifkan dalam HTML5 dan juga merupakan salah satu spesifikasi asas bagi aplikasi web generasi baharu.

Ia menembusi batasan AJAX sebelumnya. Kuncinya terletak pada prestasi masa nyata Pelayan boleh menolak kandungan secara aktif kepada pelanggan. Aplikasi yang mungkin termasuk: permainan dalam talian berbilang pemain, sembang segera, pemantauan masa nyata, desktop jauh, pelayan berita, dsb.

Bagi diri saya sendiri, perkara yang paling saya ingin cuba sekarang ialah apa yang boleh dilakukan oleh kanvas+websocket bersama.

2. Pelaksanaan
Memandangkan proses jabat tangan adalah permintaan HTTP standard, terdapat dua pilihan untuk pelaksanaan soket web: 1) Pelaksanaan pada TCP 2) Pelaksanaan pada perisian HTTP sedia ada. Kelebihan yang terakhir ialah ia boleh berkongsi port pelayan HTTP sedia ada dan tidak perlu melaksanakan semula fungsi pengesahan dan fungsi menghuraikan permintaan HTTP.

Modul HTTP Node yang digunakan dalam contoh ini. (Lihat lampiran untuk versi TCP dan semua fail)

1. Kod pelayan nod:

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. Kod pelanggan penyemak imbas:

<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. Butiran
Protokol websocket yang dilaksanakan di atas protokol http hanya mempunyai dua langkah: jabat tangan dan menghantar data.

1. Berjabat tangan
Proses jabat tangan dipanggil respons-cabaran. Mula-mula, klien memulakan permintaan HTTP GET bernama Naik Taraf Pelayan mengesahkan permintaan ini dan memberikan respons 101 untuk menunjukkan penerimaan naik taraf protokol.

Mesej jabat tangan indah pemeriksa Chrome:

Permintaan URL:ws://192.168.144.131:4400/pub/chat?q=me
Kaedah Permintaan: DAPATKAN
Kod Status:101 WebSocket Protocol Handshake

Minta Pengepala
Sambungan:Naik taraf
Hos:192.168.144.131:4400
Asal: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
Naik taraf:WebSocket
(Kunci3):7C:44:56:CA:1F:19:D2:0A

Tajuk Balasan
Sambungan:Naik taraf
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
Naik taraf:WebSocket
(Respon Cabaran):52:DF:2C:F4:50:C2:8E:98:14:B7:7D:09:CF:C8:33:40

Minta bahagian pengepala

Hos: hos pelayan soket web
Sambungan: jenis sambungan
Naik taraf: Jenis peningkatan protokol
Asal: Lawati sumber
Sec-WebSocket-Protocol: Pilihan, nama sub-protokol, ditakrifkan oleh aplikasi itu sendiri Berbilang protokol dipisahkan oleh ruang. (*Satu-satunya pilihan yang tinggal ialah kuki)
Sec-WebSocket-Key1: Kunci pengesahan keselamatan, permintaan xhr tidak boleh memalsukan pengepala permintaan bermula dengan 'sec-'.
Sec-WebSocket-Key2: Sama seperti di atas
Kunci3: Kandungan badan respons, 8 bait rawak.
Bahagian pengepala respons

Sec-WebSocket-Protocol: Mesti mengandungi nama sub-protokol yang diminta
Sec-WebSocket-Origin: mesti sama dengan asal permintaan
Sec-WebSocket-Location: mesti sama dengan alamat yang diminta
Respons Cabaran: Kandungan badan respons, dikira berdasarkan tiga kunci dalam permintaan, 16 bait.
Pseudokod proses pengiraan rentetan tindak balas:

part_1 = key1中所有数字 / key1中空格数量
part_2 同上
sum = big_endian(part_1)+big_endian(part_2)+key3
challenge_response = md5_digest(sum);

strategi pengiraan big_endian untuk integer 32-bit:

# 很类似于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. Hantar data
WebSocket API direka bentuk untuk memproses data menggunakan acara Pelanggan boleh mendapatkan data lengkap selagi ia dimaklumkan tentang acara tersebut, tanpa perlu memproses penimbal secara manual.

Dalam kes ini, setiap keping data dipanggil bingkai. Dalam definisi spesifikasi, pengepalanya mesti bermula dengan 0x00 dan atribut ekor mesti berakhir dengan 0xff, supaya setiap penghantaran data mempunyai sekurang-kurangnya dua bait.

Dalam pelaksanaan pelayan, pengepala dan ekor mesti dipotong apabila menerima data manakala pengepala dan ekor mesti dibalut semasa menghantar data. Formatnya adalah seperti berikut:

# Perwakilan binari asal 'Hello', pengepala permintaan dan ini semua pengekodan utf8
de10295d58c2ad56f71ed6fe7aa84d51
# Perwakilan binari yang dibalut.
0131994756dd869c219f29fe72c3b95a

Di atas adalah keseluruhan kandungan artikel ini, saya harap ia akan membantu kajian semua orang.

Kenyataan:
Kandungan artikel ini disumbangkan secara sukarela oleh netizen, dan hak cipta adalah milik pengarang asal. Laman web ini tidak memikul tanggungjawab undang-undang yang sepadan. Jika anda menemui sebarang kandungan yang disyaki plagiarisme atau pelanggaran, sila hubungi admin@php.cn