1.依赖

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

2.配置

/**
     * web_socket配置类
     */
    @Bean
    public ServerEndpointExporter serverEndpointExporter() {
        return new ServerEndpointExporter();
    }

3.服务器部署:

package com.ciih.workshop.controller;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.dynamic.datasource.toolkit.StringUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;

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

/**
 * @author Lenovo
 */
@Component
@Slf4j
@ServerEndpoint("/webSocket/{id}")
public class WebSocketServer {
    /**
     * 存储用户建立的连接Session
     */
    public static ConcurrentHashMap<String, Session> webSocketMap = new ConcurrentHashMap<>();


    /**
     * 建立连接调用的方法
     */
    @OnOpen
    public void onOpen(Session session, @PathParam("id") String id) throws IOException {

        if (webSocketMap.containsKey(id)) {
            webSocketMap.remove(id);
            webSocketMap.put(id, session);
        } else {
            webSocketMap.put(id, session);
        }
        log.info("连接成功:" + id);
        webSocketMap.get(id).getBasicRemote().sendText("连接成功");
    }


    /**
     * 连接关闭
     * 调用的方法
     */
    @OnClose
    public void onClose(@PathParam("id") String id) {
        webSocketMap.remove(id);
        log.info("连接关闭:" + id);
    }

    /**
     * 接收到客户端消
     **/
    @OnMessage
    public void onMessage(@PathParam("id") String id, String message) throws IOException {
        log.info("用户消息:" + id + ",报文:" + message);
        //可以群发消息
        //消息保存到数据库、redis
        if (StringUtils.isNotBlank(message)) {
            //解析发送的报文
            JSONObject jsonObject = JSON.parseObject(message);
            //追加发送人(防止串改)
            jsonObject.put("fromUserId", id);
            //解析收件人
            String toUserId = jsonObject.getString("toUserId");
            //传送给对应toUserId用户的websocket
            if (StringUtils.isNotBlank(toUserId) && webSocketMap.containsKey(toUserId)) {
                webSocketMap.get(toUserId).getBasicRemote().sendText(message);
            } else {
                //否则不在这个服务器上,发送到mysql或者redis
                log.error("请求的id:" + toUserId + "不在该服务器上");
            }
        }
    }

    @OnError
    public void onError(@PathParam("id") String id, Throwable error) {
        log.error("发生错误:" + id + ",原因:" + error.getMessage());
        error.printStackTrace();
    }
}

4.前端页面JSX

import { ref, reactive } from "vue";

let msg = ref("");

let socket;

// 主组件
const HelloWorld = () => {
  //发起websocket连接
  const openSocket = () => {
    const socketUrl = "ws://localhost:8080/webSocket/1";
    if (socket != null) {
      socket.close();
      socket = null;
    }
    socket = new WebSocket(socketUrl);
    //打开事件
    socket.onopen = function () {
      console.log("websocket已打开");
    };
    //监听服务器消息
    socket.onmessage = function (msg) {
      //存储服务器发来的消息
      messageList.set.add(msg.data);
    };
    //监听连接关闭事件
    socket.onclose = function () {
      console.log("websocket已关闭");
    };
    //监听连接错误事件
    socket.onerror = function () {
      console.log("websocket发生了错误");
    };
  };

  // 向Socket服务器发送消息
  const sendMessage = () => {
    socket.send(msg.value);
  };

  return (
    <>
      <el-button type={"primary"} size="default" onClick={openSocket}>
        连接websocket服务
      </el-button>
      <el-divider />
      <el-input style={{ width: "100px" }} v-model={msg.value}></el-input>
      <el-divider />
      <el-button type="primary" size="default" onClick={sendMessage}>
        发送消息
      </el-button>
      <el-divider />
      <h4>--------------收到消息-----------------</h4>
      <ReceivedMessage></ReceivedMessage>
    </>
  );
};

let messageList = reactive({
  set: new Set(),
});
// JSX组件 展示服务器发来的消息
const ReceivedMessage = () => {
  return (
    <>
      {Array.from(messageList.set).map((msg,index) => {
        return <div>{index}.{msg}</div>;
      })}
    </>
  );
};
export default HelloWorld;

获取IP工具类

package com.ciih.workshop.utils.sun;

import javax.websocket.RemoteEndpoint;
import javax.websocket.Session;
import java.lang.reflect.Field;
import java.net.InetSocketAddress;

public class WebSocketUtil {

    public static InetSocketAddress getRemoteAddress(Session session) {
        if (session == null) {
            return null;
        }
        RemoteEndpoint.Async async = session.getAsyncRemote();

        //在Tomcat 8.0.x版本有效
        InetSocketAddress addr0 = (InetSocketAddress) getFieldInstance(async,"base#sos#socketWrapper#socket#sc#remoteAddress");
        System.out.println("clientIP0" + addr0);
        //在Tomcat 8.5以上版本有效
        InetSocketAddress addr = (InetSocketAddress) getFieldInstance(async, "base#socketWrapper#socket#sc#remoteAddress");
        System.out.println("clientIP1" + addr);
        return addr;
    }


    private static Object getFieldInstance(Object obj, String fieldPath) {
        String fields[] = fieldPath.split("#");
        for (String field : fields) {
            obj = getField(obj, obj.getClass(), field);
            if (obj == null) {
                return null;
            }
        }

        return obj;
    }

    private static Object getField(Object obj, Class<?> clazz, String fieldName) {
        for (; clazz != Object.class; clazz = clazz.getSuperclass()) {
            try {
                Field field;
                field = clazz.getDeclaredField(fieldName);
                field.setAccessible(true);
                return field.get(obj);
            } catch (Exception e) {
            }
        }

        return null;
    }
}