WebSocket的故事(六)—— Springboot中,实现更灵活的WebSocket

概述

WebSocket的故事系列计划分五大篇六章,旨在由浅入深的介绍WebSocket以及在Springboot中如何快速构建和使用WebSocket提供的能力。本系列计划包含以下几篇文章:javascript

第一篇,什么是WebSocket以及它的用途
第二篇,Spring中如何利用STOMP快速构建WebSocket广播式消息模式
第三篇,Springboot中,如何利用WebSocket和STOMP快速构建点对点的消息模式(1)
第四篇,Springboot中,如何利用WebSocket和STOMP快速构建点对点的消息模式(2)
第五篇,Springboot中,实现网页聊天室之自定义WebSocket消息代理
第六篇,Springboot中,实现更灵活的WebSockethtml

本篇的主线

本篇是这个系列的最后一篇,将介绍另外一种实现WebSocket的方式。仍然会以一个简单聊天室为例子进行讲述。至此咱们也能够根据具体状况,选择不一样的实现方式。java

本篇适合的读者

想了解如何在Springboot上自定义实现更为复杂的WebSocket产品逻辑的各路有志青年。git

使用Tomcat提供的WebSocket支持

早在Java EE 7时,就发布了JSR356规范。Tomcat7.0.47开始,也支持了统一的WebSocket接口。在使用Springboot时,也能够轻松的使用Tomcat提供的这些API。今天咱们就来体验一把Tomcat实现的WebSocketgithub

1. 引入依赖

<dependency>
     <groupId>org.springframework.boot</groupId>
     <artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
复制代码

Springboot内置了tomcat,咱们直接引入spring的这个高级组件便可。顺便多说一句,Springboot的高级组件会自动引用基础的组件,像spring-boot-starter-websocket就引入了spring-boot-starter-webspring-boot-starter,因此不要重复引入。web

2. 使用@ServerEndpoint建立WebSocket Endpoint

首先要注入ServerEndpointExporter,这个Bean会自动注册使用了@ServerEndpoint注解声明的WebSocket Endpoint。spring

package com.draw.wsdraw.websocket;

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

@Configuration
public class WebSocketConfig {
    @Bean
    public ServerEndpointExporter serverEndpointExporter() {
        return new ServerEndpointExporter();
    }
}
复制代码

而后,咱们动手实现WebSocket服务的实现类,这里是WebSocketServer,注意别忘了用@ServerEndpint@Component声明下。虽然@Component默认是单例模式的,但Springboot仍是会为每一个WebSocket链接初始化一个Bean,因此能够用一个静态Map保存起来。换句话说,每当有一个用户向服务器发起链接时,都会建立一个WebSocketServer对象,将此对象按roomId保存在HashMap中,方便后续使用。tomcat

建立ServerEndpoint时,须要对应实现其所需的几个功能性方法:OnOpen、OnMessage、OnClose、OnError服务器

  • @OnOpen:客户端向服务端发起创建链接时,服务端调用,可传入的参数为Session(WebSocket的Session)和EndpointConfig。另外,还能够加入带@PathParam注解的参数。这里咱们注解的参数是roomId,即在创建链接时,携带的请求地址上的参数,与咱们上一篇中介绍的{INFO}是同样的做用。
  • @OnMessage:客户端消息到来时调用,包含会话Session,根据消息的形式,若是是文本消息,传入String类型参数或者Reader,若是是二进制消息,传入byte[]类型参数或者InputStream。
  • @OnClose:当断开链接,关闭WebSocket时调用。
  • @OnError:当发生错误时调用,传入异常Session和错误信息。

重写上述方法,便可实现WebSocket的服务端业务逻辑。websocket

@ServerEndpoint("/webSocket/{roomId}")
@Component
public class WebSocketServer {
    private static ConcurrentHashMap<String, List<WebSocketServer>> webSocketMap =
            new ConcurrentHashMap<>(3);

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

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

    /** * 链接创建成功调用的方法 */
    @OnOpen
    public void onOpen(Session session, EndpointConfig config, @PathParam("roomId") String roomId) {
        if (roomId == null || roomId.isEmpty()) return;
        this.session = session;
        this.roomId = roomId;
        addSocketServer2Map(this);
        try {
            sendMessage("链接成功", true);
        } catch (IOException e) {
        }
    }

    /** * 链接关闭调用的方法 */
    @OnClose
    public void onClose() {
        List<WebSocketServer> wssList = webSocketMap.get(roomId);
        if (wssList != null) {
            for (WebSocketServer item : wssList) {
                if (item.session.getId().equals(session.getId())) {
                    wssList.remove(item);
                    if (wssList.isEmpty()) {
                        webSocketMap.remove(roomId);
                    }
                    break;
                }
            }
        }
    }
    
    /** * 收到客户端消息后调用的方法 */
    @OnMessage
    public void onMessage(String message, Session session) {
        //群发消息
        String msg = filterMessage(message);
        if (msg != null) {
            sendInfo(msg, roomId, session);
        }
    }

    /** * 发生错误时,调用的方法 */
    @OnError
    public void onError(Session session, Throwable error) {
    }

复制代码

这样,服务端的代码就实现完了,这里仅贴出来部分源码,文后会给出项目源码地址。

3. 实现客户端页面

<script type="text/javascript"> var ws; function setConnected(connected){ document.getElementById('connect').disabled = connected; document.getElementById('disconnect').disabled = !connected; document.getElementById('conversationDiv').style.visibility = connected ? 'visible' : 'hidden'; $("#response").html(); } function connect(){ var roomId = $('#roomId').val(); ws = new WebSocket('ws://localhost:8080/webSocket/' + roomId); ws.onopen = WSonOpen; ws.onmessage = WSonMessage; ws.onclose = WSonClose; ws.onerror = WSonError; } function WSonOpen() { var message = { name:'Server', chatContent:'成功链接' } setConnected(true); showResponse(message) }; function WSonMessage(event) { var message = { name:'Server', chatContent:event.data } showResponse(message) }; function WSonClose() { var message = { name:'Server', chatContent:'已断开' } showResponse(message) }; function WSonError() { var message = { name:'Server', chatContent:'链接错误!' } showResponse(message) }; function disconnect(){ ws.close() setConnected(false); console.log("Disconnected"); } function sendMessage(){ var chatContent = $("#chatContent").val(); var roomId = $('#roomId').val(); ws.send(JSON.stringify({'roomId':roomId,'chatContent':chatContent})) } function showResponse(message){ var response = $("#response").val(); $("#response").val(response+message.name+': '+message.chatContent+'\n'); } </script>
复制代码

客户端页面实现了简单的链接、断开和消息发送功能。这部分就不详细介绍了。

4. 演示截图

源码地址

本篇的源码地址:

另外一种WebSocket的实现方式

总结

本篇直接使用了Tomcat提供的WebSocket,也是一种相对灵活的实现方式,只须要按照上述步骤来实现便可。集中精力编写业务逻辑代码。

整个一个系列下来,咱们介绍了几种实现WebSocket的方式,有的集成度高,有些相对灵活。你们能够按实际业务需求来选取合适的方式。至此,这个系列就结束了。感谢你们阅读。

小铭出品,必属精品

欢迎关注xNPE技术论坛,更多原创干货每日推送。

相关文章
相关标签/搜索