websocket&socket.io
2015-11-23 16:03:25

146
WebSocket
WebSocket是HTML5开始提供的一种浏览器与服务器间进行全双工通讯的网络技术。
现很多网站为了实现即时通讯,所用的技术都是轮询(polling)。
轮询
浏览器周期性的发出请求,如果服务器没有新数据需要发送就返回以空响应。这种方法问题很大:首先,大量无意义的请求造成网络压力;其次,请求周期的限制不能及时地获得最新数据。这种方法很快就被淘汰。
长轮询
长轮询是在打开一条连接以后保持连接,等待服务器推送来数据再关闭连接。然后浏览器再发出新的请求,这能更好地管理请求数量,也能及时地更新数据。AJAX调用XMLHttpRequest对象发出HTTP请求,JS响应处理函数根据服务器返回的数据更新HTML页面的展示。这个方法一定程度上消除了简单轮询的弊端,但服务器压力也是很大。
iframe流
iframe流方式是在页面中插入一个隐藏的iframe,利用其src属性在服务器和客户端之间建立一条长链接,服务器向iframe传输数据(通常是HTML,内有负责插入信息的javascript),来实时更新页面。"iframe是很早就存在的一种 HTML 标记,通过在 HTML 页面里嵌入一个隐蔵帧,然后将这个隐蔵帧的 SRC属性设为对一个长连接的请求,服务器端就能源源不断地往客户端输入数据。”其不足为:进度条会显示一直,反应在页面上就是浏览器标签页的图标会不停地转动。
WebSocket
使用WebSocket,浏览器和服务器只需要要做一个握手的动作,然后,浏览器和服务器之间就形成了一条快速通道,两者之间就直接可以数据互相传送。而且它为我们实现即时服务带来了两大好处: WebSocket是一个通信的协议,分为服务器和客户端。服务器放在后台,保持与客户端的长连接,完成双方通信的任务。客户端一般都是实现在支持HTML5浏览器核心中,通过提供JavascriptAPI使用网页可以建立websocket连接。
- 节省资源:互相沟通的Header是很小的-大概只有 2 Bytes。
- 推送信息:不需要客户端请求,服务器可以主动传送数据给客户端。
今天让我们来看看在nodejs中,如何实现websocket的通信。
图 1. 传统 HTTP 请求响应客户端服务器交互图
图 2.WebSocket 请求响应客户端服务器交互图
服务器端
var WebSocketServer = require('ws').Server , wss = new WebSocketServer({port: 8080}); wss.on('connection', function (ws) { ws.on('message', function(message) { console.log('received: %s', message); ws.send('server hello'); }); });
客户端
var WebSocket = require('ws'); var ws = new WebSocket('ws://123.57.143.189:8080/'); ws.on('open', function open() { ws.send('hello world!'); /* ws.send(2+''); ws.send(3+'');*/ }); ws.on('message', function(data, flags) { // flags.binary will be set if a binary data is received. // flags.masked will be set if the data was masked. console.log(new Buffer(data)); console.log('message ',data); });
网页客户端
<script> var socket = new WebSocket('ws://localhost:8080/'); socket.onopen = function(){ console.log('connected to websocket'); socket.send('hello server'); setInterval(function(){ socket.send('hello2222222'); },5000); } socket.onmessage = function(event){ console.log(event.data); } </script>
socket.io
Socket.IO是一个WebSocket库,包括了客户端的js和服务器端的nodejs,它的目标是构建可以在不同浏览器和移动设备上使用的实时应用。
socket.io的特点
易用性:socket.io封装了服务端和客户端,使用起来非常简单方便。 跨平台:socket.io支持跨平台,这就意味着你有了更多的选择,可以在自己喜欢的平台下开发实时应用。 自适应:它会自动根据浏览器从WebSocket、AJAX长轮询、Iframe流等等各种方式中选择最佳的方式来实现网络实时应用,非常方便和人性化,而且支持的浏览器最低达IE5.5。
安装部署
我们知道socket.io支持跨平台,在不同平台下,它的使用方法也是大同小异,所以我们只需要学习了解到它是怎么工作的,如何在某一平台使用它即可, 今天我们就来学习一下在node.js中如何使用socket.io。 在node.js中安装模块首选当然是使用npm,首要进行安装
npm install socket.io
服务监听
socket.io的服务端启动非常的简单,引用socket.io模块。
var io = require('socket.io');
然后调用listen函数,传入监听的端口号,开始服务监听。
var io = require('socket.io')(80);
注册事件
我们学习了如何启动简单的socket服务,下面来学习一下如何为服务端注册一些常用的事件:
io.on('connection',function(socket){ //连接成功 socket.on('disconnect',function(){ //用户已经离开... }); });
connection事件在客户端成功连接到服务端时触发,有了这个事件,我们可以随时掌握用户连接到服务端的信息。
当客户端成功建立连接时,在connection事件的回调函数中,我们还是可以为socket注册一些常用的事件,如:disconnect事件,它在客户端连接断开是触发,这时候我就知道用户已经离开了。
启动服务
目前为止,我们已经搭建好了一个最简单的socket服务器,为了在浏览器中能够访问到我们的服务,我们还需要在服务端搭建一个简单的web服务器,让浏览器能够访问我们的客户端页面。
我们选用node.js中常用的express框架来实现web服务
var express = require('express'); var app = express(); app.get('/',function(req,res){ res.status(200).send('socket.io'); }); var server = require('http').createServer(app); var io = require('socket.io')(server); io.on('connection',function(socket){ console.log('客户端已经连接); }); server.listen(80);
客户端引用
服务端构建完毕,下面看一看客户端应该如何使用。
服务端运行后会在根目录动态生成socket.io的客户端js文件,客户端可以通过固定路径/socket.io/socket.io.js添加引用。
首先添加网页index.html,并在网页中引用客户端js文件:
<script src="/socket.io/socket.io.js"></script>
当然这样的客户端引用方式并不是必须的,我们也可以引用官方的cdn或者下载到本地的客户端文件。一般情况下推荐引用动态生成的客户端文件,因为这样客户端和服务端的版本可以保持一致,减少出错的几率。 http://www.bootcdn.cn/socket.io/
<script src="//cdn.bootcss.com/socket.io/1.3.7/socket.io.min.js"></script>
连接服务
当客户端成功加载socket.io客户端文件后会获取到一个全局对象io,我们将通过io.connect函数来向服务端发起连接请求。
var socket = io.connect('/'); socket.on('connect',function(){ //连接成功 }); socket.on('disconnect',function(data){ //连接断开 });
connect函数可以接受一个url参数,url可以socket服务的http完整地址,也可以是相对路径,如果省略则表示默认连接当前路径。 与服务端类似,客户端也需要注册相应的事件来捕获信息,不同的是客户端连接成功的事件是connect。
了解了客户端如何使用,下面我们创建网页index.html,并添加如下内容:
<script src="/socket.io/socket.io.js"></script> <script> window.onload = function(){ var socket = io.connect('/'); socket.on('connect',function(){ document.write('连接成功!'); }); }; </script>
发送消息
当我们成功建立连接后,我们可以通过socket对象的send函数来互相发送消息,示例-客户端向服务端发送消息index.html:
var socket = io.connect('/'); socket.on('connect',function(){ //客户端连接成功后发送消息 socket.send('欢迎!'); }); socket.on('message',function(msg){ console.log(msg); });
连接成功后,我们向服务端发送消息欢迎,还为socket注册了message事件,它是send函数对应的接收消息的事件,当服务端向客户端send消息时,我们就可以在message事件中接收到发送过来的消息。
服务端向客户端发送消息也可以通过send的方式,示例 - 服务端向客户端发送消息(app.js):
var io = require('socket.io')(server); io.on('connection',function(socket){ socket.send('欢迎光临!'); socket.on('message',function(msg){ console.log(msg); socket.send('sever:'+msg); }); });
与客户端相同,服务端也需要为socket注册message事件来接收客户端发送过来的消息。
我们使用send的方式实现了信息的互发,其实send函数只是emit的封装,实际上还是使用了emit 请参阅源码 node_modules/socket.io/lib/namespace.js
Namespace.prototype.send = Namespace.prototype.write = function(){ var args = Array.prototype.slice.call(arguments); args.unshift('message'); this.emit.apply(this, args); return this; };
在send函数中,获取到原来的参数,并在原来的基础上插入了一个参数message,然后调用了emit函数。通过send函数的实现,我们也学会了emit函数的用法,它有连个参数,第一个参数是事件名称,在接收端注册该事件就可以接收到发送过去的信息,事件名称可以自由定义,在不同的场景下,我们可以定义不同的事件来接收消息。第二个参数才是发送的数据。了解清楚了工作原理,下面来将send替换成emit函数发送信息:
socket.emit('message','欢迎光临!');
服务端事件
Socket.IO内置了一些默认事件,我们在设计事件的时候应该避开默认的事件名称,并灵活运用这些默认事件。
服务器端事件:
io.sockets.on('connection’, function(socket) {}):socket连接成功之后触发,用于初始化 socket.on('message’, function(message, callback) {}):客户端通过socket.send来传送消息时触发此事件,message为传输的消息,callback是收到消息后要执行的回调 socket.on('anything’, function(data) {}):收到任何事件时触发 socket.on('disconnect’, function() {}):socket失去连接时触发(包括关闭浏览器,主动断开,掉线等任何断开连接的情况)
客户端事件:
connect:连接成功 connecting:正在连接 disconnect:断开连接 connect_failed:连接失败 error:错误发生,并且无法被其他事件类型所处理 message:同服务器端message事件 anything:同服务器端anything事件 reconnect_failed:重连失败 reconnect:成功重连 reconnecting:正在重连
在这里要提下客户端socket发起连接时的顺序。 当第一次连接时, 事件触发顺序为:connecting->connect; 当失去连接时,事件触发顺序为:disconnect->reconnecting(可能进行多次)->connecting->reconnect->connect。
命名空间
命名空间着实是一个非常实用好用的功能。我们可以通过命名空间,划分出不同的房间,在房间里的广播和通信都不会影响到房间以外的客户端。 那么如何创建房间呢?在服务端,通过of("")的方式来划分新的命名空间:
io.of('hall').on('connection',function(socket){ });
示例中,我们创建一个名为hall的房间,客户端可以通过如下方式连接到指定的房间:
var socket = io.connect('/hall');
虽然连接到指定的房间,但是我们也可以在服务端操作,自由的进出房间:
socket.join('chat');//进入chat房间 socket.leave('chat');//离开chat房间
大家要分清楚命名空间,房间和socket的关系
- socket.io支持命名空间,默认是的命名空间是 '/'
- 命名空间下面可以创建房间,没有默认的房间
socket, room, namespace三者关系:
- socket 一定是属于某个命名空间
- room 一定是属于某个命名空间
- socket可以在某个房间或者不在任何房间
如何发消息
不包括发送者
发送消息给当前的请求socket
socket.emit('message', "this is a test");
向除发送者之外的所有人发消息
socket.broadcast.emit('message', "this is a test");
向game房间里的除了发送人之外的所有用户发消息
socket.broadcast.to('game').emit('message', 'nice game');
包括发送者
发送给包括发送者在内的所有人
io.sockets.emit('message', "this is a test");
发送给包括发送者在内的game房间内的所有人
io.sockets.in('game').emit('message', 'cool game');
向特定的人发消息
io.sockets.socket(socketid).emit('message', 'for your eyes only');
不分房间
socket.emit
socket.emit信息传输对象为当前socket对应的client,各个client socket相互不影响。
socket.broadcast.emit
socket.broadcast.emit信息传输对象为所有client,排除当前socket对应的client。
io.sockets.emit
信息传输对象为所有client。
分房间
类似于之前提过的of方法生成命名空间来管理用户,socket.io可以使用分组方法,socket.join(),以及与之对应的socket.leave()。
io.sockets.on('connection', function (socket) { socket.on('firefox', function (data) { socket.join('firefox'); }); socket.on('chrome',function(data){ socket.join('chrome'); }); });
设有两个聊天室,一个名为firefox,另一个为chrome,客户端操作
socket.emit('firefox'),就可以加入firefox聊天室; socket.emit('chrome'),就可以加入chrome聊天室;
向一个分组传输消息,有两种方式:
//向发送者外chrome房间内的所有成员发送消息 socket.broadcast.to('chrome').emit('event_name', data); //向包括发送者在内的chrome房间内所有成员发送消息 io.sockets.in('chrome').emit('event_name', data)
broadcast方法允许当前socket client不在该分组内。
一个socket是否可以同时存在于几个分组,等效于一个用户会同时在几个聊天室活跃,答案是”可以“,socket.join()添加进去就可以了。官方提供了订阅模式的示例:
socket = io.connect('http://127.0.0.1/'); socket.emit('subscribe',{"room" : "chrome"}; socket.emit('unsubscribe',{"room" : "chrome"}; socket.on('subscribe', function(data) { socket.join(data.room); }) socket.on('unsubscribe', function(data) { socket.leave(data.room); })
资源大全
socket.io官网
socket.io官方文档
Socket.io:有点意思
socket.io学习笔记
Socket.IO进阶