Sockets

In addition to HTTP handling for the server and the , Ktor supports client and server, TCP and UDP raw sockets. It exposes a suspending API that uses NIO under the hoods.

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

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:

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

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

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

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

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

Simple Client Connecting to an Echo Server:

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

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.

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