`

HTML5中的服务器‘推送’技术 -WebSocket

阅读更多
HTML5中的服务器‘推送’技术 -WebSocket
转帖:http://www.developersky.net/thread-81-1-1.html

除了Server-Sent Event之外,即将到来的HTML5标准还包含了WebSockets。WebSocket使得我们可以建立双向的通信通道。和Server-Sent Event相反,WebSocket协议不是建立在HTTP之上的。但是WebSocket协议订立了HTTP握手的行为来将已经存在的HTTP连接转换为WebSocket连接。WebSocket没有试图在HTTP之上模拟server推送的通道,而是直接在TCP之上定义了帧协议,因此WebSocket能够支持双向的通信。
和server-Sent Event规范相同,WebSocket定义了API和相应的协议。WebSocket API规范中包含一个新的HTML元素:WebSocket。下面的代码就是一个使用WebSocket的HTML的例子:

<html>
   <head>
     <mce:script type='text/javascript'><!--
        var ws = new WebSocket('ws://localhost:8876/Channel', 'mySubprotocol.example.org');
        ws.onmessage = function (message) {
          var messages = document.getElementById('messages');
          messages.innerHTML += "<br>[in] " + message.data;
        };
       
        sendmsg = function() {
          var message = document.getElementById('message_to_send').value
          document.getElementById('message_to_send').value = ''
          ws.send(message);
          var messages = document.getElementById('messages');
          messages.innerHTML += "<br>[out] " + message;
        };
    
// --></mce:script>
  </head>
  <body>
     <form>
       <input type="text" id="message_to_send" name="msg"/>
       <input type="button" name="btn" id="sendMsg" value="Send" onclick="javascript:sendmsg();">
       <div id="messages"></div>
     </form>
  </body>
</html>

当创建一个WebSocket实例的时候,相应的WebSocket连接就会被建立。构造函数需要两个参数(第二个参数是可选的)。第一个参数是WebSocketURL,该参数定义了要连接的URL。WebSocketURL以ws或wss开头。ws开头的是普通的WebSocket连接,wss开头的是安全的WebSocket连接(类似https)。第二个参数是要使用的子协议,该参数是可选的。
我们可以定义WebSocket实例的onMessage处理函数,每次收到消息的时候,该处理函数都会被调用。如果要发送消息到服务器端,可以通过WebSocket的send()方法发送消息。
当一个新的WebSocket实例被创建的时候,首先底层的user agent会建立一个普通的到指定URL的HTTP(S)连接,然后对该连接进行升级(upgrade)。HTTP规范在消息头中定义了upgrade域来进行该操作。Upgrade头提供了简单的机制来将HTTP协议转换为其他的不兼容的协议。WebSocket利用了HTTP协议的这个能力来讲新创建的HTTP连接转换为WebSocket连接。同时添加了WebSocket-Protocal来制定要使用的子协议。
下面是一个Upgrade的Request和Response消息的例子。

REQUEST:
GET /Channel HTTP/1.1
Upgrade: WebSocket
Connection: Upgrade
Host: myServer:8876
Origin: http://myServer:8876
WebSocket-Protocol: mySubprotocol.example.org


RESPONSE:
HTTP/1.1 101 Web Socket Protocol Handshake
Upgrade: WebSocket
Connection: Upgrade
WebSocket-Origin: http://myServer:8876
WebSocket-Location: ws://myServer:8876/Channel
WebSocket-Protocol: mySubprotocol.example.org

当收到HTTP response消息之后,只有WebSocket帧能够通过该连接传送,所有的数据都会根据WebSocket协议进行转换。WebSocket帧能够在任何时候向任何方向发送。WebSocket协议定义了两种类型的帧:文本帧(text frame)和二进制帧(binary frame)。文本帧以字节0x00开头,以字节0xFF结尾。当中的文本内容要转换成UTF8编码。所以为了打包,文本帧需要添加两个额外的字节。下面是两个文本帧的例子:

Text frame of "GetDate":
0x00 0x47 0x65 0x74 0x44 0x61 0x74 0x65 0xFF

Text frame of "Sat Mar 13 14:00:25 CET 2010":
0x00 0x53 0x61 0x74 0x20 0x4D 0x61 0x72 0x20 0x31
0x33 0x20 0x31 0x34 0x3A 0x30 0x30 0x3A 0x32 0x35
0x20 0x43 0x45 0x54 0x20 0x32 0x30 0x31 0x30 0xFF

如果要传送二进制数据,就需要使用二进制帧。二进制帧以字节0x80开始。和文本帧相反,二进制帧没有结束标志。在二进制帧的开始标识字节(0x80)之后就是长度字节。长度字节的字节数是不固定的,根据需要来决定。下面是两个例子,第一个例子中需要传递的数据量很小,因此长度字节就只有一个字节;第二个例子中需要传递的数据量比较大,就使用了2个字节作为长度字节。

binary frame of 0x00 0x44:
0x80 0x02 0x00 0x44

binary frame of 0x30 0x31 0x32 0x33  0x34  0x35 […] 0x39 (1000 bytes):
0x80 0x87 0x68 0x30 0x31 0x32 0x33 0x34 0x35 […] 0x39

因为JavaScript不能操作字节数组形式的二进制数据,因此二进制帧目前无法被JavaScript使用。除了文本帧和二进制帧之外,WebSocket协议将来还有可能引入新的类型的帧格式。WebSocket帧在设计的时候就考虑了支持新的帧类型。
WebSocket的连接可以在任何时候关闭,不需要额外的‘结束连接’字节或帧。
管理WebSocket的额外开销是很小的。如Bayeux和BOSH这样的Comet协议是建立在HTTP协议之上的,这就迫使这些协议要实现复杂的会话和连接的管理。而WebSocket是建立在TCP协议之上的,不会碰到这些由于HTTP协议的局限性引起的麻烦。
另一方面,WebSocket基本上没有实现可靠性的功能。它既没有包括重建连接的处理,也不支持向Server-Sent Event那样的保证消息成功传递的机制。而且,由于WebSocket不是基于HTTP协议的,因此也无法利用HTTP协议内建的可靠性的特性。例如HTTP协议支持当网络故障是的自动重试功能(一个Get方法应该不会改变任何服务器端的资源的状态,因此我们可以重复的执行Get方法而没有任何的副作用。当网络故障的时候,浏览器或HttpClients可以自动重新执行Get方法)。
由于WebSocket没有这些功能,因此在应用程序(或者说子协议)层面上我们就需要实现可靠性的功能,包括发送“维持通信”消息来避免代理服务器在一段时间没有消息之后关闭连接。另外,在多个页面之间共享WebSocket通常会带来麻烦。和Server-Sent Event不同,WebSocket会包含一个难以共享的上游的管道。例如,并发的读写操作必须要同步,这并不是一个简单的任务。因此当使用WebSocket的时候,‘每个服务器一个连接’必须要仔细考虑。
Web浏览器限制浏览器端的编程语言(如JavaScript)连接到其他的服务器,因此web页面上的WebSocket只能连接和当前页面在同一个域中的服务器。对于独立的WebSocket客户端则没有这个限制。如下面的例子中我们通过Java客户端来使用WebSocket:

class MyWebSocketHandler implements IWebSocketHandler {

  public void onConnect(IWebSocketConnection wsCon) throws IOException {
               
  }

  public void onMessage(IWebSocketConnection wsCon) throws IOException {
    IWebMessage msg = wsCon.readMessage();
    System.out.println(msg.toString());
  }
           
  public void onDisconnect(IWebSocketConnection wsCon) throws IOException {
               
  }
}

       
MyWebSocketHandler hdl = new MyWebSocketHandler ();
IWebSocketConnection wsCon = httpClient.openWebSocketConnection(
    "ws://myServer:8876/WebSocketsExample",
    "mySubprotocol.example.org", hdl);
       
wsCon.writeMessage(new TextMessage("GetDate"));
// ...

下面是一个WebSocket服务器实现的简单例子。服务器要实现两个接口:IHttpRequestHandler和IWebSocketHandler。IHttpRequestHandler接口用于处理普通的HTTP请求,IWebSocketHandler据诶和用于处理WebSocket连接和消息。当一个标准的HTTP请求到来的时候(不包含upgrade请求),IHttpRequestHandler的onRequest()方法会被调用。如果客户端打开WebSocket,服务器会收到HTTP upgrade请求,IWebSocketHandler的onConnect()方法会被调用。每次收到WebSocket的消息,IWebSocketHandler的onMessage()方法都会被调用。
在onConnect()方法中,我们可以检查一些先决条件。例如检查要求的子协议是否被支持,下面的例子中如果要求的子协议不被支持,则会返回一个错误状态。下面的例子啊红我们还驾车了请求头中的origin字段。Origin字段是HTTP Origin Header RFC(还是草案)定义的,该字段由浏览器自动设置。

class ServerHandler implements IHttpRequestHandler, IWebSocketHandler {
           
           
  // IHttpRequestHandler method
  public void onRequest(IHttpExchange exchange) throws IOException {
  String requestURI = exchange.getRequest().getRequestURI();
               
  if (requestURI.equals("/WebSocketsExample")) {
    sendWebSocketPage(exchange, requestURI);
                   
  } else {
    exchange.sendError(404);
  }
}
           
           
  private void sendWebSocketPage(IHttpExchange exchange, String uri)
          throws IOException {
    String page = "<html>\r\n " +
                  "  <head>\r\n" +
                  "     <mce:script type='text/javascript'><!--
\r\n" +
                  "        var ws = new WebSocket('ws://" +
                           exchange.getRequest().getHost() + "/Channel',
                           'mySubprotocol.example.org');\r\n" +
                  "        ws.onmessage = function (message) {\r\n" +
                  "          var messages = document.getElementById('messages');\r\n" +
                  "          messages.innerHTML += \"<br>[in] \" +
                             message.data;\r\n"+
                  "        };\r\n" +
                  "        \r\n" +
                  "        sendmsg = function() {\r\n" +
                  "          var message = document.getElementById
                             ('message_to_send').value\r\n" +
                  "          document.getElementById('message_to_send').value = ''\r\n" +
                  "          ws.send(message);\r\n" +
                  "          var messages = document.getElementById('messages');\r\n" +
                  "          messages.innerHTML += \"<br>[out] \" + message;\r\n"+
                  "        };\r\n" +
                  "    
// --></mce:script>\r\n" +
                  "  </head>\r\n" +
                  "  <body>\r\n" +
                  "     <form>\r\n" +
                  "       <input type=\"text\" id=\"message_to_send\"
                          name=\"msg\"/>\r\n" +
                  "       <input type=\"button\" name=\"btn\" id=\"sendMsg\"
                          value=\"Send\" onclick=\"javascript:sendmsg();\">\r\n" +
                  "       <div id=\"messages\"></div>\r\n" +
                  "     </form>\r\n" +
                  "  </body>\r\n" +
                  "</html>\r\n ";
               
    exchange.send(new HttpResponse(200, "text/html", page));
  }

           
           
  // IWebSocketHandler method
  public void onConnect(IWebSocketConnection webStream) throws IOException, BadMessageException {
    IHttpRequestHeader header = webStream.getUpgradeRequestHeader();

    // check origin header
    String origin = header.getHeader("Origin");
    if (!isAllowed(origin)) {
      throw new BadMessageException(403);
    }
               
    // check the subprotocol 
    String subprotocol = header.getHeader("WebSocket-Protocol", "");
    if (!subprotocol.equalsIgnoreCase("mySubprotocol.example.org")) {
      throw new BadMessageException(403);
    }
  }

  private boolean isAllowed(String origin) {
    // check the origin
    // ...
    return true;
  }
           
           
  // IWebSocketHandler
  public void onMessage(IWebSocketConnection webStream) throws IOException {
    WebSocketMessage msg = webStream.readMessage();
    if (msg.toString().equalsIgnoreCase("GetDate")) {
      webStream.writeMessage(new TextMessage(new Date().toString()));
    } else {
      webStream.writeMessage(new TextMessage(
              "unknown command (supported: GetDate)"));
    }
  }
           
  // IWebSocketHandler
  public void onDisconnect(IWebSocketConnection webStream)
          throws IOException {  }

}
       
XHttpServer server = new XHttpServer(8876, new ServerHandler());
server.start();

上面的例子中,我们会使用内部的白名单来检查origin header,并拒绝我们不期望的请求。这样,我们可以防止某些攻击者将公共页面上的JavaScript代码拷贝并添加到他们自己的页面中。这种情况下,浏览器会将origin header设置为攻击者自己的页面所在的域,服务器处理升级请求的时候就会拒绝该请求。这个技术用来防范Cross-Site Request攻击。Origin header规范和WebSocket协议规范是相互独立的,但是WebSocket协议定义了WebSocket-Origin header,该字段必须被包含在WebSocket升级请求中。

因为WebSocket连接是有HTTP连接升级而来,因此WebSocket协议也可以和HTTP代理服务器一起工作。浏览器总是和代理服务器通信,代理服务器将HTTP request和response进行转发。当浏览器使用HTTP代理并打开一个WebSocket的时候,首先浏览器会打开一个到代理服务器的通道。通过发送HTTP/1.1连接请求,浏览器要求HTTP代理创建一个到被代理的服务器(WebSocket服务器)的TCP连接。当连接建立以后,HTTP代理服务器的功能就被缩小为到WebSocket服务器的TCP代理。使用这个被代理的连接,浏览器发送WebSocket升级请求到WebSocket服务器。下面的列表描述了整个过程。

1. REQUEST:
CONNECT myServer:8876 HTTP/1.1
Host: myServer:8876
User-Agent: xLightweb/2.12-HTML5Preview6
Proxy-Connection: keep-alive


1. RESPONSE:
HTTP/1.1 200 Connection established
Proxy-agent: myProxy


2. REQUEST:
GET /Channel HTTP/1.1
Upgrade: WebSocket
Connection: Upgrade
Host: myServer:8876
Origin: http://myServer:8876
WebSocket-Protocol: mySubprotocol.example.org


2. RESPONSE:
HTTP/1.1 101 Web Socket Protocol Handshake
Upgrade: WebSocket
Connection: Upgrade
WebSocket-Origin: http://myServer:8876
WebSocket-Location: ws://myServer:8876/Channel
WebSocket-Protocol: mySubprotocol.example.org 

转帖:http://www.developersky.net/thread-81-1-1.html

分享到:
评论
1 楼 itusername 2011-11-11  
请教一下,诸如 :IHttpRequestHandler,IWebSocketHandler,IHttpExchange之类的是自己写的借口还是支持包?有点不太明白!能详细说明下么?方便的话咱们加QQ探讨一下 583772189

相关推荐

    Web-服务器推送WebSocketandAjax轮询.docx

    1 服务器推送websocket:服务端主动向客户端发消息。 目标:客户端和服务器建立长连接,服务端与客户端可实时收发数据。 原理:使用HTTP协议建立全双工的TCP长连接。 方法:HTML5的WebSocket。 参考:...

    基于Websocket的消息实时推送设计与实现.pdf

    随着互联网技术的快速发展,基于B/S架构的实时通讯和消息推送的应用范围越来越广泛,服务器消息推送是很多应用中的一项重要功能,服务器推送技术的优劣直接影响着消息推送的效率。传统的解决方案有html refresh,...

    websocket_for_linux-master_websocket_websocket客户端_WEBSOCKET单片机实现

    5WebSocket 使得客户端和服务器之间的数据交换变得更加简单,允许服务端主动向客户端推送数据。在 WebSocket API 中,浏览器和服务器只需要完成一次握手,两者之间就直接可以创建持久性的连接,并进行双向数据传输。...

    微信小程序webSocket的使用方法

    WebSocket 使得客户端和服务器之间的数据交换变得更加简单,允许服务端主动向客户端推送数据。在 WebSocket API 中,浏览器和服务器只需要完成一次握手,两者之间就直接可以创建持久性的连接,并进行双向数据传输

    web服务器推(websocket)

    利用websocket技术实现了web服务器向用户端应用页面推送信息的功能,代码可以直接使用,内含使用教程及功能演示图片截图,自认为说的已经比较清晰,如果再加上使用者的稍微研究,可以轻松实现web服务器推功能。

    bash-websocket-server:用 bash 编写的 Websocket 服务器

    我这样做是因为我想用 bash 写一些东西,我需要一种方法来向浏览器发送推送通知之类的东西。 也就是说,该服务器不接收消息,只发送消息。 它适用于Chrome。 ##包含的文件 index.html - 这是一个示例网页,用于...

    HTML5的websocket使用。

    这个项目是练习使用html5的WebSocket来开发,将后台...采用websocket的来获取session,之后用session(websocket的session)来向前台界面推送字符串,避免了频繁的前台setinterval请求,造成网络拥堵,造成服务器崩溃。

    C#实现WebSocket源码(c#写的服务端html写的客户端)

    它的最大特点就是,服务器可以主动向客户端推送信息,客户端也可以主动向服务器发送信息,是真正的双向平等对话,属于服务器推送技术的一种。 其他特点包括: (1)建立在 TCP 协议之上,服务器端的实现比较容易。 ...

    基于WebSocket的实时消息推送的设计与实现.pdf

    全新的HTML5 标准中引入了WebSocket,WebSocket 实现了服务器与浏览器间的双向连接,基于事件方式,效率高,服务器负担轻。本文使用Node.js 平台和Socket.IO 组件设计并实现了WebSocket 实时消息推送网页应用。

    易语言-websocket协议开源

    WebSocket并不限于以Ajax(或XHR)方式通信,因为Ajax技术需要客户端发起请求,而WebSocket服务器和客户端可以彼此相互推送信息;XHR受到域的限制,而WebSocket允许跨域通信。Ajax技术很聪明的一点是没有设计要使用的...

    websocket协议开源

    WebSocket并不限于以Ajax(或XHR)方式通信,因为Ajax技术需要客户端发起请求,而WebSocket服务器和客户端可以彼此相互推送信息;XHR受到域的限制,而WebSocket允许跨域通信。Ajax技术很聪明的一点是没有设计要使用的...

    WebSocket实现服务器客户端带winform客户端

    C#实现WebSocket源码(c#写的服务端html写的客户端) ...它的最大特点就是,服务器可以主动向客户端推送信息,客户端也可以主动向服务器发送信息,是真正的双向平等对话,属于服务器推送技术的一种。

    JAVA整合WebSocket实现服务器消息推送(项目源码).zip

    JAVA整合WebSocket实现服务器消息推送(项目源码) (1)将Database文件夹中的扩展名为db_netExam_Data.MDF和db_netExam_Log.LDF的两个文件拷贝到SQL Server安装路径下的Data文件夹中。 (2)打开SQL Server 2005中的...

    浅析HTML5的WebSocket与服务器推送事件

    主要介绍了浅析HTML5的WebSocket与服务器推送事件,WebSocket API最大的特点就是让服务器和客户端能在给定的时间范围内的任意时刻,相互推送信息,需要的朋友可以参考下

    PHP-Push-WebSocket:WebSocket协议在PHP中的轻量级实现

    PHP推送WebSocket PHP Push WebSocket是遵循规范的WebSocket协议的实现。 它还包括一个客户端示例。 使用Composer安装 要安装最新的稳定版本: composer require "srchea/php-push-web-socket" 用法 运行服务器实例...

    simple-broadcast-chat-nodejs-websocket:Node.js Websocket中的简单广播聊天

    git checkout -b my-new-feature ) 提交更改( git commit -am 'Add some feature' ) 推送到分支( git push origin my-new-feature ) 创建新的拉取请求用法使用Chrome或.NET来运行./src/client/index.html。

    HTML5 WebSocket

    以前的服务器消息推送大部分采用的都是“轮询”和“长连接”技术,这两中技术都会对服务器产生相当大的开销,而且实时性不是特别高。WebSocket技术对只会产生很小的开销,并且实时性特别高。下面就开始讲解如何利用...

    websocket-server 简单的一个例子

    WebSocket使得客户端和服务器之间的数据交换变得更加简单,允许服务端主动向客户端推送数据。在WebSocket API中,浏览器和服务器只需要完成一次握手,两者之间就直接可以创建持久性的连接,并进行双向数据传输。 很...

    WebSocket服务器的.NET实现 SuperWebSocket

    作为HTML5的一个重要新特性,WebSocket 规范的目标是在浏览器中实现和服务器端双向通信.双向通信可以拓展浏览器上的应用类型,例如实时的数据推送(股票行情),游戏,聊天/im 等. 现已支持WebSocket的浏览器有: ...

    php-sse:一个简单高效的库通过PHP实现HTML5的服务器发送的事件,用于将事件从服务器实时推送到客户端,比Websocket更容易,而不是AJAX请求

    一个简单高效的库通过PHP实现了HTML5的服务器发送的事件,用于将事件从服务器实时推送到客户端,并且比Websocket更容易,而不是AJAX请求。 要求 PHP 5.4或更高版本 通过Composer( )安装 composer require " hhxsv5...

Global site tag (gtag.js) - Google Analytics