首先,关于websocket教程,参考详细教程javascript
WebSocket 是一种网络通讯协议,不少高级功能都须要它。html
由于 HTTP 协议有一个缺陷:通讯只能由客户端发起,单向请求,若是服务端有状态变化,却没法及时通知客户端。(“轮询”效率低,要一直保持链接或者重复链接)java
它的最大特色就是,服务器能够主动向客户端推送信息,客户端也能够主动向服务器发送信息,是真正的双向平等对话,属于服务器推送技术
的一种。
其余特色包括:
(1)创建在 TCP 协议之上,服务器端的实现比较容易。
(2)与 HTTP 协议有着良好的兼容性。默认端口也是80和443,而且握手阶段采用 HTTP 协议,所以握手时不容易屏蔽,能经过各类 HTTP 代理服务器。
(3)数据格式比较轻量,性能开销小,通讯高效。
(4)能够发送文本,也能够发送二进制数据。
(5)没有同源限制,客户端能够与任意服务器通讯。
(6)协议标识符是ws
(若是加密,则为wss
),服务器网址就是 URL。git
ws://example.com:80/some/path
//WebSocket 对象做为一个构造函数,用于新建 WebSocket 实例。 var ws = new WebSocket("wss://echo.websocket.org"); ws.onopen = function(evt) { console.log("Connection open ..."); ws.send("Hello WebSockets!"); }; ws.onmessage = function(evt) { console.log( "Received Message: " + evt.data); ws.close(); }; ws.onclose = function(evt) { console.log("Connection closed."); };
readyState
属性返回实例对象的当前状态,共有四种。github
- CONNECTING:值为0,表示正在链接。
- OPEN:值为1,表示链接成功,能够通讯了。
- CLOSING:值为2,表示链接正在关闭。
- CLOSED:值为3,表示链接已经关闭,或者打开链接失败。
示例:web
switch (ws.readyState) { case WebSocket.CONNECTING: // do something break; case WebSocket.OPEN: // do something break; case WebSocket.CLOSING: // do something break; case WebSocket.CLOSED: // do something break; default: // this never happens break; }
实例对象的onopen
属性,用于指定链接成功后的回调函数。canvas
ws.onopen = function () { ws.send('Hello Server!'); }
若是要指定多个回调函数,可使用addEventListener
方法。数组
ws.addEventListener('open', function (event) { ws.send('Hello Server!'); });
实例对象的onclose
属性,用于指定链接关闭后的回调函数。服务器
ws.onclose = function(event) { var code = event.code; var reason = event.reason; var wasClean = event.wasClean; // handle close event }; ws.addEventListener("close", function(event) { var code = event.code; var reason = event.reason; var wasClean = event.wasClean; // handle close event });
实例对象的onmessage
属性,用于指定收到服务器数据后的回调函数。websocket
ws.onmessage = function(event) { var data = event.data; // 处理数据 }; ws.addEventListener("message", function(event) { var data = event.data; // 处理数据 });
注意,服务器数据多是文本,也多是二进制数据(blob
对象或Arraybuffer
对象)。
ws.onmessage = function(event){ if(typeof event.data === String) { console.log("Received data string"); } if(event.data instanceof ArrayBuffer){ var buffer = event.data; console.log("Received arraybuffer"); } }
除了动态判断收到的数据类型,也可使用binaryType
属性,显式指定收到的二进制数据类型。
// 收到的是 blob 数据 ws.binaryType = "blob"; ws.onmessage = function(e) { console.log(e.data.size); }; // 收到的是 ArrayBuffer 数据 ws.binaryType = "arraybuffer"; ws.onmessage = function(e) { console.log(e.data.byteLength); };
实例对象的send()
方法用于向服务器发送数据。
发送文本的例子。
ws.send('your message');
发送 Blob 对象的例子。
var file = document .querySelector('input[type="file"]') .files[0]; ws.send(file);
发送 ArrayBuffer 对象的例子。
// Sending canvas ImageData as ArrayBuffer var img = canvas_context.getImageData(0, 0, 400, 320); var binary = new Uint8Array(img.data.length); for (var i = 0; i < img.data.length; i++) { binary[i] = img.data[i]; } ws.send(binary.buffer);
实例对象的bufferedAmount
属性,表示还有多少字节的二进制数据没有发送出去。它能够用来判断发送是否结束。
var data = new ArrayBuffer(10000000); socket.send(data); if (socket.bufferedAmount === 0) { // 发送完毕 } else { // 发送还没结束 }
实例对象的onerror
属性,用于指定报错时的回调函数。
socket.onerror = function(event) { // handle error event }; socket.addEventListener("error", function(event) { // handle error event });
WebSocket 服务器的实现,能够查看维基百科的列表。
经常使用的 Node 实现有如下三种。
/*app.js*/ //构建http服务 var app = require('http').createServer() //引入socket.io var io = require('socket.io')(app); //定义监听端口,能够自定义,端口不要被占用 var PORT = 8081; /*定义用户数组*/ var users = []; //监听端口 app.listen(PORT); io.on('connection', function (socket) { /*是不是新用户标识*/ var isNewPerson = true; /*当前登陆用户*/ var username = null; /*监听登陆*/ socket.on('login',function(data){ for(var i=0;i<users.length;i++){ if(users[i].username === data.username){ isNewPerson = false break; }else{ isNewPerson = true } } if(isNewPerson){ username = data.username users.push({ username:data.username }) /*登陆成功*/ socket.emit('loginSuccess',data) /*向全部链接的客户端广播add事件*/ io.sockets.emit('add',data) }else{ /*登陆失败*/ socket.emit('loginFail','') } }) /*退出登陆*/ socket.on('disconnect',function(){ /*向全部链接的客户端广播leave事件*/ io.sockets.emit('leave',username) users.map(function(val,index){ if(val.username === username){ users.splice(index,1); } }) }) socket.on('sendMessage',function(data){ io.sockets.emit('receiveMessage',data) }) }) console.log('app listen at'+PORT);
/*chat.js*/ $(function(){ /*创建socket链接,使用websocket协议,端口号是服务器端监听端口号*/ var socket=io('ws://localhost:8081'); /*定义用户名*/ var uname = null; /*登陆*/ $('.login-btn').click(function(){ uname = $.trim($('#loginName').val()); if(uname){ /*向服务端发送登陆事件*/ socket.emit('login',{username:uname}) }else{ alert('请输入昵称') } }) /*登陆成功*/ socket.on('loginSuccess',function(data){ if(data.username === uname){ checkin(data) }else{ alert('用户名不匹配,请重试') } }) /*登陆失败*/ socket.on('loginFail',function(){ alert('昵称重复') }) /*新人加入提示*/ socket.on('add',function(data){ var html = '<p>系统消息:'+data.username+'已加入群聊</p>'; $('.chat-con').append(html); }) /*隐藏登陆界面 显示聊天界面*/ function checkin(data){ $('.login-wrap').hide('slow'); $('.chat-wrap').show('slow'); } /*退出群聊提示*/ socket.on('leave',function(name){ if(name != null){ var html = '<p>FBI warning:'+name+'已退出群聊</p>'; $('.chat-con').append(html); } }) /*chat.js*/ /*发送消息*/ $('.sendBtn').click(function(){ sendMessage() }); $(document).keydown(function(event){ if(event.keyCode === 13){ sendMessage() } }) function sendMessage(){ var txt = $('#sendtxt').val(); $('#sendtxt').val(''); if(txt){ socket.emit('sendMessage',{username:uname,message:txt}); } } /*接收消息*/ socket.on('receiveMessage',function(data){ showMessage(data) }) /*显示消息*/ function showMessage(data){ var html //预防XSS攻击 var msg=data.message msg= msg.replace(/\\/g,'\\\\'); msg= msg.replace(/"/g,'\\"'); if(data.username === uname){ html = '<div class="chat-item item-right clearfix"><span class="img fr"></span><span class="message fr">'+msg+'</span></div>' }else{ html='<div class="chat-item item-left clearfix rela"><span class="abs uname">'+data.username+'</span><span class="img fl"></span><span class="fl message">'+msg+'</span></div>' } $('.chat-con').append(html); } })