首页 > 网站 > WEB开发 > 正文

Socket.io:有点意思

2024-04-27 14:11:46
字体:
来源:转载
供稿:网友

Socket.io:有点意思

个人网站 欢迎品尝edwardesire.com

下面页面就是使用Socket.io制作的口袋妖怪游戏(默认小屏下已隐藏,请切换到大分辨率查看)。左边是游戏画面,右边是按键表和聊天室。画面达到红蓝版本的水平了。


  1. 前导 ——WebSocket的介绍

      传统的Web应用采用的是客户端发出请求、服务器端响应的工作方式。在这种情景下,浏览器作为Web应用的前端,自身的处理功能是十分有限的。这种方法不能满足某些应用的实时需求(服务器需要主动更新浏览器端的数据)。不同于服务器端等待HTTP请求,这需要服务器端主动发送数据以给客户端更新。解决方案有两类:一类是基于HTTP的Comet推送技术,另一类是基于套接口(Socket)传送信息实现消息传输。  而目前使用Comet主要有两种方式,轮询和iframe流。

    • 轮询 polling  浏览器周期性的发出请求,如果服务器没有新数据需要发送就返回以空响应。这种方法问题很大:首先,大量无意义的请求造成网络压力;其次,请求周期的限制不能及时地获得最新数据。这种方法很快就被淘汰。

    • 长轮询 long polling  长轮询是在打开一条连接以后保持连接,等待服务器推送来数据再关闭连接。然后浏览器再发出新的请求,这能更好地管理请求数量,也能及时地更新数据。Ajax调用xmlHttPRequest对象发出HTTP请求,JS响应处理函数根据服务器返回的数据更新HTML页面的展示。这个方法一定程度上消除了简单轮询的弊端,但服务器压力也是很大。

    • iframe流 iframe streaming  iframe流方式是在页面中插入一个隐藏的iframe,利用其src属性在服务器和客户端之间建立一条长链接,服务器向iframe传输数据(通常是HTML,内有负责插入信息的javascript),来实时更新页面。"iframe是很早就存在的一种 HTML 标记,通过在 HTML 页面里嵌入一个隐蔵帧,然后将这个隐蔵帧的 SRC属性设为对一个长连接的请求,服务器端就能源源不断地往客户端输入数据。”其不足为:进度条会显示一直,反应在页面上就是浏览器标签页的图标会不停地转动。(当然这也是有解决方法的)

      另一类方法则是基于WebSocket  HTML5提供的Websocket不同于上面这些在老的HTML已有框架内的方法,而是在单个TCP连接上进行全双工通讯的协议。目前主流浏览器都已支持。  1. 初始化过程   不同于早期JAVA使用在浏览器安装插件的方法——-Java Applet 套接口:这种方法不足在于Java Applet再收到服务器返回的消息后,无法通过Javascript去更新HTML页面的内容。而是通过HTTP建立连接(HTTP handshake)。  2. 开始通讯   一旦初始连接建立,浏览器和服务器就打开了一个TCP socket的频道。在这个频道内就能进行双向的数据通信。

    然而Websocket依然有一些问题。比如浏览器兼容性问题(随着浏览器的发展,肯定是越来越小的),以及网络中间物(代理服务、防火墙)问题不支持WebSocket,这时Socket.io的出现就是为了完善WebSocket。

  2. Socket.IO

      Guillermo Rauch在2010年开发第一版时,目的很明确地指向Node.js实时应用。在几次版本更新后,重新定义和封装核心功能而分化出一个基础模块 Engine.io——力求建立更稳定的工具。Engine.IO有着更稳定的连接质量。使得Socket.IO在先打开一个长轮询,再在将连接推至WebSocket频道继续通信。  在使用Node的http模块创建服务器同时还要Express应用,因为这个服务器对象需要同时充当Express服务和Socket.io服务。(如下)

    var app = require('express')(); //Express服务var server = require('http').Server(app); //原生Http服务var io = require('socket.io')(server); //Socket.io服务io.on('connection', function(socket){    /* 具体操作 */});server.listen(3000);

      当客户端需要连接服务器时,它需要先建立一个握手。io.处理连接事件,socket 处理断开连接事件。在上面代码里,这套握手机制是完全自动的,我们可以通过也可以io.use()方法来设置这一过程。  客户端使用js调用socket.io的Client API即可。

    <script src="/lib/socket.io/socket.io.js"></script><script>   var socket = io();   socket.on('connect', function() {           /* 具体操作 */   });</script>

      Socket.IO还要一些系统事件,包括了连接、重连、关闭的事件。我们也可以自定义事件,以及监听方法。

    socket.on('customEvent', function(customEventData) {     /* 具体操作 */ });

      相应地,在对的时间和地方的调用.emit('customEvent', customEventData); 触发事件就行了。不过,事件是无法在客户端之间发送的。  同一个服务器可以使用namespaces创造不同的Socket连接。Socket.IO使用of()来指定不同的命名空间。

    io.of('/someNamespace').on('connection', function(socket){    socket.on('customEvent', function(customEventData) {        /* 具体操作 */    });});io.of('/someOtherNamespace').on('connection', function(socket){    socket.on('customEvent', function(customEventData) {    /* 具体操作 */    });});

      服务器端则通过在定义Socket对象时传递namespace参数。

    <script> var someSocket = io('/someNamespace'); someSocket.on('customEvent', function(customEventData) {     /* 具体操作 */ }); var someOtherSocket = io('/someOtherNamespace'); someOtherSocket.on('customEvent', function(customEventData) {     /* 具体操作 */ });</script>

      在每一个namespace中又可以使用room来进一步划分,不过sockets是使用join()、leave()来调用。

    //服务器端io.on('event', function(eventData){     //监听join事件     socket.on('join', function(roomData){          socket.join(roomData.roomName);     });     //监听leave事件     socket.on('leave', function(roomData){          socket.leave(roomData.roomName);     });});//浏览器端io.on('connection', function(socket){     //在此room下触发事件     io. in('someRoom') .emit('customEvent', customEventData);});

  下面通过《MEAN Web Development》书中的例子来实际操作一下。

  1. 配置Socket.io服务器 首先安装安装Socket.IO、connect-mongo、cookie-parser依赖我们先将依赖报引入,然后定义服务器对象。

    var http = require('http');var socketio = require('socket.io');//...var app = express();var server = http.createServer(app);var io = socketio.listen(server);
  2. 配置Socket.io session 为了是Socket.io seesion 和Express session一起工作,我们必须让他们信息共享。Express Session 默认是存储在内存,我们需要把它存在mongoDB以便Socket.io能获取。使用connect-mongo来控制session信息的存储,以及使用以前用到过的cookie-parse来解析session cookie信息。 先来修改express.js文件以便connect-mongo能够正常使用。

    var mongoStore = new MongoStore({    db: db.connection.db //通过server.js传递参数db到express的配置中});app.use(session({    saveUninitialized: true,    resave: true,    secret: config.sessionSecret,    store: mongoStore}));

     这样Session就存到数据库中来,新建配置文件socketio.js来配置socketio

    var config = require('./config'),    cookieParser = require('cookie-parser'),    passport = require('passport');/** * @description * @param {HTTP object} server 带socket服务的http服务 * @param {Socket.io Object} io 监听server的Socket服务 * @param {MongoStore Object} mongoStore mongoDB的存储 * * */module.exports = function(server, io, mongoStore){    io.use(function(socket, next){        //解析请求socket.request        cookieParser(config.sessionSecret)(socket.request, {}, function(err){            //获得sessionId            var sessionId = socket.request.signedCookies['connect.sid'];            //获得数据库中的session数据            mongoStore.get(sessionId, function(err, session){                socket.request.session = session;                //填充 socket.request.user对象                passport.initialize()(socket.request, {}, function(){                    passport.session()(socket.request, {}, function(){                        if(socket.request.user){                            next(null, true);                        }else{                            next(new Error('User is not authenticated'), false);                        }                    });                });            });        });        io.on('connection', function(socket){            console.log('a socket is connected');            require('../app/controllers/chat.server.controller')(io, socket);        });    });};

     cookieParser首先解析Express的Session,然后读取sessionId获得数据库中的session数据,填充到user对象中。如果通过passport来验证用户数据是非法的,则跳出Socket.IO的设置,并发出错误提示。接下来只需要建立Socket.IO的后端控制器即可完成后端的开发。

  3. 配置chat控制器 chat功能的控制器统一监听和触发Socket.IO事件来进行数据通信。通过事件处理的回调函数来控制数据格式的建立和分发。

    module.exports = function(io, socket){    //触发chatMessage事件,提示用户已连接    io.emit('chatMessage', {        type: 'status',        text: 'connected',        created: Date.now(),        username: socket.request.user.username    });    //监听chatMessage事件,获得用户的消息    socket.on('chatMessage', function(message){        message.type = 'message';        message.created = Date.now();        messsage.username = socket.request.user.username;        //触发事件并发送数据。        io.emit('chatMessage', message);    });    //监听断开连接事件    socket.on('disconnect', function(message){        //触发事件并发送数据。        io.emit('chatMessage', {            type: 'status',            text: 'disconnected',            created: Date.now(),            username: socket.request.user.username        });    });};

     确定监听事件规则后,将控制器载入到Socket.IO的连接事件处理函数中即可。

    io.on('connection', function(socket){    console.log('a socket is connected');    require('../app/controllers/chat.server.controller')(io, socket);});
  4. Angular前端设计 我们先通过建立ng-resource来封装Socket.IO的方法,再中前端的控制器中调用。 service是懒加载,即只有在请求时才加载。这可以阻止未验证用户调用到service的方法来获得数据,将emit()、on()、removeListenter()一套方法封装成的更相容的服务方法,减少代码的重写。然而ng的数据绑定只有在框架内执行的方法才能实时改变,也就是说第三方事件导致的数据模型的改变是未知的。那么,我们在socket中任何事件被触发时,处理函数对数据的修改可能不会及时地绑定到$scope数据模型上。(这都是抄来的)这里使用$timeout来强

发表评论 共有条评论
用户名: 密码:
验证码: 匿名发表