欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

SpringBoot + WebSocket实现简单消息推送

程序员文章站 2022-05-21 09:33:45
...

WebSocket是一种双工通信的网络协议,所谓双工就是浏览器可以给服务器发送消息,服务器也可以向浏览器发送消息,那么,有了HTTP协议,为什么还要使用它呢?假设我们需要每天定时需要服务器给客户端推送消息,那么HTTP协议就做不到主动的发送信息给客户端,而WebSocket则可以实现这一功能,它会自动监控浏览器并且做出自动回复。

WebSocket的特点:
(1)建立在 TCP 协议之上,服务器端的实现比较容易。

(2)与 HTTP 协议有着良好的兼容性。默认端口也是80和443,并且握手阶段采用 HTTP 协议,因此握手时不容易屏蔽,能通过各种 HTTP 代理服务器。

(3)数据格式比较轻量,性能开销小,通信高效。

(4)可以发送文本,也可以发送二进制数据。

(5)没有同源限制,客户端可以与任意服务器通信。

(6)协议标识符是ws(如果加密,则为wss),服务器网址就是 URL。

下面来通过一个简单案例来更好地认识WebSocket的原理以及使用:
下面的案例浏览器会发送消息‘hello’到服务器,服务器收到消息后会回复server,打印在控制台。

首先建立maven项目,导入相应的依赖:

<!-- maven项目packaging改为war类型时,必须要加这个插件 -->
    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-war-plugin</artifactId>
                <version>2.3</version>
                <configuration>
                    <failOnMissingWebXml>false</failOnMissingWebXml>
                </configuration>
            </plugin>
        </plugins>
    </build>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <!-- 测试springboot的依赖 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

        <!-- 测试时加入这两个依赖,当修改代码的时候就不用每次修改后重启服务器了 -->
        <!-- https://mvnrepository.com/artifact/org.springframework/springloaded -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>springloaded</artifactId>
        </dependency>
        <!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-devtools -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
        </dependency>
        <!-- springboot 集成jsp必须要借助这两个依赖 -->
        <!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-tomcat -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-tomcat</artifactId>
        </dependency>
        <!-- https://mvnrepository.com/artifact/org.apache.tomcat.embed/tomcat-embed-jasper -->
        <dependency>
            <groupId>org.apache.tomcat.embed</groupId>
            <artifactId>tomcat-embed-jasper</artifactId>
        </dependency>
        <!-- WebSocket相关依赖 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-websocket</artifactId>
        </dependency>
    </dependencies>

实现WebSocket需要实现WebSocketHandler这个接口,源码如下:

public interface WebSocketHandler {
    void afterConnectionEstablished(WebSocketSession session) throws Exception;
    void handleMessage(WebSocketSession session, WebSocketMessage<?> message) throws Exception;
    void handleTransportError(WebSocketSession session, Throwable exception) throws Exception;
    void afterConnectionClosed(WebSocketSession session, CloseStatus closeStatus) throws Exception;
    boolean supportsPartialMessages();

}

如果需要接收二进制消息也可以扩展抽象类AbstractWebSocketHandler,这个类实现了WebSocketHandler 接口。它额外增加了以下几个方法:

public abstract class AbstractWebSocketHandler implements WebSocketHandler {

    @Override
    public void afterConnectionEstablished(WebSocketSession session) throws Exception {
    }

    @Override
    public void handleMessage(WebSocketSession session, WebSocketMessage<?> message) throws Exception {
        if (message instanceof TextMessage) {
            handleTextMessage(session, (TextMessage) message);
        }
        else if (message instanceof BinaryMessage) {
            handleBinaryMessage(session, (BinaryMessage) message);
        }
        else if (message instanceof PongMessage) {
            handlePongMessage(session, (PongMessage) message);
        }
        else {
            throw new IllegalStateException("Unexpected WebSocket message type: " + message);
        }
    }

    protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
    }

    protected void handleBinaryMessage(WebSocketSession session, BinaryMessage message) throws Exception {
    }

    protected void handlePongMessage(WebSocketSession session, PongMessage message) throws Exception {
    }

    @Override
    public void handleTransportError(WebSocketSession session, Throwable exception) throws Exception {
    }

    @Override
    public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {
    }

    @Override
    public boolean supportsPartialMessages() {
        return false;
    }

}

handleTextMessage方法对文本消息进行处理,handleBinaryMessage接收二进制消息进行处理。也许有时候不需要接收二进制消息。那需要扩展TextWebSocketHandler类,它继承自AbstractWebSocketHandler类,在这个类中的handleBinaryMessage方法接收到二进制消息就会关闭通信。

public class TextWebSocketHandler extends AbstractWebSocketHandler {

    @Override
    protected void handleBinaryMessage(WebSocketSession session, BinaryMessage message) {
        try {
            session.close(CloseStatus.NOT_ACCEPTABLE.withReason("Binary messages not supported"));
        }
        catch (IOException ex) {
            // ignore
        }
    }

}

下面我们扩展AbstractWebSocketHandler类来实现WebSocket的使用:

package cn.shinelon.config;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.springframework.web.socket.CloseStatus;
import org.springframework.web.socket.TextMessage;
import org.springframework.web.socket.WebSocketSession;
import org.springframework.web.socket.handler.AbstractWebSocketHandler;
@Component
public class SocketHandler extends AbstractWebSocketHandler{
    public Logger log=LoggerFactory.getLogger(getClass());
    /**
     * 建立连接之后
     */
    @Override
    public void afterConnectionEstablished(WebSocketSession session) throws Exception {
        log.info("Connection established");
    }
    /**
     * 处理文本消息
     */
    @Override
    protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
            log.info("Received message: "+message.getPayload());
            //模拟延时
            Thread.sleep(2000);
            //发送文本消息
            session.sendMessage(new TextMessage("Server"));
    }
    /**
     * 连接关闭之后
     */
    @Override
    public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {
        log.info("Connection closed Status:"+status);
    }
}

另外还需要对WebSocket进行注册以及注入到Spring容器中:

package cn.shinelon.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.config.annotation.EnableWebSocket;
import org.springframework.web.socket.config.annotation.WebSocketConfigurer;
import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry;
@Configuration
@EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer{
    @Bean
    public SocketHandler socketHander(){
        return new SocketHandler();
    }
    public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
        registry.addHandler(socketHander(), "/hello").withSockJS();
    }
}

下面是jsp页面代码,主要是里面的JS代码:

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
<script type="text/javascript" src="http://cdn.jsdelivr.net/sockjs/0.3.4/sockjs.min.js"></script> 
<script type="text/javascript">
//var url='ws://'+window.location.host+'/webSocket/hello';
var url='hello';
//var socket=new WebSocket(url);
var socket=new SockJS(url);
socket.onopen=function(){
    console.log('Opening');
    sayHello();
};
//处理消息
socket.onmessage=function(e){
    console.log('Received message:',e.data);
    setTimeout(function(){sayHello()},2000);
};
//处理关闭事件
socket.onclose=function(){
    console.log('Closing');
};
function sayHello(){
    console.log('Sending hello');
    //发送消息
    socket.send('Hello');
}
</script>
</head>
<body>
    this is springboot
</body>
</html>

解释一下上面的代码:现在的大多浏览器都支持WebSocket,比如Chrom,FireFox等等主流的浏览器,但是也有一些老版本的浏览器不支持,如果你的浏览器支持WebSocket,可以使用new WebSocket(url)来创建,如果不支持,就需要使用new SockJS(url),它走的是HTTP协议,不过需要导入库(http://cdn.jsdelivr.net/sockjs/0.3.4/sockjs.min.js )。也就是说如果你的浏览器不支持WebSocket,你需要将:

var url='ws://'+window.location.host+'/webSocket/hello';
var socket=new WebSocket(url);

改为:

var url='hello';
var socket=new SockJS(url);

下面是SpringBoot的资源文件的配置:
application.properties:

spring.mvc.view.prefix=WEB-INF/jsp/
spring.mvc.view.suffix=.jsp

最后,编写一个简单的控制类来启动项目看看效果:

@SpringBootApplication
@Controller
@ComponentScan("cn.shinelon")
public class IndexController {

    @RequestMapping("/ws")
    public String ws() {
        return "index";
    }
}   

然后浏览器输入localhost:8080/ws,就会看到浏览器和服务器端不停地发送消息:
SpringBoot + WebSocket实现简单消息推送

SpringBoot + WebSocket实现简单消息推送