basic tcp connect async inmplementation for JVM/NIO
This commit is contained in:
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()
|
||||
}
|
||||
Reference in new issue
Block a user