使用WebSocket实现前后端通信
大佬榜:请查看以下大佬文档
夜萌:https://www.cnblogs.com/yemengshen/p/11561379.html
xiaozhengtongxue:https://www.cnblogs.com/xiaozhengtongxue/p/13448778.html
AndyLizh:https://blog.csdn.net/gebitan505/article/details/108727914
TheBiiigBlue:https://blog.csdn.net/Aeve_imp/article/details/96985735
b站视频:https://www.bilibili.com/video/BV1r54y1D72U?p=1(本文部分内容来源此视频,推荐观看)
1.WebSocket概念
WebSocket是一种网络通信协议。RFC6455定义了它的通信标准。
WebSocket是Html5开始提供的一种在单个TCP连接上进行全双工通讯的协议。
Http协议是一种无状态的、无连接的、单向的应用层协议。它采用请求/响应模型。通信请求只能由客户端发起,服务端对请求做出应答处理。
这种通信模型由一个弊端:Http协议无法实现服务器主动向客户端发起消息。这种单向请求的特点,注定了如果服务器有连续的状态变化,客户端要获知就非常麻烦。大多数Web应用程序将通过频繁的异步Ajax将请求实现长轮询。轮询的效率低,非常浪费资源(因为必须不停连接,或者Http连接始终打开)。
2.WebSocket协议
本协议有两部分:握手和数据传输,握手是基于http协议的。
来自客户端的握手如下形式:
GET ws://localhost/chat HTTP/1.1 Host: localhost Upgrade: websocket Connection: Upgrade Sec-WebSocket-Key: dGhlIHNhbXBsZsBub25jZQ== Sec-WebSocket-Extensions: permessage-deflate Sec-WebSocket-Version: 13
来自服务器的握手如下形式:
HTTP/1.1 101 Switching Protocols Upgrade: websocket Connection: Upgrade Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo= Sec-WebSocket-Extensions: permessage-deflate
字段说明:
头名称 | 说明 |
---|---|
Connection: Upgrade | 标识该HTTP请求是一个协议升级请求 |
Upgrade: WebSocket | 协议升级为WebSocket协议 |
Sec-WebSocket-Version:13 | 客户端支持WebSocket的版本 |
Sec-WebSocket-Key: | 客户端采用base64编码的24位随机字符序列,服务器接收客户端Http协议升级的证明,要求服务端响应一个对应加密的Sec-WebSocket-Accept头信息作为应答 |
Sec-WebSocket-Extensions: | 协议扩展类型 |
3.客户端(浏览器)实现
3.1websocket对象
实现websocket的web浏览器将通过websocket对象公开所有必需的客户端功能(主要指支持Html5的浏览器)
一下API用于创建WebSocket对象
/* 参数url格式说明:ws://ip地址:端口号/资源名称 */ var ws = new WebSocket(url);
3.2websocket事件
websocket对象相关事件
事件 | 事件处理程序 | 描述 |
---|---|---|
open | websocket对象.onopen | 连接建立时触发 |
message | websocket对象.onmessage | 客户端接收服务端数据时触发 |
error | websocket对象.onerror | 通信发生错误时触发 |
close | websocket对象.onclose | 连接关闭时触发 |
3.3websocket方法
WebSocket对象的相关方法
方法 | 描述 |
---|---|
send() | 使用连接发送数据 |
4.服务端实现
4.1理论说明
Tomcat的7.0.5版本开始支持WebSocket,并且实现了Java WebSocket规范(JSR356)。
Java WebSocket应用由一系列的WebSocketEndPoint组成。EndPoint是一个java对象,代表WebSocket连接的一端,对于服务端,我们可以视为处理具体WebSoket消息的接口,就像Servlet与http请求一样。
我们可以通过两种方式定义EndPoint:
第一种编程式,即继承类 javax.websocket.Endpoints并实现其方法:
package javax.websocket; public abstract class Endpoint { public Endpoint() { } public abstract void onOpen(Session var1, EndpointConfig var2); public void onClose(Session session, CloseReason closeReason) { } public void onError(Session session, Throwable throwable) { } }
Endpoint实例在WebSocket握手时创建,并在客户端与服务端连接过程中有效,最后在链接关闭时结束。在
Endpoint类中明确定义了与其生命周期相关的方法,规范实现者确保生命周期的各个阶段调用实例的相关方法。
生命周期方法如下:
方法 | 注解 | 含义描述 |
---|---|---|
onClose | @OnClose | 当会话关闭时调用 |
onOpen | @OnOpen | 当开启一个新的会话时调用,该方法是客户端与服务端握手成功后调用的方法 |
onError | @OnError | 当连接过程中异常时调用 |
第二种注解式,即定义一个POJO,并添加@ServerEndpoint相关注解
@Retention(RetentionPolicy.RUNTIME) @Target({ElementType.TYPE}) public @interface ServerEndpoint {
服务端接收客户端发送的数据:
通过为Session添加MessageHandler消息处理器来接收消息,当采用注解方式定义Endpoint时,我们还可以通过@OnMessage注解指定接收消息的方法。
注:Session对象不是http中域对象,是WebSocket的。
服务端推送数据给客户端:
发送消息则由RemoteEndpoint完成,其实例由Session维护,根据使用情况,我们可以通过Session.getBasicRemote获取同步消息发送的实例,然后调用其sendXxx()方法就可以发送消息,可以通过Session.getAsyncRemote获取异步消息发送实例。
4.2服务端具体应用
SpringBoot是通过ServerEndpointExporter来扫描@ServerEndpoint注解声明的类,从而实现WebSocket相关服务的,所以我们需要手动配置一个config类来在Spring上下文中提供ServerEndpointExporter。需要注意的是,如果你使用的是外部容器部署(如SpringBoot打war包并使用外置tomcat部署 等)则不需要这个config类来提供ServerEndpointExporter,因为此时SpringBoot默认将扫描@ServerEndpoint注解的行为交给了外部容器处理。
package com.leizi.websocket.config; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.web.socket.server.standard.ServerEndpointExporter; /** * Spring上下文注入ServerEndpointExporter */ @Configuration public class WebSocketConfig { /** * 注入ServerEndpointExporter bean对象,自动注册使用了@ServerEndpoint注解的bean * * @return */ @Bean public ServerEndpointExporter serverEndpointExporter() { return new ServerEndpointExporter(); } }
接下来就是编写WebSocket的服务类及相关的业务逻辑了,我这里做了一个简单的示例:
websocket的交互是在service层
package com.leizi.websocket.server; import org.springframework.stereotype.Component; import javax.websocket.*; import javax.websocket.server.ServerEndpoint; import java.io.IOException; import java.util.concurrent.CopyOnWriteArraySet; @ServerEndpoint("/socket") @Component public class WebSocketService { /** * concurrent包的线程安全Set * 用来存放每个客户端的连接会话session */ private static CopyOnWriteArraySet<Session> sessionSet = new CopyOnWriteArraySet<>(); public WebSocketDemo() { } /** * 在客户初次连接时触发 * 这里会为客户端创建一个session,这个session并不是我们所熟悉的HttpSession * * @param session * @param endpoint */ @OnOpen public void onOpen(Session session, EndpointConfig endpointConfig) { System.out.println("连接成功"); sessionSet.add(session); } /** * 收到客户端消息后调用的方法 * * @param session * @param message * @throws IOException */ @OnMessage public void onMessage(Session session, String message) throws IOException { System.out.println("接收消息"); session.getBasicRemote().sendText(message + "收到了"); } /** * 在客户端与服务器端断开连接时触发 * * @param session */ @OnClose public void onClose(Session session) { System.out.println("连接关闭"); sessionSet.remove(session); } /** * WebSocket通信发生错误触发 * * @param error * @param session * @throws IOException */ @OnError public void onError(Throwable error, Session session) throws IOException { System.out.println(error.toString()); session.getBasicRemote().sendText("发生错误:" + error.toString()); } /** * 给某个会话发送消息 * * @param session * @param message */ public static void sendMessage(Session session, String message) { try { session.getBasicRemote().sendText(message); } catch (IOException e) { System.out.println(e.toString()); } } /** * 给当前在线的人群发消息 * * @param message */ public static void groupMessage(String message) { for (Session session : sessionSet) { sendMessage(session, message); } } /** * 获取当前连接数 * * @return */ public static synchronized int getOnlineCount() { return sessionSet.size(); } }
WebSocket会根据具体的行为分别调用@OnOpen、@OnClose、@OnMessage等注解标记的方法,消息的发送以及连接都是通过Session对象实现的(这里的Session跟我们之前普通web服务里面的HttpSession不同)
为了实现群发消息,我这里通过一个线程安全的CopyOnWriteArraySet来将每个连接的Session存储起来,方便后续消息的推送。
测试效果(上述样例中WebSocket的连接地址为:ws://127.0.0.1:8080/socket 跟正常的web请求地址基本类似,只是开头的http换成了ws)
注意:
各注解标记的方法参数,与Endpoint类中方法参数其实相同,可以不将方法参数都声明,若参数错误,服务会启动报错:
java.lang.IllegalStateException: Failed to register @ServerEndpoint class: class com.leizi.websocket.server.WebSocketDemo
具体应用:
@Autowired private WebSocket webSocket; webSocket.sendMessage("传递的参数");
4.3WebSocket 在线测试
可以使用在线测试工具:http://www.websocket-test.com/
最后推荐b站比较有意思的视频,本文部分内容来源此视频,不过没有视频配套静态资源文件:
https://www.bilibili.com/video/BV1r54y1D72U?p=1
有哪位大佬做了精美前端,请评论区分享工程地址。
本文来自博客园,作者:Lz_蚂蚱,转载请注明原文链接:https://www.cnblogs.com/leizia/p/14233118.html
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步