diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/QQAndroidBotNetworkHandler.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/QQAndroidBotNetworkHandler.kt index a5bbf23b3..631b7f626 100644 --- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/QQAndroidBotNetworkHandler.kt +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/QQAndroidBotNetworkHandler.kt @@ -39,6 +39,7 @@ import net.mamoe.mirai.qqandroid.network.protocol.packet.login.StatSvc import net.mamoe.mirai.qqandroid.network.protocol.packet.login.WtLogin import net.mamoe.mirai.qqandroid.utils.NoRouteToHostException import net.mamoe.mirai.qqandroid.utils.PlatformSocket +import net.mamoe.mirai.qqandroid.utils.SocketException import net.mamoe.mirai.qqandroid.utils.io.readPacketExact import net.mamoe.mirai.qqandroid.utils.io.useBytes import net.mamoe.mirai.qqandroid.utils.retryCatching @@ -127,9 +128,9 @@ internal class QQAndroidBotNetworkHandler(bot: QQAndroidBot) : BotNetworkHandler while (isActive) { try { - channel.connect(host, port) + channel.connect(coroutineContext + CoroutineName("Socket"), host, port) break - } catch (e: NoRouteToHostException) { + } catch (e: SocketException) { logger.warning { "No route to host (Mostly due to no Internet connection). Retrying in 3s..." } delay(3000) } diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/highway/HighwayHelper.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/highway/HighwayHelper.kt index 7498107b5..a0c07e8b8 100644 --- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/highway/HighwayHelper.kt +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/highway/HighwayHelper.kt @@ -33,10 +33,12 @@ import net.mamoe.mirai.qqandroid.network.protocol.data.proto.CSDataHighwayHead import net.mamoe.mirai.qqandroid.utils.ByteArrayPool import net.mamoe.mirai.qqandroid.utils.NoRouteToHostException import net.mamoe.mirai.qqandroid.utils.PlatformSocket +import net.mamoe.mirai.qqandroid.utils.SocketException import net.mamoe.mirai.qqandroid.utils.io.serialization.readProtoBuf import net.mamoe.mirai.qqandroid.utils.io.withUse import net.mamoe.mirai.utils.MiraiInternalAPI import net.mamoe.mirai.utils.copyAndClose +import kotlin.coroutines.EmptyCoroutineContext @OptIn(MiraiInternalAPI::class, InternalSerializationApi::class) @Suppress("SpellCheckingInspection") @@ -117,9 +119,9 @@ internal object HighwayHelper { val socket = PlatformSocket() while (client.bot.network.isActive) { try { - socket.connect(serverIp, serverPort) + socket.connect(EmptyCoroutineContext, serverIp, serverPort) break - } catch (e: NoRouteToHostException) { + } catch (e: SocketException) { delay(3000) } } diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/utils/PlatformSocket.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/utils/PlatformSocket.kt index 1a6fda4b0..45441e013 100644 --- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/utils/PlatformSocket.kt +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/utils/PlatformSocket.kt @@ -13,13 +13,14 @@ import kotlinx.io.core.ByteReadPacket import kotlinx.io.core.Closeable import kotlinx.io.errors.IOException import net.mamoe.mirai.utils.Throws +import kotlin.coroutines.CoroutineContext /** * 多平台适配的 TCP Socket. */ internal expect class PlatformSocket() : Closeable { - @Throws(NoRouteToHostException::class) - suspend fun connect(serverHost: String, serverPort: Int) + @Throws(SocketException::class) + suspend fun connect(coroutineContext: CoroutineContext, serverHost: String, serverPort: Int) /** * @throws SendPacketInternalException diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/utils/io/input.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/utils/io/input.kt index 41154986d..a267e5b73 100644 --- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/utils/io/input.kt +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/utils/io/input.kt @@ -27,7 +27,6 @@ import kotlin.jvm.JvmMultifileClass import kotlin.jvm.JvmName import kotlin.jvm.JvmSynthetic -@MiraiInternalAPI internal inline fun ByteReadPacket.useBytes( n: Int = remaining.toInt(),//not that safe but adequate block: (data: ByteArray, length: Int) -> R @@ -36,7 +35,6 @@ internal inline fun ByteReadPacket.useBytes( block(it, n) } -@MiraiInternalAPI internal inline fun ByteReadPacket.readPacketExact( n: Int = remaining.toInt()//not that safe but adequate ): ByteReadPacket = this.readBytes(n).toReadPacket() diff --git a/mirai-core-qqandroid/src/jvmMain/kotlin/net/mamoe/mirai/qqandroid/utils/PlatformSocket.kt b/mirai-core-qqandroid/src/jvmMain/kotlin/net/mamoe/mirai/qqandroid/utils/PlatformSocket.kt index 770fa61e6..c89fd49cb 100644 --- a/mirai-core-qqandroid/src/jvmMain/kotlin/net/mamoe/mirai/qqandroid/utils/PlatformSocket.kt +++ b/mirai-core-qqandroid/src/jvmMain/kotlin/net/mamoe/mirai/qqandroid/utils/PlatformSocket.kt @@ -21,6 +21,7 @@ import java.io.BufferedInputStream import java.io.BufferedOutputStream import java.net.Socket import java.net.SocketException +import kotlin.coroutines.CoroutineContext /** * 多平台适配的 TCP Socket. @@ -81,7 +82,7 @@ internal actual class PlatformSocket : Closeable { } @OptIn(ExperimentalIoApi::class) - actual suspend fun connect(serverHost: String, serverPort: Int) { + actual suspend fun connect(coroutineContext: CoroutineContext, serverHost: String, serverPort: Int) { withContext(Dispatchers.IO) { socket = Socket(serverHost, serverPort) readChannel = socket.getInputStream().buffered() @@ -92,4 +93,113 @@ internal actual class PlatformSocket : Closeable { actual typealias NoRouteToHostException = java.net.NoRouteToHostException -actual typealias SocketException = SocketException \ No newline at end of file +actual typealias SocketException = SocketException + +// ktor aSocket + +/* +/* + * Copyright 2020 Mamoe Technologies and contributors. + * + * 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证. + * Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link. + * + * https://github.com/mamoe/mirai/blob/master/LICENSE + */ + +package net.mamoe.mirai.qqandroid.utils + +import io.ktor.network.selector.ActorSelectorManager +import io.ktor.network.sockets.* +import io.ktor.util.KtorExperimentalAPI +import io.ktor.utils.io.ByteReadChannel +import io.ktor.utils.io.ByteWriteChannel +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext +import kotlinx.io.core.ByteReadPacket +import kotlinx.io.core.Closeable +import kotlinx.io.core.ExperimentalIoApi +import kotlinx.io.core.buildPacket +import kotlinx.io.errors.IOException +import net.mamoe.mirai.qqandroid.utils.io.useBytes +import java.net.InetSocketAddress +import java.net.SocketException +import kotlin.coroutines.CoroutineContext + +/** + * 多平台适配的 TCP Socket. + */ +internal actual class PlatformSocket : Closeable { + private lateinit var socket: io.ktor.network.sockets.Socket + + actual val isOpen: Boolean + get() = + if (::socket.isInitialized) + !socket.isClosed + else false + + actual override fun close() { + if (::socket.isInitialized) { + socket.close() + } + } + + @PublishedApi + internal lateinit var writeChannel: ByteWriteChannel + + @PublishedApi + internal lateinit var readChannel: ByteReadChannel + + actual suspend fun send(packet: ByteArray, offset: Int, length: Int) { + withContext(Dispatchers.IO) { + writeChannel.writeFully(packet, offset, length) + } + } + + /** + * @throws SendPacketInternalException + */ + actual suspend fun send(packet: ByteReadPacket) { + withContext(Dispatchers.IO) { + try { + packet.useBytes { data: ByteArray, length: Int -> + writeChannel.writeFully(data, offset = 0, length = length) + } + } catch (e: IOException) { + throw SendPacketInternalException(e) + } + } + } + + /** + * @throws ReadPacketInternalException + */ + actual suspend fun read(): ByteReadPacket { + return withContext(Dispatchers.IO) { + try { + buildPacket { + readChannel.read { + writeFully(it) + } + } + } catch (e: IOException) { + throw ReadPacketInternalException(e) + } + } + } + + @OptIn(ExperimentalIoApi::class, KtorExperimentalAPI::class) + actual suspend fun connect(coroutineContext: CoroutineContext, serverHost: String, serverPort: Int) { + withContext(Dispatchers.IO) { + socket = aSocket(ActorSelectorManager(coroutineContext)).tcp().tcpNoDelay() + .connect(InetSocketAddress(serverHost, serverPort)) + readChannel = socket.openReadChannel() + writeChannel = socket.openWriteChannel(true) + } + } +} + +actual typealias NoRouteToHostException = java.net.NoRouteToHostException + +actual typealias SocketException = SocketException + */ \ No newline at end of file