HTML5 WebSockets规范定义了一个API,使Web页面能够使用WebSockets协议与远程主机进行双向通信。它引入了WebSocket接口并定义了一个全双工通信通道,该通道通过Web上的单个插槽进行操作。与通过维护两个连接来模拟全双工连接的不可扩展的轮询和长轮询解决方案相比,HTML5 WebSockets大大减少了不必要的网络流量和延迟。
HTML5 WebSockets解决了代理和防火墙等网络危害,可以通过任何连接实现流式传输,并且能够通过单一连接支持上游和下游通信,基于HTML5 WebSockets的应用程序减轻了服务器负担,允许现有机器支持更多并发连接。下图显示了基于WebSocket的基本体系结构,其中浏览器使用WebSocket连接进行全双工,与远程主机直接通信。
WebSockets提供的一个更独特的功能是它能够遍历防火墙和代理,这是许多应用程序的问题区域。Comet风格的应用程序通常采用长轮询作为防火墙和代理的基本防线。该技术是有效的,但不适合具有低于500毫秒延迟或高吞吐量要求的应用。基于插件的技术(如Adobe Flash)也提供了一定程度的套接字支持,但长期以来一直承受着WebSockets现在解决的代理和防火墙遍历问题。
WebSocket检测是否存在代理服务器,并自动设置隧道以通过代理。通过向代理服务器发出HTTP CONNECT语句来建立隧道,代理服务器请求代理服务器打开到特定主机和端口的TCP / IP连接。一旦建立了隧道,通信就可以畅通无阻地通过代理。由于HTTP / S以类似的方式工作,因此SSL上的安全WebSockets可以利用相同的HTTP CONNECT技术。请注意,现在的浏览器开始支持WebSockets(Chrome现在支持WebSockets)。但是,可以使用向后兼容的实现,使当今的浏览器能够利用这种新兴技术。
WebSockets与HTML5工作的其他部分(如本地存储和地理位置)一样,最初是HTML5规范的一部分,但是被转移到单独的标准文档中以保持规范的重点。WebSockets已由其创建者Web超文本应用技术工作组(WHATWG)提交给Internet工程任务组(IETF)。参与标准化的作者,传播者和公司仍将原始特征集(包括WebSockets)称为“HTML5”。
WebSocket协议旨在与现有的Web基础结构良好协作。作为此设计原则的一部分,协议规范定义WebSocket连接作为HTTP连接开始其生命周期,保证与WebSocket前世界完全向后兼容。从HTTP到WebSocket的协议切换称为WebSocket握手。
浏览器向服务器发送请求,表明它希望将协议从HTTP切换到WebSocket。客户端通过Upgrade标头表达其愿望:
GET ws://echo.websocket.org/?encoding=text HTTP/1.1
Origin: http://websocket.org
Cookie: __utma=99as
Connection: Upgrade
Host: echo.websocket.org
Sec-WebSocket-Key: uRovscZjNol/umbTt5uKmw==
Upgrade: websocket
Sec-WebSocket-Version: 13
如果服务器了解WebSocket协议,则它同意通过Upgrade标头切换协议。
HTTP/1.1 101 WebSocket Protocol Handshake
Date: Fri, 10 Feb 2012 17:38:18 GMT
Connection: Upgrade
Server: Kaazing Gateway
Upgrade: WebSocket
Access-Control-Allow-Origin: http://websocket.org
Access-Control-Allow-Credentials: true
Sec-WebSocket-Accept: rLHCkw/SKsO9GAH/ZSFhBATDKrU=
Access-Control-Allow-Headers: content-type
此时,HTTP连接中断,并由同一底层TCP / IP连接上的WebSocket连接替换。默认情况下,WebSocket连接使用与HTTP(80)和HTTPS(443)相同的端口。
通过引入一个简洁的界面(参见下面的清单),开发人员可以替换长轮询和“永久帧”等技术,从而进一步减少延迟。
[Constructor(in DOMString url, optional in DOMString protocol)]
interface WebSocket {
readonly attribute DOMString URL;
// ready state
const unsigned short CONNECTING = 0;
const unsigned short OPEN = 1;
const unsigned short CLOSED = 2;
readonly attribute unsigned short readyState;
readonly attribute unsigned long bufferedAmount;
// networking
attribute Function onopen;
attribute Function onmessage;
attribute Function onclose;
boolean send(in DOMString data);
void close();
};
WebSocket implements EventTarget;
使用WebSocket接口并不简单。要连接到端点,只需创建一个新的WebSocket实例,为新对象提供一个URL,该URL表示您希望连接的端点,如以下示例所示。请注意,建议使用ws://和wss://前缀分别表示WebSocket和安全WebSocket连接。
var myWebSocket = new WebSocket("ws://www.websockets.org");
通过在客户端和服务器之间的初始握手期间从HTTP协议升级到WebSockets协议来建立WebSocket连接。连接本身通过WebSocket接口定义的“onmessage”和“send”函数公开。
在连接到端点并发送消息之前,您可以关联一系列事件侦听器以处理连接生命周期的每个阶段,如以下示例所示。
myWebSocket.onopen = function(evt) { alert("Connection open ..."); };
myWebSocket.onmessage = function(evt) { alert( "Received Message: " + evt.data); };
myWebSocket.onclose = function(evt) { alert("Connection closed."); };
要向服务器发送消息,只需调用“发送”并提供您希望提供的内容。发送消息后,请调用“close”以终止连接,如以下示例所示。正如你所看到的,它真的不容易。
myWebSocket.send("Hello WebSockets!");
myWebSocket.close();
一个简单的服务端实现:
package com.chen.websocket;
import java.io.IOException;
import java.util.concurrent.CopyOnWriteArraySet;
import javax.websocket.*;
import javax.websocket.server.ServerEndpoint;
@ServerEndpoint("/websocket")
public class WebSocket {
private static int onlineCount;
private static CopyOnWriteArraySet webSocketSet = new CopyOnWriteArraySet();
private Session session;
@OnOpen
public void onOpen(Session session) {
this.session = session;
webSocketSet.add(this);
addOnlineCount();
System.out.println("有新连接加入!当前在线人数为" + getOnlineCount());
}
@OnClose
public void onClose() {
webSocketSet.remove(this);
subOnlineCount();
System.out.println("有一连接关闭!当前在线人数为" + getOnlineCount());
}
@OnMessage
public void onMessage(String message, Session session) {
System.out.println("来自客户端的消息:" + message);
// 群发消息
for (WebSocket item : webSocketSet) {
try {
item.sendMessage(message);
} catch (IOException e) {
e.printStackTrace();
continue;
}
}
}
@OnError
public void onError(Session session, Throwable error) {
error.printStackTrace();
}
public void sendMessage(String message) throws IOException {
this.session.getBasicRemote().sendText(message);
}
public static synchronized int getOnlineCount() {
return onlineCount;
}
public static synchronized void addOnlineCount() {
WebSocket.onlineCount++;
}
public static synchronized void subOnlineCount() {
WebSocket.onlineCount--;
}
}