bigywater.tech
Java · 424

Jakarta WebSocket Endpoint 生命周期

WebSocket 的服务端 Endpoint 有几个回调,但连接是怎样结束的、关闭码是多少、@OnError 收到什么,常常只有在出问题时才知道。这篇用真实的连接,把正常关闭、断开、异常、空闲超时、消息过大和并发发送的行为逐个量出来。

1006 / 1003 / 1001 / 1009
abort、@OnMessage 抛异常、空闲超时、消息过大时服务端 @OnClose 的关闭码
804 ms
setMaxIdleTimeout(800) 之后空闲连接被关闭的耗时(客户端收到 1001)
2000 / 2000
4 个线程并发 getBasicRemote().sendText 在 Jetty 12 上成功的次数(Tyrus 等实现可能抛异常)

正常连接

实测 · 本机执行输出
  [  303 ms] 服务端 @OnOpen:endpoint 实例 #1,session ce16…,线程 qtp…-N,默认 maxIdleTimeout = 30000 ms,maxTextMessageBufferSize = 65536
  [  305 ms] 客户端(A) onOpen
  [  517 ms] 服务端 @OnMessage:实例 #1,线程 qtp…-N,长度 5,内容 hello
  [  520 ms] 客户端(A) onText:echo:hello
  [  724 ms] 服务端 @OnMessage:实例 #1,线程 qtp…-N,长度 5,内容 again
  [  725 ms] 客户端(A) onText:echo:again
  [  933 ms] 客户端(A)发起正常关闭 sendClose(1000, "bye")
  [  940 ms] 服务端 @OnClose:实例 #1,线程 qtp…-N,code = 1000 (NORMAL_CLOSURE),reason = "bye"
  [  946 ms] 客户端(A) onClose:code = 1000,reason = "bye"
  [ 1262 ms] 服务端 @OnOpen:endpoint 实例 #2,session cd52…,线程 qtp…-N,默认 maxIdleTimeout = 30000 ms,maxTextMessageBufferSize = 65536
  [ 1263 ms] 客户端(B) onOpen
  [ 1475 ms] 服务端 @OnMessage:实例 #2,线程 qtp…-N,长度 17,内容 second-connection
  [ 1476 ms] 客户端(B) onText:echo:second-connection
  [ 1683 ms] 服务端 @OnClose:实例 #2,线程 qtp…-N,code = 1000 (NORMAL_CLOSURE),reason = "done"
  [ 1684 ms] 客户端(B) onClose:code = 1000,reason = "done"

异常关闭

实测 · 本机执行输出
  [ 2003 ms] 服务端 @OnOpen:endpoint 实例 #3,session dd3a…,线程 qtp…-N,默认 maxIdleTimeout = 30000 ms,maxTextMessageBufferSize = 65536
  [ 2004 ms] 客户端(C) onOpen
  [ 2206 ms] 客户端(C)调用 abort()(不发送 close 帧,直接断开 TCP)
  [ 2214 ms] 服务端 @OnError:实例 #3,线程 qtp…-N,ClosedChannelException:null
  [ 2215 ms] 服务端 @OnClose:实例 #3,线程 qtp…-N,code = 1006 (CLOSED_ABNORMALLY),reason = "Session Closed"
  [ 3020 ms] 服务端 @OnOpen:endpoint 实例 #4,session 14ce…,线程 qtp…-N,默认 maxIdleTimeout = 30000 ms,maxTextMessageBufferSize = 65536
  [ 3020 ms] 客户端(D) onOpen
  [ 3221 ms] 服务端 @OnMessage:实例 #4,线程 qtp…-N,长度 4,内容 boom
  [ 3222 ms] 服务端 @OnError:实例 #4,线程 qtp…-N,CloseException:Endpoint notification error
  [ 3222 ms] 服务端 @OnClose:实例 #4,线程 qtp…-N,code = 1003 (CANNOT_ACCEPT),reason = "Endpoint notification error"
  [ 3222 ms] 客户端(D) onClose:code = 1003,reason = "Endpoint notification error"
  [ 3540 ms] 服务端(idle)@OnOpen:setMaxIdleTimeout(800)
  [ 3540 ms] 客户端(E) onOpen
  [ 4344 ms] 服务端(idle)@OnError WebSocketTimeoutException:Connection Idle Timeout
  [ 4345 ms] 客户端(E) onClose:code = 1001,reason = "Connection Idle Timeout"
  [ 4345 ms] 客户端(E)一直空闲,从连接到被关闭用了约 804 ms
  [ 4346 ms] 服务端(idle)@OnClose code = 1001 (GOING_AWAY),reason = "Connection Idle Timeout"
  [ 4561 ms] 服务端 @OnOpen:endpoint 实例 #5,session a441…,线程 qtp…-N,默认 maxIdleTimeout = 30000 ms,maxTextMessageBufferSize = 65536
  [ 4562 ms] 客户端(F) onOpen
  [ 4770 ms] 服务端 @OnMessage:实例 #5,线程 qtp…-N,长度 9,内容 bigreason
  [ 4771 ms] 服务端 s.close(reason 200 字符):IllegalArgumentException:Reason Phrase cannot exceed 123 UTF-8 encoded bytes: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
  [ 4773 ms] 客户端(F) onText:bigreason-handled
  [ 5281 ms] 服务端 @OnClose:实例 #5,线程 qtp…-N,code = 1000 (NORMAL_CLOSURE),reason = "end"
  [ 5283 ms] 客户端(F) onClose:code = 1000,reason = "end"

消息大小

实测 · 本机执行输出
  [ 5603 ms] 服务端 @OnOpen:endpoint 实例 #6,session b201…,线程 qtp…-N,默认 maxIdleTimeout = 30000 ms,maxTextMessageBufferSize = 65536
  [ 5603 ms] 客户端(G) onOpen
  [ 5831 ms] 客户端(G) onClose:code = 1009,reason = "Text message too large: 69,592 > 65,536"
  [ 5832 ms] 服务端 @OnError:实例 #6,线程 qtp…-N,MessageTooLargeException:Text message too large: 69,592 > 65,536
  [ 5832 ms] 服务端 @OnClose:实例 #6,线程 qtp…-N,code = 1009 (TOO_BIG),reason = "Text message too large: 69,592 > 65,536"
  [ 6152 ms] 服务端(big)@OnOpen:把 maxTextMessageBufferSize 调到 512 KiB
  [ 6153 ms] 客户端(H) onOpen
  [ 6374 ms] 服务端(big)@OnMessage:收到 100000 字符
  [ 6375 ms] 客户端(H) onText:ok:100000
  [ 6881 ms] 服务端(big)@OnClose code = 1000
  [ 6882 ms] 客户端(H) onClose:code = 1000,reason = "ok"

并发发送

实测 · 本机执行输出
  [ 7202 ms] 客户端(I) onOpen
  [ 7469 ms] 服务端(send):4 个线程各自对同一个 session 调用 getBasicRemote().sendText 500 次:成功 2000,失败 0
  [ 7497 ms] 服务端(send):同样的发送放进 synchronized (session):成功 2000,失败 0
  [ 7515 ms] 服务端(send):改用 getAsyncRemote().sendText(并发调用):调用成功 2000,抛出异常 0
  [ 9911 ms] 客户端(I)实际收到的消息条数 6000
  [ 9917 ms] 客户端(I) onClose:code = 1000,reason = "ok"

分步演示

Jakarta WebSocket Endpoint 生命周期
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / OpenJDK(Temurin 17.0.20、Homebrew 21.0.12 与 27,每篇注明实际用的版本);用 javac 编译、java 直接运行,需要第三方库时用 Maven 解析依赖;计时用 System.nanoTime,数字每次运行会有小幅抖动。Jetty 12.0.16(jetty-ee10-websocket-jakarta-server)与 Jakarta WebSocket 2.1,客户端是 JDK 21 的 java.net.http.WebSocket;端口 9800;回调线程名被归一化为 qtp…-N;抽样的时间戳是单次运行
    • 没有验证:Tyrus / Tomcat 的 WebSocket 实现差异(尤其是并发发送与 @OnError 收到的异常)、TLS(wss)、子协议与扩展(permessage-deflate)、ping / pong 与 PongMessage、Spring WebSocket / STOMP、集群中的会话共享、背压(慢客户端)
    京ICP备2026067502号-1京公网安备11010502063487号