In addition to HTTP handling for the server and the client, Ktor supports client and server, TCP and UDP raw sockets. It exposes a suspending API that uses NIO under the hoods.
Add dependencies
To enable Sockets support, you need to include the ktor-network artifact in the build script:
In order to create either server or client sockets, you have to use the aSocket builder, with a mandatory ActorSelectorManager: aSocket(selector). For example: aSocket(ActorSelectorManager(Dispatchers.IO)).
Then use:
val socketBuilder = aSocket(selector).tcp() for a builder using TCP sockets
val socketBuilder = aSocket(selector).udp() for a builder using UDP sockets
This returns a SocketBuilder that can be used to:
val serverSocket = aSocket(selector).tcp().bind(address) to listen to an address (for servers)
val clientSocket = aSocket(selector).tcp().connect(address) to connect to an address (for clients)
If you need to control the dispatcher used by the sockets, you can instantiate a selector, that uses, for example, a cached thread pool:
val exec = Executors.newCachedThreadPool()
val selector = ActorSelectorManager(exec.asCoroutineDispatcher())
val tcpSocketBuilder = aSocket(selector).tcp()
Once you have a socket open by either binding or connecting the builder, you can read from or write to the socket, by opening read/write channels:
val input : ByteReadChannel = socket.openReadChannel()
val output: ByteWriteChannel = socket.openWriteChannel(autoFlush = true)
Server
When creating a server socket, you have to bind to a specific SocketAddress to get a ServerSocket:
val server = aSocket(selector).tcp().bind(InetSocketAddress("127.0.0.1", 2323))
The server socket has an accept method that returns, one at a time, a connected socket for each incoming connection pending in the backlog:
val socket = server.accept()
Simple Echo Server:
fun main(args: Array<String>) {
runBlocking {
val server = aSocket(ActorSelectorManager(Dispatchers.IO)).tcp().bind(InetSocketAddress("127.0.0.1", 2323))
println("Started echo telnet server at ${server.localAddress}")
while (true) {
val socket = server.accept()
launch {
println("Socket accepted: ${socket.remoteAddress}")
val input = socket.openReadChannel()
val output = socket.openWriteChannel(autoFlush = true)
try {
while (true) {
val line = input.readUTF8Line()
println("${socket.remoteAddress}: $line")
output.write("$line\r\n")
}
} catch (e: Throwable) {
e.printStackTrace()
socket.close()
}
}
}
}
}
Then you can connect to it using telnet and start typing:
telnet 127.0.0.1 2323
For each line that you type (you have to press the return key), the server will reply with the same line:
Trying 127.0.0.1...
Connected to 127.0.0.1
Escape character is '^]'.
Hello
Hello
World
World
|
Client
When creating a socket client, you have to connect to a specific SocketAddress to get a Socket:
val socket = aSocket(selector).tcp().connect(InetSocketAddress("127.0.0.1", 2323))
Simple Client Connecting to an Echo Server:
fun main(args: Array<String>) {
runBlocking {
val socket = aSocket(ActorSelectorManager(Dispatchers.IO)).tcp().connect(InetSocketAddress("127.0.0.1", 2323))
val input = socket.openReadChannel()
val output = socket.openWriteChannel(autoFlush = true)
output.write("hello\r\n")
val response = input.readUTF8Line()
println("Server said: '$response'")
}
}
Secure sockets (SSL/TLS)
Ktor supports secure sockets. To enable them you will need to include the io.ktor:ktor-network-tls:$ktor_version artifact, and call the .tls() to a connected socket.
Connect to a secure socket:
runBlocking {
val socket = aSocket(ActorSelectorManager(Dispatchers.IO)).tcp().connect(InetSocketAddress("google.com", 443)).tls()
val w = socket.openWriteChannel(autoFlush = false)
w.write("GET / HTTP/1.1\r\n")
w.write("Host: google.com\r\n")
w.write("\r\n")
w.flush()
val r = socket.openReadChannel()
println(r.readUTF8Line())
}
You can adjust a few optional parameters for the TLS connection: