spring boot中websocket的使用以及demo

零、前言

http有http的有点,但也有其不足,即只能从客户端发起请求给服务端,服务端不能推数据给客户端。而websocket就不一样了,是双工的,不仅可以由客户端向服务器发送数据,服务端也能主动像客户端推数据。这其中也就是单工跟双工的概念。


一、websocket简介

什么是WebSocket?

WebSocket协议是基于TCP的一种新的网络协议。它实现了浏览器与服务器全双工(full-duplex)通信——允许服务器主动发送信息给客户端。

为什么需要 WebSocket?
        初次接触 WebSocket 的人,都会问同样的问题:我们已经有了 HTTP 协议,为什么还需要另一个协议?它能带来什么好处?答案很简单,因为 HTTP 协议有一个缺陷:通信只能由客户端发起,HTTP 协议做不到服务器主动向客户端推送信息。举例来说,我们想要查询当前的排队情况,只能是页面轮询向服务器发出请求,服务器返回查询结果。轮询的效率低,非常浪费资源(因为必须不停连接,或者 HTTP 连接始终打开)。因此WebSocket 就是这样发明的。

与http进行对比:


二、spring boot中引入websocket的步骤

创建spring boot项目,结构如下:

(1)pom.xml引入依赖 

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

 (2)websocket配置类

package com.example.utils;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.server.standard.ServerEndpointExporter;

/**
 * @Description
 * @ClassName WebSocketConfig
 * @Author User
 * @date 2020.05.31 18:45
 */
@Configuration
public class WebSocketConfig {
    @Bean
    public ServerEndpointExporter serverEndpointExporter() {
        return new ServerEndpointExporter();
    }
}

(3)允许跨域配置

由于我在springboot中放了一个websocket客户端html,访问该html时用的html与spring boot的端口号不一致,造成了跨域,因此进行了配置。

package com.example.utils;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.filter.CorsFilter;

@Configuration
public class GlobalCorsConfig {
    @Bean
    public CorsFilter corsFilter() {
        CorsConfiguration config = new CorsConfiguration();
        config.addAllowedOrigin("*");
        config.setAllowCredentials(true);
        config.addAllowedMethod("*");
        config.addAllowedHeader("*");

        UrlBasedCorsConfigurationSource configSource = new UrlBasedCorsConfigurationSource();
        configSource.registerCorsConfiguration("/**", config);

        return new CorsFilter(configSource);
    }
}

 注意,网络上有些在config的最后还有一句:

config.addExposedHeader("*");

 这句在我这会导致项目启动失败,不知道为什么我也没去研究,我删掉了他就可以了。

(4)websocket类

package com.example.websocket;


import javax.websocket.*;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;

import org.apache.log4j.Logger;
import org.springframework.stereotype.Component;

import java.io.IOException;
import java.util.concurrent.ConcurrentHashMap;

/**
 * @Description ws的server,相当于controller
 * @ClassName MyWsServer
 * @Author User
 * @date 2020.05.31 18:24
 */
@ServerEndpoint("/api/v1/websocket/{userId}")
@Component
public class MyWsServer {
    // 引入log4j日志
    static Logger logger = Logger.getLogger(MyWsServer.class);
    // 静态变量,用来记录当前在线连接数
    private static int onlineCount = 0;
    // concurrent包的线程安全Set,用来存放客户端对象
    private static ConcurrentHashMap<String, MyWsServer> clients = new ConcurrentHashMap<>();
    // 与某个客户端的连接会话,需要通过它来给客户端发送数据
    private Session session;
    // 用户唯一标识符
    private String userId = "";

    /**
     * @return
     * @Description websocket的连接函数
     * @Param {Session} session
     * @Author User
     * @Date 2020.05.31 19:13
     **/
    @OnOpen
    public void onOpen(Session session, @PathParam("userId") String userId) {
        this.session = session;
        this.userId = userId;
        logger.info("有新的客户端连接进来了,客户端id是: " + session.getId());
        if (clients.containsKey(userId)) {
            clients.remove(userId);
            clients.put(userId, this);
        } else {
            clients.put(userId, this);
            MyWsServer.addOnlineCount();
        }

        logger.info("用户:" + userId + ", 当前在线人数为:" + getOnlineCount());

        try {
            sendMessage("连接成功");
        } catch (IOException e) {
            logger.error("用户:" + userId + ",网络异常!");
        }

    }

    /**
     * @return
     * @Description 关闭事件处理函数
     * @Param
     * @Author User
     * @Date 2020.06.12 22:15
     **/
    @OnClose
    public void onClose() {
        if (clients.containsKey(userId)) {
            clients.remove(userId);
            subOnlineCount();
        }
        logger.info("用户退出:" + userId + ",当前在线人数为:" + getOnlineCount());
    }

    /**
     * @return
     * @Description 接收消息
     * @Param
     * @Author User
     * @Date 2020.06.12 22:29
     **/
    @OnMessage
    public void onMessage(String message, Session session) {
        logger.info("用户" + userId + "发来消息, 报文:" + message);
    }

    /**
     * @return
     * @Description
     * @Param
     * @Author User
     * @Date 2020.06.12 22:31
     **/
    @OnError
    public void onError(Session session, Throwable error) {
        logger.error("用户错误:" + this.userId + ",原因:" + error.getMessage());
        error.printStackTrace();
    }

    /**
     * @return {null}
     * @Description 向客户端发送消息
     * @Param {String} message 要发送的消息
     * @Author User
     * @Date 2020.05.31 19:14
     **/
    public void sendMessage(String message) throws IOException {
        this.session.getBasicRemote().sendText(message);
    }

    /**
     * @return
     * @Description 发送自定义消息到指定用户或者群发消息
     * @Param
     * @Author User
     * @Date 2020.06.12 22:55
     **/
    public static void sendInfo(String message, String fromUserId, String toUserId) {
        logger.info("推送消息给用户" + toUserId + ",推送内容:" + message);
        for (MyWsServer client : clients.values()) {
            try {
                //这里可以设定只推送给这个userId的,为null则全部推送
                if ("every".equals(toUserId) && !client.userId.equals(fromUserId)) {
                    client.sendMessage("来自" + fromUserId + "的群发消息:" + message);
                } else if (client.userId.equals(toUserId)) {
                    client.sendMessage("来自" + fromUserId + "发给" + toUserId + "的消息:" + message);
                }
            } catch (IOException e) {
                System.out.println(e.toString());
                continue;
            }
        }
    }

    /**
     * @return {null}
     * @Description 在线客户端数加一
     * @Param {null}
     * @Author User
     * @Date 2020.05.31 19:12
     **/
    public static synchronized void addOnlineCount() {
        MyWsServer.onlineCount++;
    }

    /**
     * @return {null}
     * @Description 在线客户端数减一
     * @Param {null}
     * @Author User
     * @Date 2020.05.31 19:12
     **/
    public static synchronized void subOnlineCount() {
        MyWsServer.onlineCount--;
    }

    /**
     * @return
     * @Description 获取在线连接数
     * @Param
     * @Author User
     * @Date 2020.05.31 19:12
     **/
    public static synchronized int getOnlineCount() {
        return onlineCount;
    }

}

 最重要的一个文件,里面注释很详细。

(5)controller配置demo

package com.example.controller;

import com.example.websocket.MyWsServer;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

import java.io.IOException;

/**
 * @Description
 * @ClassName MyController
 * @Author User
 * @date 2020.06.12 23:05
 */
@RestController
@RequestMapping(("/api/v1"))
public class MyController {
    @GetMapping("/index")
    public ResponseEntity<String> index() {
        return ResponseEntity.ok("<h1>请求成功</h1>");
    }

    @GetMapping("/pushMessage/{fromUserId}/{toUserId}")
    public ResponseEntity<String> pushToClients(@RequestParam("message") String message, @PathVariable("fromUserId") String fromUserId, @PathVariable("toUserId") String toUserId) throws IOException {
        MyWsServer.sendInfo(message, fromUserId, toUserId);
        return ResponseEntity.ok("MSG SEND SUCCESS");
    }
}
<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <title>websocket测试工具</title>
</head>
<script src="./jquery.js"></script>
<script>
    var socket;

    function openSocket() {
        if (typeof (WebSocket) == "undefined") {
            console.log("您的浏览器不支持WebSocket");
        } else {
            if ($('#userId').val() === "") {
                alert("请设置您的个性id");
                return;
            }
            console.log("您的浏览器支持WebSocket");
            //实现化WebSocket对象,指定要连接的服务器地址与端口  建立连接
            var socketUrl = "ws://127.0.0.1:8000/api/v1/websocket/" + $("#userId").val();
            console.log(socketUrl);
            if (socket != null) {
                socket.close();
                socket = null;
            }
            socket = new WebSocket(socketUrl);
            //打开事件
            socket.onopen = function () {
                // alert("websocket已打开");
                //socket.send("这是来自客户端的消息" + location.href + new Date());
            };
            //获得消息事件
            socket.onmessage = function (msg) {
                alert(msg.data);
                //发现消息进入    开始处理前端触发逻辑
            };
            //关闭事件
            socket.onclose = function () {
                console.log("websocket已关闭");
            };
            //发生了错误事件
            socket.onerror = function () {
                console.log("websocket发生了错误");
            }
        }
    }

    function disconnectSocket() {
        if (typeof (WebSocket) == "undefined") {
            alert("您的浏览器不支持WebSocket");
        }
        if (socket === undefined) {
            alert("您尚未连接服务器");
        } else {
            socket.close();
        }
    }

    function sendMessage() {
        if (typeof (WebSocket) == "undefined") {
            console.log("您的浏览器不支持WebSocket");
        } else {
            if ($('#userId').val() === "") {
                alert("请设置您的个性id");
                return;
            }
            if (socket === undefined) {
                alert("您尚未连接服务器");
            } else {
                if (socket.readyState === socket.CLOSED) {
                    alert("您已断开与服务器的连接");
                } else {
                    var obj = {
                        toUserId: $("#toUserId").val(),
                        message: $('#message').val()
                    }
                    // 推送数据到指定客户端,不填toUserId则认为是群发消息
                    var toWho = "";
                    $('#toUserId').val() ? toWho = $('#toUserId').val() : toWho = "every"
                    var url = "http://127.0.0.1:8000/api/v1/pushMessage/"+$('#userId').val() + "/" + toWho + '?message=' + $('#message').val();
                    // console.log(url);
                    $.ajax(url);
                    // console.log(JSON.stringify(obj));
                    // socket.send(JSON.stringify(obj));
                }
            }


        }
    }
</script>
<body>
<h1 align="center" style="color: red">websocket测试工具</h1>
<div align="left">
    <div>【我的id】:&nbsp<input id="userId" name="userId" type="text" placeholder="设置一个个性的id">&nbsp
        <button id="open" onclick="openSocket()">连接socket</button>
        &nbsp
        <button onclick="disconnectSocket()">断开socket</button>
    </div>
    <br>
    <div>【发送至】:&nbsp<input id="toUserId" name="toUserId" type="text" placeholder="发送给某个用户"></div>
    <br>
    <div>【mesgs】:&nbsp<input id="message" name="message" type="text" placeholder="要发送的消息">&nbsp
        <button onclick="sendMessage()">发送消息</button>
    </div>
</div>

</body>

</html>

          只是一个简单的页面,但是功能齐全,包括连接断开,单发消息以及群发消息

、演示截图

(1)单发消息
启动三个客户端,

客户端1向客户端2发送消息:

只有客户端2收到了消息: 

(2)user1群发消息

用户3收到消息:

用户2收到消息:

四、总结

文章简单介绍了,springboot中如何使用websocket,demo完善,方便后续参考,扩展

五、demo下载地址

          由于经常在csdn下载东西,积分不够用,这个demo就上传到csdn下载了,多多见谅。

          csdn下载地址:https://download.csdn.net/download/qianlixiaomage/12520760

六、结束

再见!

猜你喜欢

转载自blog.csdn.net/qianlixiaomage/article/details/106738140
今日推荐