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}")
  5. while (true) {
  6. val socket = server.accept()
  7. launch {
  8. println("Socket accepted: ${socket.remoteAddress}")
  9. val input = socket.openReadChannel()
  10. val output = socket.openWriteChannel(autoFlush = true)
  11. try {
  12. while (true) {
  13. val line = input.readUTF8Line()
  14. println("${socket.remoteAddress}: $line")
  15. output.writeBytes("$line\r\n")
  16. }
  17. } catch (e: Throwable) {
  18. e.printStackTrace()
  19. socket.close()
  20. }
  21. }
  22. }
  23. }
  24. }

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 '^]'.
  4. Hello
  5. Hello
  6. World
  7. World
  8. |

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)
  6. output.writeBytes("hello\r\n")
  7. val response = input.readUTF8Line()
  8. println("Server said: '$response'")
  9. }
  10. }

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