什么是WebSocet?

WebSocket是一种在Web应用程序中实现双向通信的协议,它可以在客户端和服务器之间建立持久的连接,使得客户端可以实时地接收服务器推送的消息或数据。相比传统的HTTP请求,WebSocket可以实现更快、更实时的数据传输

WebSocket适用于什么场景?

  1. 实时通信:如聊天应用、在线游戏等需要实时通信的应用。
  2. 实时监控:如实时监控系统、实时数据显示等需要实时更新的应用。
  3. 实时交互:如在线投票、网络直播等需要实时交互的应用。

为什么要使用WebSocket?

以上三种场景,如果不使用WebSocket,那么久需要通过轮询或长轮询等方式实现实时通信,但这种方式会导致不必要的网络开销和延迟。而且,轮询和长轮询都需要客户端不断地向服务器发送请求,会占用服务器的资源。相比之下,WebSocket可以建立一次连接,保持持久连接,并且只在数据更新时传输数据,因此减少了网络开销和服务器的负载。

那么在这里我们就实现一个聊天的案例:

首先创建一个SpringBoot项目,然后maven引入以下包

    <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>

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

然后创建ServerEndpointExporterConfig,注入ServerEndpointExporter

package cc.oolo.config;

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

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

然后创建一个WebSocketConfigurator,用于获取我们连接websocket时候,url上所带的参数

package cc.oolo.config;

import javax.websocket.HandshakeResponse;
import javax.websocket.server.HandshakeRequest;
import javax.websocket.server.ServerEndpointConfig;

public class WebSocketConfigurator extends ServerEndpointConfig.Configurator {

    // WebSocketConfig类
    @Override
    public void modifyHandshake(ServerEndpointConfig sec, HandshakeRequest request, HandshakeResponse response) {
        // 获取userId参数
        String userId = request.getParameterMap().get("userId").get(0);
        // 将userId放入attributes中
        sec.getUserProperties().put("userId", userId);
    }
}

然后还需要创建一个存储消息的实体类

package cc.oolo.entity;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@AllArgsConstructor
@NoArgsConstructor
public class SocketMessage {
    private String from;
    private String to;
    /**
     * 为1代表单发给某个用户,为0代表广播
     */
    private Integer type;
    private String message;
}

最后就是创建我们的websocket请求处理类了

package cc.oolo.websocket;

import cc.oolo.config.WebSocketConfigurator;

import cc.oolo.entity.SocketMessage;
import com.alibaba.fastjson.JSON;
import org.springframework.stereotype.Component;

import javax.websocket.OnClose;
import javax.websocket.OnMessage;
import javax.websocket.OnOpen;
import javax.websocket.Session;
import javax.websocket.server.ServerEndpoint;
import java.io.IOException;
import java.util.*;


@Component
@ServerEndpoint(value = "/p2p",configurator = WebSocketConfigurator.class)
public class P2PWebSocket {

    private static Map<String,Session> sessionMap = new HashMap<>();

    /**
     * 建立连接
     */
    @OnOpen
    public void onOpen(Session session) {
        String userId = (String) session.getUserProperties().get("userId");
        sessionMap.put(userId,session);
    }

    /**
     * 连接关闭调用的方法
     */
    @OnClose
    public void onClose(Session session) throws IOException {
        String userId = (String) session.getUserProperties().get("userId");
        sessionMap.remove(userId);
        session.close();
    }

    /**
     * 向另一个会话发送消息
     */
    @OnMessage
    public void sendMessage(String message) throws IOException {
        SocketMessage socketMessage = JSON.parseObject(message, SocketMessage.class);
        Session otherSession = sessionMap.get(socketMessage.getTo());
        Integer type = socketMessage.getType();
        //type为1的时候为单发
        if (type == 1){
            otherSession.getBasicRemote().sendText(socketMessage.getMessage());
        }
        //type为0的时候为广播
        if (type == 0){
            for (String key : sessionMap.keySet()){
                sessionMap.get(key).getBasicRemote().sendText(socketMessage.getMessage());
            }
        }
    }
}

创建前端html页面

<!DOCTYPE HTML>
<html>
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    <title>WebSocket P2P</title>
</head>
<body>
用户名:<input id="userId" type="text" />
<button onclick="concatWS()">连接</button>
<br>
发送用户:<input id="toUserId" type="text" />
</br>
<input id="text" type="text" />
<button onclick="send(1)">发给某个用户</button>
<button onclick="send(0)">广播</button>
<button onclick="closeWebSocket()">关闭连接</button>
<div id="message"></div>
</body>
<script type="text/javascript">
    var websocket = null;
    var userId = null;

    function concatWS(){
        userId = document.getElementById('userId').value
        console.log(userId)
        //判断当前浏览器是否⽀持WebSocket, 主要此处要更换为⾃⼰的地址
        if ('WebSocket' in window) {
            websocket = new WebSocket("ws://localhost:8080/p2p?userId="+userId);
        } else {
            alert('Not support websocket')
        }

        //连接发⽣错误的回调⽅法
        websocket.onerror = function() {
            setMessageInnerHTML("服务器通信故障");
        };

        //连接成功建⽴的回调⽅法
        websocket.onopen = function(event) {
            setMessageInnerHTML("与服务器已建⽴连接");
        }

        //接收到消息的回调⽅法
        websocket.onmessage = function(event) {
            console.log(event.data)
            setMessageInnerHTML(event.data);
        }

        //连接关闭的回调⽅法
        websocket.onclose = function() {
            setMessageInnerHTML("WebSocket连接已关闭");
        }

        //监听窗⼝关闭事件,当窗⼝关闭时,主动去关闭websocket连接,防⽌连接还没断开就关闭
        //窗⼝,server端会抛异常。
        window.onbeforeunload = function() {
            websocket.close();
        }
        //将消息显示在⽹⻚上
        function setMessageInnerHTML(innerHTML) {
            document.getElementById('message').innerHTML += innerHTML + '<br/>';
        }
    }

    //关闭连接
    function closeWebSocket() {
        websocket.close();
    }

    //发送消息
    function send(type) {
        var message = document.getElementById('text').value;
        var to = document.getElementById("toUserId").value;
        websocket.send(JSON.stringify({from: userId,to: to,message: message,type: type}));
    }
</script>
</html>

测试:

最后修改:2023 年 03 月 16 日
如果觉得我的文章对你有用,请随意赞赏