1、什么是webSocket?
WebSocket是一种在单个TCP连接上进行全双工通信的协议。WebSocket使得客户端和服务器之间的数据交换变得更加简单,允许服务端主动向客户端推送数据。在WebSocket API中,浏览器和服务器只需要完成一次握手,两者之间就直接可以创建持久性的连接,并进行双向数据传输。
2、webSocket可以用来做什么?
利用双向数据传输的特点可以用来完成很多功能,不需要前端轮询,浪费资源。例如:
-
通告功能
-
聊天功能 (如下是逻辑图)
-
实时更新数据功能
-
弹幕
等等。。。。。。
3、webSocket协议
本协议有两部分:握手和数据传输。
握手是基于http协议的。
来自客户端的握手看起来像如下形式:
GET ws://localhost/chat HTTP/1.1
Host: localhost
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key:dGhlIHNhbXBsZSBub25jZQ==
Sec-WebSocket-Protocol: chat,superchat
Sec-WebSocket-Version: 13
来自服务器的握手看起来像如下形式:
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept:s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
Sec-WebSocket-Protocol: chat
4、服务端
maven依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.7.2</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
WebSocket配置类
@Configuration
public class WebSocketConfig {
/**
* 注入ServerEndpointExporter,
* 这个bean会自动注册使用了@ServerEndpoint注解声明的Websocket endpoint
*/
@Bean
public ServerEndpointExporter serverEndpointExporter() {
ServerEndpointExporter exporter = new ServerEndpointExporter();
return exporter;
}
}
WebSocket操作类
通过该类WebSocket可以进行群推送以及单点推送
import cn.hutool.json.JSONUtil;
import dto.Msg;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import javax.websocket.*;
import javax.websocket.server.ServerEndpoint;
import java.io.IOException;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
* WebSocket 服务
*/
@Slf4j
@Getter
@ServerEndpoint("/ws") // 接口路径 ws://localhost:8080/ws
@Component
@Scope("prototype")
public class WebSocketServer {
private final static String ALL_USER_ID = "ALL";
// 用来存在线连接用户信息
private static final Map<String, Session> userSessions = new ConcurrentHashMap<>();
/**
* 连接成功
*
* @param session
*/
@OnOpen
public void onOpen(Session session) {
userSessions.put(session.getQueryString(), session);
log.info("连接成功, user:{},sessionId:{}", session.getQueryString(), session.getId());
}
/**
* 接收到消息
*
* @param json
*/
@OnMessage
public void onMessage(String json, Session fromSession) {
Msg message = JSONUtil.toBean(json, Msg.class);
String from = message.getFrom();
String to = message.getTo();
String text = message.getText();
log.info("服务器收到[{}]发送给[{}]的消息[{}]", from, to, text);
//send
if (StringUtils.hasText(to)) {
if (to.equalsIgnoreCase(ALL_USER_ID)) {
noticeAll(text);
} else {
Session toSession = userSessions.get(to);
sendMessage(fromSession, "我" + ":" + text);
sendMessage(toSession, from + ":" + text);
}
}
}
/**
* 连接关闭
*
* @param session
*/
@OnClose
public void onClose(Session session) {
userSessions.forEach((key, item) -> {
if (item.getQueryString().equals(session.getQueryString())) {
userSessions.remove(key);
}
});
log.info("连接关闭");
}
/**
* 连接错误
*
* @param session
*/
@OnError
public void onError(Session session, Throwable error) {
userSessions.forEach((key, item) -> {
if (item.getQueryString().equals(session.getQueryString())) {
userSessions.remove(key);
sessionClose(session);
}
});
log.info("连接出错", error);
}
public void noticeAll(String text) {
userSessions.forEach((key, value) -> {
sendMessage(value, text);
});
}
private void sendMessage(Session session, String text) {
if (session == null || !session.isOpen()) {
return;
}
try {
if (session.isOpen()) {
session.getBasicRemote().sendText(text);
} else {
log.error("session:[{}] is closed!", session.getId());
}
} catch (IOException ioException) {
log.error("send message error", ioException);
sessionClose(session);
}
}
private void sessionClose(Session session) {
try {
session.close();
} catch (IOException e) {
log.error("close session error", e);
}
}
}
封装的消息对象
/**
* Ws消息对象
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Msg {
private String from;
private String to;
private String text;
}
注入我们的操作类
@Resource
private WebSocket webSocket;
5、客户端
前端中Thymeleaf 使用WebSocket
<!DOCTYPE HTML>
<html>
<head>
<meta charset="utf-8">
<title>Websocket Demo</title>
</head>
<body>
<button onclick="connection()">连接</button>
<button onclick="close()">关闭连接</button>
<br/>
<input id="from" type="text"/>
<br/>
<label for="to">发给:</label>
<input id="to" type="text"/>
<br/>
<label for="text">消息:</label>
<input id="text" type="text"/>
<button onclick="send()">发送消息</button>
<br/>
<hr/>
<div id="messages"></div>
<br/>
<script type="text/javascript">
var websocket = null;
function connection(){
var username = document.getElementById('from').value;
//判断当前浏览器是否支持WebSocket
if ('WebSocket' in window) {
websocket = new WebSocket("ws://localhost:8080/ws?"+username);//此处地址在分情况测试的时候需要修改
} else {
alert('Not support websocket')
}
//发生错误时
websocket.onerror = function () {
setMessageInnerHTML("error");
};
//成功建立连接时
websocket.onopen = function (event) {
setMessageInnerHTML("open");
}
//接收到消息时
websocket.onmessage = function (event) {
setMessageInnerHTML(event.data);
}
//关闭连接时
websocket.onclose = function () {
setMessageInnerHTML("close");
}
//监听窗口关闭事件,当窗口关闭时,主动去关闭websocket连接,防止连接还没断开就关闭窗口,server端会抛异常。
window.onbeforeunload = function () {
websocket.close();
}
}
//显示服务器端返回的消息
function setMessageInnerHTML(text) {
document.getElementById('messages').innerHTML = document.getElementById('messages').innerHTML + text + '<br/>';
}
//关闭连接按钮
function close() {
websocket.close();
}
//发送消息按钮
function send() {
var from = document.getElementById('from').value;
var to = document.getElementById('to').value;
var text = document.getElementById('text').value;
var msg={
from:from,
to:to,
text:text
};
console.log(msg);
websocket.send(JSON.stringify(msg));
}
</script>
</body>
</html>
接口调用顺序,进来页面 :
- 先建立连接–》调用websocketonopen方法,链接成功调用的方法
- websocketonmessage方法为接收后端时处理。
- 当我们要发送消息给后端时调用websocketsend。
- 当我们要关闭连接时调用websocketclose。
- 当发现错误时调用websocketonerror。