珠峰培训

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进阶

上一篇 :

mysql入门

下一篇 :

yeoman入门