소켓
필수 의존성: io.ktor:ktor-network, io.ktor:ktor-network-tls
코드 예제: sockets-server, sockets-client, sockets-client-tls
Ktor는 서버 및 클라이언트를 위한 HTTP/WebSocket 처리 외에도 TCP 및 UDP 로우 소켓(raw sockets)을 지원합니다. 이는 내부적으로 java.nio를 사용하는 서스펜딩(suspending) API를 노출합니다.
소켓은 실험적인(experimental) API를 사용하며, 향후 업데이트에서 잠재적으로 파괴적인 변경 사항과 함께 진화할 것으로 예상됩니다.
의존성 추가
Sockets을 사용하려면 빌드 스크립트에 ktor-network 아티팩트를 포함해야 합니다:
클라이언트에서 보안 소켓을 사용하려면 io.ktor:ktor-network-tls도 추가해야 합니다.
서버
서버 소켓 생성
서버 소켓을 빌드하려면 SelectorManager 인스턴스를 생성하고, 해당 인스턴스에서 SocketBuilder.tcp() 함수를 호출한 다음, bind를 사용하여 서버 소켓을 특정 포트에 바인딩하십시오:
val selectorManager = SelectorManager(Dispatchers.IO)
val serverSocket = aSocket(selectorManager).tcp().bind("127.0.0.1", 9002)위의 코드 조각은 ServerSocket 인스턴스인 TCP 소켓을 생성합니다. UDP 소켓을 생성하려면 SocketBuilder.udp()를 사용하십시오.
들어오는 연결 수락
서버 소켓을 생성한 후에는 소켓 연결을 수락하고 연결된 소켓(Socket 인스턴스)을 반환하는 ServerSocket.accept 함수를 호출해야 합니다:
val socket = serverSocket.accept()연결된 소켓이 생성되면 소켓에서 읽거나 소켓에 씀으로써 데이터를 수신/전송할 수 있습니다.
데이터 수신
클라이언트로부터 데이터를 수신하려면 Socket.openReadChannel 함수를 호출해야 하며, 이 함수는 ByteReadChannel을 반환합니다:
val receiveChannel = socket.openReadChannel()ByteReadChannel은 데이터의 비동기 읽기를 위한 API를 제공합니다. 예를 들어, ByteReadChannel.readUTF8Line을 사용하여 UTF-8 문자열 한 줄을 읽을 수 있습니다:
val name = receiveChannel.readUTF8Line()데이터 전송
클라이언트에 데이터를 전송하려면 Socket.openWriteChannel 함수를 호출해야 하며, 이 함수는 ByteWriteChannel을 반환합니다:
val sendChannel = socket.openWriteChannel(autoFlush = true)ByteWriteChannel은 바이트 시퀀스의 비동기 쓰기를 위한 API를 제공합니다. 예를 들어, ByteWriteChannel.writeStringUtf8을 사용하여 UTF-8 문자열 한 줄을 쓸 수 있습니다:
val name = receiveChannel.readUTF8Line()
sendChannel.writeStringUtf8("Hello, $name!
")소켓 닫기
연결된 소켓과 관련된 리소스를 해제하려면 Socket.close를 호출하십시오:
socket.close()예제
아래 코드 예제는 서버 측에서 소켓을 사용하는 방법을 보여줍니다:
package com.example
import io.ktor.network.selector.*
import io.ktor.network.sockets.*
import io.ktor.utils.io.*
import kotlinx.coroutines.*
fun main(args: Array<String>) {
runBlocking {
val selectorManager = SelectorManager(Dispatchers.IO)
val serverSocket = aSocket(selectorManager).tcp().bind("127.0.0.1", 9002)
println("Server is listening at ${serverSocket.localAddress}")
while (true) {
val socket = serverSocket.accept()
println("Accepted ${socket.remoteAddress}")
launch {
val receiveChannel = socket.openReadChannel()
val sendChannel = socket.openWriteChannel(autoFlush = true)
sendChannel.writeStringUtf8("Please enter your name
")
try {
while (true) {
val name = receiveChannel.readUTF8Line()
sendChannel.writeStringUtf8("Hello, $name!
")
}
} catch (e: Throwable) {
socket.close()
}
}
}
}
}전체 예제는 여기서 확인할 수 있습니다: sockets-server.
클라이언트
소켓 생성
클라이언트 소켓을 빌드하려면 SelectorManager 인스턴스를 생성하고, 해당 인스턴스에서 SocketBuilder.tcp() 함수를 호출한 다음, connect를 사용하여 연결을 설정하고 연결된 소켓(Socket 인스턴스)을 얻으십시오:
val selectorManager = SelectorManager(Dispatchers.IO)
val socket = aSocket(selectorManager).tcp().connect("127.0.0.1", 9002)연결된 소켓이 생성되면 소켓에서 읽거나 소켓에 씀으로써 데이터를 수신/전송할 수 있습니다.
보안 소켓 생성 (SSL/TLS)
보안 소켓을 사용하면 TLS 연결을 설정할 수 있습니다. 보안 소켓을 사용하려면 ktor-network-tls 의존성을 추가해야 합니다. 그런 다음 연결된 소켓에서 Socket.tls 함수를 호출하십시오:
val selectorManager = SelectorManager(Dispatchers.IO)
val socket = aSocket(selectorManager).tcp().connect("127.0.0.1", 8443).tls()tls 함수를 사용하면 TLSConfigBuilder에서 제공하는 TLS 파라미터를 조정할 수 있습니다:
val selectorManager = SelectorManager(Dispatchers.IO)
val socket = aSocket(selectorManager).tcp().connect("youtrack.jetbrains.com", port = 443).tls(coroutineContext = coroutineContext) {
trustManager = object : X509TrustManager {
override fun getAcceptedIssuers(): Array<X509Certificate?> = arrayOf()
override fun checkClientTrusted(certs: Array<X509Certificate?>?, authType: String?) {}
override fun checkServerTrusted(certs: Array<X509Certificate?>?, authType: String?) {}
}
}전체 예제는 여기서 확인할 수 있습니다: sockets-client-tls.
데이터 수신
서버로부터 데이터를 수신하려면 Socket.openReadChannel 함수를 호출해야 하며, 이 함수는 ByteReadChannel을 반환합니다:
val receiveChannel = socket.openReadChannel()ByteReadChannel은 데이터의 비동기 읽기를 위한 API를 제공합니다. 예를 들어, ByteReadChannel.readUTF8Line을 사용하여 UTF-8 문자열 한 줄을 읽을 수 있습니다:
val greeting = receiveChannel.readUTF8Line()데이터 전송
서버에 데이터를 전송하려면 Socket.openWriteChannel 함수를 호출해야 하며, 이 함수는 ByteWriteChannel을 반환합니다:
val sendChannel = socket.openWriteChannel(autoFlush = true)ByteWriteChannel은 바이트 시퀀스의 비동기 쓰기를 위한 API를 제공합니다. 예를 들어, ByteWriteChannel.writeStringUtf8을 사용하여 UTF-8 문자열 한 줄을 쓸 수 있습니다:
val myMessage = readln()
sendChannel.writeStringUtf8("$myMessage
")연결 닫기
연결된 소켓과 관련된 리소스를 해제하려면 Socket.close 및 SelectorManager.close를 호출하십시오:
socket.close()
selectorManager.close()예제
아래 코드 예제는 클라이언트 측에서 소켓을 사용하는 방법을 보여줍니다:
package com.example
import io.ktor.network.selector.*
import io.ktor.network.sockets.*
import io.ktor.utils.io.*
import kotlinx.coroutines.*
import kotlin.system.*
fun main(args: Array<String>) {
runBlocking {
val selectorManager = SelectorManager(Dispatchers.IO)
val socket = aSocket(selectorManager).tcp().connect("127.0.0.1", 9002)
val receiveChannel = socket.openReadChannel()
val sendChannel = socket.openWriteChannel(autoFlush = true)
launch(Dispatchers.IO) {
while (true) {
val greeting = receiveChannel.readUTF8Line()
if (greeting != null) {
println(greeting)
} else {
println("Server closed a connection")
socket.close()
selectorManager.close()
exitProcess(0)
}
}
}
while (true) {
val myMessage = readln()
sendChannel.writeStringUtf8("$myMessage
")
}
}
}전체 예제는 여기서 확인할 수 있습니다: sockets-client.
