Skip to content
Server Plugin

Ktor 서버에서 WebSockets

필수 종속성: io.ktor:ktor-server-websockets

코드 예시: server-websockets

네이티브 서버
Ktor는 Kotlin/Native를 지원하며 추가 런타임이나 가상 머신 없이 서버를 실행할 수 있습니다.
지원: ✅

WebSocket은 단일 TCP 연결을 통해 사용자의 브라우저와 서버 간에 전이중 통신 세션을 제공하는 프로토콜입니다. 서버로/부터 실시간 데이터 전송이 필요한 애플리케이션을 생성하는 데 특히 유용합니다.

Ktor는 서버 및 클라이언트 측 모두에서 WebSocket 프로토콜을 지원합니다.

Ktor를 사용하면 다음을 수행할 수 있습니다.

  • 기본 WebSocket 설정(예: 프레임 크기, 핑 주기 등)을 구성합니다.
  • 서버와 클라이언트 간 메시지 교환을 위한 WebSocket 세션을 처리합니다.
  • WebSocket 확장 기능을 추가합니다. 예를 들어, Deflate 확장 기능을 사용하거나 사용자 지정 확장 기능을 구현할 수 있습니다.

클라이언트 측 WebSocket 지원에 대해 알아보려면 WebSockets 클라이언트 플러그인을 참조하세요.

단방향 통신 세션의 경우 Server-Sent Events (SSE) 사용을 고려해 보세요. SSE는 서버가 클라이언트에 이벤트 기반 업데이트를 전송해야 하는 경우에 특히 유용합니다.

의존성 추가

WebSockets을(를) 사용하려면 빌드 스크립트에 ktor-server-websockets 아티팩트를 포함해야 합니다:

Kotlin
Groovy
XML

WebSockets 설치

애플리케이션에 WebSockets 플러그인을 설치하려면 지정된

모듈
모듈을 사용하면 라우트를 그룹화하여 애플리케이션을 구성할 수 있습니다.
install 함수에 전달하세요. 아래 코드 스니펫은 WebSockets을(를) 설치하는 방법을 보여줍니다...

  • ... embeddedServer 함수 호출 내에서.
  • ... Application 클래스의 확장 함수인 명시적으로 정의된 module 내에서.
kotlin
kotlin

WebSockets 구성

선택적으로, install 블록 내에서 WebSocketOptions을(를) 전달하여 플러그인을 구성할 수 있습니다:

  • pingPeriod 속성을 사용하여 핑 간의 지속 시간을 지정합니다.
  • timeout 속성을 사용하여 연결이 닫힐 시간 초과를 설정합니다.
  • maxFrameSize 속성을 사용하여 수신하거나 보낼 수 있는 최대 Frame 크기를 설정합니다.
  • masking 속성을 사용하여 마스킹을 활성화할지 여부를 지정합니다.
  • contentConverter 속성을 사용하여 직렬화/역직렬화를 위한 컨버터를 설정합니다.
kotlin
install(WebSockets) {
    pingPeriod = 15.seconds
    timeout = 15.seconds
    maxFrameSize = Long.MAX_VALUE
    masking = false
}

WebSockets 세션 처리

API 개요

WebSockets 플러그인을 설치하고 구성한 후 WebSocket 세션을 처리할 엔드포인트를 정의할 수 있습니다. 서버에 WebSocket 엔드포인트를 정의하려면 라우팅 블록 내에서 webSocket 함수를 호출합니다:

kotlin
routing { 
    webSocket("/echo") {
       // Handle a WebSocket session
    }
}

이 예시에서, 기본 구성이 사용될 때 서버는 ws://localhost:8080/echo로 WebSocket 요청을 수락합니다.

webSocket 블록 내에서 WebSocket 세션에 대한 핸들러를 정의하며, 이는 DefaultWebSocketServerSession 클래스로 표현됩니다. 블록 내에서 다음 함수와 속성을 사용할 수 있습니다:

  • send 함수를 사용하여 클라이언트에 텍스트 콘텐츠를 보냅니다.
  • incomingoutgoing 속성을 사용하여 WebSocket 프레임을 수신하고 보내기 위한 채널에 접근합니다. 프레임은 Frame 클래스로 표현됩니다.
  • close 함수를 사용하여 지정된 이유와 함께 종료 프레임을 보냅니다.

세션을 처리할 때, 프레임 유형을 확인할 수 있습니다. 예를 들어:

  • Frame.Text는 텍스트 프레임입니다. 이 프레임 유형의 경우 Frame.Text.readText()를 사용하여 콘텐츠를 읽을 수 있습니다.
  • Frame.Binary는 바이너리 프레임입니다. 이 유형의 경우 Frame.Binary.readBytes()를 사용하여 콘텐츠를 읽을 수 있습니다.

incoming 채널에는 핑/퐁 또는 종료 프레임과 같은 제어 프레임이 포함되어 있지 않습니다. 제어 프레임을 처리하고 분할된 프레임을 재조립하려면 webSocketRaw 함수를 사용하여 WebSocket 세션을 처리하세요.

클라이언트에 대한 정보(예: 클라이언트의 IP 주소)를 얻으려면 call 속성을 사용하세요. 일반 요청 정보에 대해 알아보세요.

아래에서 이 API를 사용하는 예시를 살펴보겠습니다.

예시: 단일 세션 처리

아래 예시는 단일 클라이언트와의 세션을 처리하기 위해 echo WebSocket 엔드포인트를 생성하는 방법을 보여줍니다:

kotlin
routing {
    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(Frame.Text("Hi, $receivedText!"))
            }
        }
    }
}

전체 예시는 server-websockets를 참조하세요.

예시: 다중 세션 처리

여러 WebSocket 세션을 효율적으로 관리하고 브로드캐스팅을 처리하려면 Kotlin의 SharedFlow를 활용할 수 있습니다. 이 접근 방식은 WebSocket 통신을 관리하기 위한 확장 가능하고 동시성 친화적인 방법을 제공합니다. 이 패턴을 구현하는 방법은 다음과 같습니다:

  1. 메시지 브로드캐스팅을 위한 SharedFlow를 정의합니다:
kotlin
val messageResponseFlow = MutableSharedFlow<MessageResponse>()
val sharedFlow = messageResponseFlow.asSharedFlow()
  1. WebSocket 라우트에서 브로드캐스팅 및 메시지 처리 로직을 구현합니다:
kotlin

        webSocket("/ws") {
            send("You are connected to WebSocket!")

            val job = launch {
                sharedFlow.collect { message ->
                    send(message.message)
                }
            }

            runCatching {
                incoming.consumeEach { frame ->
                    if (frame is Frame.Text) {
                        val receivedText = frame.readText()
                        val messageResponse = MessageResponse(receivedText)
                        messageResponseFlow.emit(messageResponse)
                    }
                }
            }.onFailure { exception ->
                println("WebSocket exception: ${exception.localizedMessage}")
            }.also {
                job.cancel()
            }
        }

runCatching 블록은 수신 메시지를 처리하고 SharedFlow로 내보내며, SharedFlow는 모든 컬렉터에게 브로드캐스팅합니다.

이 패턴을 사용하면 개별 연결을 수동으로 추적하지 않고도 여러 WebSocket 세션을 효율적으로 관리할 수 있습니다. 이 접근 방식은 동시 WebSocket 연결이 많은 애플리케이션에 잘 맞고, 메시지 브로드캐스팅을 처리하는 깔끔하고 반응적인 방법을 제공합니다.

전체 예시는 server-websockets-sharedflow를 참조하세요.

WebSocket API와 Ktor

WebSocket API의 표준 이벤트는 Ktor에 다음과 같이 매핑됩니다:

  • onConnect는 블록의 시작 부분에서 발생합니다.
  • onMessage는 메시지를 성공적으로 읽은 후(예: incoming.receive() 사용) 또는 for(frame in incoming)과 같은 일시 중단된 반복을 사용할 때 발생합니다.
  • onCloseincoming 채널이 닫힐 때 발생합니다. 이는 일시 중단된 반복을 완료하거나 메시지를 수신하려고 할 때 ClosedReceiveChannelException을 발생시킵니다.
  • onError는 다른 예외와 동일합니다.

onCloseonError 모두에서 closeReason 속성이 설정됩니다.

다음 예시에서 무한 루프는 예외( ClosedReceiveChannelException 또는 다른 예외)가 발생할 때만 종료됩니다:

kotlin
webSocket("/echo") {
    println("onConnect")
    try {
        for (frame in incoming){
            val text = (frame as Frame.Text).readText()
            println("onMessage")
            received += text
            outgoing.send(Frame.Text(text))
        }
    } catch (e: ClosedReceiveChannelException) {
        println("onClose ${closeReason.await()}")
    } catch (e: Throwable) {
        println("onError ${closeReason.await()}")
        e.printStackTrace()
    }
}