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消息

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 195,898评论 5 462
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 82,401评论 2 373
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 143,058评论 0 325
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,539评论 1 267
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,382评论 5 358
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,319评论 1 273
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,706评论 3 386
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,370评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,664评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,715评论 2 312
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,476评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,326评论 3 313
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,730评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,003评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,275评论 1 251
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,683评论 2 342
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,877评论 2 335

推荐阅读更多精彩内容

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