fixes, tcp/ip optimizations

This commit is contained in:
sergeych committed 2023-11-15 02:11:52 +03:00
1 parent bcf0140edb
commit 96edbb2040
6 files changed
+97 -50

No files matched your search

@@ -3,13 +3,14 @@ package net.sergeych.kiloparsec.adapter
import kotlinx.coroutines.*
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.channels.ClosedReceiveChannelException
import kotlinx.coroutines.channels.ClosedSendChannelException
import net.sergeych.bintools.toDump
import net.sergeych.crypto.encodeVarUnsigned
import net.sergeych.crypto.readVarUnsigned
import net.sergeych.kiloparsec.Transport
import net.sergeych.mp_logger.LogTag
import net.sergeych.mp_logger.warning
import net.sergeych.mp_tools.globalLaunch
import java.net.InetSocketAddress
import java.net.StandardSocketOptions.TCP_NODELAY
import java.nio.ByteBuffer
import java.nio.channels.AsynchronousSocketChannel
@@ -18,52 +19,73 @@ import kotlin.coroutines.suspendCoroutine
private val log = LogTag("ASTD")
/**
* Prepend block with its size, varint-encoded
*/
private fun encode(block: UByteArray): ByteArray
= (encodeVarUnsigned(block.size.toUInt()) + block).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): Transport.Device {
val deferredDevice = CompletableDeferred<Transport.Device>()
suspend fun asyncSocketToDevice(socket: AsynchronousSocketChannel): InetProxyDevice {
val deferredDevice = CompletableDeferred<InetProxyDevice>()
globalLaunch {
coroutineScope {
fun stop() {
cancel()
}
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 output = Channel<UByte>(1024)
// copy from socket to input
// copy from socket to input:
launch {
val inb = ByteBuffer.allocate(1024)
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 for (i in 0..<size) input.send(inb[i].toUByte().also { print(it.toInt().toChar()) })
else {
println("recvd:\n${data.sliceArray(0..<size).toDump()}\n------------------")
for (i in 0..<size) input.send(data[i].toUByte())
}
}
}
// copy from output tp socket
// output is blocks, so we sent blocks:
val outputBlocks = Channel<UByteArray>()
// copy from output to socket:
launch {
val outBuff = ArrayList<Byte>(1024)
try {
while (isActive) {
outBuff.clear()
outBuff.add(output.receive().toByte())
while (!output.isEmpty)
outBuff.add(output.receive().toByte())
suspendCoroutine { continuation ->
socket.write(ByteBuffer.wrap(outBuff.toByteArray()), continuation, IntCompletionHandler)
// wait for the first block to send
var data = encode(outputBlocks.receive())
// if there are more, take them all (NO_DELAY optimization)
while (!outputBlocks.isEmpty)
data += encode(outputBlocks.receive())
// now send the aggregate:
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("PArtial write!")
}
}
} catch (_: ClosedReceiveChannelException) {
stop()
}
}
// pump blocks from socket output to device input
// transport device copes with blocks:
val inputBlocks = Channel<UByteArray?>()
// decode blocks from a byte channel read from the socket:
launch {
try {
while (isActive) {
@@ -84,20 +106,10 @@ suspend fun asyncSocketToDevice(socket: AsynchronousSocketChannel): Transport.De
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()
}
}
// SocketAddress.
val addr = socket.remoteAddress as InetSocketAddress
deferredDevice.complete(
ProxyDevice(inputBlocks, outputBlocks) { stop() }
InetProxyDevice(inputBlocks, outputBlocks, JvmNetworkAddress(addr.address,addr.port)) { stop() }
)
}
globalLaunch { socket.close() }