node.js之websocket協議的實現
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]);
}