Ktor client WebSockets plugin for Linux x64 platform - enables full-duplex communication between client and server over TCP connection
—
Low-level WebSocket frame handling for text, binary, and control frame processing with full control over WebSocket protocol features and frame-level operations.
Base sealed class for all WebSocket frame types with common properties and frame metadata.
/**
* Base sealed class for WebSocket frames
* Not reusable and not thread-safe
* @param fin Final fragment flag - true for complete frames
* @param frameType Type of the WebSocket frame
* @param data Frame payload data
* @param disposableHandle Handle for frame cleanup
* @param rsv1 First extension bit
* @param rsv2 Second extension bit
* @param rsv3 Third extension bit
*/
sealed class Frame private constructor(
fin: Boolean,
frameType: FrameType,
data: ByteArray,
disposableHandle: DisposableHandle = NonDisposableHandle,
rsv1: Boolean = false,
rsv2: Boolean = false,
rsv3: Boolean = false
) {
/** Final fragment flag - should be true for control frames */
val fin: Boolean
/** Frame type enumeration */
val frameType: FrameType
/** Frame payload data */
val data: ByteArray
/** Handle for frame disposal/cleanup */
val disposableHandle: DisposableHandle
/** First extension bit for WebSocket extensions */
val rsv1: Boolean
/** Second extension bit for WebSocket extensions */
val rsv2: Boolean
/** Third extension bit for WebSocket extensions */
val rsv3: Boolean
}Application-level text frame for UTF-8 encoded string messages.
/**
* Text frame for UTF-8 encoded string messages
* Can be fragmented in RAW WebSocket sessions (fin=false)
*/
class Frame.Text : Frame {
/**
* Create text frame with fragment control and extension bits
* @param fin Final fragment flag
* @param data UTF-8 encoded text data
* @param rsv1 First extension bit
* @param rsv2 Second extension bit
* @param rsv3 Third extension bit
*/
constructor(
fin: Boolean,
data: ByteArray,
rsv1: Boolean = false,
rsv2: Boolean = false,
rsv3: Boolean = false
)
/**
* Create complete text frame from byte array
* @param fin Final fragment flag
* @param data UTF-8 encoded text data
*/
constructor(fin: Boolean, data: ByteArray)
/**
* Create complete text frame from string
* @param text Text content (automatically UTF-8 encoded)
*/
constructor(text: String)
/**
* Create text frame from Source
* @param fin Final fragment flag
* @param packet Source containing UTF-8 text data
*/
constructor(fin: Boolean, packet: Source)
}Usage Examples:
// Create text frame from string
val textFrame = Frame.Text("Hello WebSocket!")
// Create fragmented text frame
val fragment1 = Frame.Text(false, "Hello ".toByteArray())
val fragment2 = Frame.Text(true, "World!".toByteArray())
// Send text frame
session.outgoing.send(Frame.Text("Message content"))
// Create text frame with extension bits
val compressedText = Frame.Text(
fin = true,
data = compressedData,
rsv1 = true // Indicate compression extension
)Application-level binary frame for raw byte data transmission.
/**
* Binary frame for raw byte data
* Can be fragmented in RAW WebSocket sessions (fin=false)
*/
class Frame.Binary : Frame {
/**
* Create binary frame with fragment control and extension bits
* @param fin Final fragment flag
* @param data Binary payload data
* @param rsv1 First extension bit
* @param rsv2 Second extension bit
* @param rsv3 Third extension bit
*/
constructor(
fin: Boolean,
data: ByteArray,
rsv1: Boolean = false,
rsv2: Boolean = false,
rsv3: Boolean = false
)
/**
* Create complete binary frame from byte array
* @param fin Final fragment flag
* @param data Binary payload data
*/
constructor(fin: Boolean, data: ByteArray)
/**
* Create binary frame from Source
* @param fin Final fragment flag
* @param packet Source containing binary data
*/
constructor(fin: Boolean, packet: Source)
}Usage Examples:
// Create binary frame from byte array
val imageData = loadImageBytes()
val binaryFrame = Frame.Binary(true, imageData)
// Send binary frame
session.outgoing.send(Frame.Binary(true, fileBytes))
// Create fragmented binary frame
val chunk1 = Frame.Binary(false, data.sliceArray(0..1023))
val chunk2 = Frame.Binary(true, data.sliceArray(1024..data.size))Control frames for WebSocket protocol management (Close, Ping, Pong).
/**
* Close frame for connection termination
* Must not be fragmented (fin always true)
*/
class Frame.Close : Frame
/**
* Ping frame for connection keep-alive
* Must not be fragmented (fin always true)
*/
class Frame.Ping : Frame
/**
* Pong frame for ping response
* Must not be fragmented (fin always true)
*/
class Frame.Pong : FrameUsage Examples:
// Send ping with payload
session.outgoing.send(Frame.Ping(byteArrayOf(1, 2, 3)))
// Respond to ping with pong
when (val frame = session.incoming.receive()) {
is Frame.Ping -> session.outgoing.send(Frame.Pong(frame.data))
}
// Close connection with reason
session.outgoing.send(Frame.Close())Enumeration defining WebSocket frame types with protocol opcodes.
/**
* WebSocket frame type enumeration
* @param controlFrame Whether this is a control frame type
* @param opcode Frame type opcode for wire protocol
*/
enum class FrameType(val controlFrame: Boolean, val opcode: Int) {
/** Application level text frame */
TEXT(false, 1),
/** Application level binary frame */
BINARY(false, 2),
/** Control close frame */
CLOSE(true, 8),
/** Control ping frame */
PING(true, 9),
/** Control pong frame */
PONG(true, 0xa)
companion object {
/**
* Find FrameType by numeric opcode
* @param opcode Frame type opcode
* @return FrameType instance or null if invalid
*/
operator fun get(opcode: Int): FrameType?
}
}Utility functions for extracting data from frames.
/**
* Read text content from text frame as UTF-8 string
* @return Decoded text content
*/
fun Frame.Text.readText(): String
/**
* Read raw bytes from any frame type
* @return Frame payload data
*/
fun Frame.readBytes(): ByteArray
/**
* Read close reason from close frame
* @return CloseReason with code and message, or null if no reason
*/
fun Frame.Close.readReason(): CloseReason?Usage Examples:
when (val frame = session.incoming.receive()) {
is Frame.Text -> {
val message = frame.readText()
println("Text message: $message")
}
is Frame.Binary -> {
val bytes = frame.readBytes()
println("Binary data: ${bytes.size} bytes")
}
is Frame.Close -> {
val reason = frame.readReason()
println("Connection closed: ${reason?.message}")
}
}Structure for WebSocket close frame reasons.
/**
* WebSocket close reason with code and message
* @param code Numeric close code (RFC 6455)
* @param message Optional human-readable message
*/
data class CloseReason(val code: Short, val message: String) {
/** Constructor using predefined codes */
constructor(code: Codes, message: String)
/** Get enum value for this code or null */
val knownReason: Codes?
/** Standard close reason codes */
enum class Codes(val code: Short) {
/** Normal closure (1000) */
NORMAL(1000),
/** Going away (1001) */
GOING_AWAY(1001),
/** Protocol error (1002) */
PROTOCOL_ERROR(1002),
/** Cannot accept data type (1003) */
CANNOT_ACCEPT(1003),
/** Not consistent UTF-8 (1007) */
NOT_CONSISTENT(1007),
/** Policy violation (1008) */
VIOLATED_POLICY(1008),
/** Message too big (1009) */
TOO_BIG(1009),
/** No extension (1010) */
NO_EXTENSION(1010),
/** Internal error (1011) */
INTERNAL_ERROR(1011),
/** Service restart (1012) */
SERVICE_RESTART(1012),
/** Try again later (1013) */
TRY_AGAIN_LATER(1013);
companion object {
fun byCode(code: Short): Codes?
}
}
}client.webSocket("ws://echo.websocket.org") {
// Send different frame types
outgoing.send(Frame.Text("Hello WebSocket!"))
outgoing.send(Frame.Binary(true, byteArrayOf(1, 2, 3, 4)))
// Process incoming frames
for (frame in incoming) {
when (frame) {
is Frame.Text -> {
val text = frame.readText()
println("Received text: $text")
}
is Frame.Binary -> {
val bytes = frame.readBytes()
println("Received ${bytes.size} bytes")
}
is Frame.Close -> {
val reason = frame.readReason()
println("Connection closed: ${reason?.code} - ${reason?.message}")
break
}
is Frame.Ping -> {
// Respond to ping
outgoing.send(Frame.Pong(frame.data))
println("Responded to ping")
}
is Frame.Pong -> {
println("Received pong response")
}
}
}
}client.webSocket("ws://fileserver.example.com") {
// Send file as binary frames
val fileBytes = File("large-file.dat").readBytes()
val chunkSize = 64 * 1024 // 64KB chunks
for (i in fileBytes.indices step chunkSize) {
val chunk = fileBytes.sliceArray(i until minOf(i + chunkSize, fileBytes.size))
val isLast = i + chunkSize >= fileBytes.size
outgoing.send(Frame.Binary(isLast, chunk))
}
// Wait for acknowledgment
val ack = incoming.receive()
if (ack is Frame.Text && ack.readText() == "ACK") {
println("File transfer completed")
}
}client.webSocket("ws://api.example.com/json") {
// Send JSON as text frames
val request = JsonObject(mapOf(
"action" to JsonPrimitive("subscribe"),
"channel" to JsonPrimitive("trades")
))
outgoing.send(Frame.Text(request.toString()))
// Process JSON responses
for (frame in incoming) {
when (frame) {
is Frame.Text -> {
val json = Json.parseToJsonElement(frame.readText())
handleJsonMessage(json)
}
is Frame.Close -> break
}
}
}client.webSocket("ws://compressed.example.com") {
// Send compressed frame (using rsv1 for compression flag)
val originalData = "Large text content...".toByteArray()
val compressedData = compress(originalData)
outgoing.send(Frame.Text(
fin = true,
data = compressedData,
rsv1 = true // Indicate compression
))
// Handle compressed incoming frames
for (frame in incoming) {
when (frame) {
is Frame.Text -> {
val data = if (frame.rsv1) {
decompress(frame.data)
} else {
frame.data
}
val text = String(data, Charsets.UTF_8)
println("Message: $text")
}
}
}
}client.webSocket("ws://custom.example.com") {
// Custom protocol: first byte indicates message type
fun sendCustomMessage(type: Byte, payload: String) {
val data = byteArrayOf(type) + payload.toByteArray()
outgoing.send(Frame.Binary(true, data))
}
// Send different message types
sendCustomMessage(0x01, "User message")
sendCustomMessage(0x02, "System command")
// Process custom protocol messages
for (frame in incoming) {
when (frame) {
is Frame.Binary -> {
val data = frame.readBytes()
if (data.isNotEmpty()) {
val type = data[0]
val payload = String(data, 1, data.size - 1)
when (type.toInt()) {
0x01 -> handleUserMessage(payload)
0x02 -> handleSystemCommand(payload)
0x03 -> handleErrorMessage(payload)
}
}
}
is Frame.Close -> break
}
}
}client.webSocket("ws://api.example.com") {
try {
// Normal operation
for (frame in incoming) {
when (frame) {
is Frame.Text -> processMessage(frame.readText())
is Frame.Close -> {
val reason = frame.readReason()
println("Server closed connection: ${reason?.message}")
break
}
}
}
} finally {
// Send close frame before terminating
try {
outgoing.send(Frame.Close())
} catch (e: Exception) {
// Channel might be already closed
}
}
}Install with Tessl CLI
npx tessl i tessl/maven-io-ktor--ktor-client-websockets-linuxx64