node.js之websocket协议的实现

jopen 10年前

websocket已经不是什么新鲜的东西了,要在node.js上实现也有socket.io这样好用的第三方模块.但是个人有代码洁癖,实在是受不了在HTML页面上多出一行如下代码:

    <script src='http://192.168.0.143:4000/socket.io/socket.io.js'></script>

而且,项目上要实现的效果是和canvas交互,有些东西还是和socket封装在一起比较简单,所以自己踏上了探究websocket的道路.

顺便共享下我的劳动成果,还不算完善,因为项目就我一个人在搞...进程有点慢(demo下载后,node启动nodeCanvas.js)

GitHub:nodeCanvas

因为在github里面的代码有注释,所以我只说一下websocket大概的实现步骤.


Browser端:

 websocket = new WebSocket("ws://" + ip + ":3000/");            //websocket的各种事件          websocket.onopen = function () {              console.log("Connected to WebSocket server.");          };                     websocket.onclose = function () {              console.log("Disconnected");              //zwei.close();          };            websocket.onmessage = function (evt) {              console.log('Retrieved data from server: ' + evt.data);              //zwei.msg();          };                    websocket.onerror = function (evt) {              console.log('Error occured: ' + evt.data);              //zwei.error();          };                    websocket.send("zwei");

上面的是一看就懂的东西有几个要注意的东西是send发送的东西可以是对象或者数组等,但是不能是多个,只能是一个,而且在server端接收的时候全部都变成十六进制的.

 

Server端:

server端一开始就比较蛋疼,搞了我将近一个星期,因为之前没弄个http头啥的,对这方面了解不深也不多....总体上分为2个部分.第一个是协议的升级,让2端意识到协议升级为websocket.第二个部分就是解码(后面在介绍).

第一个部分,直接上代码:

  var key = req.headers['sec-websocket-key'],              steam = new Buffer(0),              resHeaders,              MAGIC_STRING = '258EAFA5-E914-47DA-95CA-C5AB0DC85B11',              websocket;            key = require('crypto')              .createHash('sha1')              .update(key + MAGIC_STRING)              .digest('base64');          resHeaders = [              'HTTP/1.1 101 Switching Protocols',              'Upgrade: websocket',              'Connection: Upgrade',              'Sec-WebSocket-Accept: ' + key          ];          resHeaders = resHeaders.concat('', '').join('\r\n');          socket.setNoDelay(true);          socket.write(resHeaders);

这部分就是握手的过程。首先客户端发起一个名为Upgrade的HTTP GET请求,服务器验证此请求,给出101响应以表示接受此次协议升级,握手即完成了。

因为在 http 协议之上的 websocket 协议实现只有两步:握手,发送数据。所以这样就算建立起了websocket的连接了.

上面代码各个部分的作用可以自行百度,或者看<<深入浅出Node.js>>,我也是大部分从这本书里学的


第二部分:

这里我卡了很久,因为websocket传送到server端时,数据是十六进制的格式,而且还是加密的,虽然解码是有规律的不过也折腾了我好久,之后在cnode论坛看到有人写了一个解码的,试了下可以,之后就拿他的代码改了下(毕竟我自己基础知识不好,对位运算毫无办法),这2段代码还是不错的,一个解码,一个加密用于传送回browser(在browser端接收到server端数据时是自动解码的所以不用解码),直接贴上代码:

//处理掩码Buffer流(接收)  function decodeFrame(frame) {      if (frame.length < 2) {          return null;      }        var counter = 0,          fin_offset = 7,          opcode_offset = parseInt(1111, 2),   //15          mask_offset = 7,          payload_len_offset = parseInt(1111111, 2),   //127          FIN ,          Opcode ,          MASK ,          Payload_len,          buffer,          Masking_key,          i,          j;        FIN = frame[counter] >> fin_offset;        Opcode = frame[counter++] & opcode_offset;      MASK = frame[counter] >> mask_offset;      Payload_len = frame[counter++] & payload_len_offset;      Payload_len === 126 && (Payload_len = frame.readUInt16BE(counter)) && (counter += 2);      Payload_len === 127 && (Payload_len = frame.readUInt32BE(counter + 4)) && (counter += 8);        buffer = new Buffer(Payload_len);      if (MASK) {          Masking_key = frame.slice(counter, counter + 4);          counter += 4;          for (i = 0; i < Payload_len; i++) {              j = i % 4;              buffer[i] = frame[counter + i] ^ Masking_key[j];          }      }      if (frame.length < counter + Payload_len) {          return undefined;      }        frame = frame.slice(counter + Payload_len);        return {          FIN: FIN,          Opcode: Opcode,          MASK: MASK,          Payload_len: Payload_len,          Payload_data: buffer,          frame: frame      };  }      //处理掩码Buffer流(发送)  function encodeFrame(frame) {      var preBytes = [],            payBytes = new Buffer(frame.Payload_data),          dataLength = payBytes.length;      preBytes.push((frame.FIN << 7) + frame.Opcode);        if (dataLength < 126) {          preBytes.push((frame.MASK << 7) + dataLength);      }        else if (dataLength < Math.pow(2, 16)) {          preBytes.push(              (frame.MASK << 7) + 126,              (dataLength && 0xFF00) >> 8,              dataLength && 0xFF          );      }      else {          preBytes.push(              (frame.MASK << 7) + 127,              0, 0, 0, 0,              (dataLength && 0xFF000000) >> 24,              (dataLength && 0xFF0000) >> 16,              (dataLength && 0xFF00) >> 8,              dataLength && 0xFF          );      }      preBytes = new Buffer(preBytes);      return Buffer.concat([preBytes, payBytes]);  }