SpringBoot + WebSocket实现简单消息推送

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 接口。它额外增加了以下几个方法:

扫描二维码关注公众号,回复: 34958 查看本文章
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,就会看到浏览器和服务器端不停地发送消息:
这里写图片描述

这里写图片描述

猜你喜欢

转载自blog.csdn.net/qq_37142346/article/details/80002298