• SpringBoot实现WebSocket


    一、什么是websocket

    WebSocket是HTML5下一种新的协议(websocket协议本质上是一个基于tcp的协议)
    它实现了浏览器与服务器全双工通信,能更好的节省服务器资源和带宽并达到实时通讯的目的
    Websocket是一个持久化的协议

    二、新建SpringBoot工程

    在这里插入图片描述
    按照上图这样配置即可,点击下一步
    因为本文章只实现websocket功能,所以只勾选SpringWeb,同时要保证springboot版本要低于3.0
    在这里插入图片描述

    创建完的工程目录如下
    在这里插入图片描述

    三、修改配置文件

    将application.properties修改为application.yml,然后修改内容为

    #服务器配置
    server:
      port: 8081
      servlet:
        context-path: /api
    
    • 1
    • 2
    • 3
    • 4
    • 5

    在这里插入图片描述

    修改POM.xml,在dependencies下加入所需依赖,加入后在空白处右键点击重新加载项目,下载对应依赖

    <dependency>
    	<groupId>org.springframework</groupId>
    	<artifactId>spring-websocket</artifactId>
    	<version>5.2.8.RELEASE</version>
    </dependency>
    <!--工具类 -->
    	<dependency>
    	<groupId>cn.hutool</groupId>
    	<artifactId>hutool-all</artifactId>
    	<version>5.4.1</version>
    </dependency>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    新建一个websocket包,包中新建两个类,代码如下

    package com.example.demo.websocket;
    
    import cn.hutool.json.JSONUtil;
    import org.springframework.stereotype.Component;
    
    import javax.websocket.OnClose;
    import javax.websocket.OnMessage;
    import javax.websocket.OnOpen;
    import javax.websocket.Session;
    import javax.websocket.server.PathParam;
    import javax.websocket.server.ServerEndpoint;
    import java.io.IOException;
    import java.util.concurrent.ConcurrentHashMap;
    
    @ServerEndpoint(value = "/websocket/{userId}")
    @Component
    public class WebSocket {
    	private static ConcurrentHashMap<String, WebSocket> webSocketMap = new ConcurrentHashMap<>();
    	//实例一个session,这个session是websocket的session
    	private Session session;
    
    	//新增一个方法用于主动向客户端发送消息
    	public static void sendMessage(Object message, String userId) {
    		WebSocket webSocket = webSocketMap.get(userId);
    		if (webSocket != null) {
    			try {
    				webSocket.session.getBasicRemote().sendText(JSONUtil.toJsonStr(message));
    				System.out.println("【websocket消息】发送消息成功,用户="+userId+",消息内容"+message.toString());
    			} catch (IOException e) {
    				e.printStackTrace();
    			}
    		}
    	}
    	
    	public static ConcurrentHashMap<String, WebSocket> getWebSocketMap() {
    		return webSocketMap;
    	}
    
    	public static void setWebSocketMap(ConcurrentHashMap<String, WebSocket> webSocketMap) {
    		WebSocket.webSocketMap = webSocketMap;
    	}
    
    	//前端请求时一个websocket时
    	@OnOpen
    	public void onOpen(Session session, @PathParam("userId") String userId) {
    		this.session = session;
    		webSocketMap.put(userId, this);
    		sendMessage("CONNECT_SUCCESS", userId);
    		System.out.println("【websocket消息】有新的连接,连接id"+userId);
    	}
    
    	//前端关闭时一个websocket时
    	@OnClose
    	public void onClose(@PathParam("userId") String userId) {
    		webSocketMap.remove(userId);
    		System.out.println("【websocket消息】连接断开,总数:"+ webSocketMap.size());
    	}
    
    	//前端向后端发送消息
    	@OnMessage
    	public void onMessage(String message) {
    		if (!message.equals("ping")) {
    			System.out.println("【websocket消息】收到客户端发来的消息:"+message);
    		}
    	}
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    package com.example.demo.websocket;
    
    
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.web.socket.server.standard.ServerEndpointExporter;
    
    //开启WebSocket的支持,并把该类注入到spring容器中
    @Configuration
    public class WebSocketConfig {
    
    	@Bean
    	public ServerEndpointExporter serverEndpointExporter() {
    		return new ServerEndpointExporter();
    	}
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    四、项目完整结构如下

    在这里插入图片描述

    五、测试功能

    点击运行按钮,可以看到控制台对应的输出,输出看到JVM running for 表示启动成功
    在这里插入图片描述

    在桌面新建一个websocket.html,将代码复制进去

    DOCTYPE html>
    <html lang="en">
    	<head>
    		<meta charset="utf-8" />
    		<meta http-equiv="X-UA-Compatible" content="IE=edge">
    		<meta name="viewport" content="width=device-width, initial-scale=1">
    		<title>本地websocket测试title>
    		<meta name="robots" content="all" />
    		<meta name="keywords" content="本地,websocket,测试工具" />
    		<meta name="description" content="本地,websocket,测试工具" />
    		<style>
    			.btn-group{
    				display: inline-block;
    			}
    		style>
    	head>
    	<body>
    		<input type='text' value='通信地址, ws://开头..' class="form-control" style='width:390px;display:inline'
    		 id='wsaddr' />
    		<div class="btn-group" >
    			<button type="button" class="btn btn-default" onclick='addsocket();'>连接button>
    			<button type="button" class="btn btn-default" onclick='closesocket();'>断开button>
    			<button type="button" class="btn btn-default" onclick='$("#wsaddr").val("")'>清空button>
    		div>
    		<div class="row">
    			<div id="output" style="border:1px solid #ccc;height:365px;overflow: auto;margin: 20px 0;">div>
    			<input type="text" id='message' class="form-control" style='width:810px' placeholder="待发信息" onkeydown="en(event);">
    			<span class="input-group-btn">
    				<button class="btn btn-default" type="button" onclick="doSend();">发送button>
    			span>
    			div>
    		div>
    	body>		
    		
    		<script src="https://code.jquery.com/jquery-3.1.1.min.js">script>
    		<script language="javascript" type="text/javascript">
    			function formatDate(now) {
    				var year = now.getFullYear();
    				var month = now.getMonth() + 1;
    				var date = now.getDate();
    				var hour = now.getHours();
    				var minute = now.getMinutes();
    				var second = now.getSeconds();
    				return year + "-" + (month = month < 10 ? ("0" + month) : month) + "-" + (date = date < 10 ? ("0" + date) : date) +
    					" " + (hour = hour < 10 ? ("0" + hour) : hour) + ":" + (minute = minute < 10 ? ("0" + minute) : minute) + ":" + (
    						second = second < 10 ? ("0" + second) : second);
    			}
    			var output;
    			var websocket;
     
    			function init() {
    				output = document.getElementById("output");
    				testWebSocket();
    			}
     
    			function addsocket() {
    				var wsaddr = $("#wsaddr").val();
    				if (wsaddr == '') {
    					alert("请填写websocket的地址");
    					return false;
    				}
    				StartWebSocket(wsaddr);
    			}
     
    			function closesocket() {
    				websocket.close();
    			}
     
    			function StartWebSocket(wsUri) {
    				websocket = new WebSocket(wsUri);
    				websocket.onopen = function(evt) {
    					onOpen(evt)
    				};
    				websocket.onclose = function(evt) {
    					onClose(evt)
    				};
    				websocket.onmessage = function(evt) {
    					onMessage(evt)
    				};
    				websocket.onerror = function(evt) {
    					onError(evt)
    				};
    			}
     
    			function onOpen(evt) {
    				writeToScreen("连接成功,现在你可以发送信息啦!!!");
    			}
     
    			function onClose(evt) {
    				writeToScreen("websocket连接已断开!!!");
    				websocket.close();
    			}
     
    			function onMessage(evt) {
    				writeToScreen('服务端回应 ' + formatDate(new Date()) + '
    ' + evt.data + ''
    ); } function onError(evt) { writeToScreen('发生错误: ' + evt.data); } function doSend() { var message = $("#message").val(); if (message == '') { alert("请先填写发送信息"); $("#message").focus(); return false; } if (typeof websocket === "undefined") { alert("websocket还没有连接,或者连接失败,请检测"); return false; } if (websocket.readyState == 3) { alert("websocket已经关闭,请重新连接"); return false; } console.log(websocket); $("#message").val(''); writeToScreen('你发送的信息 ' + formatDate(new Date()) + '
    '
    + message); websocket.send(message); } function writeToScreen(message) { var div = "
    " + message + "
    "
    ; var d = $("#output"); var d = d[0]; var doScroll = d.scrollTop == d.scrollHeight - d.clientHeight; $("#output").append(div); if (doScroll) { d.scrollTop = d.scrollHeight - d.clientHeight; } } function en(event) { var evt = evt ? evt : (window.event ? window.event : null); if (evt.keyCode == 13) { doSend() } }
    script> html>
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144

    双击打开这个html文件,页面的功能如下,websocket的通信地址要以ws://开头,因为我的springboot启动在8081端口,所以我的地址就是 ws://localhost:8081/api/websocket/{连接id},websocket前面加了api是前面在yml文件中配置了context-path,没有配置可以去掉。实际项目中,将连接id换成唯一的用户id即可向指定用户发送消息。
    在这里插入图片描述

    效果如下
    在这里插入图片描述

    这时候服务器控制台输出为
    在这里插入图片描述

    六、遇到的一些问题

    1. 在控制台输出的消息是乱码

    在这里插入图片描述
    在Pom.xml的properties加入下面代码

    <properties>
            <!--1.8配置在一起,设置编码集-->
            <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
            <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
            <java.version>1.8</java.version>
    </properties>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    七、结束

    到此,SpringBoot搭建WebSocket就已经完成,还有什么问题可以私信作者~

  • 相关阅读:
    @EnableAutoConfiguration记录一下
    柯桥日语培训:语法 | 「あまり 」知识解析
    程序员福音!BAT企业联合出品《Java开发手册》,每一条都是血的教训
    活动图高阶讲解-03
    -星号梯形-
    【信号处理】基于LDPC编译码误码率附matlab代码
    VC++几种加载图片方法的讨论(附源码)
    python 线程 (概念+示例代码)
    一文学会TextureID渲染到Surface
    EasyRAFT
  • 原文地址:https://blog.csdn.net/qq_15752347/article/details/128190762