添加依赖

  1. <dependency>
  2. <groupId>org.springframework.boot</groupId>
  3. <artifactId>spring-boot-starter-websocket</artifactId>
  4. </dependency>
  5. <dependency>
  6. <groupId>org.springframework.boot</groupId>
  7. <artifactId>spring-boot-starter-thymeleaf</artifactId>
  8. </dependency>
  9. <dependency>
  10. <groupId>cn.hutool</groupId>
  11. <artifactId>hutool-log</artifactId>
  12. <version>4.1.17</version>
  13. </dependency>

配置 websocket

创建一个 WebSocketConfig 以启用 WebSocket 的支持

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

ws 服务器配置

以下, 为 websocket 添加一个入口地址, 并实现 @OnOpen @onClose @onMessage 已完成对 ws 的监听

@ServerEndpoint("/websocket/{sid}")
@Component
public class WebSocketServer {
    static Log log = LogFactory.get(WebSocketServer.class);
    // 静态变量,用来记录当前在线连接数。应该把它设计成线程安全的。
    private static int onlineCount = 0;
    // concurrent包的线程安全Set,用来存放每个客户端对应的MyWebSocket对象。
    private static CopyOnWriteArraySet<WebSocketServer> webSocketSet = new CopyOnWriteArraySet<WebSocketServer>();

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

    // 接收sid
    private String sid = "";

    /**
     * 连接建立成功调用的方法
     * */
    @OnOpen
    public void onOpen(Session session,@PathParam("sid") String sid) {
        this.session = session;
        webSocketSet.add(this);     //加入set中
        addOnlineCount();           //在线数加1
        log.info("有新窗口开始监听:" + sid + ",当前在线人数为" + getOnlineCount());
        this.sid = sid;
        // 群发消息
        for (WebSocketServer item : webSocketSet) {
            try {
                item.sendMessage("有新用户加入: " + sid);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        try {
            sendMessage("连接成功");
        } catch (IOException e) {
            log.error("websocket IO异常");
        }
    }

    /**
     * 连接关闭调用的方法
     */
    @OnClose
    public void onClose() {
        webSocketSet.remove(this);  // 从set中删除
        subOnlineCount();           // 在线数减1
        log.info("有一连接关闭!当前在线人数为" + getOnlineCount());
        // 群发消息
        for (WebSocketServer item : webSocketSet) {
            try {
                item.sendMessage(sid + "离开群聊");
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * 收到客户端消息后调用的方法
     *
     * @param message 客户端发送过来的消息
     * */
    @OnMessage
    public void onMessage(String message, Session session) {
        log.info("收到来自窗口"+sid+"的信息:"+message);
        // 群发消息
        for (WebSocketServer item : webSocketSet) {
            try {
                item.sendMessage(sid + "说: " + message);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    /**
     *
     * @param session
     * @param error
     */
    @OnError
    public void onError(Session session, Throwable error) {
        log.error("发生错误");
        error.printStackTrace();
    }

    /**
     * 实现服务器主动推送
     */
    public void sendMessage(String message) throws IOException {
        this.session.getBasicRemote().sendText(message);
    }

    /**
     * 群发/单发自定义消息
     * */
    public static void send(String message, @PathParam("from") String from, @PathParam("to") String to) throws IOException {
        log.info(from + "发送给" + to + ", 内容: " + message);
        for (WebSocketServer item : webSocketSet) {
            try {
                //这里可以设定只推送给这个sid的,为null则全部推送
                if(to==null) {
                    item.sendMessage(message);
                }else if(item.sid.equals(to)){
                    item.sendMessage(from + "对你说: " + message);
                }
            } catch (IOException e) {
                continue;
            }
        }
    }

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

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

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

在控制器中实现推送方法

@Controller
@RequestMapping("/checkcenter")
public class CheckCenterController {
    // 页面请求
    @GetMapping("/socket/{cid}")
    public String index(ModelMap map, @PathVariable String cid) {
        map.addAttribute("cid", cid);
        return "socket";
    }

    // 单发
    @ResponseBody
    @RequestMapping("/socket/push/{from}/{to}")
    public Result pushToOne(@PathVariable String from, @PathVariable String to, String message) {
        System.out.println("one");
        try {
            WebSocketServer.send(message, from, to);
        } catch (IOException e) {
            e.printStackTrace();
            return Result.error(from + " : " + to + "#" + e.getMessage());
        }
        return Result.ok("发送成功");
    }

    // 群发
    @ResponseBody
    @RequestMapping("/socket/push/{from}")
    public Result pushToAll(@PathVariable String from, String message) {
        System.out.println("all");
        try {
            WebSocketServer.send(message, from, null);
        } catch (IOException e) {
            e.printStackTrace();
            return Result.error(from + " : " + null + "#" + e.getMessage());
        }
        return Result.ok("发送成功");
    }
}

视图的实现

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title th:text="${cid}"></title>
  <script src="//cdn.bootcss.com/vue/2.5.21/vue.min.js"></script>
  <script src="//cdn.bootcss.com/axios/0.19.0-beta.1/axios.min.js"></script>
</head>
<body>

<div id="app">
  <div>
    <h1 v-if="cid">欢迎: {{cid}}</h1>
    <h1 v-else>您的浏览器不支持WebSocket</h1>
  </div>
  <ul>
    <li v-for="msg in messages">{{msg}}</li>
  </ul>
  <div>
    <p>消息内容</p>
    <textarea v-model="info" id="" cols="30" rows="10"></textarea>
    <p>
      <button @click="sendAll">群发消息</button>
    </p>
    <p>
      <span>给</span>
      <input type="text" v-model="toCid"/>
      <button @click="sendOne">发送消息</button>
    </p>
  </div>
</div>

<script>
  const app = new Vue({
    el: '#app',
    data () {
      return {
        cid: [[${cid}]], // 获取控制器传来的值, 当前用户的cid
        messages: [], // 要发送的消息
        info: '',
        toCid: '', // 接收用户的cid
      }
    },
    created () {
      if(typeof(WebSocket) === "undefined") {
        console.log("您的浏览器不支持WebSocket");
      } else {
        console.log("您的浏览器支持WebSocket");

        let socket = new WebSocket(`ws://localhost:8080/websocket/${this.cid}`)

        socket.onopen = function() {
          console.log("Socket 已打开")
        };

        socket.onmessage = msg => {
          console.log(msg.data)
          this.messages.push(msg.data)
        };

        socket.onclose = () => {
          console.log("Socket已关闭")
        };

        socket.onerror = () => {
          alert("Socket发生了错误")
        }
      }
    },
    methods: {
      sendAll () {
        axios
          .get(`/checkcenter/socket/push/${this.cid}?message=${this.info}`)
          .then((response) => {
            this.server = response.data
            this.info = ""
          })
      },
      sendOne () {
        axios
          .get(`/checkcenter/socket/push/${this.cid}/${this.toCid}?message=${this.info}`)
          .then((response) => {
            this.server = response.data
            this.info = ""
          })
      }
    }
  })
</script>
</body>
</html>

浏览器分别访问:

  • http://localhost:8080/checkcenter/socket/1
  • http://localhost:8080/checkcenter/socket/2
  • http://localhost:8080/checkcenter/socket/3

实现效果:
001.png

参考资料