basic tcp connect async inmplementation for JVM/NIO

This commit is contained in:
sergeych committed 2023-11-14 01:47:30 +03:00
1 parent 8fc24567f0
commit fe29bec1b0
13 files changed
+249 -48

No files matched your search

@@ -0,0 +1,22 @@
package net.sergeych.kiloparsec.adapter
import java.nio.channels.CompletionHandler
import kotlin.coroutines.Continuation
import kotlin.coroutines.resume
import kotlin.coroutines.resumeWithException
open class ContinuationHandler<T> : CompletionHandler<T, Continuation<T>> {
override fun completed(result: T, attachment: Continuation<T>) {
println("completed $result")
attachment.resume(result)
}
override fun failed(exc: Throwable, attachment: Continuation<T>) {
println("failed $exc")
attachment.resumeWithException(exc)
}
}
object VoidCompletionHandler: ContinuationHandler<Void>()
object IntCompletionHandler: ContinuationHandler<Int>()
@@ -1,8 +0,0 @@
package net.sergeych.kiloparsec.adapter
import java.net.InetAddress
actual fun networkAddressOf(address: String): NetworkAddress {
val (host,port) = address.split(":")
return JvmNetworkAddress(InetAddress.getByName(host), port.toInt())
}
@@ -1,6 +1,37 @@
package net.sergeych.kiloparsec.adapter
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.channels.SendChannel
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.withContext
import net.sergeych.kiloparsec.Transport
import java.net.InetAddress
import java.nio.channels.AsynchronousSocketChannel
import kotlin.coroutines.suspendCoroutine
actual fun NetworkAddress(host: String, port: Int): NetworkAddress =
JvmNetworkAddress(InetAddress.getByName(host), port)
actual fun acceptTcpDevice(pord: Int): Flow<Transport.Device> {
return flow {
TODO("Not yet implemented")
}
}
actual suspend fun connectTcpDevice(address: NetworkAddress): Transport.Device {
address as JvmNetworkAddress
val socket = withContext(Dispatchers.IO) {
AsynchronousSocketChannel.open()
}
suspendCoroutine { cont ->
socket.connect(address.socketAddress, cont, VoidCompletionHandler)
}
println("connected")
return asyncSocketToDevice(socket)
}
suspend fun SendChannel<UByte>.sendAll(bytes: Collection<UByte>) {
for( b in bytes) send(b)
}
@@ -3,15 +3,11 @@ package net.sergeych.kiloparsec.adapter
import kotlinx.coroutines.*
import kotlinx.coroutines.channels.BufferOverflow
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import net.sergeych.mp_logger.LogTag
import net.sergeych.mp_logger.exception
import net.sergeych.mp_logger.info
import net.sergeych.mp_logger.warning
import java.net.DatagramPacket
import java.net.DatagramSocket
import java.net.InetAddress
import java.net.*
import java.util.concurrent.atomic.AtomicInteger
private val counter = AtomicInteger(0)
@@ -28,6 +24,8 @@ class JvmNetworkAddress(val inetAddress: InetAddress, override val port: Int) :
return true
}
val socketAddress: SocketAddress by lazy { InetSocketAddress(inetAddress,port) }
override fun hashCode(): Int {
var result = inetAddress.hashCode()
result = 31 * result + port
@@ -42,28 +40,12 @@ class UdpDatagram(override val message: UByteArray, val inetAddress: InetAddress
JvmNetworkAddress(inetAddress, port)
}
private val access = Mutex()
private var socket: DatagramSocket? = null
override suspend fun respondWith(message: UByteArray) {
withContext(Dispatchers.IO) {
access.withLock {
if (socket == null) socket = DatagramSocket()
val packet = DatagramPacket(
message.toByteArray(),
message.size,
inetAddress,
port
)
socket!!.send(packet)
}
}
}
}
@OptIn(DelicateCoroutinesApi::class)
class UdpServer(val port: Int) :
DatagramReceiver, LogTag("UDPS:${counter.incrementAndGet()}") {
DatagramConnector, LogTag("UDPS:${counter.incrementAndGet()}") {
private var isClosed = false
@@ -0,0 +1,101 @@
package net.sergeych.kiloparsec.adapter
import kotlinx.coroutines.CompletableDeferred
import kotlinx.coroutines.cancel
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.channels.ClosedReceiveChannelException
import kotlinx.coroutines.channels.ClosedSendChannelException
import kotlinx.coroutines.isActive
import kotlinx.coroutines.launch
import net.sergeych.crypto.encodeVarUnsigned
import net.sergeych.crypto.readVarUnsigned
import net.sergeych.kiloparsec.Transport
import net.sergeych.mp_tools.globalLaunch
import java.nio.ByteBuffer
import java.nio.channels.AsynchronousSocketChannel
import kotlin.coroutines.cancellation.CancellationException
import kotlin.coroutines.suspendCoroutine
/**
* Convert asynchronous socket to a [Transport.Device] using non-blocking nio,
* in a coroutine-effective manner. Note that it runs coroutines to read/write
* to the socket in a global scope.These are closed when transport is closed
* or the socket is closed, for example, by network failure.
*/
suspend fun asyncSocketToDevice(socket: AsynchronousSocketChannel): Transport.Device {
val deferredDevice = CompletableDeferred<Transport.Device>()
globalLaunch {
fun stop() {
cancel()
runCatching { socket.close() }
}
val input = Channel<UByte>(1024)
val output = Channel<UByte>(1024)
// copy from socket to input
launch {
val inb = ByteBuffer.allocate(1024)
while (isActive) {
val size: Int = suspendCoroutine { continuation ->
socket.read(inb, continuation, IntCompletionHandler)
}
if (size < 0) stop()
else for (i in 0..<size) input.send(inb[i].toUByte().also { print(it.toInt().toChar())})
}
}
// copy from output tp socket
launch {
val outb = ByteBuffer.allocate(1024)
try {
while (isActive) {
var count = 0
outb.put(count++, output.receive().toByte())
while (!output.isEmpty && count < outb.capacity())
outb.put(count++, output.receive().toByte())
suspendCoroutine { continuation ->
socket.write(outb, continuation, IntCompletionHandler)
}
}
} catch (_: ClosedReceiveChannelException) {
stop()
}
}
// pump blocks from socket output to device input
val inputBlocks = Channel<UByteArray?>()
launch {
try {
while (isActive) {
val size = readVarUnsigned(input)
if (size == 0u) println("*** zero size block is ignored!")
else {
val block = UByteArray(size.toInt())
for (i in 0..<size.toInt()) {
block[i] = input.receive()
}
inputBlocks.send(block)
}
}
} catch (_: CancellationException) {
inputBlocks.send(null)
} catch (_: ClosedReceiveChannelException) {
inputBlocks.send(null)
stop()
}
}
val outputBlocks = Channel<UByteArray>()
launch {
try {
while (isActive) {
val block = outputBlocks.receive()
output.sendAll(encodeVarUnsigned(block.size.toUInt()))
output.sendAll(block)
}
} catch (_: ClosedSendChannelException) {
stop()
}
}
deferredDevice.complete(
ProxyDevice(inputBlocks, outputBlocks) { stop() }
)
}
return deferredDevice.await()
}