Sockets

note

Sockets - 图1

This help topic is in development and will be updated in the future.

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.

Sockets

note

Sockets - 图2

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)

note

Sockets - 图3

You can read the KDoc for ByteReadChannel and ByteWriteChannel for further information on the available methods.

Server

When creating a server socket, you have to bind to a specific SocketAddress to get a 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()

note

Sockets - 图4

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

Simple Echo Server:

  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.write("$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 reply with 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 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. }

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:

  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