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/**
相关推荐
fantasy_arch3 小时前
CPU性能优化-磁盘空间和解析时间
网络·性能优化
njnu@liyong4 小时前
图解HTTP-HTTP报文
网络协议·计算机网络·http
是Dream呀5 小时前
Python从0到100(七十八):神经网络--从0开始搭建全连接网络和CNN网络
网络·python·神经网络
kaixin_learn_qt_ing6 小时前
了解RPC
网络·网络协议·rpc
安全小王子7 小时前
Kali操作系统简单介绍
网络·web安全
Hacker_LaoYi8 小时前
【漏洞分析】DDOS攻防分析(四)——TCP篇
网络·tcp/ip·ddos
爱吃水果蝙蝠汤8 小时前
DATACOM-IP单播路由(BGP)-复习-实验
网络·网络协议·tcp/ip
Sun_12_29 小时前
SQL注入(SQL lnjection Base)21
网络·数据库
网络安全Jack9 小时前
网络安全概论——身份认证
网络·数据库·web安全