1. Agreement
WebSocket is a protocol based on full duplex communication between clients and servers 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 limitations of the earlier AJAX, the key is real-time, and the server can actively push content to the client! Possible applications include: multiplayer online gaming, live chat, real-time monitoring, remote desktop, news server, etc.
For myself, what I want to try most at the moment is what can be done with canvas+websocket combination.
2. Realization
Since the handshake process is a standard HTTP request, there are two options for implementing websocket: 1) implementation on TCP; 2) implementation on existing HTTP software. The latter advantage is that it can share existing HTTP server ports and does not need to re-implement the authentication function and parse HTTP requests.
The HTTP module of node is used in this example. (See attachment for TCP version and all files)
1. Node server-side 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 ){ // Non-upgrade request selection: interrupt or provide a normal web page res.writeHead(200, { 'content-type': 'text/plain' }); res.write( 'WebSocket server works!' ); } res.end(); return;};var onupgrade = function (req,sock,head) { // console.log('Method:',Object.keys(sock)); if(req.headers.upgrade !== 'WebSocket'){ console.warn('Illegal Connection'); sock.end(); return; } bind_sock_event(sock); try{ handshake(req,sock,head); }catch(e){ console.error(e); sock.end(); }};// wrap the frame to be sent 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;}// Unwrap the received frame 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) { //Custom event 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">// localhostvar 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);// domvar 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 implementation above the http protocol has only two steps: handshake and send data.
1. Shake hands
The process of handshake is called challenge-response. First, the client initiates an HTTP GET request named Upgrade, the server verifies the request, gives a 101 response to indicate that the protocol upgrade is accepted, and the handshake is completed.
The handshake information beautified by chrome inspector:
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 ZQ ^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
Host: websocket server host
Connection: Connection type
Upgrade: Protocol upgrade type
Origin: Visit source
Sec-WebSocket-Protocol: Optional, subprotocol name, defined by the application itself, and multiple protocols are divided by spaces. (*One other option left is cookies)
Sec-WebSocket-Key1: Security authentication key, xhr request cannot forge request headers starting with 'sec-'.
Sec-WebSocket-Key2: Same as above
Key3: Response body content, 8 bytes random.
Response header
Sec-WebSocket-Protocol: Must contain the requested subprotocol name
Sec-WebSocket-Origin: Must be equal to the source of the request
Sec-WebSocket-Location: Must be equal to the requested address
Challenge Response: The response body content, calculated based on the three keys in the request, 16 bytes.
Response string calculation process pseudocode:
part_1 = all numbers in key1 / number of spaces in key1 part_2 Same as above sum = big_endian(part_1)+big_endian(part_2)+key3challenge_response = md5_digest(sum);
Big_endian calculation strategy for 32-bit integers:
# It is very similar to rgba color calculation. From the following function, we can see that the calculation process 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
The WebSocket API is designed to process data using events. Clients can obtain complete data as long as they get event notifications without manually processing the buffer.
In this case, each data is called a frame. In the specification definition, its head must start with 0x00 and the tail attribute ends with 0xff, so that each data sending has at least two bytes.
In the server implementation, the head and tail must be cut off when receiving data; while the head and tail must be packaged when sending data. The format is as follows:
# 'Hello' original binary representation, the request header and here are utf8 encodings
<Buffer e4 bd a0 e5 a5 bd>
# The wrapped binary representation.
<Buffer 00 e4 bd a0 e5 a5 bd ff>
The above is all about this article, I hope it will be helpful to everyone's learning.