Java 中的 WebSocket

Java JavaScript WebSocket 大约 5239 字

介绍

握手阶段基于HTTP。握手成功后升级为TCP

  • HTTP/1.1 101:表示使用HTTP1.1版本且HTTP状态码为101
  • Connection: Upgrade:表示链接升级了。
  • Upgrade: websocket:表示升级为websocket了(即长链接)。
  • Sec-WebSocket-Version:告诉服务端使用的WebSocket Draft协议版本号。
  • Sec-WebSocket-Key:浏览器随机生成的一个Base64字符串密钥。
  • Sec-WebSocket-Accept:服务端返回的验证密钥。

请求头

GET ws://localhost:10000/test/ws/testusername HTTP/1.1
Host: localhost:10000
Connection: Upgrade
Pragma: no-cache
Cache-Control: no-cache
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.128 Safari/537.36 Edg/89.0.774.77
Upgrade: websocket
Origin: http://localhost:10000
Sec-WebSocket-Version: 13
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6
Cookie: JSESSIONID=B0C9AA2839BED68667B14332FC1459DF
Sec-WebSocket-Key: BDf7DVkhHpGGIZYhvxj20A==
Sec-WebSocket-Extensions: permessage-deflate; client_max_window_bits

响应头

HTTP/1.1 101
Upgrade: websocket
Connection: upgrade
Sec-WebSocket-Accept: MKTv1hNVb4gVXQ8l/rbhsbjIW0U=
Sec-WebSocket-Extensions: permessage-deflate;client_max_window_bits=15
Date: Tue, 20 Apr 2021 13:16:16 GMT

特点

  1. 长链接。
  2. 不受浏览器跨域限制。
  3. 若开启了Tomcat服务,WebSocket端口与Tomcat端口一致。

示例代码

Java

添加依赖

<dependency>
    <groupId>javax.websocket</groupId>
    <artifactId>javax.websocket-api</artifactId>
    <version>1.1</version>
</dependency>

WebSocket实例

import javax.websocket.*;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;

@ServerEndpoint(value = "/test/ws/{username}")
public class WebSocketServer {

    // 记录当前连接数
    private static final AtomicLong COUNT = new AtomicLong(0);

    // 保存用户名与 WebSocketSession 间的映射,有助于服务端主动推送
    private static final ConcurrentHashMap<String, Session> map = new ConcurrentHashMap<>(1 << 10);

    @OnOpen
    public void onOpen(@PathParam("username") String username, Session session) {
        map.putIfAbsent(username, session);
        COUNT.incrementAndGet();
        // 最大超时时间 60 秒
        session.setMaxIdleTimeout(TimeUnit.MINUTES.toMillis(5));
        session.setMaxBinaryMessageBufferSize(8192 * 1024); // 8KB
        session.setMaxTextMessageBufferSize(8192 * 1024); // 字符数
        session.getAsyncRemote().sendText("123");
    }

    @OnClose
    public void onClose(@PathParam("username") String username, Session session, CloseReason closeReason) {
        map.remove(username, session);
        System.out.printf("onClose username#%s Session#%s closed because of %s%n", username,session.toString(), closeReason);
    }

    @OnError
    public void onError(Session session, Throwable t) {
        System.out.println("onError#" + session);
        t.printStackTrace();
    }

    @OnMessage
    public String onMessage(@PathParam("username") String username, String message, Session session) {
        System.out.println("onMessage username#" + username + "#" + message);
        return message + "#" + ThreadLocalRandom.current().nextInt(1 << 20);
    }
}

JavaScript

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>WebSocket-Test</title>
    <script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.6.0/jquery.js"></script>
</head>
<body>

<script>
    var socket;
    if (!window.WebSocket) {
        alert("不支持WebSocket");
    } else {
        socket = new WebSocket("ws://" +window.location.host+ "/test/ws/testusername");
        socket.onmessage = function (ev) {
            var resText = document.getElementById("resText");
            resText.value = resText.value + "\n" + ev.data;
        };

        socket.onopen = function (ev) {
            var resText = document.getElementById("resText");
            resText.value = "连接开启!";
        };

        socket.onclose = function (ev) {
            var resText = document.getElementById("resText");
            resText.value = resText.value + "\n" + "连接关闭!";
        }
    }

    function send(message) {
        if (!window.WebSocket) {
            return;
        }

        if (socket.readyState === WebSocket.OPEN) {
            socket.send(message);
        } else {
            alert("连接尚未打开!");
        }
    }

</script>

<form onsubmit="return false">
    <textarea name="message" id="" cols="30" rows="10"></textarea>
    <input type="button" value="发送" onclick="send(this.form.message.value)">

    <h3>服务端输出</h3>

    <textarea name="" id="resText" cols="30" rows="10"></textarea>

    <input type="button" onclick="javascript:document.getElementById('resText').value('')" value="清空">

</form>

</body>
</html>

Tomcat WebSocket 加载流程

通过WsSci对添加了@ServerEndpoint注解的类进行加载。

@ServerEndpoint标注的类不是单例的。

参考

https://tools.ietf.org/html/rfc6455

https://stackoverflow.com/questions/18265128/what-is-sec-websocket-key-for

https://tomcat.apache.org/tomcat-9.0-doc/web-socket-howto.html

https://www.oracle.com/webfolder/technetwork/tutorials/obe/java/HomeWebsocket/WebsocketHome.html

阅读 51 · 发布于 2021-04-20

————        END        ————

扫描下方二维码关注公众号和小程序↓↓↓

扫描二维码关注我
昵称:
随便看看 换一批