1. Agreement
WebSocket is a protocol for full-duplex communication between client and server based on TCP. It is defined in HTML5 and is also one of the basic specifications of the new generation of webapps.
It breaks through the previous limitations of AJAX. The key lies in real-time performance. The server can actively push content to the client! Possible applications include: multiplayer online games, instant chat, real-time monitoring, remote desktop, news server, etc.
For myself, what I want to try most right now is what canvas+websocket can do together.
2. Implementation
Since the handshake process is a standard HTTP request, there are two options for websocket implementation: 1) Implementation on TCP; 2) Implementation on existing HTTP software. The advantage of the latter is that it can share the existing HTTP server port and does not need to reimplement the authentication function and the function of parsing HTTP requests.
Node’s HTTP module used in this example. (See the attachment for the TCP version and all files)
1. node server code:
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. Browser client code:
<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. Details
The websocket protocol implemented on top of the http protocol has only two steps: handshake and sending data.
1. Shake hands
The handshake process is called challenge-response. First, the client initiates an HTTP GET request named Upgrade. The server verifies this request and gives a 101 response to indicate acceptance of the protocol upgrade. The handshake is completed.
Chrome inspector’s beautified handshake message:
Request URL:ws://192.168.144.131:4400/pub/chat?q=me
Request Method:GET
Status Code:101 WebSocket Protocol Handshake
Request Headers
Connection:Upgrade
Host:192.168.144.131:4400
Origin: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
Upgrade:WebSocket
(Key3):7C:44:56:CA:1F:19:D2:0A
Response Headers
Connection:Upgrade
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
Upgrade:WebSocket
(Challenge Response):52:DF:2C:F4:50:C2:8E:98:14:B7:7D:09:CF:C8:33:40
Request header part
Host: websocket server host
Connection: connection type
Upgrade: Protocol upgrade type
Origin: Visit source
Sec-WebSocket-Protocol: Optional, sub-protocol name, defined by the application itself, multiple protocols separated by spaces. (*The only remaining option is cookie)
Sec-WebSocket-Key1: Security authentication key, xhr requests cannot forge request headers starting with 'sec-'.
Sec-WebSocket-Key2: Same as above
Key3: Response body content, 8 bytes random.
Response header part
Sec-WebSocket-Protocol: Must contain the requested sub-protocol name
Sec-WebSocket-Origin: must equal the origin of the request
Sec-WebSocket-Location: must equal the requested address
Challenge Response: Response body content, calculated based on the three keys in the request, 16 bytes.
Pseudocode of the response string calculation process:
part_1 = key1中所有数字 / key1中空格数量 part_2 同上 sum = big_endian(part_1)+big_endian(part_2)+key3 challenge_response = md5_digest(sum);
big_endian calculation strategy for 32-bit integers:
# 很类似于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. Send data
WebSocket API is designed to process data using events. The client can obtain complete data as long as it is notified of the event, without the need to manually process the buffer.
In this case, each piece of data is called a frame. In the definition of the specification, its header must start with 0x00 and the tail attribute must end with 0xff, so that each data transmission has at least two bytes.
In the server implementation, the header and tail must be truncated when receiving data; while the header and tail must be wrapped when sending data. The format is as follows:
# The original binary representation of 'Hello', the request header and here are all utf8 encoding
# Wrapped binary representation.
The above is the entire content of this article, I hope it will be helpful to everyone’s study.