Springboot+WebSocket实现一个聊天室

为什么需要 WebSocket?

  1. 因为 HTTP 协议有一个缺陷:通信只能由客户端发起
  2. 举例来说,我们想了解今天的天气,只能是客户端向服务器发出请求,服务器返回查询结果。HTTP 协议做不到服务器主动向客户端推送信息。
  3. 这种单向请求的特点,注定了如果服务器有连续的状态变化,客户端要获知就非常麻烦。我们只能使用"轮询":每隔一段时候,就发出一个询问,了解服务器有没有新的信息。最典型的场景就是聊天室。
  4. 轮询的效率低,非常浪费资源(因为必须不停连接,或者 HTTP连接始终打开)。因此,工程师们一直在思考,有没有更好的方法。WebSocket 就是这样发明的。

区别

在这里插入图片描述

引入依赖

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

配置config

/**
 * 开启WebSocket支持
 * @author kewen
 */
@Configuration  
public class WebSocketConfig {  
    @Bean  
    public ServerEndpointExporter serverEndpointExporter() {  
        return new ServerEndpointExporter();  
    }  
} 

客户端

/**
 * 该注解用来指定一个URI,客户端可以通过这个URI来连接到WebSocket。
 */
@ServerEndpoint("/websocket/{username}")
@Component
public class MyWebSocket {
    /**
     * 静态变量,用来记录当前在线连接数。应该把它设计成线程安全的。
     */
    private static int onlineCount = 0;

    /**
     * concurrent包的线程安全Set,用来存放每个客户端对应的MyWebSocket对象。若要实现服务端与单一客户端通信的话,可以使用Map来存放,其中Key可以为用户标识
     */
//    private static CopyOnWriteArraySet<MyWebSocket> webSocketSet = new CopyOnWriteArraySet<MyWebSocket>();
    private static Map<String, MyWebSocket> map = new HashMap<>();

    /**
     * 与某个客户端的连接会话,需要通过它来给客户端发送数据
     * tomcat
     */
    private Session session;

    @OnOpen
    public void onOpen(Session session, @PathParam("username") String username) {
        this.session = session;
        map.put(username, this); // 加入set中
        addOnlineCount(); // 在线数加1
        System.out.println("有新连接加入!当前在线人数为" + getOnlineCount());
    }

    /**
     * 连接关闭调用的方法
     */
    @OnClose
    public void onClose(@PathParam("username") String username) {
        map.remove(username); // 从set中删除
        subOnlineCount(); // 在线数减1
        System.out.println("有一连接关闭!当前在线人数为" + getOnlineCount());
    }

    @OnMessage
    public void onMessage(String msg, @PathParam("username") String username) throws IOException {
        System.out.println("来自客户端的消息:" + msg);
        msg = msg.trim();
        if (msg.contains("@") && msg.startsWith("@") && msg.contains(":")) {
            String name = "";
            String[] split = msg.split(":");
            if (split.length == 2) {
                msg = split[1];
                name = split[0].substring(1, split[0].length());
                if (map.get(name) != null) {
                    map.get(name).sendMessage(username + ":" + msg);
                    this.sendMessage(username + ":" + msg);
                } else {
                    this.sendMessage(username + ":你@的人不存在");
                }
            } else {
                msg = "你发了个寂寞";
                this.sendMessage(username + ":" + msg);
            }

        } else {
            for (String s : map.keySet()) {
                map.get(s).sendMessage(username + ":" + msg);
            }
        }
    }

    @OnError
    public void onError(Session session, Throwable error) {
        System.out.println("发生错误");
        error.printStackTrace();
    }

    public void sendMessage(String message) throws IOException {
        this.session.getBasicRemote().sendText(message);
        // this.session.getAsyncRemote().sendText(message);
    }

    public static synchronized int getOnlineCount() {
        return onlineCount;
    }

    public static synchronized void addOnlineCount() {
        MyWebSocket.onlineCount++;
    }

    public static synchronized void subOnlineCount() {
        MyWebSocket.onlineCount--;
    }
}  

客户端

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>HappyRoom</title>
    <script>
        var url = "ws://" + window.location.host + "/websocket/";
        var ws = null;
        //加入聊天室
        function joinRoom() {
            if (ws) {
                alert("你已经在聊天室,不能再加入");
                return;
            }
            var username = document.getElementById("user").value;
            if (username==""){
                alert("用户不能为空")
                return;
            }
            ws = new WebSocket(url + username);
            //与服务端建立连接触发
            ws.onopen = function () {
                alert("与服务器成功建立连接")
            };
            //服务端推送消息触发
            ws.onmessage = function (ev) {
                talking(ev.data);
            };

            //发生错误触发
            ws.onerror = function () {
                console.log("连接错误")
            };
            //正常关闭触发
            ws.onclose = function () {
                console.log("连接关闭");
            };
        }

        //退出聊天室
        function exitRoom() {
            closeWebSocket();
        }

        function sendMsg() {
            if(!ws){
                alert("你已掉线,请重新加入");
                return;
            }
            //消息发送
            ws.send(document.getElementById("sendMsg").value);
            document.getElementById("sendMsg").value = "";
        }

        function closeWebSocket() {
            if(ws){
                ws.close();
                ws = null;
            }
        }

        function talking(content) {
            document.getElementById("content").append(content + "\r\n");
        }
    </script>
</head>
<body>
<div style="text-align: center;background-color: rgba(129,86,255,0.35);margin:0 auto;border:1px solid #000;width:600px;height:650px">
    <br>欢迎使用<strong>bbox</strong>牌极简聊天室:<br/><br/>
    <textarea id="content" cols="60" rows="30" readonly="readonly"></textarea><br>
    <input type="text" id="sendMsg">
    <button type="button" onclick="sendMsg()">发送消息</button>
    <br/><br/>
    用户:<input type="text" id="user">
    <button onclick="joinRoom()">加入群聊</button>
    <button onclick="exitRoom()">退出群聊</button>
</div>
</body>
</html>



版权声明:本文为qq_43419627原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。