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
- Server
- Simple Echo Server:
- Client
- Simple Client Connecting to an Echo Server:
- Secure Sockets (SSL/TLS)
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 socketsval 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
