Raw Sockets

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.

Table of contents:

Sockets

This functionality is exposed through the io.ktor:ktor-network:$ktor_version artifact.

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:

1. val exec = Executors.newCachedThreadPool()
2. val selector = ActorSelectorManager(exec.asCoroutineDispatcher())
3. 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:

1. val input : ByteReadChannel  = socket.openReadChannel()
2. val output: ByteWriteChannel = socket.openWriteChannel(autoFlush = true)

You can read the KDoc for ByteReadChanneland ByteWriteChannelfor further information on the available methods.

Server

When creating a server socket, you have to bind to a specific SocketAddress to geta ServerSocket:

1. 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:

1. val socket = server.accept()

If you want to support multiple clients at once, remember to call launch { } to preventthe function that is accepting the sockets from suspending.

Simple Echo Server:

echo-server.kt

1. fun main(args: Array<String>) {
2. runBlocking {
3. val server = aSocket(ActorSelectorManager(Dispatchers.IO)).tcp().bind(InetSocketAddress("127.0.0.1", 2323))
4. println("Started echo telnet server at ${server.localAddress}")

6. while (true) {
7. val socket = server.accept()

9. launch {
10. println("Socket accepted: ${socket.remoteAddress}")

12. val input = socket.openReadChannel()
13. val output = socket.openWriteChannel(autoFlush = true)

15. try {
16. while (true) {
17. val line = input.readUTF8Line()

19. println("${socket.remoteAddress}: $line")
20. output.writeBytes("$line\r\n")
21. }
22. } catch (e: Throwable) {
23. e.printStackTrace()
24. socket.close()
25. }
26. }
27. }
28. }
29. }

Then you can connect to it using telnet and start typing:

1. telnet 127.0.0.1 2323

For each line that you type (you have to press the return key), the server will replywith the same line:

1. Trying 127.0.0.1...
2. Connected to 127.0.0.1
3. Escape character is '^]'.

5. Hello
6. Hello
7. World
8. World
9. |

Client

When creating a socket client, you have to connect to a specific SocketAddress to geta Socket:

1. val socket = aSocket(selector).tcp().connect(InetSocketAddress("127.0.0.1", 2323))

Simple Client Connecting to an Echo Server:

echo-client.kt

1. fun main(args: Array<String>) {
2. runBlocking {
3. val socket = aSocket(ActorSelectorManager(Dispatchers.IO)).tcp().connect(InetSocketAddress("127.0.0.1", 2323))
4. val input = socket.openReadChannel()
5. val output = socket.openWriteChannel(autoFlush = true)

7. output.writeBytes("hello\r\n")
8. val response = input.readUTF8Line()
9. println("Server said: '$response'")
10. }
11. }

Secure Sockets (SSL/TLS)

Ktor supports secure sockets. To enable them you will need to include theio.ktor:ktor-network-tls:$ktor_version artifact, and call the .tls() to a connected socket.

Connect to a secure socket:

1. runBlocking {
2. val socket = aSocket(ActorSelectorManager(Dispatchers.IO)).tcp().connect(InetSocketAddress("google.com", 443)).tls()
3. val w = socket.openWriteChannel(autoFlush = false)
4. w.write("GET / HTTP/1.1\r\n")
5. w.write("Host: google.com\r\n")
6. w.write("\r\n")
7. w.flush()
8. val r = socket.openReadChannel()
9. println(r.readUTF8Line())
10. }

You can adjust a few optional parameters for the TLS connection:

1. suspend fun Socket.tls(
2. trustManager: X509TrustManager? = null,
3. randomAlgorithm: String = "NativePRNGNonBlocking",
4. serverName: String? = null,
5. coroutineContext: CoroutineContext = Dispatchers.IO
6. ): Socket