【Jetty 基础知识】WebSocket 的用法总结

文章首发于公众号【大数据学徒】,感兴趣请搜索 dashujuxuetu 或者文末扫码关注。html

由于 Apache Zeppelin 中使用了 Jetty,因此我须要补充一下 Jetty 的相关背景,以便阅读 Zeppelin 的相关代码,其中 WebSocket 是很重要的内容,所以经过阅读学习 Jetty 的 WebSocket API 文档 总结出了本文的内容。java


内容提要:web

  1. WebSocket 简介
  2. Jetty 的 WebSocket 事件
  3. Jetty 的 WebSocket 注解
  4. WebSocket 服务端 API
  5. WebSocket 客户端 API

1. WebSocket 简介

WebSocket 是一种应用层传输协议,能够在单个 TCP 链接上进行全双工通讯,容许服务端主动向客户端推送数据,协议开销小,协议头是 ws(明文) 或 wss(加密),端口和 HTTP 同样使用 80 或 443,经过 HTTP/1.1 的 Upgrade 头部来创建链接。更多 WebSocket 的介绍请参考 WebSocket 维基百科apache

一个 WebSocket 链接有四种基本的状态:设计模式

  • CONNECTING:正在经过 HTTP Upgrade 头部创建链接;
  • OPEN:链接已创建,能够进行消息的读写;
  • CLOSING:关闭链接的握手已经开始;
  • CLOSED:链接以关闭

2. Jetty 的 WebSocket 事件

Jetty 提供了比 Java 原生 WebSocket 更强大的 API,包含了一些服务端和客户端通用的 API,是基于 WebSocket 消息的事件驱动型 API。每一个 WebSocket 链接会接收到四种事件:api

  • Connect 事件:表示链接创建成功,能够从中得到 org.eclipse.jetty.websocket.api.Session 对象,这是一个重要的对象,通常经过它来和对端通讯。
  • Close 事件:表示链接已经关闭
  • Error 事件:表示发生了错误
  • Message 事件:表示接收到了一条完整的消息准备由 WebSocket 来处理,消息能够是文本的(UTF-8),也能够是二进制的。

3. Jetty 的 WebSocket 注解

WebSocket 的最基本用法是对一个 POJO 类使用 WebSocket 注解,好比:websocket

import org.eclipse.jetty.websocket.api.Session;
import org.eclipse.jetty.websocket.api.annotations.OnWebSocketMessage;
import org.eclipse.jetty.websocket.api.annotations.WebSocket;

// WebSocket 注解
@WebSocket(maxTextMessageSize = 64 * 1024)
public class AnnotatedEchoSocket {
  	// onWebSocketMessage注解,用于标记收到消息时的处理函数
    @OnWebSocketMessage
    public void onText(Session session, String message) {
        if (session.isOpen())
        {
            System.out.printf("Echoing back message [%s]%n", message);
            // 将收到的消息原样返回
            session.getRemote().sendString(message, null);
        }
    }
}
复制代码

可用的注解有如下几种:session

注解 做用
@WebSocket 必需的类级别注解,标记 POJO 类为 WebSocket
@OnWebSocketConnect 可选的方法级别注解,标记处理 Connect 事件的函数
@OnWebSocketClose 可选的方法级别注解,标记处理 Close 事件的函数
@OnWebSocketMessage 可选的方法级别注解,标记处理 Message 事件的函数
@OnWebSocketError 可选的方法级别注解,标记处理 Error 事件的函数
@OnWebSocketFrame 可选的方法级别注解,标记处理 Frame 事件的函数

其中 @WebSocket 注解的必须是是公有的非抽象类,其它注解所注解的必须是公有的非抽象的、且返回值为空的方法eclipse

Jetty 也提供了WebSocketListener 接口,有上面的 5 个方法,以及 WebSocketAdapter类,实现了这个接口,不过都是空实现,但提供了如下很是实用的方法来检查 Session 的状态。socket

4. WebSocket 服务端 API

服务端有两个重要的抽象类和接口,WebSocketServletWebSocketCreator,使用 WebSocketServlet 的例子:

@WebServlet(name = "MyEcho WebSocket Servlet", urlPatterns = {"/echo"})
public class MyEchoServlet extends WebSocketServlet {
    @Override
    public void configure(WebSocketServletFactory factory) {
        // set a 10 second timeout
        factory.getPolicy().setIdleTimeout(10000);

        // register MyEchoSocket as the WebSocket to create on Upgrade
        factory.register(MyEchoSocket.class);
    }
}
复制代码

类上有一个注解,代表这个 Servlet 会在 /echo 这个 URI 上收到 HTTP Upgrade 请求时建立,这个类有一个 configure() 方法,参数是 WebSocketServletFactory 类型,在这个方法内须要作的事,除了设置一些参数之外,须要使用 register() 方法注册用来处理请求的 Servlet,在这个例子中注册了 MyEchoServlet 自身,也能够不这么作,另一种作法是,调用 WebSocketServletFactorysetCreator() 方法,不直接注册 Servlet,而是让设置的 WebSocketCreator 来建立合适的 Servlet,看一个 WebSocketCreator 的例子:

public class MyAdvancedEchoCreator implements WebSocketCreator {
    private MyBinaryEchoSocket binaryEcho;
    private MyEchoSocket textEcho;

    public MyAdvancedEchoCreator() {
        // 建立好能够复用的 Socket
        this.binaryEcho = new MyBinaryEchoSocket();
        this.textEcho = new MyEchoSocket();
    }

    @Override
    public Object createWebSocket(ServletUpgradeRequest req, ServletUpgradeResponse resp) {
        for (String subprotocol : req.getSubProtocols()) {
            if ("binary".equals(subprotocol)) {
                resp.setAcceptedSubProtocol(subprotocol);
                return binaryEcho;
            }
            if ("text".equals(subprotocol)) {
                resp.setAcceptedSubProtocol(subprotocol);
                return textEcho;
            }
        }
        // 其它状况就返回 null
        return null;
    }
}
复制代码

WebSocketCreator 接口须要实现 createWebSocket(ServletUpgradeRequest, ServletUpgradeResponse) 方法,这里 MyAdvancedEchoCreator 实现类,根据子协议是文本类型仍是二进制类型生成了不一样的 WebSocket 来处理请求。

看起来这里使用了抽象工厂模式,但我对设计模式所知有限,就不展开讲了。

5. WebSocket 客户端 API

客户端须要的依赖是:

<dependency>
  <groupId>org.eclipse.jetty.websocket</groupId>
  <artifactId>websocket-client</artifactId>
  <version>${project.version}</version>
</dependency>
复制代码

直接看一个客户端例子,很清晰:

public class SimpleEchoClient {
    public static void main(String[] args) {
      
        // 指定服务端 URI
        String destUri = "ws://echo.websocket.org";
        if (args.length > 0) {
            destUri = args[0];
        }
				// 建立 WebSocketClient 对象
        WebSocketClient client = new WebSocketClient();
        SimpleEchoSocket socket = new SimpleEchoSocket();
        try {
            client.start();

            URI echoUri = new URI(destUri);
            ClientUpgradeRequest request = new ClientUpgradeRequest();
          	// 发送 HTTP Upgrade 请求,创建链接
            client.connect(socket, echoUri, request);
            System.out.printf("Connecting to : %s%n", echoUri);

            // 等待关闭
            socket.awaitClose(5, TimeUnit.SECONDS);
        }
        catch (Throwable t) {...}
        finally {...}
    }
}
复制代码

欢迎交流讨论,吐槽建议,分享收藏。

勤学似春起之苗,不见其增,日有所长
辍学如磨刀之石,不见其损,日有所亏
关注【大数据学徒】,用技术干货助你日有所长

大数据学徒
相关文章
相关标签/搜索