WebSocket&webWorkers

WebSocket是HTML5开始提供的一种在 单个 TCP 连接 上进行全双工通讯的协议。


实现背景

WebSocket协议之前,双工通信是通过多个http链接来实现,这导致了效率低下。
长久以来,创建实现客户端和用户端之间双工通讯的web app都会造成HTTP轮询的滥用: 客户端向主机不断发送不同的HTTP呼叫来进行询问。
   
   
     
     
     
     
这会导致一系列的问题:
1.服务器被迫为每个客户端使用 许多不同的底层TCP连接 :一个用于向客户端发送信息,其它用于接收每个传入消息。
2.有线协议有很高的开销,每一个客户端和服务器之间都有HTTP头。
3.客户端脚本被迫维护从传出连接到传入连接的映射来追踪回复
实现原理

在实现websocket连线过程中,需要通过浏览器发出websocket连线请求,然后服务器发出回应,这个过程通常称为“握手” 。在 WebSocket API,浏览器和服务器 只需要做一个握手 的动作,然后,浏览器和服务器之间就形成了一条快速通道。两者之间就直接可以数据互相传送。在此WebSocket 协议中,为我们实现即时服务带来了两大好处:
1. Header
互相沟通的Header是很小的-大概只有 2 Bytes
2. Server Push
服务器的推送,服务器不再被动的接收到浏览器的请求之后才返回数据,而是在有新数据时就主动推送给浏览器。


var Socket = new WebSocket(url, [protocol] );

第一个参数 url, 指定连接的 URL。第二个参数 protocol 是可选的,指定了可接受的子协议。


WebSocket 属性

以下是 WebSocket 对象的属性。假定我们使用了以上代码创建了 Socket 对象:

属性 描述
Socket.readyState

只读属性 readyState 表示连接状态,可以是以下值:

  • 0 - 表示连接尚未建立。

  • 1 - 表示连接已建立,可以进行通信。

  • 2 - 表示连接正在进行关闭。

  • 3 - 表示连接已经关闭或者连接不能打开。

Socket.bufferedAmount

只读属性 bufferedAmount 已被 send() 放入正在队列中等待传输,但是还没有发出的 UTF-8 文本字节数。


WebSocket 事件

以下是 WebSocket 对象的相关事件。假定我们使用了以上代码创建了 Socket 对象:

事件 事件处理程序 描述
open Socket.onopen 连接建立时触发
message Socket.onmessage 客户端接收服务端数据时触发
error Socket.onerror 通信发生错误时触发
close Socket.onclose 连接关闭时触发

WebSocket 方法

以下是 WebSocket 对象的相关方法。假定我们使用了以上代码创建了 Socket 对象:

方法 描述
Socket.send()

使用连接发送数据

Socket.close()

关闭连接


websocket的应用场景:

决定手头的工作是否需要使用WebSocket技术的方法很简单:

  • 你的应用提供多个用户相互交流吗?
  • 你的应用是展示服务器端经常变动的数据吗?

如果你的回答是肯定的,那么请考虑使用WebSocket。如果你仍然不确定,并想要更多的灵感,这有一些杀手锏的案例。

 

1.社交订阅

对社交类的应用的一个裨益之处就是能够即时的知道你的朋友正在做什么。虽然听起来有点可怕,但是我们都喜欢这样做。你不会想要在数分钟之后才能知道一个家庭成员在馅饼制作大赛获胜或者一个朋友订婚的消息。你是在线的,所以你的订阅的更新应该是实时的。

2.多玩家游戏

网络正在迅速转变为游戏平台。在不使用插件(我指的是Flash)的情况下,网络开发者现在可以在浏览器中实现和体验高性能的游戏。无论你是在处理DOM元素、CSS动画,HTML5的canvas或者尝试使用WebGL,玩家之间的互动效率是至关重要的。我不想在我扣动扳机之后,我的对手却已经移动位置。

 

3.协同编辑/编程

我们生活在分布式开发团队的时代。平时使用一个文档的副本就满足工作需求了,但是你最终需要有一个方式来合并所有的编辑副本。版本控制系统,比如Git能够帮助处理某些文件,但是当git发现一个它不能解决的冲突时,你仍然需要去跟踪人们的修改历史。通过一个协同解决方案,比如WebSocket,我们能够工作在同一个文档,从而省去所有的合并版本。这样会很容易看出谁在编辑什么或者你在和谁同时在修改文档的同一部分。

4.点击流数据

分析用户与你网站的互动是提升你的网站的关键。HTTP的开销让我们只能优先考虑和收集最重要的数据部分。然后,经过六个月的线下分析,我们意识到我们应该收集一个不同的判断标准——一个看起来不是那么重要但是现在却影响了一个关键的决定。与HTTP请求的开销方式相比,使用Websocket,你可以由客户端发送不受限制的数据。想要在除页面加载之外跟踪鼠标的移动?只需要通过WebSocket连接发送这些数据到服务器,并存储在你喜欢的NoSQL数据库中就可以了(MongoDB是适合记录这样的事件的)。现在你可以通过回放用户在页面的动作来清楚的知道发生了什么。

 

5.股票基金报价

金融界瞬息万变——几乎是每毫秒都在变化。我们人类的大脑不能持续以那样的速度处理那么多的数据,所以我们写了一些算法来帮我们处理这些事情。虽然你不一定是在处理高频的交易,但是,过时的信息也只能导致损失。当你有一个显示盘来跟踪你感兴趣的公司时,你肯定想要随时知道他们的价值,而不是10秒前的数据。使用WebSocket可以流式更新这些数据变化而不需要等待。

6.体育实况更新

现在我们开始讨论一个让人们激情澎湃的愚蠢的东西——体育。我不是运动爱好者,但是我知道运动迷们想要什么。当爱国者在打比赛的时候,我的妹夫将会沉浸于这场比赛中而不能自拔。那是一种疯狂痴迷的状态,完全发自内心的。我虽然不理解这个,但是我敬佩他们与运动之间的这种强烈的联系,所以,最后我能做的就是给他的体验中降低延迟。如果你在你的网站应用中包含了体育新闻,WebSocket能够助力你的用户获得实时的更新。

 
7.多媒体聊天

视频会议并不能代替和真人相见,但当你不能在同一个屋子里见到你谈话的对象时,视频会议是个不错的选择。尽管视频会议私有化做的“不错”,但其使用还是很繁琐。我可是开放式网络的粉丝,所以用WebSockets getUserMedia API和html5音视频元素明显是个不错的选择。WebRTC的出现顺理成章的成为我刚才概括的组合体,它看起来很有希望,但其缺乏目前浏览器的支持,所以就取消了它成为候选人的资格。

 
8.基于位置的应用

越来越多的开发者借用移动设备的GPS功能来实现他们基于位置的网络应用。如果你一直记录用户的位置(比如运行应用来记录运动轨迹),你可以收集到更加细致化的数据。如果你想实时的更新网络数据仪表盘(可以说是一个监视运动员的教练),HTTP协议显得有些笨拙。借用WebSocket TCP链接可以让数据飞起来。

9.在线教育

上学花费越来越贵了,但互联网变得更快和更便宜。在线教育是学习的不错方式,尤其是你可以和老师以及其他同学一起交流。很自然,WebSockets是个不错的选择,可以多媒体聊天、文字聊天以及其它优势如与别人合作一起在公共数字黑板上画画...



一、概述

HTML5 WebSockets是HTML5中最强大的通信功能,它定义了一个全双工通信信道,仅通过Web上的一个Socket即可进行通信。

目前实时Web应用的实现方式,大部分是围绕轮询和其他服务器端推送技术展开的,Comet、轮询、长轮询、流(streaming)解决方案,所有这些提供实时数据的方式包含有大量额外的、不必要的报头数据,会造成传输延迟。最重要的是为了在半双工HTTP的基础上模拟全双工通信,目前的许多解决方案都是使用了两个连接:一个用于下行数据流,另一个用于上行数据流。这两个连接的保持和协作也会造成大量的资源消耗,并增加了复杂度。

WebSockets就是解决以上问题的方案。为了建立WebSocket通信,客户端和服务器在初始握手时,将HTTP协议升级到WebSocket协议。

 

现在WebSocket服务器有很多,还在开发中的更多。有一下几种:

  • Kaazing WebSocket Gateway:一种基于Java的WebSocket网关。
  • mod_pywebsocket:一种基于Python的Apache HTTP服务器扩展。
  • Netty:一种包含WebSocket的Java框架。
  • node.js:一种驱动多个WebSocket服务器的服务器端JavaScript框架。
对于非原生支持WebSocket的浏览器来说,Kazzing的WebSocket网关包含了完整的客户端浏览器WebSocket模拟支持。

二、HTML5 WebSockets API

1、浏览器支持情况检测

检测浏览器支持情况代码   收藏代码
  1. function loadDemo() {  
  2.     if (window.WebSocket) {  
  3.         //supported  
  4.     } else {  
  5.         // not supported  
  6.     }  
  7. }  

2、WebSocket对象的创建和服务器连接

要连接通信端点,只需要创建一个新的WebSocket实例,并提供希望连接的对端URL。ws://和wss://前缀分别表示WebSocket连接和安全的WebSocket连接。

Js代码   收藏代码
  1. url = "ws://localhost:8080/echo";  
  2. w = new WebSocket(url);  

 

建立WebSocket连接时,可以列出Web应用能够使用的协议。WebSocket构造函数的第二个参数既可以是字符串,也可以是字符串组。

Js代码   收藏代码
  1. w = new WebSocket(url, ["proto1""proto2"]);  

假设proto1和proto2是定义明确、可能已注册且标准化的协议名称,它们能够同时为客户端和服务器端所理解。服务器会从列表中选择首选协议。

Js代码   收藏代码
  1. onopen = function(e) {  
  2.     //确定服务器选择的协议  
  3.     log(e.target.protocol);  
  4. }  

 

3、添加事件监听器

WebSocket编程遵循异步编程模型;打开socket后,只需等待事件发生,而不需要主动向服务器轮询,所以需要在WebSocket对象中添加回调函数来监听事件。

WebSocket对象有三个事件:open、close和message。

Js代码   收藏代码
  1. w.onopen = function() {  
  2.     log("open");  
  3.     w.send("send message");  
  4. }  
  5. w.onmessage = function(e) {  
  6.     log(e.data);  
  7. }  
  8. w.onclose = function(e) {  
  9.     log("closed");  
  10. }  
  11. w.onerror = function(e) {  
  12.     log("error");  
  13. }  

 

4、发送消息

当socket处于打开状态(即onopen之后,onclose之前),可以用send方法来发送消息。消息发送完,可以调用close方法来终止连接,也可以不这么做,让其保持打开状态。

Js代码   收藏代码
  1. w.send();  

 

你可能想测算在调用Send()函数之前,有多少数据备份在发送缓冲区中。bufferAmount属性表示已在WebSocket上发送但尚未写入网络的字节数。它对于调节发送速率很有用。

Js代码   收藏代码
  1. document.getElementById("sendButton").onclick = function() {  
  2.     if (w.bufferedAmount < bufferThreshold) {  
  3.         w.send(document.getElementById("inputMessage").value);  
  4.     }  
  5. }  

WebSocket API支持以二进制数据的形式发送Blob和ArrayBuffer实例

Js代码   收藏代码
  1. var a = new Uint8Array([8, 6, 7, 5, 3, 0, 9]);  
  2. w.send(a.buffer);  

 

三、例子

书中介绍了一个用Python写的Echo服务,书中的代码可以在http://www.apress.com/9781430238645 的“Source Code/Downloads”中下载下载。

对于JAVA开发人员Tomcat是最熟悉的,在Tomcat8中已经实现了WebSocket API 1.0。Tomcat7也会在不久实现(现在的实现不是WebSocket API 1.0)。

在这里写一下在Tomcat8下执行的例子。

例子由客户端页面和WebSocket服务程序组成,功能在Echo基础上增加一个服务端定时发送信息的功能。

 

页面程序 ws.html代码   收藏代码
  1.   
  2.   
  3.   
  4. "UTF-8">  
  5. Test WebSocket  
  6. "text/javascript">  
  7.     //显示信息  
  8.     var log = function(s) {  
  9.         if (document.readyState !== "complete") {  
  10.             log.buffer.push(s);  
  11.         } else {  
  12.             document.getElementById("output").textContent += (s + "\n");  
  13.             document.getElementById("outputdiv").scrollTop = document.getElementById("outputdiv").scrollHeight;  
  14.         }  
  15.     }  
  16.     log.buffer = [];  
  17.     //显示连接状态  
  18.     function setConnected(status) {  
  19.         document.getElementById("socketstatus").innerHTML = status;  
  20.     }  
  21.     var ws = null;  
  22.       
  23.     //连接  
  24.     function connect() {  
  25.         if (ws != null) {  
  26.             log("现已连接");  
  27.             return ;  
  28.         }  
  29.         url = "ws://localhost:8080/websocket/mywebsocket";  
  30.         if ('WebSocket' in window) {  
  31.             ws = new WebSocket(url);  
  32.         } else if ('MozWebSocket' in window) {  
  33.             ws = new MozWebSocket(url);  
  34.         } else {  
  35.             alert("您的浏览器不支持WebSocket。");  
  36.             return ;  
  37.         }  
  38.         ws.onopen = function() {  
  39.             log("open");  
  40.             setConnected("已连接");  
  41.             //设置发信息送类型为:ArrayBuffer  
  42.             ws.binaryType = "arraybuffer";  
  43.               
  44.             //发送一个字符串和一个二进制信息  
  45.             ws.send("thank you for accepting this WebSocket request");  
  46.             var a = new Uint8Array([8675309]);  
  47.             ws.send(a.buffer);  
  48.         }  
  49.         ws.onmessage = function(e) {  
  50.             log(e.data.toString());  
  51.         }  
  52.         ws.onclose = function(e) {  
  53.             log("closed");  
  54.         }  
  55.         ws.onerror = function(e) {  
  56.             log("error");  
  57.         }  
  58.     }  
  59.       
  60.     //断开连接  
  61.     function disconnect() {  
  62.         if (ws != null) {  
  63.             ws.close();  
  64.             ws = null;  
  65.             setConnected("已断开");  
  66.         }  
  67.     }  
  68.       
  69.     window.onload = function() {  
  70.         connect();  
  71.         log(log.buffer.join("\n"));  
  72.         //发送页面上输入框的信息  
  73.         document.getElementById("sendButton").onclick = function() {  
  74.             if (ws != null) {  
  75.                 ws.send(document.getElementById("inputMessage").value);   
  76.             }  
  77.         }  
  78.         //停止心跳信息  
  79.         document.getElementById("stopButton").onclick = function() {  
  80.             if (ws != null) {  
  81.                 var a = new Uint8Array([19201516]);  
  82.                 ws.send(a.buffer);   
  83.             }  
  84.         }  
  85.     }  
  86.   
  87.   
  88. "disconnect();">  
  89.     
    连接状态:"socketstatus">
      
  90.     
      
  91.         "text" id="inputMessage" value="Hello, WebSocket!">  
  92.         "sendButton">发送"stopButton" style="margin-left:15px">停止心跳信息  
  93.     
  
  •     
      
  •         "connect" οnclick="connect();">连接  
  •         "disconnect" οnclick="disconnect();">断开  
  •     
  •   
  •     "height:300px; overflow:auto;" id="outputdiv">  
  •         "output">  
  •     
  •   
  •   
  •   
  •  

    服务端程序用注解方式驱动

     

    Websocket服务端程序代码   收藏代码
    1. package com.test.wsocket;  
    2.   
    3. import java.io.IOException;  
    4. import java.nio.ByteBuffer;  
    5. import java.util.Random;  
    6. import java.util.Timer;  
    7. import java.util.TimerTask;  
    8.   
    9. import javax.websocket.OnClose;  
    10. import javax.websocket.OnMessage;  
    11. import javax.websocket.OnOpen;  
    12. import javax.websocket.PongMessage;  
    13. import javax.websocket.Session;  
    14. import javax.websocket.server.ServerEndpoint;  
    15.   
    16. @ServerEndpoint("/mywebsocket")  
    17. public class MyWebSocket {  
    18.       
    19.     private Session session;  
    20.     private static final Random random = new Random();  
    21.     private Timer timer = null;  
    22.     //停止信息信息指令  
    23.     private static final ByteBuffer stopbuffer  = ByteBuffer.wrap(new byte[]{19201516});  
    24.       
    25.     /**  
    26.      * 打开连接时执行  
    27.      * @param session  
    28.      */  
    29.     @OnOpen  
    30.     public void start(Session session) {  
    31.         this.session = session;  
    32.         try {  
    33.             System.out.println("open");  
    34.             if (session.isOpen()) {  
    35.                 //设置心跳发送信息。每2秒发送一次信息。  
    36.                 timer = new Timer(true);  
    37.                 timer.schedule(task, 10002000);  
    38.             }  
    39.         } catch (Exception e) {  
    40.             try {  
    41.                 session.close();  
    42.             } catch (IOException e1) {}  
    43.         }  
    44.     }  
    45.   
    46.     /**  
    47.      * 接收信息时执行  
    48.      * @param session  
    49.      * @param msg 字符串信息  
    50.      * @param last  
    51.      */  
    52.     @OnMessage  
    53.     public void echoTextMessage(Session session, String msg, boolean last) {  
    54.         try {  
    55.             if (session.isOpen()) {  
    56.                 System.out.println("string:" + msg);  
    57.                 session.getBasicRemote().sendText(msg, last);  
    58.             }  
    59.         } catch (IOException e) {  
    60.             try {  
    61.                 session.close();  
    62.             } catch (IOException e1) {  
    63.                 // Ignore  
    64.             }  
    65.         }  
    66.     }  
    67.   
    68.     /**  
    69.      * 接收信息时执行  
    70.      * @param session  
    71.      * @param bb 二进制数组  
    72.      * @param last  
    73.      */  
    74.     @OnMessage  
    75.     public void echoBinaryMessage(Session session, ByteBuffer bb, boolean last) {  
    76.         try {  
    77.             if (session.isOpen()) {  
    78.                 //如果是停止心跳指令,则停止心跳信息  
    79.                 if (bb.compareTo(stopbuffer) == 0) {  
    80.                     if (timer != null) {  
    81.                         timer.cancel();  
    82.                     }  
    83.                 } else {  
    84.                     session.getBasicRemote().sendBinary(bb, last);  
    85.                 }  
    86.             }  
    87.         } catch (IOException e) {  
    88.             try {  
    89.                 session.close();  
    90.             } catch (IOException e1) {  
    91.                 // Ignore  
    92.             }  
    93.         }  
    94.     }  
    95.       
    96.     /**  
    97.      * 接收pong指令时执行。  
    98.      *  
    99.      * @param pm    Ignored.  
    100.      */  
    101.     @OnMessage  
    102.     public void echoPongMessage(PongMessage pm) {  
    103.         // 无处理  
    104.     }  
    105.       
    106.     @OnClose  
    107.     public void end(Session session) {  
    108.         try {  
    109.             System.out.println("close");  
    110.             if (timer != null) {  
    111.                 timer.cancel();  
    112.             }  
    113.         } catch(Exception e) {  
    114.         }  
    115.     }  
    116.       
    117.     /*  
    118.      * 发送心跳信息  
    119.      */  
    120.     public void sendLong(long param) {  
    121.         try {  
    122.             if (session.isOpen()) {  
    123.                 this.session.getBasicRemote().sendText(String.valueOf(param));  
    124.             }  
    125.         } catch (IOException e) {  
    126.             try {  
    127.                 this.session.close();  
    128.             } catch (IOException e1) {}  
    129.         }  
    130.     }  
    131.       
    132.     /**  
    133.      * 心跳任务。发送随机数。  
    134.      */  
    135.     TimerTask task = new TimerTask() {  
    136.         public void run() {     
    137.             long param = random.nextInt(100);  
    138.             sendLong(param);  
    139.         }     
    140.     };  
    141.   
    142. }  

     

     

    Web.xml代码   收藏代码
    1. "1.0" encoding="UTF-8"?>  
    2. "http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd" id="WebApp_ID" version="3.0">  
    3.   websocket  
    4.     
    5.     index.html  
    6.     
    7.     
    8.     
    9.         Set Character Encoding  
    10.         org.apache.catalina.filters.SetCharacterEncodingFilter  
    11.           
    12.             encoding  
    13.             UTF-8  
    14.           
    15.           
    16.             ignore  
    17.             true  
    18.           
    19.       
    20.       
    21.       
    22.         Set Character Encoding  
    23.         /*  
    24.       
    25.   

     

    将以上程序部署到Tomcat8下,启动服务。


    碍于原作者的代码繁琐,我自己在尝试的过程中也写了一个:


    详见http://toplchx.iteye.com/blog/1929427






    webWorker


    web worker 是运行在后台的 JavaScript,不会影响页面的性能。

    什么是 Web Worker?

    当在 HTML 页面中执行脚本时,页面的状态是不可响应的,直到脚本已完成。

    web worker 是运行在后台的 JavaScript,独立于其他脚本,不会影响页面的性能。您可以继续做任何愿意做的事情:点击、选取内容等等,而此时 web worker 在后台运行。

    浏览器支持

    所有主流浏览器均支持 web worker,除了 Internet Explorer

    使用web worker需要以下几步:

    检测 Web Worker 支持

    在创建 web worker 之前,请检测用户的浏览器是否支持它:

    if(typeof(Worker)!=="undefined")
      {
      // Yes! Web worker support!
      // Some code.....
      }
    else
      {
      // Sorry! No Web Worker support..
      }
    

    创建 web worker 文件

    现在,让我们在一个外部 JavaScript 中创建我们的 web worker。

    在这里,我们创建了计数脚本。该脚本存储于 "demo_workers.js" 文件中:

    var i=0;
    
    function timedCount()
    {
    i=i+1;
    postMessage(i);
    setTimeout("timedCount()",500);
    }
    
    timedCount();
    

    以上代码中重要的部分是 postMessage() 方法 - 它用于向 HTML 页面传回一段消息。

    注释:web worker 通常不用于如此简单的脚本,而是用于更耗费 CPU 资源的任务。

    创建 Web Worker 对象

    我们已经有了 web worker 文件,现在我们需要从 HTML 页面调用它。

    下面的代码检测是否存在 worker,如果不存在,- 它会创建一个新的 web worker 对象,然后运行 "demo_workers.js" 中的代码:

    if(typeof(w)=="undefined")
      {
      w=new Worker("demo_workers.js");
      }
    

    然后我们就可以从 web worker 发生和接收消息了。

    向 web worker 添加一个 "onmessage" 事件监听器:

    w.onmessage=function(event){
    document.getElementById("result").innerHTML=event.data;
    };
    

    当 web worker 传递消息时,会执行事件监听器中的代码。event.data 中存有来自 event.data 的数据。

    终止 Web Worker

    当我们创建 web worker 对象后,它会继续监听消息(即使在外部脚本完成之后)直到其被终止为止。

    如需终止 web worker,并释放浏览器/计算机资源,请使用 terminate() 方法:

    w.terminate();

    你可能感兴趣的:(webSocket,webWorker)