有三种实现方式:Servlet技术栈的实现方式(也就是Spring MVC);响应式的实现方式(也就是Spring WebFlux);暴露端点的方式实现。

1.SpringMVC:
a.实现WebSocketHandler消息处理类

public class TextMessageHandler extends TextWebSocketHandler {
    private final Log log = LogFactory.getLog(TextMessageHandler.class);
    @Override
    protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
        log.info(message.toString());
        session.sendMessage(new TextMessage("hello world!!!"));
        session.close();
    }    
}

b.实现WebSocketConfigurer配置类

@Configuration
@EnableWebSocket
public class TextWebsocketConfig implements WebSocketConfigurer {
    @Override
    public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
        registry.addHandler(webSocketHandler(), "/websocket").setAllowedOrigins("*");
    }
    @Bean
    public WebSocketHandler webSocketHandler() {
        return new TextMessageHandler();
    }
}

注:
a.是org.springframework.web.socket包下面的类
b.WebSocketHandler控制生命周期,WebSocketConfigurer配置和管理连接属性(比如,上面的地址与允许的域)
c.依赖:

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

2.SpringWebFlux
a.实现WebSocketHandler

public class TextMessageHandler implements WebSocketHandler{
    private final Log log = LogFactory.getLog(TextMessageHandler.class);

    @Override
    public Mono<Void> handle(WebSocketSession session) {
        Flux<WebSocketMessage>  result = session.receive().doOnNext(data->log.info(data.getPayloadAsText())).map(data->session.textMessage(data.getPayloadAsText()+"Hello world!!!"));
        return session.send(result);
    }
}

b.配置mapping

@Configuration
public class TextWebsocketConfig  {
    @Bean 
    public HandlerMapping handlerMapping() {
        Map<String, WebSocketHandler> map = new HashMap<>();
        map.put("/websocket", new TextMessageHandler());
        int order = -1; // before annotated controllers
        SimpleUrlHandlerMapping mapping = new SimpleUrlHandlerMapping();
        mapping.setUrlMap(map);
        mapping.setOrder(order);
        return mapping;
    }    
    @Bean
    public WebSocketHandlerAdapter handlerAdapter() {
        return new WebSocketHandlerAdapter();
    }
}

a.是org.springframework.web.reactive.socket包下面的类
b.依赖:

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

3.暴露端点的方式
a. 配置ServerEndpointExporter

@Configuration
public class WebsocketConfig {

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

b. 编写ServerEndpoint行为

@ServerEndpoint(value = "/websocket")
@Component
public class SocketHandler {

    private  volatile static int onlineCount = 0;
    private static CopyOnWriteArraySet<SocketHandler> webSocketSet = new CopyOnWriteArraySet<SocketHandler>();
    private Session session;

    @OnOpen
    public void onOpen(Session session){
        this.session = session;
        System.out.println(session.getId()+"---=====");
        webSocketSet.add(this);     //加入set中
        addOnlineCount();           //在线数加1
        System.out.println("有新连接加入!当前在线人数为" + getOnlineCount());
    }

    /**
     * 连接关闭调用的方法
     */
    @OnClose
    public void onClose() {
        webSocketSet.remove(this);  //从set中删除
        subOnlineCount();           //在线数减1
        System.out.println("有一连接关闭!当前在线人数为" + getOnlineCount());
    }

    /**
     * 收到客户端消息后调用的方法
     *
     * @param message 客户端发送过来的消息
     * @throws IOException */
    @OnMessage
    public void onMessage(String message, Session session) throws IOException {
        JsonMapper mapper=new JsonMapper();
        MessageContent messageContent=mapper.readValue(message, MessageContent.class);
        webSocketSet.parallelStream().filter(socketHandler->socketHandler.getSession().getId().equals(messageContent.getUserId())).forEach((socketHandler)->{
            try {
                socketHandler.getSession().getBasicRemote().sendText(messageContent.getMessage());
            } catch (IOException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        });
        System.out.println("来自客户端的消息:" + message);
        System.out.println(session.getId()+"------");
        session.getBasicRemote().sendText("hhhhhh");
    }

    /**
     * 发生错误时调用***/
    @OnError
    public void onError(Session session, Throwable error) {
        System.out.println("发生错误");
        error.printStackTrace();
    }


    public void sendMessage(String message) throws IOException {
        this.session.getBasicRemote().sendText(message);
        //this.session.getAsyncRemote().sendText(message);
    }


    /**
     * 群发自定义消息
     * */
    public static void sendInfo(String message) throws IOException {
        for (SocketHandler item : webSocketSet) {
            try {
                item.sendMessage(message);
            } catch (IOException e) {
                continue;
            }
        }
    }

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

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

    public static synchronized void subOnlineCount() {
        SocketHandler.onlineCount--;
    }
    public Session getSession() {
        return session;
    }
}

注:
a.很显然你只要会第一种用法就ok了,第一种已经是一套成熟的体系了。
b.Spring MVC是依赖servlet容器的,而Spring WebFlux不依赖容器默认使用netty。在绝大多数情况下他们的用法是区别不大的,但是我依然主张你使用Spring MVC,因为它更加规范也更加健壮。
c.本质上来说websocket是一个基础的套接字,他没有http本身那么多条条框框,这使得它的实现更加随意(就是处理套接字连接)。
d.有意见或者建议请评论区留言;转载请标明作者与来源。

标签: springboot, spring mvc, websocket

添加新评论