假设有这样一个场景:服务端的资源常常在更新,客户端须要尽可能及时地了解到这些更新发生后展现给用户,若是是 HTTP 1.1,一般会开启 ajax 请求询问服务端是否有更新,经过定时器反复轮询服务端响应的资源是否有更新。html
ajax 轮询git
在长时间不更新的状况下,反复地去询问会对服务器形成很大的压力,对网络也有很大的消耗,若是定时的时间比较大,服务端有更新的话,客户端可能须要等待定时器达到之后才能获知,这个信息也不能很及时地获取到。github
而有了 WebSocket 协议,就能很好地解决这些问题,WebSocket 能够反向通知的,一般向服务端订阅一类消息,服务端发现这类消息有更新就会不停地通知客户端。web
WebSocketajax
WebSocket 协议是基于 TCP 的一种新的网络协议,它实现了浏览器与服务器全双工(full-duplex)通讯—容许服务器主动发送信息给客户端,这样就能够实现从客户端发送消息到服务器,而服务器又能够转发消息到客户端,这样就可以实现客户端之间的交互。对于 WebSocket 的开发,Spring 也提供了良好的支持,目前不少浏览器已经实现了 WebSocket 协议,可是依旧存在着不少浏览器没有实现该协议,为了兼容那些没有实现该协议的浏览器,每每还须要经过 STOMP 协议来完成这些兼容。spring
下面咱们在 Spring Boot 中集成 WebSocket 来实现服务端推送消息到客户端。json
首先建立一个 Spring Boot 项目,而后在 pom.xml
加入以下依赖集成 WebSocket:浏览器
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-websocket</artifactId> </dependency>
接下来在 config
包下建立一个 WebSocket 配置类 WebSocketConfiguration
,在配置类上加入注解 @EnableWebSocket
,代表开启 WebSocket,内部实例化 ServerEndpointExporter
的 Bean,该 Bean 会自动注册 @ServerEndpoint
注解声明的端点,代码以下:服务器
@Configuration @EnableWebSocket public class WebSocketConfiguration { @Bean public ServerEndpointExporter serverEndpointExporter() { return new ServerEndpointExporter(); } }
接下来使用 @ServerEndpoint
定义一个端点服务类,在端点服务类中,能够定义 WebSocket 的打开、关闭、错误和发送消息的方法,具体代码以下所示:websocket
@ServerEndpoint("/websocket/{userId}") @Component public class WebSocketServer { private static final Logger log = LoggerFactory.getLogger(WebSocketServer.class); /** * 当前在线链接数 */ private static AtomicInteger onlineCount = new AtomicInteger(0); /** * 用来存放每一个客户端对应的 WebSocketServer 对象 */ private static ConcurrentHashMap<String, WebSocketServer> webSocketMap = new ConcurrentHashMap<>(); /** * 与某个客户端的链接会话,须要经过它来给客户端发送数据 */ private Session session; /** * 接收 userId */ private String userId = ""; /** * 链接创建成功调用的方法 */ @OnOpen public void onOpen(Session session, @PathParam("userId") String userId) { this.session = session; this.userId = userId; if (webSocketMap.containsKey(userId)) { webSocketMap.remove(userId); webSocketMap.put(userId, this); } else { webSocketMap.put(userId, this); addOnlineCount(); } log.info("用户链接:" + userId + ",当前在线人数为:" + getOnlineCount()); try { sendMessage("链接成功!"); } catch (IOException e) { log.error("用户:" + userId + ",网络异常!!!!!!"); } } /** * 链接关闭调用的方法 */ @OnClose public void onClose() { if (webSocketMap.containsKey(userId)) { webSocketMap.remove(userId); subOnlineCount(); } log.info("用户退出:" + userId + ",当前在线人数为:" + getOnlineCount()); } /** * 收到客户端消息后调用的方法 * * @param message 客户端发送过来的消息 */ @OnMessage public void onMessage(String message, Session session) { log.info("用户消息:" + userId + ",报文:" + message); if (!StringUtils.isEmpty(message)) { try { JSONObject jsonObject = JSON.parseObject(message); jsonObject.put("fromUserId", this.userId); String toUserId = jsonObject.getString("toUserId"); if (!StringUtils.isEmpty(toUserId) && webSocketMap.containsKey(toUserId)) { webSocketMap.get(toUserId).sendMessage(jsonObject.toJSONString()); } else { log.error("请求的 userId:" + toUserId + "不在该服务器上"); } } catch (Exception e) { e.printStackTrace(); } } } /** * 发生错误时调用 * * @param session * @param error */ @OnError public void onError(Session session, Throwable error) { log.error("用户错误:" + this.userId + ",缘由:" + error.getMessage()); error.printStackTrace(); } /** * 实现服务器主动推送 */ public void sendMessage(String message) throws IOException { this.session.getBasicRemote().sendText(message); } public static synchronized AtomicInteger getOnlineCount() { return onlineCount; } public static synchronized void addOnlineCount() { WebSocketServer.onlineCount.getAndIncrement(); } public static synchronized void subOnlineCount() { WebSocketServer.onlineCount.getAndDecrement(); } }
其中,@ServerEndpoint("/websocket/{userId}")
表示让 Spring 建立 WebSocket 的服务端点,其中请求地址是 /websocket/{userId}
。
另外 WebSocket 一共有四个事件,分别对应 JSR-356 定义的 @OnOpen、@OnMessage、@OnClose、@OnError
注解。
@OnOpen:标注客户端打开 WebSocket 服务端点调用方法
@OnClose:标注客户端关闭 WebSocket 服务端点调用方法
@OnMessage:标注客户端发送消息,WebSocket 服务端点调用方法
@OnError:标注客户端请求 WebSocket 服务端点发生异常调用方法
接下来启动项目,使用 WebSocket 在线测试工具(http://www.easyswoole.com/wstool.html
)进行测试,有能力的也能够本身写个 html 测试。
打开网页后,在服务地址中输入ws://127.0.0.1:8080/websocket/wupx
,点击开启链接
按钮,消息记录中会多一条由服务器端发送的链接成功!
记录。
接下来再打开一个网页,服务地址中输入ws://127.0.0.1:8080/websocket/huxy
,点击开启链接
按钮,而后回到第一次打开的网页在消息框中输入{"toUserId":"huxy","message":"i love you"}
,点击发送到服务端
,第二个网页中会收到服务端推送的消息{"fromUserId":"wupx","message":"i love you","toUserId":"huxy"}
。
一样,项目的日志中也会有相应的日志:
2020-06-30 12:40:48.894 INFO 78908 --- [nio-8080-exec-1] com.wupx.server.WebSocketServer : 用户链接:wupx,当前在线人数为:1 2020-06-30 12:40:58.073 INFO 78908 --- [nio-8080-exec-2] com.wupx.server.WebSocketServer : 用户链接:huxy,当前在线人数为:2 2020-06-30 12:41:05.870 INFO 78908 --- [nio-8080-exec-3] com.wupx.server.WebSocketServer : 用户消息:wupx,报文:{"toUserId":"huxy","message":"i love you"}总结
本文简单地介绍了 Spring Boot 集成 WebSocket 实现服务端主动推送消息到客户端,是否是十分简单呢?你们能够本身也写个 demo 试试!
本文的完整代码在 https://github.com/wupeixuan/SpringBoot-Learn
的 websocket
目录下。