Last active
September 7, 2020 06:01
-
-
Save ErikHellman/7f2b28a68fd01c6c079725083e8fc202 to your computer and use it in GitHub Desktop.
A very simple example of an echo-server using the non-blocking Java I/O APIs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package se.hellsoft.nonblocking.echo | |
import java.net.InetSocketAddress | |
import java.nio.ByteBuffer | |
import java.nio.channels.SelectionKey | |
import java.nio.channels.Selector | |
import java.nio.channels.ServerSocketChannel | |
import java.nio.channels.SocketChannel | |
/** | |
* A simple non-blocking echo server. | |
* | |
* To test this, start the application and use something like netcat to connect to the port (9191) | |
*/ | |
fun main() { | |
val selector = Selector.open() | |
val buffer = ByteBuffer.allocate(1024) | |
val serverChannel = ServerSocketChannel.open() | |
serverChannel.configureBlocking(false) | |
serverChannel.bind(InetSocketAddress(9191)) | |
serverChannel.register(selector, SelectionKey.OP_ACCEPT) | |
selector.selectNow() | |
while (true) { | |
val count = selector.select() | |
println("Selected $count channels!") | |
val selectedKeys = selector.selectedKeys() | |
selectedKeys.forEach { | |
if (!it.isValid) { | |
println("Invalid key - cancel it!") | |
it.cancel() | |
return@forEach | |
} | |
try { | |
when { | |
it.isAcceptable -> { // New connection on our ServerSocketChannel | |
val sc = it.channel() as ServerSocketChannel | |
val newClient = sc.accept() | |
if (newClient != null) { | |
println("New client!") | |
newClient.configureBlocking(false) | |
newClient.register(selector, SelectionKey.OP_READ) | |
} | |
} | |
it.isReadable && it.isReadInteresting -> { // Incoming data on a SocketChannel | |
val socket = it.channel() as SocketChannel | |
if (socket.isConnected) { | |
socket.read(buffer) | |
buffer.flip() | |
val content = ByteArray(buffer.limit()) | |
buffer.get(content) | |
val text = String(content, Charsets.UTF_8) | |
if (text.isNotEmpty()) { | |
println("Received: $text") | |
socket.register(selector, SelectionKey.OP_WRITE, content) | |
} | |
buffer.rewind() | |
} | |
} | |
it.isWritable && it.isWriteInteresting -> { // SocketChannel ready for writing | |
val socket = it.channel() as SocketChannel | |
if (socket.isConnected) { | |
val content = it.attachment() as ByteArray | |
buffer.put(content) | |
println("Wrote ${String(content, Charsets.UTF_8)} on channel") | |
buffer.flip() | |
socket.write(buffer) | |
socket.register(selector, SelectionKey.OP_READ, null) | |
buffer.rewind() | |
} | |
} | |
} | |
} catch (e: Exception) { | |
println("Closing channel!") | |
it.channel().close() | |
} | |
} | |
} | |
} | |
val SelectionKey.isReadInteresting | |
get() = this.interestOps() and SelectionKey.OP_READ == SelectionKey.OP_READ | |
val SelectionKey.isWriteInteresting | |
get() = this.interestOps() and SelectionKey.OP_WRITE == SelectionKey.OP_WRITE |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
How is this non-blocking?
selector.selectNow() -> is non blocking: it will almost never have anything.
selector.select() -> is blocking, so it will wait until at least one selector is selected, while(true) is redundant.