Package dev.relism.flash.websocket
Class WebSocketSession
java.lang.Object
dev.relism.flash.websocket.WebSocketSession
Per-connection WebSocket I/O state. One instance per virtual thread.
-
With
TCP_NODELAYenabled on the socket (set by the connection runner), Nagle's algorithm is disabled: the kernel sends data as soon as it lands in the send buffer, without waiting.BufferedOutputStreamwill still batch multiple small writes into one syscall when they happen in the same virtual-thread scheduling quantum — giving us coalescing where it's free, and immediate delivery where it matters.The only place an explicit flush is still needed is after the WS handshake (one-time, not on the hot path) and after the CLOSE frame (end of session). Both are handled in
close(int)and inWebSocketUpgrade#performHandshake. - Flush on CLOSE frame:
close(int)still flushes explicitly because the CLOSE frame is the last thing written before the stream is abandoned — without a flush, the 4 bytes could sit in the buffer forever.
Thread safety
sendText(byte[], int, int), send(byte[], int, int), and close(int) are serialized on a
blocking inside synchronized pins its carrier platform thread on Java 21, and a
blocking socket write is exactly the kind of call that can block. ReentrantLock
unmounts the blocked virtual thread instead) and are safe to call from threads other than the
session loop. close(int) uses a CAS on open to guarantee exactly-once CLOSE
frame emission under concurrent calls.
Fragmentation, masking, and control frames (RFC 6455 §5)
readFrame(dev.relism.flash.websocket.WebSocketFrame) reassembles continuation frames into one logical message (bounded by the
read buffer's capacity — the same bound a single unfragmented frame already had), enforces
that incoming frames are masked exactly when this session's role requires it (server sessions
require masked frames from the client; client-mode sessions require unmasked frames from the
server), validates the opcode against RFC 6455's defined set, and enforces the control-frame
constraints (FIN must be set, payload ≤ 125 bytes). A violation throws
WebSocketProtocolException carrying the correct close code (1002 protocol error, 1009
message too big) for the caller to send before closing.-
Constructor Summary
ConstructorsConstructorDescriptionWebSocketSession(InputStream in, OutputStream out, int bufferSize) WebSocketSession(InputStream in, OutputStream out, int bufferSize, Request request, boolean maskOutgoing) -
Method Summary
Modifier and TypeMethodDescriptionvoidclose(int code) Sends a CLOSE frame exactly once.intvoidcloseFromPeer(WebSocketFrame frame) voidecho(WebSocketFrame frame) voidbooleanisOpen()booleanisSecure()Whether this connection is TLS (WSS).booleanreadFrame(WebSocketFrame frame) Reads the next complete message, reassembling continuation frames and delivering control frames (CLOSE/PING/PONG) as soon as they arrive — RFC 6455 §5.4 explicitly permits a control frame to interleave with a fragmented data message, and this must not disturb the data message's in-progress reassembly.request()The request that upgraded this connection, ornull— see the 4-arg constructor.voidsend(byte[] payload, int off, int len) voidsendPong(WebSocketFrame ping) voidsendText(byte[] utf8, int off, int len) TLS session for this connection, ornullfor plain WS or no backing request.
-
Constructor Details
-
WebSocketSession
-
WebSocketSession
public WebSocketSession(InputStream in, OutputStream out, int bufferSize, Request request, boolean maskOutgoing) - Parameters:
request- the HTTP request that upgraded to this session, ornullif the caller has no use for it (e.g. a session opened as a WS client rather than accepted as a WS server). Stored as-is, no copy.maskOutgoing-trueif this session is acting as a WS client — RFC 6455 requires client-to-server frames to be masked, unlike the server-to-client directionwriteFrame(byte, byte[], int, int)originally only supported. SeewriteFrame(byte, byte[], int, int)for how masking is applied without allocating. Also determines the expected masking of *incoming* frames — seerequireMaskedIncoming.
-
-
Method Details
-
isOpen
public boolean isOpen() -
closeCode
public int closeCode() -
request
The request that upgraded this connection, ornull— see the 4-arg constructor. -
isSecure
public boolean isSecure()Whether this connection is TLS (WSS). Delegates to the upgradingrequest'sRequest.isSecure()rather than tracking the socket a second time — the request already carries it.falseif this session has no backing request (e.g. one opened in WS *client* mode via the 5-arg constructor withrequest == null). -
sslSession
TLS session for this connection, ornullfor plain WS or no backing request. -
sendText
- Throws:
IOException
-
send
- Throws:
IOException
-
close
Sends a CLOSE frame exactly once. No flush needed —outis the raw socket OutputStream (not buffered); each write goes directly to the kernel send buffer, and TCP_NODELAY ensures it's transmitted immediately.- Throws:
IOException
-
readFrame
Reads the next complete message, reassembling continuation frames and delivering control frames (CLOSE/PING/PONG) as soon as they arrive — RFC 6455 §5.4 explicitly permits a control frame to interleave with a fragmented data message, and this must not disturb the data message's in-progress reassembly.- Returns:
falseonly on a clean EOF between messages (the peer closed the TCP connection without sending a CLOSE frame); an EOF in the middle of a frame is a protocol violation and throws, it is not reported asfalse.- Throws:
WebSocketProtocolException- on any RFC 6455 violation (bad opcode, unmasked/masked frame when the opposite was required, oversized control frame, fragmented control frame, message exceeding the buffer) — carries the correct close code.IOException
-
sendPong
- Throws:
IOException
-
echo
- Throws:
IOException
-
closeFromPeer
-
forceClose
public void forceClose()
-