0.2.5-snapshot native support for tcp client/server
This commit is contained in:
1 parent
26d1f3522f
commit
ffcdcf7350
16 files changed
+524
-580
No files matched your search
@@ -1,118 +1,118 @@
|
||||
package net.sergeych.kiloparsec.adapter
|
||||
|
||||
import kotlinx.coroutines.*
|
||||
import kotlinx.coroutines.channels.BufferOverflow
|
||||
import kotlinx.coroutines.channels.Channel
|
||||
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.*
|
||||
import java.util.concurrent.atomic.AtomicInteger
|
||||
|
||||
private val counter = AtomicInteger(0)
|
||||
|
||||
class JvmNetworkAddress(val inetAddress: InetAddress, override val port: Int) : NetworkAddress {
|
||||
override val host: String by lazy { inetAddress.canonicalHostName }
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (this === other) return true
|
||||
if (other !is JvmNetworkAddress) return false
|
||||
|
||||
if (inetAddress != other.inetAddress) return false
|
||||
if (port != other.port) return false
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
val socketAddress: SocketAddress by lazy { InetSocketAddress(inetAddress,port) }
|
||||
|
||||
override fun hashCode(): Int {
|
||||
var result = inetAddress.hashCode()
|
||||
result = 31 * result + port
|
||||
return result
|
||||
}
|
||||
|
||||
override fun toString(): String = "$host:$port"
|
||||
}
|
||||
|
||||
class UdpDatagram(override val message: UByteArray, val inetAddress: InetAddress, val port: Int) : Datagram {
|
||||
|
||||
override val address: NetworkAddress by lazy {
|
||||
JvmNetworkAddress(inetAddress, port)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
@OptIn(DelicateCoroutinesApi::class)
|
||||
class UdpServer(val port: Int) :
|
||||
DatagramConnector, LogTag("UDPS:${counter.incrementAndGet()}") {
|
||||
private var isClosed = false
|
||||
|
||||
|
||||
private val deferredSocket = CompletableDeferred<DatagramSocket>()
|
||||
private var job: Job? = null
|
||||
|
||||
private suspend fun start() = try {
|
||||
coroutineScope {
|
||||
val socket = DatagramSocket(port)
|
||||
val buffer = ByteArray(16384)
|
||||
val packet = DatagramPacket(buffer, buffer.size)
|
||||
deferredSocket.complete(socket)
|
||||
while (isActive && !isClosed) {
|
||||
try {
|
||||
socket.receive(packet)
|
||||
val data = packet.data.sliceArray(0..<packet.length)
|
||||
val datagram = UdpDatagram(data.toUByteArray(), packet.address, packet.port)
|
||||
if (!channel.trySend(datagram).isSuccess) {
|
||||
warning { "packet lost!" }
|
||||
// and we cause overflow that overwrites the oldest
|
||||
channel.send(datagram)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
if (!isClosed)
|
||||
e.printStackTrace()
|
||||
throw e
|
||||
}
|
||||
}
|
||||
info { "closing socket and reception loop" }
|
||||
|
||||
}
|
||||
} catch (_: CancellationException) {
|
||||
info { "server is closed" }
|
||||
} catch (t: Throwable) {
|
||||
exception { "unexpected end of server" to t }
|
||||
}
|
||||
|
||||
init {
|
||||
job = GlobalScope.launch(Dispatchers.IO) {
|
||||
start()
|
||||
}
|
||||
}
|
||||
|
||||
override fun close() {
|
||||
if (!isClosed) {
|
||||
if (deferredSocket.isCompleted) {
|
||||
runCatching {
|
||||
deferredSocket.getCompleted().close()
|
||||
}
|
||||
}
|
||||
isClosed = true
|
||||
job?.cancel(); job = null
|
||||
}
|
||||
}
|
||||
|
||||
private val channel = Channel<Datagram>(2048, BufferOverflow.DROP_OLDEST)
|
||||
override val incoming = channel
|
||||
|
||||
override suspend fun send(message: UByteArray, networkAddress: NetworkAddress) {
|
||||
networkAddress as JvmNetworkAddress
|
||||
withContext(Dispatchers.IO) {
|
||||
val packet = DatagramPacket(
|
||||
message.toByteArray(), message.size,
|
||||
networkAddress.inetAddress, networkAddress.port
|
||||
)
|
||||
deferredSocket.await().send(packet)
|
||||
}
|
||||
}
|
||||
}
|
||||
//package net.sergeych.kiloparsec.adapter
|
||||
//
|
||||
//import kotlinx.coroutines.*
|
||||
//import kotlinx.coroutines.channels.BufferOverflow
|
||||
//import kotlinx.coroutines.channels.Channel
|
||||
//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.*
|
||||
//import java.util.concurrent.atomic.AtomicInteger
|
||||
//
|
||||
//private val counter = AtomicInteger(0)
|
||||
//
|
||||
//class JvmNetworkAddress(val inetAddress: InetAddress, override val port: Int) : NetworkAddress {
|
||||
// override val host: String by lazy { inetAddress.canonicalHostName }
|
||||
// override fun equals(other: Any?): Boolean {
|
||||
// if (this === other) return true
|
||||
// if (other !is JvmNetworkAddress) return false
|
||||
//
|
||||
// if (inetAddress != other.inetAddress) return false
|
||||
// if (port != other.port) return false
|
||||
//
|
||||
// return true
|
||||
// }
|
||||
//
|
||||
// val socketAddress: SocketAddress by lazy { InetSocketAddress(inetAddress,port) }
|
||||
//
|
||||
// override fun hashCode(): Int {
|
||||
// var result = inetAddress.hashCode()
|
||||
// result = 31 * result + port
|
||||
// return result
|
||||
// }
|
||||
//
|
||||
// override fun toString(): String = "$host:$port"
|
||||
//}
|
||||
//
|
||||
//class UdpDatagram(override val message: UByteArray, val inetAddress: InetAddress, val port: Int) : Datagram {
|
||||
//
|
||||
// override val address: NetworkAddress by lazy {
|
||||
// JvmNetworkAddress(inetAddress, port)
|
||||
// }
|
||||
//
|
||||
//}
|
||||
//
|
||||
//
|
||||
//@OptIn(DelicateCoroutinesApi::class)
|
||||
//class UdpServer(val port: Int) :
|
||||
// DatagramConnector, LogTag("UDPS:${counter.incrementAndGet()}") {
|
||||
// private var isClosed = false
|
||||
//
|
||||
//
|
||||
// private val deferredSocket = CompletableDeferred<DatagramSocket>()
|
||||
// private var job: Job? = null
|
||||
//
|
||||
// private suspend fun start() = try {
|
||||
// coroutineScope {
|
||||
// val socket = DatagramSocket(port)
|
||||
// val buffer = ByteArray(16384)
|
||||
// val packet = DatagramPacket(buffer, buffer.size)
|
||||
// deferredSocket.complete(socket)
|
||||
// while (isActive && !isClosed) {
|
||||
// try {
|
||||
// socket.receive(packet)
|
||||
// val data = packet.data.sliceArray(0..<packet.length)
|
||||
// val datagram = UdpDatagram(data.toUByteArray(), packet.address, packet.port)
|
||||
// if (!channel.trySend(datagram).isSuccess) {
|
||||
// warning { "packet lost!" }
|
||||
// // and we cause overflow that overwrites the oldest
|
||||
// channel.send(datagram)
|
||||
// }
|
||||
// } catch (e: Exception) {
|
||||
// if (!isClosed)
|
||||
// e.printStackTrace()
|
||||
// throw e
|
||||
// }
|
||||
// }
|
||||
// info { "closing socket and reception loop" }
|
||||
//
|
||||
// }
|
||||
// } catch (_: CancellationException) {
|
||||
// info { "server is closed" }
|
||||
// } catch (t: Throwable) {
|
||||
// exception { "unexpected end of server" to t }
|
||||
// }
|
||||
//
|
||||
// init {
|
||||
// job = GlobalScope.launch(Dispatchers.IO) {
|
||||
// start()
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// override fun close() {
|
||||
// if (!isClosed) {
|
||||
// if (deferredSocket.isCompleted) {
|
||||
// runCatching {
|
||||
// deferredSocket.getCompleted().close()
|
||||
// }
|
||||
// }
|
||||
// isClosed = true
|
||||
// job?.cancel(); job = null
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// private val channel = Channel<Datagram>(2048, BufferOverflow.DROP_OLDEST)
|
||||
// override val incoming = channel
|
||||
//
|
||||
// override suspend fun send(message: UByteArray, networkAddress: NetworkAddress) {
|
||||
// networkAddress as JvmNetworkAddress
|
||||
// withContext(Dispatchers.IO) {
|
||||
// val packet = DatagramPacket(
|
||||
// message.toByteArray(), message.size,
|
||||
// networkAddress.inetAddress, networkAddress.port
|
||||
// )
|
||||
// deferredSocket.await().send(packet)
|
||||
// }
|
||||
// }
|
||||
//}
|
||||
@@ -1,155 +1,155 @@
|
||||
package net.sergeych.kiloparsec.adapter
|
||||
|
||||
import kotlinx.coroutines.*
|
||||
import kotlinx.coroutines.channels.Channel
|
||||
import kotlinx.coroutines.channels.ClosedReceiveChannelException
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import net.sergeych.crypto2.Contrail
|
||||
import net.sergeych.crypto2.encodeVarUnsigned
|
||||
import net.sergeych.crypto2.readVarUnsigned
|
||||
import net.sergeych.kiloparsec.RemoteInterface
|
||||
import net.sergeych.kiloparsec.Transport
|
||||
import net.sergeych.mp_logger.LogTag
|
||||
import net.sergeych.mp_logger.warning
|
||||
import net.sergeych.mp_tools.globalLaunch
|
||||
import net.sergeych.tools.waitFor
|
||||
import java.net.InetSocketAddress
|
||||
import java.net.StandardSocketOptions.TCP_NODELAY
|
||||
import java.nio.ByteBuffer
|
||||
import java.nio.channels.AsynchronousSocketChannel
|
||||
import kotlin.coroutines.cancellation.CancellationException
|
||||
import kotlin.coroutines.suspendCoroutine
|
||||
|
||||
private val log = LogTag("ASTD")
|
||||
|
||||
/**
|
||||
* Prepend block with its size, varint-encoded
|
||||
*/
|
||||
private fun encode(block: UByteArray): ByteArray {
|
||||
val c = Contrail.create(block)
|
||||
return (encodeVarUnsigned(c.size.toUInt()) + c).toByteArray()
|
||||
}
|
||||
|
||||
/**
|
||||
* 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): InetTransportDevice {
|
||||
val deferredDevice = CompletableDeferred<InetTransportDevice>()
|
||||
globalLaunch {
|
||||
coroutineScope {
|
||||
val sendQueueEmpty = MutableStateFlow(true)
|
||||
val receiving = MutableStateFlow(false)
|
||||
// We're in block mode, every block we send worth immediate sending, we do not
|
||||
// send partial blocks, so:
|
||||
socket.setOption(TCP_NODELAY, true)
|
||||
|
||||
// socket input is to be parsed for blocks, so we receive bytes
|
||||
// and decode them to blocks
|
||||
val input = Channel<UByte>(1024)
|
||||
val inputBlocks = Channel<UByteArray>()
|
||||
// output is blocks, so we sent transformed, framed blocks:
|
||||
val outputBlocks = Channel<UByteArray>()
|
||||
|
||||
fun stop() {
|
||||
kotlin.runCatching { inputBlocks.close(RemoteInterface.ClosedException()) }
|
||||
kotlin.runCatching { outputBlocks.close() }
|
||||
socket.close()
|
||||
cancel()
|
||||
}
|
||||
|
||||
|
||||
// copy incoming data from the socket to input channel:
|
||||
launch {
|
||||
val data = ByteArray(1024)
|
||||
val inb = ByteBuffer.wrap(data)
|
||||
kotlin.runCatching {
|
||||
while (isActive) {
|
||||
inb.position(0)
|
||||
val size: Int = suspendCoroutine { continuation ->
|
||||
socket.read(inb, continuation, IntCompletionHandler)
|
||||
}
|
||||
if (size < 0) stop()
|
||||
else {
|
||||
// println("recvd:\n${data.sliceArray(0..<size).toDump()}\n------------------")
|
||||
for (i in 0..<size) input.send(data[i].toUByte())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// copy from output to socket:
|
||||
launch {
|
||||
try {
|
||||
while (isActive) {
|
||||
// wait for the first block to send
|
||||
sendQueueEmpty.value = outputBlocks.isEmpty
|
||||
var data = encode(outputBlocks.receive())
|
||||
|
||||
// now we're sending, so queue state is sending:
|
||||
sendQueueEmpty.value = false
|
||||
|
||||
// if there are more, take them all (NO_DELAY optimization)
|
||||
while (!outputBlocks.isEmpty)
|
||||
data += encode(outputBlocks.receive())
|
||||
|
||||
// now send it all together:
|
||||
val outBuff = ByteBuffer.wrap(data)
|
||||
val cnt = suspendCoroutine { continuation ->
|
||||
socket.write(outBuff, continuation, IntCompletionHandler)
|
||||
}
|
||||
// be sure it was all sent
|
||||
if (outBuff.position() != data.size || cnt != data.size) {
|
||||
throw RuntimeException("unexpected partial write")
|
||||
}
|
||||
}
|
||||
// in the case of just breaking out of the loop:
|
||||
sendQueueEmpty.value = true
|
||||
} catch (_: ClosedReceiveChannelException) {
|
||||
stop()
|
||||
}
|
||||
}
|
||||
// transport device copes with blocks:
|
||||
// decode blocks from a byte channel read from the socket:
|
||||
launch {
|
||||
try {
|
||||
while (isActive) {
|
||||
receiving.value = !input.isEmpty
|
||||
val size = readVarUnsigned(input)
|
||||
receiving.value = true
|
||||
if (size == 0u) log.warning { "zero size block is ignored!" }
|
||||
else {
|
||||
val block = UByteArray(size.toInt())
|
||||
for (i in 0..<size.toInt()) {
|
||||
block[i] = input.receive()
|
||||
}
|
||||
Contrail.unpack(block)?.let { inputBlocks.send(it) }
|
||||
?: log.warning { "skipping bad block ${block.size} bytes" }
|
||||
}
|
||||
}
|
||||
} catch (_: CancellationException) {
|
||||
} catch (_: ClosedReceiveChannelException) {
|
||||
stop()
|
||||
}
|
||||
receiving.value = false
|
||||
}
|
||||
|
||||
val addr = socket.remoteAddress as InetSocketAddress
|
||||
deferredDevice.complete(
|
||||
InetTransportDevice(inputBlocks, outputBlocks, JvmNetworkAddress(addr.address, addr.port), {
|
||||
yield()
|
||||
// wait until all received data are parsed, but not too long
|
||||
withTimeoutOrNull(500) {
|
||||
receiving.waitFor { !it }
|
||||
}
|
||||
// then stop it
|
||||
stop()
|
||||
})
|
||||
)
|
||||
}
|
||||
globalLaunch { socket.close() }
|
||||
}
|
||||
return deferredDevice.await()
|
||||
}
|
||||
//package net.sergeych.kiloparsec.adapter
|
||||
//
|
||||
//import kotlinx.coroutines.*
|
||||
//import kotlinx.coroutines.channels.Channel
|
||||
//import kotlinx.coroutines.channels.ClosedReceiveChannelException
|
||||
//import kotlinx.coroutines.flow.MutableStateFlow
|
||||
//import net.sergeych.crypto2.Contrail
|
||||
//import net.sergeych.crypto2.encodeVarUnsigned
|
||||
//import net.sergeych.crypto2.readVarUnsigned
|
||||
//import net.sergeych.kiloparsec.RemoteInterface
|
||||
//import net.sergeych.kiloparsec.Transport
|
||||
//import net.sergeych.mp_logger.LogTag
|
||||
//import net.sergeych.mp_logger.warning
|
||||
//import net.sergeych.mp_tools.globalLaunch
|
||||
//import net.sergeych.tools.waitFor
|
||||
//import java.net.InetSocketAddress
|
||||
//import java.net.StandardSocketOptions.TCP_NODELAY
|
||||
//import java.nio.ByteBuffer
|
||||
//import java.nio.channels.AsynchronousSocketChannel
|
||||
//import kotlin.coroutines.cancellation.CancellationException
|
||||
//import kotlin.coroutines.suspendCoroutine
|
||||
//
|
||||
//private val log = LogTag("ASTD")
|
||||
//
|
||||
///**
|
||||
// * Prepend block with its size, varint-encoded
|
||||
// */
|
||||
//private fun encode(block: UByteArray): ByteArray {
|
||||
// val c = Contrail.create(block)
|
||||
// return (encodeVarUnsigned(c.size.toUInt()) + c).toByteArray()
|
||||
//}
|
||||
//
|
||||
///**
|
||||
// * 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): InetTransportDevice {
|
||||
// val deferredDevice = CompletableDeferred<InetTransportDevice>()
|
||||
// globalLaunch {
|
||||
// coroutineScope {
|
||||
// val sendQueueEmpty = MutableStateFlow(true)
|
||||
// val receiving = MutableStateFlow(false)
|
||||
// // We're in block mode, every block we send worth immediate sending, we do not
|
||||
// // send partial blocks, so:
|
||||
// socket.setOption(TCP_NODELAY, true)
|
||||
//
|
||||
// // socket input is to be parsed for blocks, so we receive bytes
|
||||
// // and decode them to blocks
|
||||
// val input = Channel<UByte>(1024)
|
||||
// val inputBlocks = Channel<UByteArray>()
|
||||
// // output is blocks, so we sent transformed, framed blocks:
|
||||
// val outputBlocks = Channel<UByteArray>()
|
||||
//
|
||||
// fun stop() {
|
||||
// kotlin.runCatching { inputBlocks.close(RemoteInterface.ClosedException()) }
|
||||
// kotlin.runCatching { outputBlocks.close() }
|
||||
// socket.close()
|
||||
// cancel()
|
||||
// }
|
||||
//
|
||||
//
|
||||
// // copy incoming data from the socket to input channel:
|
||||
// launch {
|
||||
// val data = ByteArray(1024)
|
||||
// val inb = ByteBuffer.wrap(data)
|
||||
// kotlin.runCatching {
|
||||
// while (isActive) {
|
||||
// inb.position(0)
|
||||
// val size: Int = suspendCoroutine { continuation ->
|
||||
// socket.read(inb, continuation, IntCompletionHandler)
|
||||
// }
|
||||
// if (size < 0) stop()
|
||||
// else {
|
||||
//// println("recvd:\n${data.sliceArray(0..<size).toDump()}\n------------------")
|
||||
// for (i in 0..<size) input.send(data[i].toUByte())
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// // copy from output to socket:
|
||||
// launch {
|
||||
// try {
|
||||
// while (isActive) {
|
||||
// // wait for the first block to send
|
||||
// sendQueueEmpty.value = outputBlocks.isEmpty
|
||||
// var data = encode(outputBlocks.receive())
|
||||
//
|
||||
// // now we're sending, so queue state is sending:
|
||||
// sendQueueEmpty.value = false
|
||||
//
|
||||
// // if there are more, take them all (NO_DELAY optimization)
|
||||
// while (!outputBlocks.isEmpty)
|
||||
// data += encode(outputBlocks.receive())
|
||||
//
|
||||
// // now send it all together:
|
||||
// val outBuff = ByteBuffer.wrap(data)
|
||||
// val cnt = suspendCoroutine { continuation ->
|
||||
// socket.write(outBuff, continuation, IntCompletionHandler)
|
||||
// }
|
||||
// // be sure it was all sent
|
||||
// if (outBuff.position() != data.size || cnt != data.size) {
|
||||
// throw RuntimeException("unexpected partial write")
|
||||
// }
|
||||
// }
|
||||
// // in the case of just breaking out of the loop:
|
||||
// sendQueueEmpty.value = true
|
||||
// } catch (_: ClosedReceiveChannelException) {
|
||||
// stop()
|
||||
// }
|
||||
// }
|
||||
// // transport device copes with blocks:
|
||||
// // decode blocks from a byte channel read from the socket:
|
||||
// launch {
|
||||
// try {
|
||||
// while (isActive) {
|
||||
// receiving.value = !input.isEmpty
|
||||
// val size = readVarUnsigned(input)
|
||||
// receiving.value = true
|
||||
// if (size == 0u) log.warning { "zero size block is ignored!" }
|
||||
// else {
|
||||
// val block = UByteArray(size.toInt())
|
||||
// for (i in 0..<size.toInt()) {
|
||||
// block[i] = input.receive()
|
||||
// }
|
||||
// Contrail.unpack(block)?.let { inputBlocks.send(it) }
|
||||
// ?: log.warning { "skipping bad block ${block.size} bytes" }
|
||||
// }
|
||||
// }
|
||||
// } catch (_: CancellationException) {
|
||||
// } catch (_: ClosedReceiveChannelException) {
|
||||
// stop()
|
||||
// }
|
||||
// receiving.value = false
|
||||
// }
|
||||
//
|
||||
// val addr = socket.remoteAddress as InetSocketAddress
|
||||
// deferredDevice.complete(
|
||||
// InetTransportDevice(inputBlocks, outputBlocks, JvmNetworkAddress(addr.address, addr.port), {
|
||||
// yield()
|
||||
// // wait until all received data are parsed, but not too long
|
||||
// withTimeoutOrNull(500) {
|
||||
// receiving.waitFor { !it }
|
||||
// }
|
||||
// // then stop it
|
||||
// stop()
|
||||
// })
|
||||
// )
|
||||
// }
|
||||
// globalLaunch { socket.close() }
|
||||
// }
|
||||
// return deferredDevice.await()
|
||||
//}
|
||||
Reference in new issue
Block a user