fixed automatic reconnection in TCP client/server

This commit is contained in:
sergeych committed 2024-06-17 17:42:16 +07:00
1 parent 0d3a8ae95c
commit 825c0bd5f7
7 files changed
+95 -49

No files matched your search

@@ -6,8 +6,10 @@ import kotlinx.coroutines.channels.ClosedReceiveChannelException
import kotlinx.coroutines.flow.MutableStateFlow
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.info
import net.sergeych.mp_logger.warning
import net.sergeych.mp_tools.globalLaunch
import net.sergeych.tools.waitFor
@@ -37,9 +39,6 @@ suspend fun asyncSocketToDevice(socket: AsynchronousSocketChannel): InetTranspor
coroutineScope {
val sendQueueEmpty = MutableStateFlow(true)
val receiving = MutableStateFlow(false)
fun stop() {
cancel()
}
// We're in block mode, every block we send worth immediate sending, we do not
// send partial blocks, so:
socket.setOption(TCP_NODELAY, true)
@@ -47,25 +46,37 @@ suspend fun asyncSocketToDevice(socket: AsynchronousSocketChannel): InetTranspor
// 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)
while (isActive) {
inb.position(0)
val size: Int = suspendCoroutine { continuation ->
socket.read(inb, continuation, IntCompletionHandler)
}
if (size < 0) stop()
else {
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())
for (i in 0..<size) input.send(data[i].toUByte())
}
}
}
}
// output is blocks, so we sent transformed, framed blocks:
val outputBlocks = Channel<UByteArray>()
// copy from output to socket:
launch {
try {
@@ -98,7 +109,6 @@ suspend fun asyncSocketToDevice(socket: AsynchronousSocketChannel): InetTranspor
}
}
// transport device copes with blocks:
val inputBlocks = Channel<UByteArray>()
// decode blocks from a byte channel read from the socket:
launch {
try {
@@ -122,30 +132,21 @@ suspend fun asyncSocketToDevice(socket: AsynchronousSocketChannel): InetTranspor
receiving.value = false
}
// wait until send queue is empty
suspend fun flush() {
yield()
// do not slow down with collect if it is ok by now:
if (!sendQueueEmpty.value || !outputBlocks.isEmpty)
// wait until all output is sent
sendQueueEmpty.waitFor { it && outputBlocks.isEmpty }
}
val addr = socket.remoteAddress as InetSocketAddress
deferredDevice.complete(
InetTransportDevice(inputBlocks, outputBlocks, JvmNetworkAddress(addr.address, addr.port),
{ flush() }
) {
InetTransportDevice(inputBlocks, outputBlocks, JvmNetworkAddress(addr.address, addr.port), {
val log = LogTag("S:${addr.address}:${addr.port}")
log.info { "ASTD is waitig to close" }
yield()
// wait until all received data are parsed, but not too long
withTimeoutOrNull( 1000 ) {
withTimeoutOrNull(500) {
receiving.waitFor { !it }
}
// graceful close: flush output
flush()
// then stop it
log.info { "ASTd is calling STOP" }
stop()
}
log.info { "STopped" }
})
)
}
globalLaunch { socket.close() }