WebSocket 入门 Java Springboot + Html5 JavaScript 简单实现 home 编辑时间 2021/10/12 ![](/api/file/getImage?fileId=616506ecda74050013006dda) <br><br> ## 前言 入门 WebSocket 后端 Springboot 前端 HTML5 (原生方法) 简单介绍下WebSocket,是类似于HTTP的基于TCP连接协议,相比于HTTP是单次单向传输的短连接,WebSocket是多次双向传输的长连接,更适合用于游戏、聊天室等有实时监听的需求 本来是设想直接用H5,其中的一个房主作为主机,就可以省去服务端,但考虑到可能存在公网ip变动、端口不开放、防火墙等问题,决定放弃该方案 所以本文还是使用主流方案实现前后端websocket交互,即所有用户与服务器端建立连接,服务器端代理实现用户端所有请求和响应 <br><br> ## 折腾 Java SpringBoot端 新建一个空的SpringBoot项目 我这里选择 JDK8 + maven + war包 (可以根据需要自行选择) (过程省略) <br><br> 修改访问端口 `application.yml` ```yml server: port: 3000 ``` <br><br> 增加maven依赖 (这里如果只提供socket接口,可以把不需要的去掉,例如web依赖可以不要,只保留websocket) `pom.xml` ```xml <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-websocket</artifactId> </dependency> ``` <br><br> 新建 websocket配置 `WebSocketConfig.java` ```java package com.zzzmh.ws.config; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.web.socket.server.standard.ServerEndpointExporter; /** * @author zzzmh * @date 2021/10/11 */ @Configuration public class WebSocketConfig { /** * 注入一个ServerEndpointExporter,该Bean会自动注册使用@ServerEndpoint注解申明的websocket endpoint */ @Bean public ServerEndpointExporter serverEndpointExporter() { return new ServerEndpointExporter(); } } ``` <br><br> 新建 websocket接口 `TestSocket.java` ```java package com.zzzmh.ws.socket; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.stereotype.Component; import org.springframework.web.bind.annotation.RequestParam; import javax.websocket.*; import javax.websocket.server.PathParam; import javax.websocket.server.ServerEndpoint; import java.util.HashMap; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicInteger; /** * @author zzzmh * @date 2021/10/11 */ @Component @ServerEndpoint(value = "/test/{username}") public class TestSocket { private Logger log = LoggerFactory.getLogger(getClass()); /** * 记录当前在线连接数 (线程安全) */ private static AtomicInteger onlineCount = new AtomicInteger(0); /** * 存放所有在线的客户端 (线程安全) */ private static Map<String, Session> clients = new ConcurrentHashMap<>(); /** * 连接建立成功调用的方法 */ @OnOpen public void onOpen(Session session) { clients.put(session.getId(), session); // 在线人数加1 onlineCount.incrementAndGet(); log.info("有新连接加入:{},当前在线人数为:{}", session.getId(), onlineCount.get()); } /** * 连接关闭调用的方法 */ @OnClose public void onClose(Session session) { onlineCount.decrementAndGet(); // 在线数减1 clients.remove(session.getId()); log.info("有一连接关闭:{},当前在线人数为:{}", session.getId(), onlineCount.get()); } /** * 收到客户端消息后调用的方法 * * @param message 客户端发送过来的消息 */ @OnMessage public void onMessage(String message, Session session) { log.info("服务端收到客户端[{}]的消息:{}", session.getId(), message); this.sendMessage(message, session); } @OnError public void onError(Session session, Throwable error) { log.error("发生错误"); error.printStackTrace(); } /** * 群发消息 * * @param message 消息内容 */ private void sendMessage(String message, Session fromSession) { for (Map.Entry<String, Session> sessionEntry : clients.entrySet()) { Session toSession = sessionEntry.getValue(); String username = toSession.getPathParameters().get("username"); // 排除掉自己 if (!fromSession.getId().equals(toSession.getId())) { String fromUsername = fromSession.getPathParameters().get("username"); log.info("服务端给客户端[{}]发送消息{}", toSession.getId(), message); toSession.getAsyncRemote().sendText(message + "( 发送者: " + fromUsername + ", 接受者: " + username + ")"); } } } } ``` 前端html代码 (需要放在容器里才能执行,本地执行无法请求接口,我这里用webstorm编辑和执行,也可以用vscode) ```html <!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8"> <title>websocket</title> </head> <body> <input id="text" type="text"/> <button onclick="send()">Send</button> <button onclick="closeWebSocket()">Close</button> <div id="message"></div> <script type="text/javascript"> if (!'WebSocket' in window) { alert('当前浏览器不支持WebSocket 无法继续进行游戏,请更换浏览器或设备!') } let username; while (!username) { username = prompt("请输入你的游戏昵称:"); } const websocket = new WebSocket("ws://localhost:3000/test/" + username); //连接发生错误的回调方法 websocket.onerror = function () { setMessageInnerHTML("连接错误"); }; //连接成功建立的回调方法 websocket.onopen = function (event) { setMessageInnerHTML("已连接"); } //接收到消息的回调方法 websocket.onmessage = function (event) { setMessageInnerHTML("已收到消息:" + event.data); } //连接关闭的回调方法 websocket.onclose = function () { setMessageInnerHTML("已断开"); } //监听窗口关闭事件,当窗口关闭时,主动去关闭websocket连接,防止连接还没断开就关闭窗口,server端会抛异常。 window.onbeforeunload = function () { websocket.close(); } //将消息显示在网页上 function setMessageInnerHTML(innerHTML) { document.getElementById('message').innerHTML += '<br>' + innerHTML; } //关闭连接 function closeWebSocket() { websocket.close(); } //发送消息 function send() { const message = document.getElementById('text').value; setMessageInnerHTML("已发送消息:" + message) websocket.send(message); } </script> </body> </html> ``` <br><br> **服务端大致目录如下** ```shell ws ├── src │ ├── main │ │ ├── java │ │ │ ├── com.zzzmh.ws │ │ │ │ ├── config │ │ │ │ │ ├── WebSocketConfig.java │ │ │ │ ├── socket │ │ │ │ │ ├── TestSocket.java │ │ │ │ ├── ServletInitializer.java │ │ │ │ ├── WsApplication.java │ │ ├── resources │ │ │ ├── application.yml ├── pom.xml ``` <br><br> ## 效果展示 首先我这里打开3个页面模拟3个用户,分别输入昵称 `user1` `user2` `user3` 分别都可以看到已连接 说明连接websocket正常 在user1 中输入666并点击发送 在user2 和 user3可以看到效果 截图如下 <br> `user1` ![](/api/file/getImage?fileId=61654bc4da74050013006e23) <br> `user2` ![](/api/file/getImage?fileId=61654bd8da74050013006e24) <br> `user3` ![](/api/file/getImage?fileId=61654bf2da74050013006e25) <br><br> ## END 后续打算写几个能联机的多人游戏玩一玩 当其中一个人进行操作,就可以用ws实时同步到其他玩家的界面 包括实时的多人在线聊天也可以用这个实现 用户本地缓存数据用 localStorage 还可以实现断线重连 <br> 最终代码 [https://gitee.com/tczmh/ws-java](https://gitee.com/tczmh/ws-java) [https://gitee.com/tczmh/websocket-html](https://gitee.com/tczmh/websocket-html) <br><br> 参考 [https://www.cnblogs.com/xuwenjin/p/12664650.html](https://www.cnblogs.com/xuwenjin/p/12664650.html) 送人玫瑰,手留余香 赞赏 Wechat Pay Alipay 404 Not Found Page 创意 纯静态页面 仅4kb单文件 Docker 零代码 快速搭建 各类网站