【WebSocket】一个简单的前后端交互Demo

 

 WebSocket资料参考:

https://www.jianshu.com/p/d79bf8174196 

使用SpringBoot整合参考:

https://blog.csdn.net/KeepStruggling/article/details/105543449

  

一、通信实现

后端部分:

直接使用Springboot,依赖只有内嵌tomcat和对应的websocket封装启动包

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.1.3.RELEASE</version>
    </parent>

    <groupId>cn.cloud9</groupId>
    <artifactId>WebSocket</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
    </properties>

    <dependencies>
        <!-- 内嵌Tomcat库来提供WebSocketAPI -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <!-- Spring对WebSocket的扩展Starter -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-websocket</artifactId>
        </dependency>
    </dependencies>

</project>

  

定义WebSocket接口

package cn.cloud9.endpoint;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

import javax.websocket.*;
import javax.websocket.server.ServerEndpoint;
import java.io.IOException;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * @author OnCloud9
 * @description
 * @project WebSocket
 * @date 2022年06月15日 20:13
 *
 * ws://localhost:8080/ws/test
 */
@Component
@ServerEndpoint("/ws/test")
public class TestEndpoint {
    private static final Logger LOGGER = LoggerFactory.getLogger(TestEndpoint.class);

    private static final Map<String, Session> CLIENT_SESSION_MAP = new ConcurrentHashMap<>();

    /**
     * 侦测客户端向此服务建立连接,此终端实例会新创建出来
     * @param session
     */
    @OnOpen
    public void onOpen(Session session) {
        LOGGER.info("客户端 {} 开启了连接", session.getId());
        // 默认按照ID保管存放这个客户端的会话信息
        CLIENT_SESSION_MAP.put(session.getId(), session);
    }

    /**
     * 侦测客户端异常
     * @param session
     * @param error
     */
    @OnError
    public void onError(Session session, Throwable error) {
        LOGGER.info("客户端 {} 连接异常... 异常信息:{}", session.getId(), error.getMessage());
        LOGGER.error(error.getMessage());
    }

    /**
     * 侦测客户端向此服务端发送消息
     * @param session
     * @param message
     */
    @OnMessage
    public void onMessage(Session session, String message) {
        // 可根据会话ID或者message中自定义唯一标识从容器中取出会话对象来进行操作
        LOGGER.info("收到消息, 来自客户端 {}, 消息内容 -> {}", session.getId(), message);
    }

    /**
     * 侦测客户端关闭事件
     * @param session
     */
    @OnClose
    public void onClose(Session session) {
        LOGGER.info("客户端 {} 关闭了连接...", session.getId());
        // 客户端关闭时,从保管容器中踢出会话
        CLIENT_SESSION_MAP.remove(session.getId());
    }

    /**
     * 给所有客户端发送消息
     * @param message
     */
    public static void sendMessageForAllClient(String message) {
        CLIENT_SESSION_MAP.values().forEach(session -> {
            try {
                LOGGER.info("给客户端 {} 发送消息 消息内容: {}", session.getId(), message);
                session.getBasicRemote().sendText(message);
            } catch (IOException e) {
                e.printStackTrace();
                LOGGER.info("给客户端 {} 发送消息失败, 异常信息:{}", session.getId(), e.getMessage());
            }
        });
    }


}

 

配置WebSocket接口暴露器

package cn.cloud9.config;

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

/**
 * @author OnCloud9
 * @description
 * @project WebSocket
 * @date 2022年06月15日 20:42
 */
@Configuration
public class WebSocketConfig {
    /**
     * 如果使用Springboot默认内置的tomcat容器,则必须注入ServerEndpoint的bean;
     * 如果使用外置的web容器,则不需要提供ServerEndpointExporter,下面的注入可以注解掉
     */
    @Bean
    public ServerEndpointExporter serverEndpointExporter(){
        return new ServerEndpointExporter();
    }
}

  

写一个Controller,通过一般Http接口向WebSocket客户端推送消息

package cn.cloud9.controller;

import cn.cloud9.endpoint.TestEndpoint;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

/**
 * @author OnCloud9
 * @description
 * @project WebSocket
 * @date 2022年06月15日 20:46
 */
@RestController
@RequestMapping("/api/ws")
public class WebSocketController {

    /**
     * http://localhost:8080/api/ws/send
     * @param message
     * @return
     */
    @GetMapping("/send")
    public boolean send(@RequestParam String message) {
        TestEndpoint.sendMessageForAllClient(message);
        return true;
    }
}

  

前端页面:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>WebSocket 页面客户端</title>
</head>
<body>

<div>
    <button onclick="initConnection()">开启WebSocket连接</button>
</div>
<div>
    <input type="text" id="message" ><button onclick="sendMessageFromClient()">发送消息</button>
</div>

<div>
    <button onclick="closeConnection()">关闭连接</button>
</div>

<script>
    const connectionUrl = 'ws://localhost:8080/ws/test'
    var webSocket = null

    function initConnection() {
        webSocket = new WebSocket(connectionUrl)

        webSocket.onopen = (event) => {
            console.log('建立新的WebSocket连接')
        }

        webSocket.onmessage = (event) => {
            const message = JSON.stringify(event.data)
            console.log(`收到服务端发送的消息,消息内容 -> ${message}`)
        }

        webSocket.onerror = (error) => {
            console.log(`WebSocket连接异常 -> ${JSON.stringify(error)}`)
        }

        webSocket.onclose = (event) => {
            console.log(`WebSocket连接关闭 -> ${JSON.stringify(event)}`)
        }
    }

    function sendMessageFromClient() {
        const message = document.querySelector('#message').value
        webSocket.send(JSON.stringify({ message: message }))
    }

    function closeConnection() {
        webSocket.close()
    }
</script>
</body>
</html>

  

 

二、解决客户端标识区分问题:

WebSocket提供了一个@PathParam注解

在开启和关闭时通过该注解的参数传递URL路径值

通过这个在这个路径值放置唯一标识即可区分客户端连接

 

菜坑点:

1、路径值不能随意放置,必须是在最后面

2、可以放置JSON,但是接收的参数将会移除JSON对象的大括号符号,因为路径值的占位符原因...,解决办法就是追加大括号即可

 

 

 

 完整代码:

添加了身份区分的终端案例:

package cn.cloud9.server.struct.websocket;

import com.alibaba.fastjson.JSONObject;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Service;

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

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

import static cn.cloud9.server.struct.websocket.WsMessageEndPoint.PATH;

/**
 * @author OnCloud9
 * @description
 * @project tt-server
 * @date 2022年11月09日 下午 09:56
 */
@Slf4j
@Service
@ServerEndpoint(PATH)
public class WsMessageEndPoint {
    /**
     * localhost:8080/websocket/message
     * 路径参数用于客户端身份标识,区分每个客户端的连接
     */
    public static final String PATH = "/websocket/message/{token}";

    /**
     * 客户端会话管理容器
     */
    private static final Map<String, Session> CLIENT_SESSION_MAP = new ConcurrentHashMap<>();

    /**
     * 侦测客户端开启连接?,通过客户端身份标识 重复创建连接则覆盖原有的连接
     * @param session
     */
    @OnOpen
    public void onConnectionOpen(Session session, @PathParam("token") String clientToken) {
        log.info("客户端 {} 开启了连接", clientToken);
        final Map<String, String> map = JSONObject.parseObject(clientToken, Map.class);
        // 默认按照ID保管存放这个客户端的会话信息
        CLIENT_SESSION_MAP.put(map.get("userId"), session);
    }

    /**
     * 侦测客户端关闭事件
     * @param session
     */
    @OnClose
    public void onClose(Session session,  @PathParam("token") String clientToken) {
        final Map<String, String> map = JSONObject.parseObject(clientToken, Map.class);
        log.info("客户端 {} 关闭了连接...", clientToken);
        // 客户端关闭时,从保管容器中踢出会话
        final String userId = map.get("userId");
        CLIENT_SESSION_MAP.remove(userId);
    }

    /**
     * 侦测客户端异常
     * @param session
     * @param error
     */
    @OnError
    public void onError(Session session, Throwable error) {
        log.error("客户端 {} 连接异常... 异常信息:{}", session.getId(), error.getMessage());
    }



    /**
     * 收到客户端消息
     * @param session
     */
    @OnMessage
    public void receiveClientMessage(String message, Session session) throws IOException {
        log.info("来自客户端 {} 的消息: {}", session.getId(), message);
        session.getBasicRemote().sendText("服务器已收到");
    }

    /**
     * 给所有客户端发送消息
     * @param message
     */
    public static void sendMessageForAllClient(String message) {
        CLIENT_SESSION_MAP.values().forEach(session -> {
            try {
                log.info("给客户端 {} 发送消息 消息内容: {}", session.getId(), message);
                session.getBasicRemote().sendText(message);
            } catch (IOException e) {
                e.printStackTrace();
                log.info("给客户端 {} 发送消息失败, 异常信息:{}", session.getId(), e.getMessage());
            }
        });
    }

    @SneakyThrows
    public static void sendMessageForClient(String clientId, String message) {
        final Session session = CLIENT_SESSION_MAP.get(clientId);
        session.getBasicRemote().sendText(message);
    }
}

 

服务端发送消息接口:

给PostMan调用,然后看对应的客户端是否更新消息

package cn.cloud9.server.test.controller;

import cn.cloud9.server.struct.websocket.WsMessageEndPoint;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.Map;

/**
 * @author OnCloud9
 * @description
 * @project tt-server
 * @date 2022年11月09日 下午 10:03
 */
@RestController
@RequestMapping("/websocket/sender")
public class WsMessageController {

    @PostMapping("/all")
    public void sendMessageToAllClient(@RequestBody Map<String, String> map) {
        final String text = map.get("text");
        WsMessageEndPoint.sendMessageForAllClient(text);
    }

    @PostMapping("/one")
    public void sendMessageToClient(@RequestBody Map<String, String> map) {
        final String text = map.get("text");
        final String client = map.get("client");
        WsMessageEndPoint.sendMessageForClient(client, text);
    }

}

  

浏览器客户端:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>WebSocket 页面客户端</title>
</head>
<body>
 
<div>
    <button onclick="initConnection()">开启WebSocket连接</button>
</div>
<div>
    <input type="text" id="message" ><button onclick="sendMessageFromClient()">发送消息</button>
</div>

<div>
  server message list 
  <ul id="msgList"></ul>
</div>
 
<div>
    <button onclick="closeConnection()">关闭连接</button>
</div>
 
<script>
    let clientInfo = { userId: 1001, username: '张三' }
    clientInfo = JSON.stringify(clientInfo)
    let connectionUrl = `ws://localhost:8080/websocket/message/{${clientInfo}}`
    console.log(connectionUrl)
    var webSocket = null
 
    function initConnection() {
        webSocket = new WebSocket(connectionUrl)
 
        webSocket.onopen = (event) => {
            console.log('建立新的WebSocket连接')
        }
 
        webSocket.onmessage = (event) => {
            const message = JSON.stringify(event.data)
            console.log(`收到服务端发送的消息,消息内容 -> ${message}`)
            const msgList = document.querySelector('#msgList')
            msgList.innerHTML += `<li>${message}</li>`
        }
 
        webSocket.onerror = (error) => {
            console.log(`WebSocket连接异常 -> ${JSON.stringify(error)}`)
        }
 
        webSocket.onclose = (event) => {
            console.log(`WebSocket连接关闭 -> ${JSON.stringify(event)}`)
        }
    }
 
    function sendMessageFromClient() {
        const message = document.querySelector('#message').value
        webSocket.send(JSON.stringify({ message: message }))
    }
 
    function closeConnection() {
        webSocket.close()
    }

    
</script>
</body>
</html>

  

  

 

posted @ 2022-06-15 21:39  emdzz  阅读(2557)  评论(0编辑  收藏  举报