WebSocket

背景

通常,当客户端访问一个网页时,会向Web服务器发送一个HTTP请求,Web服务器接收该请求,并返回响应,客户端在接收到响应后再将信息呈现出来。

对于那些信息变化不是特别频繁的应用来说,也许不会造成多大的影响,但是对于那些对实时性要求比较高即信息经常变化的应用来说(比如在线游戏,信息推送等),就必须采用某种机制来确保服务器与浏览器间的信息同步。

在WebSocket规范出来之前,可供选择的机制一般三种:

  1. 轮询(Polling)
  2. Comet
  3. Flash插件

轮询

这是最早的一种实现实时Web应用的方案,客户端按照一定的时间间隔频繁的向服务器发送请求,来实现服务端与客户端的同步。这种方案十分低效,因为并没有什么机制能确定每次发送的请求都能从服务端获得更新的数据(由于服务器更新数据的延时性,会造成客户端发送很多无用的请求,从而浪费了很多通信资源)。

comet

Comet本质上还是轮询,只是对上述轮询的缺点上做了些改进,最大限度的降低无效的网络传输。 Comet又分为长轮询技术和流技术,长轮询技术的实现是,给轮询设置条件(比如设置过期时间),当该条件被触发时再发送请求。流技术通常就是在客户端的页面使用一个隐藏窗口向服务端发出一个长连接请求,服务端响应该请求并不断更新连接状态以保证客户端和服务端的连接不过期,在面对并发量比较大的应用时,采用这一方案会消耗很多服务端的资源。

Flash 插件

AdobeFlash通过自己的Socket完成数据交换,JavaScript调用Flash提供的API,来实现数据的实时传输。这种方式比轮询要高效得多,但由于需要使用Flash插件,在一些不支持Flash插件或支持得不好客户端上,仍然不能实现实时需求。

不管是轮询还是comet,这些技术都不能称之为真正的实时技术,它们只是通过Ajax方式来模拟实时效果,客户端和服务端的每次交互都是一次完整的HTTP协议的传输过程(HTTP头信息作为传输内容),大大增加了应用的信息传输量,而且为了实现这些方案,往往需要构建较为复杂的服务端和客户端的编程实现。总体而言,这些技术是即增加了服务端的负载又增加了编程复杂度。

针对以上技术的缺陷以及web进一步的高并发和实时性需求的环境下,基于HTML5规范的WebSocket应运而生。

WebSocket是一个基于TCP协议之上解决客户端和服务端之间双向通信的协议,它能高效的实现实现需求。目前有关实时功能的实现基本上都采用WebSocket来实现。

实现

WebSocket的实现分为客户端和服务端两部分,客户端发出WebSocket连接请求,服务端响应,实现类似TCP握手的动作,客户端和服务端可以通过这个连接通道传递消息,这个连接会持续存在直到一方主动关闭连接时为止。

服务端

rails 5中引入了一个全新的基于WebSocket的框架—Action Cable,可以很方便的构建实时通知系统。下面简单列一下基础代码,有兴趣的朋友也可以点击后面的参考链接作深入的学习。

def push_to_client
  user = User.find(self.user_id)
  user.following_by_type("User").distinct.pluck("id").each do |uid|
    ActionCable.server.broadcast "notifications/#{uid}", {id: self.id, notifyType: "createTweet"}
  end
rescue
  nil
end

扩展链接: https://github.com/rails/actioncable-examples https://www.sitepoint.com/create-a-chat-app-with-rails-5-actioncable-and-devise/

客户端

// 发布消息
var initWebsocket = function() {
    var self = this;
    if (window.UasApp && window.UasApp.cable) {
        if (!this.notificationChannel) {
            this.notificationChannel = window.UasApp.cable.subscriptions.create("NotificationsChannel", {
                connected: function() {
                    console.log("connected ................");
                },
                received: function(data) {
                    self.trigger("notified", {
                        data: data
                    });
                }
            });
        }
    } 
};

// 监听消息
var bindNotifications = function() {
    // 监听websocket发布的消息,构建从关注者过来的内容
    var self = this;
    this.own(runtime.on("notified", function(evt) {
        var data = evt.data;
        if (data.notifyType === "createTweet") {
            self.newTweetIds.push(data.id);
            self.addToNoti();
        }
    }));
};

要点说明:

1. 上面的rails代码主要用到了Action Cable模块,目前已整合到rails 5.0版本中,属于rails的一部分,源代码。Action Cable 包含了后台和前端的实现,可以方便的为项目添加基于websocket的通信功能。

2. 上面的前端代码,主要实现了事件分发的功能,首先定制了action cable提供的received方法,该方法会触发notified事件的执行,然后在各实例DOM中监听notified事件,处理其对应的DOM操作,比如样例中的添加新tweet。

总结:如果需要在客户端与服务端之间建立极低延迟、近乎即时的连接,则可以使用WebSocket,比如下面的一些实用场景:

  1. 多人在线游戏
  2. 即时聊天
  3. 体育赛况直播
  1. 即时更新社交信息流

参考