websocket介绍和使用

什么是WebSocket?

WebSocket是html5开始提供的一种全双工网络通信协议,全双工是指WebSocket连接的两端都可以接受和发送消息。

为什么需要WebSocket?
  • 如果你了解Http协议,name应该知道Http协议是无状态、无连接、单向的应用层协议。它采用了请求-响应模式,由客户端发送一个请求,由服务端返回一个响应。它有一个弊端就是服务端无法主动向客户端发起消息。这样就导致客户端想要获取服务端连续的状态变化很困难,大多是web程序将通过频繁的异步JavaScript和XML(AJAX)请求实现长轮询。轮询的效率低,非常浪费资源(因为必须不停连接,或者 HTTP 连接始终打开)。
  • WebSocket就是在这种情况下诞生的,WebSocket 连接允许客户端和服务器之间进行全双工通信,以便任一方都可以通过建立的连接将数据推送到另一端。WebSocket 只需要建立一次连接,就可以一直保持连接状态。这相比于轮询方式的不停建立连接显然效率要大大提高。


    WebSocket连接
WebSocket如何工作?

Web浏览器和服务器都必须实现 WebSockets 协议来建立和维护连接。由于 WebSockets 连接长期存在,与典型的HTTP连接不同,对服务器有重要的影响。
基于多线程或多进程的服务器无法适用于 WebSockets,因为它旨在打开连接,尽可能快地处理请求,然后关闭连接。任何实际的 WebSockets 服务器端实现都需要一个异步服务器。


Spring中WebSocket相关API介绍
  • WebSocket处理器接口,所有的WebSocket处理器都需要实现该接口。
public interface WebSocketHandler {
    // 在WebSocket连接成功后调用
    void afterConnectionEstablished(WebSocketSession var1) throws Exception;  
    // 当消息到达后调用
    void handleMessage(WebSocketSession var1, WebSocketMessage<?> var2) throws Exception;
    // 处理来自底层WebSocket消息传输的错误
    void handleTransportError(WebSocketSession var1, Throwable var2) throws Exception;
    // 在WebSocket连接关闭后调用
    void afterConnectionClosed(WebSocketSession var1, CloseStatus var2) throws Exception;
    // 
    boolean supportsPartialMessages();
}
  • 该类实现了WebSocketHandler接口,除了handlerMessage()方法,其他都是空实现。除此之外还提供了三个方法分别处理文本消息、二进制消息和pong消息,根据需要重写对应的方法即可。
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;
    }
}
  • AbstractWebSocketHandler也有实现类
  1. TextWebSocketHandler
    重写了handleBinaryMessage方法,如果收到二进制消息将会关闭webSocke连接
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
        }
    }
}
  1. BinaryWebSocketHandler
    重写了handleTextMessage方法,如果收到文本消息也将关闭WebSocket连接。
public class BinaryWebSocketHandler extends AbstractWebSocketHandler {

    @Override
    protected void handleTextMessage(WebSocketSession session, TextMessage message) {
        try {
        session.close(CloseStatus.NOT_ACCEPTABLE.withReason("Text messages not supported"));
        }
        catch (IOException ex) {
            // ignore
        }
    }
}
  1. 我们可以选择性的扩展不同的类来实现功能。
下面是WebSocket的HelloWorld

实现的功能是:浏览器接收到服务器发送的消息后向服务端发送“Marco!”,服务器收到浏览器发送的消息后向浏览器发送“Polo!”,这样不断的循环下去。

创建服务端

1.新建一个SpringBoot项目(这个不用介绍了吧)

需要注意的是需要引入WebSocket的Starter,引入了它就不用再引入spring-boot-starter-web,因为spring-boot-starter-websocket里面引入了web模块的。

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

2.创建一个WebSocket处理器类

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
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;

/**
 * @author 陈治远
 * @create 2019-08-09 17:26
 * @Description
 */
public class MarcoHandler extends AbstractWebSocketHandler {

    private static final Logger logger = LoggerFactory.getLogger(MarcoHandler.class);

    // websocket连接建立监听,实际业务中可以在连接建立的时候开启一些资源
    @Override
    public void afterConnectionEstablished(WebSocketSession session) throws Exception {
        logger.info("连接已建立...");
    }

    // websocket连接断开监听,实际业务中可以在连接建立的时候清理一些资源
    @Override
    public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {
        logger.info("连接已关闭...状态:" + status);
    }

    // 处理文本消息
    @Override
    protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
        logger.info("收到消息:" + message.getPayload());
        // 模拟延时1500ms
        java.lang.Thread.sleep(1500);
        // 发送文本消息
        session.sendMessage(new TextMessage("Polo!"));
    }
}

3.配置消息处理器类(即上一步的MarcoHandler)

import com.czy.websocket.handle.MarcoHandler;
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;
import org.springframework.web.socket.server.standard.ServerEndpointExporter;

/**
 * @author 陈治远
 * @create 2019-08-09 17:43
 * @Description
 */
@Configuration
@EnableWebSocket    // 启用WebSocket
public class WebSocketConfig implements WebSocketConfigurer {
    @Override
    public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
        // 注册MarcoHandler处理器,该处理器将映射到ws://ip:port/marco
        // 调用setAllowedOrigins处理跨域,*代表全部访问地址
        registry.addHandler(marcoHandler(), "/marco")
                .setAllowedOrigins("*");
    }

    public MarcoHandler marcoHandler() {
        return new MarcoHandler();
    }

    /**
     * 向容器中注入服务器节点,只有使用SpringBoot内嵌servlet容器的时候才需要这一步。
     * 如果使用外置servlet容器,则可以删除这个方法和该类上的@Configuration注解。
     *
     * @return
     */
    @Bean
    public ServerEndpointExporter serverEndpointExporter() {
        return new ServerEndpointExporter();
    }
}

创建客户端

4.创建一个HTML文件作为客户端

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
WebSocketDemo客户端,看控制台打印。
<script type="text/javascript">
    var url = 'ws://127.0.0.1:8080/marco';
    var socket = new WebSocket(url);

    // 处理连接建立事件
    socket.onopen = function () {
        console.log("连接已建立...")
        sayMarco();
    }

    // 处理消息
    socket.onmessage = function (event) {
        console.log("收到消息:" + event.data)
        // 模拟延时1500ms
        setTimeout(function () {
            sayMarco();
        }, 1500)
    }

    // 处理连接断开事件
    socket.onclose = function (ev) {
        console.log("连接已关闭...");
    }

    function sayMarco() {
        console.log("发送消息:Marco")
        socket.send("Marco!")
    }
</script>
</body>
</html>

5.启动服务端,并在浏览器中打开页面,查看控制台,说明客户端与服务端成功建立了WebSocket连接并互相发送消息。

Marco&Polo.gif


注意

WebSocket是一个相对比较新的规范,在web浏览器和服务器上还没有得到一致的支持。Firefox和Chorm早已完整支持WebSocket了,但是一些其他的浏览器刚刚开始支持WebSoket。
应对不支持WebSocket的场景,有了SocketJS。SockJS是一种WebSocket技术的一种模拟,表面上还是使用那些WebSocket API,但是底层是十分智能的,它会优先选择WebSocket,如果WebSocket不可以的话,它会选用其他替代方案,具体的替代方案有XHR流、XDR流、iFrame事件源、iFrame HTML文件、XHR轮询、XDR轮询、iFrame XHR轮询、iFrame XHR轮询、JSONP轮询,但是你不必了解这些方案,就和面向接口编程一样,有统一的api,但是底层实现不一样而已。

使用SockJS只需要少量的修改

1. 服务端修改
在注册处理器的时候调用WithSockJS()方法
2. 客户端修改
2.1 引入SockJS的库

百度网盘链接如下:
链接:https://pan.baidu.com/s/1O4BmqQoNNbu5otzk71q5sQ
提取码:w9qx

2.2 修改socket对象为SockJS对象
修改内容
3. ok,现在使用那些老旧版本的浏览器应该也可以实现demo功能了吧

最后,demo目录结构如下

demo目录结构

下一篇介绍STOMP消息

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容

  • 什么是WebSocket呢? WebSocket是HTML5新增的一种通信协议,目标主流的浏览器都支持这个协议,比...
    JunChow520阅读 12,095评论 1 5
  • 原文地址:http://www.ibm.com/developerworks/cn/java/j-lo-WebSo...
    敢梦敢当阅读 12,878评论 0 50
  • 一.WebSocket简单介绍 随着互联网的发展,传统的HTTP协议已经很难满足Web应用日益复杂的需求了。近年来...
    huoyl0410阅读 11,034评论 3 2
  • 从童星出道,到独挑大梁,山田孝之尝试过各种角色,有热血,有搞笑,有神经质,有温情,总之这是一个颜值上乘,演技炸裂的...
    日本优酱阅读 3,984评论 0 0
  • 方式一 config - index.js将assetsPublicPath中的/改为./router - ind...
    lesdom阅读 7,741评论 2 0