WebSocket 协议W在2008年诞生,2011年成为国际标准,所有浏览器都已经支持了。其是基于TCP的一种新的网络协议,是 HTML5 开始提供的一种在单个TCP连接上进行全双工通讯的协议,它实现了浏览器与服务器全双工(full-duplex)通信——允许服务器主动发送信息给客户端。
WebSocket使得客户端和服务器之间的数据交换变得更加简单,允许服务端主动向客户端推送数据。在 WebSocket API 中,浏览器和服务器只需要完成一次握手,两者之间就直接可以创建持久性的连接,并进行双向数据传输。在 WebSocket API 中,浏览器和服务器只需要做一个握手的动作,然后浏览器和服务器之间就形成了一条快速通道,两者之间就直接可以数据互相传送。
HTTP协议是一种无状态、单向的应用层协议,其采用的是请求/响应模型,通信请求只能由客户端发起,服务端对请求做出应答响应,无法实现服务器主动向客户端发起消息,这就注定如果服务端有连续的状态变化,客户端想要获知就非常的麻烦。而大多数Web应用程序通过频繁的异步JavaScript 和 aJax 请求实现长轮询,其效率很低,而且非常的浪费很多的带宽等资源。
HTML5定义的WebSocket协议,能更好的节省服务器资源和带宽,并且能够更实时地进行通讯。WebSocket 连接允许客户端和服务器之间进行全双工通信,以便任一方都可以通过建立的连接将数据推送到另一端。WebSocket 只需要建立一次连接,就可以一直保持连接状态,这相比于轮询方式的不停建立连接显然效率要大大提高。
浏览器通过JavaScript向服务器发出建立WebSocket连接的请求,连接建立以后,客户端和服务器端就可以通过 TCP 连接直接交换数据。当你获取 Web Socket 连接后,你可以通过 send() 方法来向服务器发送数据,并通过 onmessage 事件来接收服务器返回的数据。
WebSocket 服务端在各个主流应用服务器厂商中已基本获得符合 JEE JSR356 标准规范 API 的支持(详见JSR356 WebSocket API 规范),以下列举了部分常见的商用及开源应用服务器对 WebSocket Server 端的支持情况
对于 WebSocket 客户端,主流的浏览器(包括 PC 和移动终端)现已都支持标准的 HTML5 的 WebSocket API,这意味着客户端的 WebSocket JavaScirpt 脚本具备良好的一致性和跨平台特性,以下列举了常见的浏览器厂商对 WebSocket 的支持情况。
对于 WebSocket 的整个生命周期,主要由以下几个事件组成
WebSocket 协议使用 ws 和 wss URL协议,以分别代表不安全和安全的WebSocket请求。使用WebSocket构造函数来创建一个WebSocket连接,构造函数会返回一个WebSocket实例,可以用来监听事件。以下代码用于创建Web Socket连接 :
// 创建一个新的WebSocket.
var websocket = new WebSocket(url, [protocol] );
以上代码中的第一个参数url,指定连接的URL。第二个参数protocol是可选的,指定了可接受的子协议。
以下是 WebSocket 对象的属性。
WebSocket 是纯事件驱动,通过监听事件可以处理到来的数据和改变的链接状态。以下是 WebSocket 对象的相关事件。
websocket.onopen = function(evt) {
console.log(evt);
};
> 注意:服务器发送给客户端的消息可能是文本,也可能是二进制数据(blob对象或Arraybuffer对象)。
websocket.onerror = function(evt) {
console.log(evt);
};
以下是 WebSocket 对象的相关方法。
所需pom依赖
<dependency>
<groupid>org.java-websocketgroupid>
<artifactid>Java-WebSocketartifactid>
<version>1.5.1version>
dependency>
WebSocket服务端核心类
import org.java_websocket.WebSocket;
import org.java_websocket.handshake.ClientHandshake;
import org.java_websocket.server.WebSocketServer;
import java.net.InetSocketAddress;
import java.util.Iterator;
/**
* 把今天最好的表现当作明天最新的起点..~
*
* Today the best performance as tomorrow newest starter!
*
* @类描述: TODO(这里用一句话描述这个类的作用)
* @author: 独泪了无痕
* @创建时间: 2020/11/27 13:41
* @版本: V 1.0.1
* @since: JDK 1.8
*/
public class MsgWebSocketServer extends WebSocketServer {
public MsgWebSocketServer(int port) {
super(new InetSocketAddress(port));
}
/**
* websocket进行握手之后调用,并且给WebSocket写做准备
*
* @param webSocket
* @param clientHandshake
*/
@Override
public void onOpen(WebSocket webSocket, ClientHandshake clientHandshake) {
System.out.println("---onOpen---"+webSocket.isOpen()+"--"+webSocket.getReadyState()+"--"+webSocket.getAttachment());
for(Iterator<string> it = clientHandshake.iterateHttpFields(); it.hasNext();) {
String key = it.next();
System.out.println(key+":"+clientHandshake.getFieldValue(key));
}
}
/**
* WebSocket连接关闭时调用
*
* @param webSocket
* @param i
* @param s
* @param b
*/
@Override
public void onClose(WebSocket webSocket, int i, String s, boolean b) {
System.out.println("------------------onClose-------------------");
}
@Override
public void onMessage(WebSocket webSocket, String message) {
System.out.println("收到消息:"+message);
//收到什么消息,回复什么
webSocket.send(message);
if(webSocket.isClosed()) {
} else if (webSocket.isClosing()) {
System.out.println("ws连接正在关闭...");
} else if(webSocket.isOpen()) {
System.out.println("ws连接已打开...");
System.out.println(webSocket);
}
}
/**
* 错误发生时调用。
*
* @param webSocket
* @param e
*/
@Override
public void onError(WebSocket webSocket, Exception e) {
System.out.println("------------------onError-------------------");
if(webSocket != null) {
}
e.getStackTrace();
}
/**
* 当服务器成功启动时调用
*/
@Override
public void onStart() {
System.out.println("------------------onStart-------------------");
}
}
服务端基于4个注解实现,如下所是:
注解 | 说明 |
---|---|
@OnOpen | 被该注解注释的方法,将在客户端与服务端建立连接时执行 |
@OnMessage | 被该注解注释的方法,将在服务端收到消息时执行 |
@OnClose | 被该注解注释的方法,将在链接关闭时执行 |
@OnError | 被该注解注释的方法,将在链接发生错误时执行 |
启动类
/**
* 把今天最好的表现当作明天最新的起点..~
*
* Today the best performance as tomorrow newest starter!
*
* @类描述: TODO(这里用一句话描述这个类的作用)
* @author: 独泪了无痕
* @创建时间: 2020/11/27 13:48
* @版本: V 1.0.1
* @since: JDK 1.8
*/
public class WsServer {
public static void main(String[] args) {
new MsgWebSocketServer(8290).start();
}
}
访问页面
<meta charset="UTF-8">
<title>HTML5 WebSockettitle>
<script type="text/javascript">
function websocketTest() {
let webSocket;
let commWebSocket;
if ("WebSocket" in window) {
webSocket = new WebSocket("ws://localhost:8205/websocket/webChat");
//连通之后的回调事件
webSocket.onopen = function() {
//webSocket.send( document.getElementById('username').value+"已经上线了");
console.log("已经连通了websocket");
};
//接收后台服务端的消息
webSocket.onmessage = function(evt) {
const received_msg = evt.data;
console.log("数据已接收:" + received_msg);
}
//连接关闭的回调事件
webSocket.onclose = function() {
console.log("连接已关闭...");
};
} else {
// 浏览器不支持 WebSocket
alert("您的浏览器不支持 WebSocket!");
}
}
script>
<div id="see">
<a href="javascript:websocketTest()">运行 WebSocketa>
div>
所需pom依赖
<!-- https://mvnrepository.com/artifact/org.java-websocket/Java-WebSocket -->
<dependency>
<groupid>org.java-websocket</groupid>
<artifactid>Java-WebSocket</artifactid>
<version>1.5.1</version>
</dependency>
客户端
webSocket客户端的实现基于webSocketClient类实现,实例化webSocketClient并重写以下四个方法:
onOpen | 与服务端建立连接时执行 |
onMessage | 收到服务端消息时执行 |
onClose | 连接关闭时执行 |
onError | 发生错误时执行 |
import lombok.extern.slf4j.Slf4j;
import org.java_websocket.client.WebSocketClient;
import org.java_websocket.handshake.ServerHandshake;
import java.net.URI;
import java.net.URISyntaxException;
@Slf4j
public final class MyWebSocketClient {
/**
* 创建webSocketClient客户端
*/
private static WebSocketClient webSocketClient;
/**
* 创建webSocketClient客户端
*
* @param serverUri
* @return
* @throws URISyntaxException
*/
public static WebSocketClient getWebSocketClient(String serverUri) throws URISyntaxException {
webSocketClient = new WebSocketClient(new URI(serverUri)) {
@Override
public void onOpen(ServerHandshake serverHandshake) {
log.info("[websocket] 连接成功");
}
@Override
public void onMessage(String message) {
log.info("[websocket] 收到消息={}", message);
}
@Override
public void onClose(int code, String reason, boolean remote) {
log.info("[websocket] 退出连接");
}
@Override
public void onError(Exception exp) {
log.info("[websocket] 连接错误={}", exp.getMessage());
}
};
return webSocketClient;
}
}
启动类
通过参阅 webSocket API 文档,我们可以了解到,在Java中webSocket包含以下几种种状态:
状态 | 说明 |
---|---|
NOT_YET_CONNECTED | 表示该webSocket实例还未开始链接,并处于等待链接的状态 |
OPEN | 链接已打开 |
CLOSING | 链接正在关闭 |
CLOSED | 表示链接关闭,该webSocket实例到了消亡的时候。 |
不难看出,这几种种状态表明着webSocket的整个生命周期,这对于我们在使用webSocket时解决一些问题是非常关键的。
import org.java_websocket.client.WebSocketClient;
import org.java_websocket.enums.ReadyState;
import org.joda.time.DateTime;
import java.net.URISyntaxException;
public class WsClient {
public static void main(String[] args) throws URISyntaxException, InterruptedException {
String serverUri = "ws://localhost:8290";
// 创建webSocketClient客户端
WebSocketClient client = MyWebSocketClient.getWebSocketClient(serverUri);
client.connect();
// 检测连接状态,重复尝试连接
while (!client.getReadyState().equals(ReadyState.OPEN)) {
System.out.println("连接状态:" + client.getReadyState());
Thread.sleep(1000);
if (client.getReadyState().equals(ReadyState.CLOSING) || client.getReadyState().equals(ReadyState.CLOSED)) {
client.reconnect();
}
}
client.send("测试数据!");
while (client.getReadyState() == ReadyState.OPEN) {
client.send("测试数据!" + new DateTime().toString("yyyy-MM-dd HH:mm:ss"));
Thread.sleep(1000);
}
client.close();
}
}
当webSocketClient初始化完毕之后,webSocketClient提供了两种链接方式,分别是connect、reconnect 。这两者虽然都是没有链接的状态,但本质上是有区别的。webSocket想要链接,则只能在 NOT_YET_CONNECTED 状态下进行,一旦状态改变,则无法再次链接,只能reconnect链接。