Created
July 6, 2011 03:08
-
-
Save ambar/1066475 to your computer and use it in GitHub Desktop.
websocket的node实现笔记,http和tcp两版
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<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://localhost:4400/pub/chat?q=me','my-custom-chat-protocol') | |
// var socket = new WebSocket('ws://192.168.144.131:4400/pub/chat?q=me','my-custom-chat-protocol') | |
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> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
var http = require('http'); | |
var url = require('url'); | |
// var mime = require('mime'); | |
var crypto = require('crypto'); | |
var cp = require('child_process'), | |
spawn = cp.spawn, | |
exec = cp.exec; | |
var bash_exec = function(cmd,callback) { | |
exec(cmd,function(err,stdout,stderr) { | |
callback(err||stdout,stderr); | |
}); | |
}; | |
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 ',Object.keys(s),typeof 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).toString(); | |
console.log('socket receive data : ',buffer,data,'\n>>> '+data); | |
// send('hello html5,'+Date.now()) | |
var d = data.split(' '); | |
if(d.shift() === 'bash'){ | |
bash_exec(d.join(''),function(data) { | |
sock.emit('send',data); | |
}) | |
} | |
else if(data === 'close'){ | |
sock.end(); | |
}else{ | |
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'); | |
console.log(output.join(br),new Buffer(c,'binary').length); | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
var net = require('net'); | |
var crypto = require('crypto'); | |
var fs = require('fs'); | |
var port = 4400; | |
var server = net.createServer(function (sock) { | |
// c.pipe(c) | |
// console.log(Object.keys(c),arguments); | |
// sock.ondata = function(data,start,end) { | |
// fs.writeFile('data_'+Date.now()+'.txt',data); | |
// console.log(data,data.length,start,end); | |
// console.log(data.slice(end-8,end)); | |
// }; | |
var handshaked = false; | |
sock | |
.on('close',function() { | |
console.log('sock close'); | |
}) | |
.on('data',function(data) { | |
// first conn | |
console.log('on data',data); | |
if(!handshaked){ | |
console.log('begin Handshake...'); | |
var req = parse(data.toString()); | |
var len = data.length; | |
handshake(sock,req,data.slice(len-8,len)); | |
handshaked = true; | |
}else{ | |
sock.write(data); | |
} | |
}) | |
.on('error',function() { | |
console.log('error'); | |
}) | |
.on('timeout',function() { | |
console.log('timeout'); | |
}) | |
.on('connect',function() { | |
console.log('connect'); | |
}) | |
}); | |
server.listen(port); | |
console.log('server is running on localhost:',port); | |
var err_head = function(msg) { | |
throw {name:'WrongRequestError',message:msg}; | |
} | |
var parse = function(raw_head) { | |
var raw = raw_head.split('\r\n'); | |
var headers = {}; | |
var err = function() { | |
err_head('Invalid request headers!'); | |
} | |
console.log('Request >>> ',raw_head); | |
// 请求首行 | |
var request_line = raw.shift().split(' '), | |
method = request_line[0],path = request_line[1]; | |
if(request_line.length !==3 && method !== 'GET'){ | |
err(); | |
} | |
console.log(method,path); | |
raw.pop(); | |
if(raw.pop() !== '' || !raw.length){ | |
err(); | |
} | |
var re = /(^[^:]+)(:\s)(.*)$/; | |
raw.forEach(function(line,i) { | |
var m = line.match(re); | |
if( m.length !== 4 || !m[1] ) err(); | |
headers[m[1].toLowerCase()] = m[3]; | |
}); | |
var is_nil = function(s) { return !s; } | |
if( [headers.host, headers.origin].some(is_nil) ){ | |
err(); | |
} | |
return { | |
headers : headers, | |
url : path | |
} | |
} | |
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(sock,req,head) { | |
var h = req.headers; | |
var headers = [ | |
'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: ' + h['sec-websocket-protocol'] | |
]; | |
var prove = challenge(h['sec-websocket-key1'],h['sec-websocket-key2'],head); | |
var line = '\r\n'; | |
var output = headers.concat(['',prove]).join(line); | |
sock.write(output,'binary'); | |
console.log('Response >>>'); | |
console.log(output,line,new Buffer(prove,'binary').length); | |
} | |
server | |
.on('connection',function(client) { | |
console.log('on connection'); | |
}) | |
.on('close',function(client) { | |
console.log('on close'); | |
}) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment