kotlinokhttpkotlin-multiplatformktorcompose-multiplatform

Websockets Ktor/OkHTTP server sends ping, doesn't receive pong


I implemented a basic websockets server, following the instructions on the Ktor site (server, client).

Server (complete source):

webSocket("/echo") {
    send("Please enter your name")
    for (frame in incoming) {
        frame as? Frame.Text ?: continue
        val receivedText = frame.readText()
        if (receivedText.equals("bye", ignoreCase = true)) {
            close(CloseReason(CloseReason.Codes.NORMAL, "Client said BYE"))
        } else {
            send("Hi, $receivedText!")
            while (true) {
                send("1")
                Thread.sleep(2000)
            }
        }
    }
}

Client (complete source):

LaunchedEffect(deps.client) {
    deps.socketsClient.webSocket(
        method = HttpMethod.Get,
        host = host,
        port = 8080,
        path = "/echo"
    ) {
        val othersMessage = incoming.receive() as? Frame.Text
        println(othersMessage?.readText())
        send("somename")
        val reply = incoming.receive() as? Frame.Text
        println(reply?.readText())

        for (frame in incoming) {
            if (frame is Frame.Text) {
                val number = frame.readText()
                println("Received number: $number")
            }
        }
    }
}

The server sends a ping to the client

2025-05-16 17:05:14.664 [eventLoopGroupProxy-3-3] TRACE io.ktor.websocket.WebSocket - WebSocket Pinger: sending ping frame

It works for a bit, then times out

2025-05-16 17:05:29.667 [eventLoopGroupProxy-3-5] TRACE io.ktor.websocket.WebSocket - WebSocket pinger has timed out

On the desktop client:

Exception in thread "AWT-EventQueue-0" java.io.EOFException at okio.RealBufferedSource.require(RealBufferedSource.kt:223) at okio.RealBufferedSource.readByte(RealBufferedSource.kt:233) at okhttp3.internal.ws.WebSocketReader.readHeader(WebSocketReader.kt:119) at okhttp3.internal.ws.WebSocketReader.processNextFrame(WebSocketReader.kt:102) at okhttp3.internal.ws.RealWebSocket.loopReader(RealWebSocket.kt:293) at okhttp3.internal.ws.RealWebSocket$connect$1.onResponse(RealWebSocket.kt:195) at okhttp3.internal.connection.RealCall$AsyncCall.run(RealCall.kt:519) at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(Unknown Source) at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source) at java.base/java.lang.Thread.run(Unknown Source) Suppressed: kotlinx.coroutines.internal.DiagnosticCoroutineContextException: [androidx.compose.ui.scene.ComposeContainer$DesktopCoroutineExceptionHandler@73344c2d, androidx.compose.runtime.BroadcastFrameClock@24265a26, StandaloneCoroutine{Cancelling}@783b937, FlushCoroutineDispatcher@7dacd0c]

The iOS (Darwin engine) client has the same problem.

Here's the complete Compose Multiplatform project where it can be reproduced.

Note that to run on iOS it currently requires to set the Kotlin version to 2.1.10 and uncomment the skie plugin (here)

Any ideas?


Solution

  • i checked your codes
    now it will ok
    try this one

    webSocket("/echo") {
        send("Please enter your name")
        try {
            for (frame in incoming) {
                when (frame) {
                    is Frame.Text -> {
                        val receivedText = frame.readText()
                        if (receivedText.equals("bye", ignoreCase = true)) {
                            close(CloseReason(CloseReason.Codes.NORMAL, "Client said BYE"))
                            break
                        } else {
                            send("Hi, $receivedText!")
                            
                            
                            while (isActive) {
                                send("1")
                                delay(2000) 
                            }
                        }
                    }
                    is Frame.Ping -> send(Frame.Pong(frame.buffer)) 
                    else -> {} 
                }
            }
        } catch (e: Exception) {
            println("WebSocket error: ${e.message}")
        }
    }
    

    and in client side

    deps.socketsClient.webSocket(
        method = HttpMethod.Get,
        host = host,
        port = 8080,
        path = "/echo"
    ) {
        try {
          
            for (frame in incoming) {
                when (frame) {
                    is Frame.Text -> {
                        val number = frame.readText()
                        println("Received number: $number")
                    }
                    is Frame.Ping -> send(Frame.Pong(frame.buffer))
                    else -> {}
                }
            }
        } catch (e: Exception) {
            println("WebSocket error: ${e.message}")
        }
    }