SpringBoot集成WebSocket实现简易版微信

一、前言

WebSocket 是 HTML5 开始提供的一种在单个 TCP 连接上进行全双工通讯的协议。这意味着它是一种持久性连接,且服务端可以发消息给客户端。这便容易实现即时通讯通知的功能,本文将介绍 WebSocket 在 SpringBoot 中的用法,以及一个简单的网上聊天的 demo。

二、集成步骤

2.1 引入依赖

<!-- 集成websocket -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-websocket</artifactId>
</dependency>

2.2 开启WebSocket支持

@Configuration
public class WebSocketConfig {
    @Bean
    public ServerEndpointExporter serverEndpointExporter() {
        return new ServerEndpointExporter();
    }
}

2.3 配置服务端

使用注解 @ServerEndpoint 来定义一个 WebSocket 服务端。

@ServerEndpoint("/test")
@Component
@Slf4j
public class MyWebSocketServer {
    /**
     * 存放所有在线的客户端userId->session
     */
    private static Map<String, Session> clients = new ConcurrentHashMap<>();

    @OnOpen
    public void onOpen(Session session) {
        log.info("onOpen前存活: {}", clients.keySet());
        Map<String, List<String>> requestParameterMap = session.getRequestParameterMap();
        String userId = requestParameterMap.get("fromId").get(0);
        log.info("用户上线了, userId为:{}, sessionId为:{}", userId, session.getId());
        //将新用户存入在线的组
        clients.put(userId, session);
        log.info("onOpen后存活: {}", clients.keySet());
    }

    /**
     * 客户端关闭
     * @param session session
     */
    @OnClose
    public void onClose(Session session) {
        log.info("onClose前存活: {}", clients.keySet());
        Map<String, List<String>> requestParameterMap = session.getRequestParameterMap();
        String userId = requestParameterMap.get("fromId").get(0);
        log.info("有用户断开了, userId为:{}, sessionId为:{}", userId, session.getId());
        //将掉线的用户移除在线的组里
        clients.remove(userId);
        log.info("onClose后存活: {}", clients.keySet());
    }

    /**
     * 发生错误
     * @param throwable e
     */
    @OnError
    public void onError(Throwable throwable) {
        log.error("onError: {}, 当前存活:{}", throwable.getMessage(), clients.keySet());
    }

    /**
     * 收到客户端发来消息
     */
    @OnMessage
    public void onMessage(Session session, String dataStr) {
        log.info("服务端收到消息体: {}", dataStr);
        JSONObject data = JSON.parseObject(dataStr);
        // type不为空且等于heartbeat代表心跳消息,必须转发到目标自身以避免客户端断线
        String type = data.getString("type");
        if (!StringUtils.isEmpty(type) && "heartbeat".equals(type)) {
            log.info("心跳消息: {}", dataStr);
            session.getAsyncRemote().sendText(dataStr);
        } else {
            String toId = data.getString("toId");
            Session toSession = clients.get(toId);
            // 目标对象是否已下线,若ignore此元素,会造成自身session被关闭
            if (toSession != null) {
                toSession.getAsyncRemote().sendText(dataStr);
            } else {
                log.warn("对方{}已下线", toId);
                data.put("fromId", toId);
                data.put("state", "fail");
                data.put("message", String.format("<发送失败>: 对方%s已下线", toId));
                session.getAsyncRemote().sendText(data.toJSONString());
            }
        }
    }

}

定义了一个成员变量 ConcurrentHashMap,用来保存所有的 WebSocket 会话连接;定义了4个注解的方法,@OnOpen@OnClose@OnError@OnMessage 分别代表有客户端连接到、有客户端关闭、发生错误和服务端接收到消息的处理逻辑。

服务端发送消息通过目标 Session 来操作。服务端的流程大概如此,接下来看看客户端的架构。

2.4 配置客户端

首先,客户端(浏览器)必须要支持 WebSocket 协议;其次,创建 WebSocket 对象,同服务端一样,定义4个阶段对应的函数实现;最后,客户端发送消息,可通过创建的 WebSocket 对象直接 send 即可。

function openSocket() {
    if (typeof(WebSocket) == "undefined") {
        console.log("您的浏览器不支持WebSocket");
    } else {
        console.log("您的浏览器支持WebSocket");
        //实现化WebSocket对象,指定要连接的服务器地址与端口,建立连接
        var curId = $(".curAccount").attr("id");
        var socketUrl = window.location.origin.replace("https","ws").replace("http","ws") + "/test?fromId=" + curId;
        console.log(socketUrl);
        if (socket != null) {
            socket.close();
            socket = null;
        }
        socket = new WebSocket(socketUrl);
        //打开事件
        socket.onopen = function() {
            console.log("websocket已打开");
        };
        //获得消息事件
        socket.onmessage = function(event) {
            // 发现消息进入    开始处理前端触发逻辑
            let dataStr = event.data;
            let data = JSON.parse(dataStr);
            // TODO 消息处理
        };
        //关闭事件
        socket.onclose = function(event) {
            console.log(event);
            console.log("websocket已关闭");
        };
        //发生了错误事件
        socket.onerror = function() {
            console.log("websocket发生了错误");
        }
    }
}
openSocket();
function sendMsg() {
    if (typeof(WebSocket) == "undefined") {
        console.log("您的浏览器不支持WebSocket");
    } else {
        console.log("您的浏览器支持WebSocket");
        let toId = $(".userRow.activeFriend").attr("data-id");
        let msg = $(".msgarea").val();
        console.log(toId + "->" + msg);
        let data = {};
        data["fromId"] = $(".curAccount").attr("id");
        data["toId"] = toId;
        data["message"] = msg;
        socket.send(JSON.stringify(data));
    }
}

2.5 心跳检测

通常情况下,WebSocket 连接创建后,如果一段时间内没有任何活动,服务器端会对连接进行超时处理。

以下是我在测试过程中的日志情况,可以发现一个连接5分钟没有活动后,会被服务端做超时关闭处理:

[INFO ] 2020-03-11 11:24:34,020 com.szh.wechat.controller.MyWebSocketServer.onOpen(MyWebSocketServer.java:35)
用户上线了, userId为:66666666, sessionId为:5
[INFO ] 2020-03-11 11:29:34,023 com.szh.wechat.controller.MyWebSocketServer.onClose(MyWebSocketServer.java:49)
有用户断开了, userId为:66666666, sessionId为:5

所以,我们需要让客户端每隔一段时间向服务端发送一次心跳活动,以免被服务端关闭,js代码如下:

//心跳检测
var heartCheck = {
    timeout: 30000,
    timeoutObj: null,
    serverTimeoutObj: null,
    start: function() {
        console.log('start heartCheck');
        var self = this;
        this.timeoutObj && clearTimeout(this.timeoutObj);
        this.serverTimeoutObj && clearTimeout(this.serverTimeoutObj);
        this.timeoutObj = setTimeout(function() {
            console.log('heartCheck doing...');
            //发送测试信息,后端收到后,返回一个消息,
            let data = {};
            data["type"] = "heartbeat";
            data["fromId"] = currentUser.id;
            data["message"] = "心跳检测: " + data["fromId"];
            socket.send(JSON.stringify(data));
            self.serverTimeoutObj = setTimeout(function() {
                socket.close();
            }, self.timeout);
        }, this.timeout);
    }
};

最后,在合适的地方,如onOpen、onMessage处添加 heartCheck.start() 即可续签心跳。

三、项目分享

【github地址】:GitHub - oaHeZgnoS/wechat_web: web简易版微信qq。springboot、html、jQuery、websocket

【相关技术】: SpringBoot、WebSocket、H5、JQuery等。

【项目截图】:如下

评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值