概念:WebSocket是一种在单个TCP连接上进行全双工通信的协议。
WebSocket使得客户端和服务器之间的数据交换变得更加简单,允许服务端主动向客户端推送数据。在WebSocket API中,浏览器和服务器只需要完成一次握手,两者之间就直接可以创建持久性的连接,并进行双向数据传输
背景:很多网站为了实现推送技术,所用的技术都是轮询。轮询是在特定的的时间间隔(如每1秒),由浏览器对服务器发出HTTP请求,然后由服务器返回最新的数据给客户端的浏览器。这种传统的模式带来很明显的缺点,即浏览器需要不断的向服务器发出请求,然而HTTP请求可能包含较长的头部,其中真正有效的数据可能只是很小的一部分,显然这样会浪费很多的带宽等资源。
而比较新的技术去做轮询的效果是Comet。这种技术虽然可以双向通信,但依然需要反复发出请求。而且在Comet中,普遍采用的长链接,也会消耗服务器资源。
在这种情况下,HTML5定义了WebSocket协议,能更好的节省服务器资源和带宽,并且能够更实时地进行通讯。
websocket运用场景:
原理:
WebSocket并不是全新的协议,而是利用了HTTP协议来建立连接。我们来看看WebSocket连接是如何创建的。
首先,WebSocket连接必须由浏览器发起,因为请求协议是一个标准的HTTP请求,格式如下:
GET ws://localhost:3000/ws/chat HTTP/1.1
Host: localhost
Upgrade: websocket
Connection: Upgrade
Origin: http://localhost:3000
Sec-WebSocket-Key: client-random-string
Sec-WebSocket-Version: 13
该请求和普通的HTTP请求有几点不同:
随后,服务器如果接受该请求,就会返回如下响应:
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: server-random-string
该响应代码101表示本次连接的HTTP协议即将被更改,更改后的协议就是Upgrade: websocket指定的WebSocket协议。
为了创建Websocket连接,需要通过浏览器发出请求,之后服务器进行回应,这个过程通常称为“握手”(handshaking)
版本号和子协议规定了双方能理解的数据格式,以及是否支持压缩等等。如果仅使用WebSocket的API,就不需要关心这些。
现在,一个WebSocket连接就建立成功,浏览器和服务器就可以随时主动发送消息给对方。消息有两种,一种是文本,一种是二进制数据。通常,我们可以发送JSON格式的文本,这样,在浏览器处理起来就十分容易。
为什么WebSocket连接可以实现全双工通信而HTTP连接不行呢?实际上HTTP协议是建立在TCP协议之上的,TCP协议本身就实现了全双工通信,但是HTTP协议的请求-应答机制限制了全双工通信。WebSocket连接建立以后,其实只是简单规定了一下:接下来,咱们通信就不使用HTTP协议了,直接互相发数据吧。
HTTP协议与websocket协议对比:
WebSocket目前支持两种统一资源标志符ws和wss,类似于HTTP和HTTPS。
浏览器支持:
很显然,要支持WebSocket通信,浏览器得支持这个协议,这样才能发出ws://xxx的请求。目前,支持WebSocket的主流浏览器如下:
客户端简单实现:
新建websocket.html
websocket 客户端
客户端API:
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() |
关闭连接 |
JAVA 服务端实现:
maven依赖
org.java-websocket
Java-WebSocket
1.4.0
服务端示例:ServerDemo.java
import org.java_websocket.WebSocket;
import org.java_websocket.handshake.ClientHandshake;
import org.java_websocket.server.WebSocketServer;
import java.net.InetSocketAddress;
import java.net.UnknownHostException;
/**
* @Author [email protected]
* @Create 2020/2/3 23:03
**/
public class ServerDemo extends WebSocketServer {
public ServerDemo() throws UnknownHostException {
}
public ServerDemo(int port) throws UnknownHostException {
super(new InetSocketAddress(port));
System.out.println("websocket Server start at port:"+port);
}
/**
* 触发连接事件
*/
@Override
public void onOpen(WebSocket conn, ClientHandshake clientHandshake) {
System.out.println("new connection ===" + conn.getRemoteSocketAddress().getAddress().getHostAddress());
}
/**
*
* 连接断开时触发关闭事件
*/
@Override
public void onClose(WebSocket conn, int code, String reason, boolean remote) {
}
/**
* 客户端发送消息到服务器时触发事件
*/
@Override
public void onMessage(WebSocket conn, String message) {
System.out.println("you have a new message: "+ message);
//向客户端发送消息
conn.send(message);
}
/**
* 触发异常事件
*/
@Override
public void onError(WebSocket conn, Exception e) {
//e.printStackTrace();
if( conn != null ) {
//some errors like port binding failed may not be assignable to a specific websocket
}
}
/**
* 启动服务端
* @param args
* @throws UnknownHostException
*/
public static void main(String[] args) throws UnknownHostException {
new ServerDemo(8887).start();
}
}
由于WebSocket是一个协议,服务器具体怎么实现,取决于所用编程语言和框架本身。