什么是WebSocket?
WebSocket是html5开始提供的一种全双工网络通信协议,全双工是指WebSocket连接的两端都可以接受和发送消息。
为什么需要WebSocket?
- 如果你了解Http协议,name应该知道Http协议是无状态、无连接、单向的应用层协议。它采用了请求-响应模式,由客户端发送一个请求,由服务端返回一个响应。它有一个弊端就是服务端无法主动向客户端发起消息。这样就导致客户端想要获取服务端连续的状态变化很困难,大多是web程序将通过频繁的异步JavaScript和XML(AJAX)请求实现长轮询。轮询的效率低,非常浪费资源(因为必须不停连接,或者 HTTP 连接始终打开)。
-
WebSocket就是在这种情况下诞生的,WebSocket 连接允许客户端和服务器之间进行全双工通信,以便任一方都可以通过建立的连接将数据推送到另一端。WebSocket 只需要建立一次连接,就可以一直保持连接状态。这相比于轮询方式的不停建立连接显然效率要大大提高。
WebSocket连接
WebSocket如何工作?
Web浏览器和服务器都必须实现 WebSockets 协议来建立和维护连接。由于 WebSockets 连接长期存在,与典型的HTTP连接不同,对服务器有重要的影响。
基于多线程或多进程的服务器无法适用于 WebSockets,因为它旨在打开连接,尽可能快地处理请求,然后关闭连接。任何实际的 WebSockets 服务器端实现都需要一个异步服务器。
Spring中WebSocket相关API介绍
- WebSocket处理器接口,所有的WebSocket处理器都需要实现该接口。
public interface WebSocketHandler {
// 在WebSocket连接成功后调用
void afterConnectionEstablished(WebSocketSession var1) throws Exception;
// 当消息到达后调用
void handleMessage(WebSocketSession var1, WebSocketMessage<?> var2) throws Exception;
// 处理来自底层WebSocket消息传输的错误
void handleTransportError(WebSocketSession var1, Throwable var2) throws Exception;
// 在WebSocket连接关闭后调用
void afterConnectionClosed(WebSocketSession var1, CloseStatus var2) throws Exception;
//
boolean supportsPartialMessages();
}
- 该类实现了WebSocketHandler接口,除了handlerMessage()方法,其他都是空实现。除此之外还提供了三个方法分别处理文本消息、二进制消息和pong消息,根据需要重写对应的方法即可。
public abstract class AbstractWebSocketHandler implements WebSocketHandler {
@Override
public void afterConnectionEstablished(WebSocketSession session) throws Exception {
}
@Override
public void handleMessage(WebSocketSession session, WebSocketMessage<?> message) throws Exception {
if (message instanceof TextMessage) {
handleTextMessage(session, (TextMessage) message);
}
else if (message instanceof BinaryMessage) {
handleBinaryMessage(session, (BinaryMessage) message);
}
else if (message instanceof PongMessage) {
handlePongMessage(session, (PongMessage) message);
}
else {
throw new IllegalStateException("Unexpected WebSocket message type: " + message);
}
}
protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
}
protected void handleBinaryMessage(WebSocketSession session, BinaryMessage message) throws Exception {
}
protected void handlePongMessage(WebSocketSession session, PongMessage message) throws Exception {
}
@Override
public void handleTransportError(WebSocketSession session, Throwable exception) throws Exception {
}
@Override
public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {
}
@Override
public boolean supportsPartialMessages() {
return false;
}
}
- AbstractWebSocketHandler也有实现类
- TextWebSocketHandler
重写了handleBinaryMessage方法,如果收到二进制消息将会关闭webSocke连接
public class TextWebSocketHandler extends AbstractWebSocketHandler {
@Override
protected void handleBinaryMessage(WebSocketSession session, BinaryMessage message) {
try {
session.close(CloseStatus.NOT_ACCEPTABLE.withReason("Binary messages not supported"));
}
catch (IOException ex) {
// ignore
}
}
}
- BinaryWebSocketHandler
重写了handleTextMessage方法,如果收到文本消息也将关闭WebSocket连接。
public class BinaryWebSocketHandler extends AbstractWebSocketHandler {
@Override
protected void handleTextMessage(WebSocketSession session, TextMessage message) {
try {
session.close(CloseStatus.NOT_ACCEPTABLE.withReason("Text messages not supported"));
}
catch (IOException ex) {
// ignore
}
}
}
- 我们可以选择性的扩展不同的类来实现功能。
下面是WebSocket的HelloWorld
实现的功能是:浏览器接收到服务器发送的消息后向服务端发送“Marco!”,服务器收到浏览器发送的消息后向浏览器发送“Polo!”,这样不断的循环下去。
创建服务端
1.新建一个SpringBoot项目(这个不用介绍了吧)
需要注意的是需要引入WebSocket的Starter,引入了它就不用再引入spring-boot-starter-web,因为spring-boot-starter-websocket里面引入了web模块的。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
2.创建一个WebSocket处理器类
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.socket.CloseStatus;
import org.springframework.web.socket.TextMessage;
import org.springframework.web.socket.WebSocketSession;
import org.springframework.web.socket.handler.AbstractWebSocketHandler;
/**
* @author 陈治远
* @create 2019-08-09 17:26
* @Description
*/
public class MarcoHandler extends AbstractWebSocketHandler {
private static final Logger logger = LoggerFactory.getLogger(MarcoHandler.class);
// websocket连接建立监听,实际业务中可以在连接建立的时候开启一些资源
@Override
public void afterConnectionEstablished(WebSocketSession session) throws Exception {
logger.info("连接已建立...");
}
// websocket连接断开监听,实际业务中可以在连接建立的时候清理一些资源
@Override
public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {
logger.info("连接已关闭...状态:" + status);
}
// 处理文本消息
@Override
protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
logger.info("收到消息:" + message.getPayload());
// 模拟延时1500ms
java.lang.Thread.sleep(1500);
// 发送文本消息
session.sendMessage(new TextMessage("Polo!"));
}
}
3.配置消息处理器类(即上一步的MarcoHandler)
import com.czy.websocket.handle.MarcoHandler;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.config.annotation.EnableWebSocket;
import org.springframework.web.socket.config.annotation.WebSocketConfigurer;
import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry;
import org.springframework.web.socket.server.standard.ServerEndpointExporter;
/**
* @author 陈治远
* @create 2019-08-09 17:43
* @Description
*/
@Configuration
@EnableWebSocket // 启用WebSocket
public class WebSocketConfig implements WebSocketConfigurer {
@Override
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
// 注册MarcoHandler处理器,该处理器将映射到ws://ip:port/marco
// 调用setAllowedOrigins处理跨域,*代表全部访问地址
registry.addHandler(marcoHandler(), "/marco")
.setAllowedOrigins("*");
}
public MarcoHandler marcoHandler() {
return new MarcoHandler();
}
/**
* 向容器中注入服务器节点,只有使用SpringBoot内嵌servlet容器的时候才需要这一步。
* 如果使用外置servlet容器,则可以删除这个方法和该类上的@Configuration注解。
*
* @return
*/
@Bean
public ServerEndpointExporter serverEndpointExporter() {
return new ServerEndpointExporter();
}
}
创建客户端
4.创建一个HTML文件作为客户端
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
WebSocketDemo客户端,看控制台打印。
<script type="text/javascript">
var url = 'ws://127.0.0.1:8080/marco';
var socket = new WebSocket(url);
// 处理连接建立事件
socket.onopen = function () {
console.log("连接已建立...")
sayMarco();
}
// 处理消息
socket.onmessage = function (event) {
console.log("收到消息:" + event.data)
// 模拟延时1500ms
setTimeout(function () {
sayMarco();
}, 1500)
}
// 处理连接断开事件
socket.onclose = function (ev) {
console.log("连接已关闭...");
}
function sayMarco() {
console.log("发送消息:Marco")
socket.send("Marco!")
}
</script>
</body>
</html>
5.启动服务端,并在浏览器中打开页面,查看控制台,说明客户端与服务端成功建立了WebSocket连接并互相发送消息。
注意
WebSocket是一个相对比较新的规范,在web浏览器和服务器上还没有得到一致的支持。Firefox和Chorm早已完整支持WebSocket了,但是一些其他的浏览器刚刚开始支持WebSoket。
应对不支持WebSocket的场景,有了SocketJS。SockJS是一种WebSocket技术的一种模拟,表面上还是使用那些WebSocket API,但是底层是十分智能的,它会优先选择WebSocket,如果WebSocket不可以的话,它会选用其他替代方案,具体的替代方案有XHR流、XDR流、iFrame事件源、iFrame HTML文件、XHR轮询、XDR轮询、iFrame XHR轮询、iFrame XHR轮询、JSONP轮询,但是你不必了解这些方案,就和面向接口编程一样,有统一的api,但是底层实现不一样而已。
使用SockJS只需要少量的修改
1. 服务端修改
2. 客户端修改
2.1 引入SockJS的库
百度网盘链接如下:
链接:https://pan.baidu.com/s/1O4BmqQoNNbu5otzk71q5sQ
提取码:w9qx
2.2 修改socket对象为SockJS对象
3. ok,现在使用那些老旧版本的浏览器应该也可以实现demo功能了吧
最后,demo目录结构如下
下一篇介绍STOMP消息