(一)WebSocket简介与应用场景
[1]介绍:和Socket一样,都是用于进行长连接通讯。JavaWeb传统通信方式都是请求-响应的方式进行,即浏览器必须发起请求后,才能收到服务器的响应数据。而WebSocket实现了服务器主动向浏览器发送数据,无需浏览器请求的效果。
[2]应用场景:
-
web版的在线聊天室
-
实时广播消息到浏览器
(二)WebSocket整合入SpringBoot
[1]准备工作
(1)导入WebSocket的SpringBoot依赖
org.springframework.boot spring-boot-starter-websocket
(2)导入JQuery,前端发起WebSocket连接需要JQuery
[2]配置WebSocket
和其他框架整合入SpringBoot一样,再config包中创建一个,MyWebSocketConfig的,并使用@Configuration注解修饰,在该类中用@Bean装配ServerEndpointExporter对象
@Configuration public class MyWebSocketConfig implements WebSocketMessageBrokerConfigurer { @Bean public ServerEndpointExporter getServerEndpointExporter (){ return new ServerEndpointExporter(); } }
[3]创建WebSocket服务节点ServerEndpoint类与WebSocket连接对象类
在component包下创建ChatWebSocketServer类该类封装了整个WebSocket的通信逻辑和回调函数,作为一个组件存在,且同样交给SpringIOC容器来管理,因此使用@Component注解修饰,该组件作为单例存在。
ChatWebSocketServer的设计如下
@Component @ServerEndpoint("/chatServer/{userId}") public class ChatWebSocket { private static int onlineCount = 0; public static ConcurrentHashMapwebSocketConnMap = new ConcurrentHashMap<>(); @onOpen public void onOpen(Session session,@PathParam("userId") String userId) { //建立了ws新连接 WebSocketConnection webSocketConnection=new WebSocketConnection(); webSocketConnection.setUserId(userId); webSocketConnection.setWsSession(session); if(webSocketConnMap.containsKey(userId)){ //如果Map中有该key,则先清除掉 webSocketConnMap.remove(userId); webSocketConnMap.put(userId,webSocketConnection); }else{ //没有则直接加入 webSocketConnMap.put(userId,webSocketConnection); //在线数加1 addonlineCount(); } System.out.println("用户连接:"+userId+",当前在线人数为:" + getonlineCount()); try { webSocketConnection.sendMessage("连接成功"); } catch (IOException e) { System.err.print("用户:"+userId+",网络异常!!!!!!"); } } @onClose public void onClose(@PathParam("userId")String userId) { if(webSocketConnMap.containsKey(userId)){ webSocketConnMap.remove(userId); //从set中删除 subonlineCount(); } System.out.println("用户退出:"+userId+",当前在线人数为:" + getonlineCount()); } @onMessage public void onMessage(String message, @PathParam("userId")String userId) { System.out.println("用户消息:"+userId+",报文:"+message); //可以群发消息,目前仅发送给接收者ID if(!StringUtils.isEmpty(message)){ try { //解析发送的报文 JSonObject jsonObject =new JSonObject(message); //追加发送人(防止串改) jsonObject.put("fromUserId",userId); String toUserId=jsonObject.getString("toUserId"); //传送给对应toUserId用户的websocket连接 if(!StringUtils.isEmpty(toUserId)&&webSocketConnMap.containsKey(toUserId)){ webSocketConnMap.get(toUserId).sendMessage(jsonObject.toString()); }else{ System.err.println("请求的userId:"+toUserId+"不在该服务器上"); //否则不在这个服务器上,发送到mysql或者redis } //也给发送人自己发送一份,能看到自己的发送记录 webSocketConnMap.get(userId).sendMessage(jsonObject.toString()); }catch (Exception e){ e.printStackTrace(); } } } @onError public void onError(@PathParam("userId")String userId, Throwable error) { System.err.println("用户错误:"+userId+",原因:"+error.getMessage()); error.printStackTrace(); } public static synchronized int getonlineCount() { return onlineCount; } public static synchronized void addonlineCount() { onlineCount++; } public static synchronized void subonlineCount() { onlineCount--; } }
ServerPoint服务点类有4个关键的生命周期函数,使用ws特有的注解进行修饰:
(1)@OnOpen:前端建立ws连接时调用
在该方法中,主要将建立连接时的两大参数获取到,其一是建立该ws连接的用户id,其二是该ws连接的Session会话对象。前者将用于作为凭证去Map中寻找对应的服务节点对象,后者将用于发送消息给前端。因此再该类中将这两个参数构造为WebSocketConnection对象,放入Map中存储,以便以后的使用。
(2)@OnMessage:当收到前端发送的消息时调用
在该方法中,可以获取到两个参数,其一是消息发送者的id即此处为userId,其二是发送内容String类型,在该方法中具体的 *** 作取决于具体的业务需求。此处可以通过userId在Map中获得对应的WebSocketConnetion连接对象,用连接对象中提供的方法将业务逻辑中处理后的信息返回给客户端。在本演示项目中,模拟的是两人聊天。因此OnMessage中做了判断处理,只对接受方ID发送信息,也对发送本人也留了一份。
(3)@OnClose:当连接意外断开或是前端发起了断开连接后调用
在该方法中,可以获取到消息发送的ID即此处为userId。该函数的主要目的就是将该userId对应的WebSocketConnection连接对象从Map中移除。
(4)@OnError:当发生错误时调用
在该方法中,可以获取到消息发送的ID即此处为userId。该函数的主要目的就是将错误日志输出和记录。
在pojo包下创建WebSocketConnection类,该类作为WS连接的实体类,包含两个字段,其一是userId,其二是ws的Session对象。并提供了一个方法sendMessage,直接调用该方法就可将消息发回给前端,在前端的ws回调中进行处理
WebSocketConnection的设计如下
import javax.websocket.*; import java.io.IOException; public class WebSocketConnection { private String userId;//用户ID private Session wsSession;//ws会话对象 //省略getter setter和构造 public void sendMessage(String message) throws IOException { wsSession.getBasicRemote().sendText(message); } }
[4]WebSocket服务节点ServerEndpoint类注入其他bean的特殊处理
由于Socket特性,长连接通信中,socket对象本身是多实例(即每建立一个连接就会产生一个Socket对象),且WebSocket线程是独立于Spirng主线程异步存在,新建立的Socket对象不是由Spring创建而是由异步线程创建,因此自动注入无效。
正确的方法应该使用静态工具类BeanUtil的方式来获取Bean,需要实现ApplicationContextAware接口,拿到ApplicationContext对象来静态getBean()【详见SpringBoot高级用法】
(三)前端使用WebSocket
在前端页面使用websocket前先导入jQuery
[1]指定ws的连接url
ws的连接格式为ws://localhost:8080/serverEndpointxx的形式
-
若为http则使用ws://作为协议头
-
若为https则使用wss://作为协议头【此模式下仅可使用域名,不能使用ip】
在本项目中的ws连接为:
var socketUrl="ws://localhost:8080/websocket/chatServer/"+$("#userId").val();
其中$("#userId").val()代表建立ws连接的用户Id,真实情况应该是从session中获取
[2]构建WebSocket对象并绑定监听函数
var socket=new WebSocket()传入刚刚ws的url
监听函数同样为那4个,此处以匿名函数的方式进行绑定
socket = new WebSocket(socketUrl); socket.onopen = function() { console.log("websocket已打开"); //socket.send("这是来自客户端的消息" + location.href + new Date()); }; //获得消息事件 socket.onmessage = function(msg) { console.log(msg.data); //发现消息进入 开始处理前端触发逻辑 $("#chatBox").append(""+msg.data+""); }; //关闭事件 socket.onclose = function() { console.log("websocket已关闭"); }; //发生了错误事件 socket.onerror = function() { console.log("websocket发生了错误"); }
其中最重要的就是onmessage函数,该函数携带msg参数,可以获得后端发回的数据,然后在该函数
中进行前端的逻辑。
需要注意:在创建WebSocket对象的时候,就已经建立好ws连接了,不用手动去连。
[3]使用WebSocket对象发送消息和断开连接
分别为以下两个函数
socket.send('{"toUserId":"'+$("#toUserId").val()+'","contentText":"'+$("#contentText").val()+'"}');
socket.close();
完整的前端代码展示
聊天室页面 开两个这个页面,分别用两个ID作为发送者和接收者,发送信息即可测试【发送者ID(也是当前登录者)】:
【接收者ID】:
【发送内容】:
【 *** 作】:开启socket
【 *** 作】:发送消息
【接收到的内容】:...
效果
可能出现的问题:
1:因为配置了AOP切面而导致启动项目时WebSocket报错:it is not annotated with @ServerEndpoint无法启动。
解决办法:AOP切面配置切点时排出掉WebSocket自定组件(这里是ChatWebSocketServer )所在的包路径。
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)