16.WebSocket聊天室

基于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>
        &nbsp;&nbsp;&nbsp;&nbsp;
        <button style="color: red;" id="out">离开聊天室</button>
    </div>
    <br><br>
    <div class="infoBox">
        消&nbsp;&nbsp;&nbsp;息:<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/**
相关推荐
DYWorker0011 小时前
第1课:基于正点原子RK3568开发板读懂Linux内核
linux·运维·网络
Lzg_na3 小时前
数据链路层差错控制使用的算法详解
网络
努力努力再努力wz3 小时前
【C++ 高阶系列】C++ 高性能网络编程:当 Reactor 遇见协程:从 epoll 回调到异步 MySQL,彻底打通任务挂起与恢复
网络·c++·mysql
loulanyue_3 小时前
当AI学会自主攻击与渗透破壁:Agent行为链失控与Mythos级攻防颠覆下的安全范式重构——读CNCC2026第六届网络生态治理论坛实录
网络·人工智能·安全
2401_868534784 小时前
英语演讲:麻省理工校长2024年毕业典礼演讲
服务器·网络
码完就睡4 小时前
Linux——以太网协议
运维·服务器·网络·1024程序员节
CV爱数码4 小时前
论文精读|RED-CNN:残差编解码网络,低剂量 CT 去噪的经典基线
网络·人工智能·算法·cnn·ct·重建·x-ray
星恒讯工业路由器5 小时前
Modbus透传和Modbus网关有什么区别?从字节转发到协议翻译的选型思路
网络·物联网·modbus·工业物联网·工业网关·rs485·协议转换
李&@杰5 小时前
交换机PoE供电不足导致AP反复重启故障处理
网络·华为
香蕉大香蕉(06敬畏市场)5 小时前
高频八股 · Java 并发编程(10):异步、ThreadLocal 与虚拟线程(收官)
java·开发语言·网络·jvm·python·ai·实习