WebSocket 测试
浏览器直连你填的 ws 地址收发消息,记录消息时间线并解释握手算法与关闭码。
只用浏览器原生 WebSocket 的测试台,没有第三方依赖,也没有本站的中转:连接只在你亲自填了地址并点了「连接」的那一刻发生,路径是浏览器直连那个地址;页面加载不发一个包,不自动重连,地址框空着、我们不预填任何公共 echo 服务,没有统计也没有日志。连之前的地址校验要求显式写 ws:// 或 wss://(WHATWG 其实允许构造器吃 http/https 并内部按 ws/wss 处理,本页不接受这种含糊),并分开报错。可选的子协议按逗号拆开直接交给构造器,会检查 RFC 6455 的 token 字符集与重复项(重复项本身就让它抛 SyntaxError)。binaryType 只在 blob 与 arraybuffer 之间选,而且只管收不管发;切换它不用重连,且只影响之后收到的二进制消息。
收发内容只留在当前页面内存里,离开就没了;要留档可以点时间线面板上的「下载 .txt」,那是 Blob 在本地生成的文件。单条消息本页拦在 262144 字节(256 KiB),这是页面自设的护栏,协议本身没有单条上限。主动关闭时的码值与 reason 长度都有硬约束,不带码就没法带 reason。握手那一屏说清楚的是:101 响应的这些头由用户代理自己消费,JavaScript 里没有读响应头的口子,所以这一屏是在解释算法,不是假装在抓现成报文;同理浏览器发不了 ping 帧,本页不伪造,只给可选的定时文本保活并说明它是替代而不是等价。DNS 失败、端口没人监听、TCP 被拒、证书不被信任、响应不是 101、子协议没对上,在页面里全都长得一样——一个不带信息的 error 事件加一个 1006,本页把这类归为「不可区分」,而不编造一个精确原因。
WebSocket 测试怎么用
- 填地址、点连接:页面顶部只留一句常驻说明:连接只在你点「连接」时发生,浏览器直连你填的那个地址,本站没有中转和代理、不记录不统计、页面加载不发包、地址框默认空着。下面第一行依次是地址、可选子协议、binaryType、连接/断开,只有点「连接」才发起;地址校验不过会在横幅里说清是哪一类问题:缺前缀、中间有空格、带 # 片段(规范规定带片段直接抛 SyntaxError)、带用户名/密码、端口不在 1–65535 或写成 0、IPv6 忘了方括号(ws://[::1]:8080 才解得开,ws://::1:8080 会被当成端口分隔符),同一时刻时间线也会补一条系统记录。再往下是四个 readyState 胶囊(数字与名字一起显示,只有当前态高亮;还没点过连接、时间线也空着时四个都不亮)加两个计时。宽屏是两列:左列发送 + 消息时间线,右列关闭与诊断 + 保活、握手参考报文、Key 计算器三个折叠;窄屏并成一列,折叠收起不改变任何行为。握手相关的两屏现在都收在默认收起的折叠里,点开才占高度。其中「Sec-WebSocket-Key → Accept 计算器」真算:base64(SHA-1(key + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11")),走 WebCrypto,示例 Key 得到 RFC 6455 §1.3 里那串 Accept。同一列的另一折给 14 行静态参考报文,标题里就写明「本页读不到真实握手头」。保活整块也收在折叠里,但收起不会把开关关掉——摘要行右侧始终写着「开关已开 · 间隔 Ns」或「开关已关」;计时只在开关开且有 readyState=1 的连接时才真的起,开关开着却没连接时状态条直接写「一条也不会发出去」,连上之后自动开始计时。页底另有「协议口径说明」一折,6 条纯文字(单条 256 KiB 上限、帧头不计入字节、binaryType 只管收、二进制不当文本渲染、离开页面时的行为、主动关闭码的合法范围),收起或展开都不影响连接、收发与上面的计数。
- 三种发送模式:切到 JSON 时输入框下方边打边解析,非法就拒绝发送并给出行列与病因;切到十六进制时按字节校验(奇数长度、非 hex 字符都会指出位置)。发送前还会核对状态与 256 KiB 的单条上限,每一次被拦下都会在时间线留一条系统记录,不会静默丢弃。发送三种模式各有拒绝路径:文本原样发出;JSON 先本地解析,不过就绝不出网,并把解析器原文、换算成的行列与常见病因(尾逗号、单引号、裸控制字符)一起给出,通过后才发重新序列化的紧凑形式;十六进制逐字符校验,空白与逗号当分隔符、可带 0x 前缀(会写明忽略了几个),奇数长度与非 0-9a-fA-F 字符报出位置。readyState 不是 1 时点发送会拿到针对 CONNECTING / CLOSING / CLOSED 各不相同的说明:规范在这三种状态下都让 send() 抛 InvalidStateError,浏览器不替你排队,所以不存在「消息静默丢掉」。
- 读时间线与留档:每行是 时刻 / 方向(↓ 收、↑ 发、· 系统)/ 类型 / 字节 / 预览;收与发各有一道左侧色条,预览与条数的上限写在面板头部那一行,截断显式标出而不是藏起来。预览、计数与上限都明写在面板头部那一行:发多少条、收多少条、列表当前几条对上限 300(超出后丢弃最早的并写明丢了几条)、文本预览 160 个码点(按码点算,emoji 不会被从中间劈开)、二进制预览 40 字节的十六进制,超出后在行尾显式标出这条预览被哪条上限砍了——文本写「只显示前 160 个码点」、二进制写「只显示前 40 字节」,都附载荷总字节数,而不是悄悄砍掉;这几个数字在下载文件头里也写一份。载荷文本插入 DOM 前一律转义,控制字符换成 ␊ ␉ \xNN 这类可见记号,二进制永远只显示十六进制与长度、不猜 UTF-8。时间线默认自动滚到底,「暂停滚动」是真正的 role=switch 开关:按下去只是视野停住,记录与发/收计数照常累加,开关下方会写出暂停期间又到了几条,再点一次恢复滚底并立刻跳到底部。「清空」把列表归零,「下载 .txt」导出当前列表,文件头一行写目标地址、发送与接收各多少条、记录时间与本页地址——文件在本地生成,不经过任何服务器,那个下载用的临时链接在点下之后 3 秒才回收(当场回收会让浏览器拿到失效 URL 而静默不下)。
- 连上之后的状态机与时间线字段:连上之后是一条显式状态机加事件流:状态条把 0 CONNECTING、1 OPEN、2 CLOSING、3 CLOSED 连同数字一起显示,计时由本页自建定时器驱动(custom 版面拿不到外壳每秒的 tick),「尝试」从点击那刻起算、「在线」从收到 open 起算。时间线记录每次收发与每次状态变化,字段是毫秒时间戳加相对毫秒数、方向(↓ 收 / ↑ 发 / · 系统)、类型、载荷字节数与预览;字节按 UTF-8 全量算,帧头那 2–14 字节不计入(客户端→服务器的帧总带掩码,掩码不加长度)。
- binaryType 两种取值的取舍:blob 给你 Blob(大消息更省内存,能直接交给 Worker 或 createObjectURL,但要看字节必须 await blob.arrayBuffer()),arraybuffer 给你 ArrayBuffer(事件里同步可用,适合逐帧解析,代价是每条消息立刻分配一块等长内存)。切换它不用重连:已经有连接时当场把值写进这条连接的 binaryType 属性并在时间线记一句,没有连接时留给下一次构造——两种情形都只影响之后收到的二进制消息。
常见问题
连不上时为什么只说「1006」,不告诉我是 DNS 挂了还是端口没开?
因为浏览器故意不给。DNS 失败、连接被拒、TLS 证书不被信任、响应不是 101、子协议没协商上,这些情况被收敛成同一个不携带细节的 error 事件,close 事件的码填 1006(RFC 6455 §7.4.1 明确 1006 是保留值,端点不能把它写进 Close 帧——它是用户代理补上去的),页面能拿到的就这么多。这样做的原因写在 WHATWG WebSockets 里:如果脚本能区分这些情况,就能拿它逐个探测用户内网的开放端口。所以本页不替这些情况编结论,只归为「不可区分」,并建议从看得清的一侧查:用普通 https 请求或本机 TLS 客户端验证端口与证书链,看服务器访问日志里握手是否到过。你能确信的只有「没成功」和「码是 1006」,另加它不是混合内容(那种会被提前判出来)。
这个页面是 https,为什么连不了 ws://?
明文 WebSocket 被算作混合内容:安全页面里的 ws:// 会在出网卡之前被浏览器拦掉,对端根本收不到东西。这不是本页的限制,也不是服务器的错,换 wss:// 即可(本地 http 的开发构建连 ws:// 是允许的)。本页按 isSecureContext 加地址方案提前判出这一类,就是为了不让你把它误读成「服务器拒绝连接」。反向也记一句:wss 的证书按常规信任链校验,自签名不会放行,而这在页面侧同样表现为说不清的 1006。
WebSocket 不受同源策略限制,那把 token 放在 URL 里行不行?
不建议。同源策略管的是 fetch/XHR 那套跨源读取,对 WebSocket 不管:任意页面都能对你的 ws 端点发起握手,浏览器只把 Origin 送出去,拦不拦全看服务器——所以服务器必须自己校验 Origin,并把子协议当握手的一部分核对(Sec-WebSocket-Protocol 由服务器选一个回来,构造器再暴露成 WebSocket.protocol,选不上就失败)。因此把长期凭证写进地址或查询串,它会原样出现在服务器访问日志与任何中间代理的日志里,而跨源页面带着 Cookie 连你的端点也没有同源策略拦着——Cookie 默认随握手发出。可取的做法是握手只换取一次性的短期票据(凭证放在首条消息或某个子协议里),服务器侧强制 Origin 白名单,握手之后再授权。
主动关闭时能填哪些码值与 reason?收到 close 事件后又读得出什么?
断开时可以填关闭码与 reason:ws.close() 只接受 1000 或 3000–4999,其余码值抛 InvalidAccessError;reason 超过 123 字节抛 SyntaxError;不带码就没法带 reason(那种情形对端只能按 1005 No Status Received 理解)。收到 close 事件后页面把 code、reason、wasClean 摊开并查码表:1000–1011 与 1015 出自 RFC 6455 §7.4.1,1012/1013/1014 出自 IANA 追加注册,1016–2999、3000–3999、4000–4999 按 §7.4.2 分段说明。最容易被误解的是 1006:RFC 写明它是保留值、端点 MUST NOT 把它写进 Close 帧,你在 JS 里看到的那个 1006 是浏览器从「连接没了但没交换 Close 帧」推断出来补上的;1015 同理,也设不了。
页面卸载或站内切页时,这条连接和它的定时器会怎样?
主动动手的时刻有两处:页面卸载(pagehide)时本页替你调一次 close(),免得连接在服务器侧半开着;站内切页(astro:navigate)时这一屏自己拆干净——自建的计时与保活定时器清掉、挂在 window 上的监听摘掉、连接实例的事件监听摘掉、还开着的连接补一次 close()、来不及回收的下载链接当场回收,并且只保留最后一个清理器,翻几次页不会累积。
对应功能编号 #60无需安装,手机端同样可用按 Ctrl + K 换工具