SpringBoot整合WebSocket

SpringBoot整合WebSocket,第1张

SpringBoot整合WebSocket

(一)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 ConcurrentHashMap webSocketConnMap = 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 )所在的包路径。

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/zaji/5582641.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-12-14
下一篇 2022-12-14

发表评论

登录后才能评论

评论列表(0条)

保存