소켓
필수 의존성: io.ktor:ktor-network
, io.ktor:ktor-network-tls
코드 예시: sockets-server, sockets-client, sockets-client-tls
서버 및 클라이언트의 HTTP/WebSocket 처리 외에도 Ktor는 TCP 및 UDP 로우 소켓을 지원합니다. Ktor는 내부적으로 java.nio를 사용하는 코루틴 기반 API를 제공합니다.
소켓은 향후 업데이트에서 호환성이 깨지는 변경 사항과 함께 발전할 것으로 예상되는 실험적인 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)
위 코드 스니펫은 TCP 소켓인 ServerSocket 인스턴스를 생성합니다. UDP 소켓을 생성하려면 SocketBuilder.udp()
를 사용합니다.
수신 연결 수락
서버 소켓을 생성한 후에는 소켓 연결을 수락하고 연결된 소켓(Socket 인스턴스)을 반환하는 ServerSocket.accept
함수를 호출해야 합니다.
val socket = serverSocket.accept()
연결된 소켓을 얻으면 소켓에서 읽거나 소켓에 쓰는 방식으로 데이터를 송수신할 수 있습니다.
데이터 수신
클라이언트로부터 데이터를 수신하려면 ByteReadChannel을 반환하는 Socket.openReadChannel
함수를 호출해야 합니다.
val receiveChannel = socket.openReadChannel()
ByteReadChannel
은 비동기 데이터 읽기를 위한 API를 제공합니다. 예를 들어, ByteReadChannel.readUTF8Line
을 사용하여 UTF-8 문자열 한 줄을 읽을 수 있습니다.
val name = receiveChannel.readUTF8Line()
데이터 전송
클라이언트로 데이터를 전송하려면 ByteWriteChannel을 반환하는 Socket.openWriteChannel
함수를 호출합니다.
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")
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.
데이터 수신
서버로부터 데이터를 수신하려면 ByteReadChannel을 반환하는 Socket.openReadChannel
함수를 호출해야 합니다.
val receiveChannel = socket.openReadChannel()
ByteReadChannel
은 비동기 데이터 읽기를 위한 API를 제공합니다. 예를 들어, ByteReadChannel.readUTF8Line
을 사용하여 UTF-8 문자열 한 줄을 읽을 수 있습니다.
val greeting = receiveChannel.readUTF8Line()
데이터 전송
서버로 데이터를 전송하려면 ByteWriteChannel을 반환하는 Socket.openWriteChannel
함수를 호출합니다.
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.