基于SpringBoot 2.6.11
1.WebSocket
WebSocket 是 HTML5 开始提供的一种在单个 TCP 连接上进行全双工通讯的协议,可以在html页面直接使用。
WebSocket 使得客户端和服务器之间的数据交换变得更加简单,允许服务端主动向客户端推送数据。在 WebSocket API 中,浏览器和服务器只需要完成一次握手,两者之间就直接可以创建持久性的连接,并进行双向数据传输。
在 WebSocket API 中,浏览器和服务器只需要做一个握手的动作,然后,浏览器和服务器之间就形成了一条快速通道。两者之间就直接可以数据互相传送。
过去,很多网站为了实现推送技术,所用的技术都是 Ajax 轮询。轮询是在特定的的时间间隔(如每1秒),由浏览器对服务器发出HTTP请求,然后由服务器返回最新的数据给客户端的浏览器。这种传统的模式带来很明显的缺点,即浏览器需要不断的向服务器发出请求,然而HTTP请求可能包含较长的头部,其中真正有效的数据可能只是很小的一部分,显然这样会浪费很多的带宽等资源。
HTML5 定义的 WebSocket 协议,能更好的节省服务器资源和带宽,并且能够更实时地进行通讯。
浏览器通过 JavaScript 向服务器发出建立 WebSocket 连接的请求,连接建立以后,客户端和服务器端就可以通过TCP连接直接交换数据。
当获取WebSocket连接后,可以通过 send() 方法来向服务器发送数据,并通过 onmessage 事件来接收服务器返回的数据。
WebSocket在传输的过程中不再使用http协议,而是Stomp协议
1.1 STOMP
STOMP即Simple (or Streaming) Text Orientated Messaging Protocol,简单(流)文本定向消息协议,它提供了一个可互操作的连接格式,允许STOMP客户端与任意STOMP消息代理(Broker)进行交互。STOMP协议由于设计简单,易于开发客户端,因此在多种语言和多种平台上得到广泛地应用。
STOMP协议的前身是TTMP协议(一个简单的基于文本的协议),专为消息中间件设计。
STOMP是一个非常简单和容易实现的协议,其设计灵感源自于HTTP的简单性。尽管STOMP协议在服务器端的实现可能有一定的难度,但客户端的实现却很容易。例如,可以使用Telnet登录到任何的STOMP代理,并与STOMP代理进行交互。
1.2 WebSocket 事件
以下是 WebSocket 对象的相关事件。
事件 | 事件处理程序 | 描述 |
---|---|---|
open | Socket.onopen | 连接建立时触发 |
message | Socket.onmessage | 客户端接收服务端数据时触发 |
error | Socket.onerror | 通信发生错误时触发 |
close | Socket.onclose | 连接关闭时触发 |
1.3 WebSocket 方法
以下是 WebSocket 对象的相关方法。
方法 | 描述 |
---|---|
Socket.send() | 使用连接发送数据 |
Socket.close() | 关闭连接 |
2.用WebSocket实现网页端聊天室
导入依赖
md-end-block
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
创建配置类
md-end-block
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.server.standard.ServerEndpointExporter;
/**
* @author: Elivs.Xiang
* @Email: 406862257@qq.com
* @create 2022-12-06 15:09
* @verson IDEA 2020.3
*/
@Configuration
public class WebsocketConfig {
@Bean //在容器中创建bean对象,在WebSocketUtil中需要用到的RemoteEndpoint对象
public ServerEndpointExporter serverEndpointExporter() {
return new ServerEndpointExporter();
}
}
创建工具类
md-end-block
import java.io.IOException;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import javax.websocket.Session;
import javax.websocket.RemoteEndpoint;
public class WebSocketUtil {
//HashMap:不支持多线程,并发情况下线程不安全
public static final Map<String, Session> MESSAGEMAP = new ConcurrentHashMap<>();
//发送消息给客户端
public static void sendMessage(Session session,String message) {
if (session!=null) {
final RemoteEndpoint.Basic basic = session.getBasicRemote();
if (basic!=null) {
try {
basic.sendText(message);//发送消息回客户端
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
//将消息给所有聊天室的人
//循环发送
public static void sendMessageToAll(String message) {
MESSAGEMAP.forEach((sessionId,session)->sendMessage(session, message));
}
}
创建handler
md-end-block
import java.io.IOException;
import javax.websocket.OnClose;
import javax.websocket.OnError;
import javax.websocket.OnMessage;
import javax.websocket.OnOpen;
import javax.websocket.Session;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import com.example.web.util.WebSocketUtil;
import org.springframework.web.bind.annotation.RestController;
@RestController
@ServerEndpoint("/WebSocketHandler/{userName}") //表示接受的是STOMP协议提交的数据
public class WebSocketHandler {
//建立连接
@OnOpen
public void openSession(@PathParam("userName")String userName,Session session) {
//消息
String message = "欢迎:"+userName+"加入群聊";
//加入聊天室
WebSocketUtil.MESSAGEMAP.put(userName, session);
//发送消息
WebSocketUtil.sendMessageToAll(message);
}
@OnMessage
public void onMessage(@PathParam("userName")String userName,String message) {
message = userName+":"+message;
WebSocketUtil.sendMessageToAll(message);
}
//离开聊天室
@OnClose
public void onClose(@PathParam("userName")String userName,Session session) {
//将当前用户从map中移除 注销
WebSocketUtil.MESSAGEMAP.remove(userName);
//群发消息
WebSocketUtil.sendMessageToAll("用户:"+userName+"离开聊天室");
//关闭session
try {
session.close();
} catch (IOException e) {
e.printStackTrace();
}
}
//连接异常
@OnError
public void onError(Session session,Throwable throwable) {
try {
session.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
主启动类开启websocket
md-end-block
@SpringBootApplication
@EnableWebSocket
public class WebApplication {
public static void main(String[] args) {
SpringApplication.run(WebApplication.class, args);
}
}
前端代码
md-end-block
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
<link rel="stylesheet" type="text/css" href="css/index.css">
<script type="text/javascript" src="js/jquery.min.js"></script>
<script type="text/javascript">
$(document).ready(function(){
let url = "ws://192.168.7.131:9988/WebSocketHandler/";
let ws = null;
//连接服务器
$("#join").click(function(){
let userName = $("#userName").val();
let newUrl = url + userName; //传递用户名
console.info(newUrl);
//创建对象,连接服务器
ws = new WebSocket(newUrl); //html5中提供了
//给open事件绑定方法
ws.onopen = function(){
console.info("连接成功");
}
//接收到数据
ws.onmessage = function(result){
var textarea = document.getElementById('textarea');
textarea.append(result.data+"\n");
//将文本域的滚动条滚动到最后
textarea.scrollTop = textarea.scrollHeight;
}
//关闭连接
ws.onclose = function(){
$("#textarea").append("用户:"+userName+"离开聊天室"+"\n");
console.info("关闭连接");
}
});
//发送消息
$("#send").click(function(){
//将输入框中的消息发送给服务器,并且显示到消息框中
var messageInput = $("#message");
var message = messageInput.val();
if(ws!=null){
ws.send(message); //发送消息
messageInput.val("");
}
});
//断开连接
$("#out").click(function(){
if(ws!=null){
ws.close();
}
});
})
</script>
</head>
<body>
<div id="box">
<p>蜗牛聊天室</p>
<textarea rows="10" cols="50" disabled="disabled" id="textarea"></textarea><br>
<div class="infoBox">
用户名:<input type="text" id="userName"><br><br>
<button style="color: green;" id="join">加入聊天室</button>
<button style="color: red;" id="out">离开聊天室</button>
</div>
<br><br>
<div class="infoBox">
消 息:<input type="text" id="message"><br><br>
<button id="send">发送消息</button>
</div>
<br>
</div>
</body>
</html>
css
md-end-block
#box{
width: 500px;
background: pink;
text-align: center;
}
.infoBox{
text-align:left;
position: relative;
left: 62px;
}
#message{
width: 322px;
}
#send{
position:relative;
left:50px;
height:30px;
width: 326px;
}
启动项目报错
md-end-block
Caused by: java.lang.IllegalStateException: javax.websocket.server.ServerContainer not available
在测试类上的@SpringBootTest中加上
webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT
md-end-block
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
class WebApplicationTests {
@Test
void contextLoads() {
}
}
整合Gateway
在application.yml中配置websocket路由
md-end-block
spring:
application:
name: gateway
cloud:
gateway:
routes:
- id: chat #
uri: "ws://127.0.0.1:8000" # ws 协议 需要指定IP,通过微服务名字会报503
predicates:
- Path=/WebSocketHandler/**