Commit a0098966 authored by Him188's avatar Him188

Reconnection supported

parent 05ca6424
# UpdateLog
## Main version 0
### 0.3.0
- 更新
\ No newline at end of file
# style guide
kotlin.code.style=official
# config
mirai_version=0.3.0
mirai_version=0.5.0
kotlin.incremental.multiplatform=true
kotlin.parallel.tasks.in.project=true
# kotlin
......
......@@ -12,11 +12,13 @@ import net.mamoe.mirai.contact.internal.QQImpl
import net.mamoe.mirai.network.BotNetworkHandler
import net.mamoe.mirai.network.protocol.tim.TIMBotNetworkHandler
import net.mamoe.mirai.network.protocol.tim.packet.login.LoginResult
import net.mamoe.mirai.network.protocol.tim.packet.login.isSuccess
import net.mamoe.mirai.utils.BotConfiguration
import net.mamoe.mirai.utils.DefaultLogger
import net.mamoe.mirai.utils.MiraiLogger
import net.mamoe.mirai.utils.internal.coerceAtLeastOrFail
import kotlin.coroutines.CoroutineContext
import kotlin.coroutines.coroutineContext
import kotlin.jvm.JvmOverloads
data class BotAccount(
......@@ -24,6 +26,15 @@ data class BotAccount(
val password: String//todo 不保存 password?
)
@Suppress("FunctionName")
suspend inline fun Bot(account: BotAccount, logger: MiraiLogger): Bot = Bot(account, logger, coroutineContext)
@Suppress("FunctionName")
suspend inline fun Bot(account: BotAccount): Bot = Bot(account, coroutineContext)
@Suppress("FunctionName")
suspend inline fun Bot(qq: UInt, password: String): Bot = Bot(qq, password, coroutineContext)
/**
* Mirai 的机器人. 一个机器人实例登录一个 QQ 账号.
* Mirai 为多账号设计, 可同时维护多个机器人.
......@@ -54,15 +65,16 @@ data class BotAccount(
* @author NaturalHG
* @see Contact
*/
class Bot(val account: BotAccount, val logger: MiraiLogger) : CoroutineScope {
override val coroutineContext: CoroutineContext = SupervisorJob()
class Bot(val account: BotAccount, val logger: MiraiLogger, context: CoroutineContext) : CoroutineScope {
private val supervisorJob = SupervisorJob(context[Job])
override val coroutineContext: CoroutineContext = context + supervisorJob
constructor(qq: UInt, password: String) : this(BotAccount(qq, password))
constructor(account: BotAccount) : this(account, DefaultLogger("Bot(" + account.id + ")"))
constructor(qq: UInt, password: String, context: CoroutineContext) : this(BotAccount(qq, password), context)
constructor(account: BotAccount, context: CoroutineContext) : this(account, DefaultLogger("Bot(" + account.id + ")"), context)
val contacts = ContactSystem()
var network: BotNetworkHandler<*> = TIMBotNetworkHandler(this.coroutineContext, this)
lateinit var network: BotNetworkHandler<*>
init {
launch {
......@@ -76,19 +88,40 @@ class Bot(val account: BotAccount, val logger: MiraiLogger) : CoroutineScope {
* [关闭][BotNetworkHandler.close]网络处理器, 取消所有运行在 [BotNetworkHandler] 下的协程.
* 然后重新启动并尝试登录
*/
@JvmOverloads
suspend fun reinitializeNetworkHandler(
@JvmOverloads // shouldn't be suspend!! This function MUST NOT inherit the context from the caller because the caller(NetworkHandler) is going to close
fun tryReinitializeNetworkHandler(
configuration: BotConfiguration,
cause: Throwable? = null
): Job = launch {
repeat(configuration.reconnectionRetryTimes) {
if (reinitializeNetworkHandlerAsync(configuration, cause).await().isSuccess()) {
logger.info("Reconnected successfully")
return@launch
} else {
delay(configuration.reconnectPeriod.millisecondsLong)
}
}
}
/**
* [关闭][BotNetworkHandler.close]网络处理器, 取消所有运行在 [BotNetworkHandler] 下的协程.
* 然后重新启动并尝试登录
*/
@JvmOverloads // shouldn't be suspend!! This function MUST NOT inherit the context from the caller because the caller(NetworkHandler) is going to close
fun reinitializeNetworkHandlerAsync(
configuration: BotConfiguration,
cause: Throwable? = null
): LoginResult {
): Deferred<LoginResult> = async {
logger.info("Initializing BotNetworkHandler")
try {
if (::network.isInitialized) {
network.close(cause)
}
} catch (e: Exception) {
logger.error(e)
logger.error("Cannot close network handler", e)
}
network = TIMBotNetworkHandler(this.coroutineContext, this)
return network.login(configuration)
network = TIMBotNetworkHandler(coroutineContext + configuration, this@Bot)
network.login()
}
/**
......
......@@ -70,13 +70,13 @@ suspend inline fun Bot.login(noinline configuration: BotConfiguration.() -> Unit
contract {
callsInPlace(configuration, InvocationKind.EXACTLY_ONCE)
}
return this.network.login(BotConfiguration().apply(configuration))
return this.reinitializeNetworkHandlerAsync(BotConfiguration().apply(configuration)).await()
}
/**
* 使用默认的配置 ([BotConfiguration.Default]) 登录, 返回登录结果
*/
suspend inline fun Bot.login(): LoginResult = this.network.login(BotConfiguration.Default)
suspend inline fun Bot.login(): LoginResult = this.reinitializeNetworkHandlerAsync(BotConfiguration.Default).await()
/**
* 使用默认的配置 ([BotConfiguration.Default]) 登录, 返回 [this]
......@@ -91,7 +91,7 @@ suspend inline fun Bot.alsoLogin(noinline configuration: BotConfiguration.() ->
contract {
callsInPlace(configuration, InvocationKind.EXACTLY_ONCE)
}
this.network.login(BotConfiguration().apply(configuration)).requireSuccess()
this.reinitializeNetworkHandlerAsync(BotConfiguration().apply(configuration)).await().requireSuccess()
return this
}
......
package net.mamoe.mirai.network
import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.CompletableJob
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.cancelChildren
import net.mamoe.mirai.Bot
import net.mamoe.mirai.network.protocol.tim.handler.DataPacketSocketAdapter
import net.mamoe.mirai.network.protocol.tim.handler.TemporaryPacketHandler
......@@ -12,7 +11,6 @@ import net.mamoe.mirai.network.protocol.tim.packet.Packet
import net.mamoe.mirai.network.protocol.tim.packet.login.HeartbeatPacket
import net.mamoe.mirai.network.protocol.tim.packet.login.LoginResult
import net.mamoe.mirai.network.protocol.tim.packet.login.RequestSKeyPacket
import net.mamoe.mirai.utils.BotConfiguration
import net.mamoe.mirai.utils.io.PlatformDatagramChannel
/**
......@@ -38,7 +36,7 @@ interface BotNetworkHandler<Socket : DataPacketSocketAdapter> : CoroutineScope {
val socket: Socket
val bot: Bot
val supervisor get() = SupervisorJob()
val supervisor: CompletableJob
val session: BotSession
......@@ -46,7 +44,7 @@ interface BotNetworkHandler<Socket : DataPacketSocketAdapter> : CoroutineScope {
* 依次尝试登录到可用的服务器. 在任一服务器登录完成后返回登录结果
* 本函数将挂起直到登录成功.
*/
suspend fun login(configuration: BotConfiguration): LoginResult
suspend fun login(): LoginResult
/**
* 添加一个临时包处理器, 并发送相应的包
......@@ -70,6 +68,6 @@ interface BotNetworkHandler<Socket : DataPacketSocketAdapter> : CoroutineScope {
* 关闭网络接口, 停止所有有关协程和任务
*/
suspend fun close(cause: Throwable? = null) {
supervisor.cancelChildren(CancellationException("handler closed", cause))
supervisor.cancel(CancellationException("handler closed", cause))
}
}
\ No newline at end of file
......@@ -123,6 +123,13 @@ fun LoginResult.requireSuccess() = requireSuccess { "Login failed: $this" }
*/
fun LoginResult.requireSuccessOrNull(): Unit? = if (this == SUCCESS) Unit else null
/**
* 返回 [this] 是否为 [LoginResult.SUCCESS].
*/
@Suppress("NOTHING_TO_INLINE")
@UseExperimental(ExperimentalContracts::class)
inline fun LoginResult.isSuccess(): Boolean = this == SUCCESS
/**
* 检查 [this] 为 [LoginResult.SUCCESS].
* 失败则返回 `null`
......
......@@ -5,6 +5,8 @@ import com.soywiz.klock.seconds
import kotlinx.io.core.IoBuffer
import net.mamoe.mirai.Bot
import net.mamoe.mirai.network.protocol.tim.packet.login.TouchPacket.TouchResponse
import kotlin.coroutines.CoroutineContext
import kotlin.coroutines.coroutineContext
import kotlin.jvm.JvmField
/**
......@@ -22,7 +24,7 @@ expect var DefaultCaptchaSolver: CaptchaSolver
/**
* 网络和连接配置
*/
class BotConfiguration {
class BotConfiguration : CoroutineContext.Element {
/**
* 等待 [TouchResponse] 的时间
*/
......@@ -42,6 +44,18 @@ class BotConfiguration {
* 一旦心跳超时, 整个网络服务将会重启 (将消耗约 1s). 除正在进行的任务 (如图片上传) 会被中断外, 事件和插件均不受影响.
*/
var heartbeatTimeout: TimeSpan = 2.seconds
/**
* 心跳失败后的第一次重连前的等待时间.
*/
var firstReconnectDelay: TimeSpan = 5.seconds
/**
* 重连失败后, 继续尝试的每次等待时间
*/
var reconnectPeriod: TimeSpan = 60.seconds
/**
* 最多尝试多少次重连
*/
var reconnectionRetryTimes: Int = 3
/**
* 有验证码要求就失败
*/
......@@ -51,11 +65,15 @@ class BotConfiguration {
*/
var captchaSolver: CaptchaSolver = DefaultCaptchaSolver
companion object {
companion object Key : CoroutineContext.Key<BotConfiguration> {
/**
* 默认的配置实例
*/
@JvmField
val Default = BotConfiguration()
}
override val key: CoroutineContext.Key<*> get() = Key
}
suspend inline fun currentBotConfiguration(): BotConfiguration = coroutineContext[BotConfiguration] ?: error("No BotConfiguration found")
\ No newline at end of file
......@@ -17,7 +17,6 @@ import net.mamoe.mirai.network.protocol.tim.packet.login.CaptchaKey
import net.mamoe.mirai.network.protocol.tim.packet.login.LoginResult
import net.mamoe.mirai.network.protocol.tim.packet.login.ShareKey
import net.mamoe.mirai.network.protocol.tim.packet.login.TouchKey
import net.mamoe.mirai.utils.BotConfiguration
import net.mamoe.mirai.utils.DecryptionFailedException
import net.mamoe.mirai.utils.decryptBy
import net.mamoe.mirai.utils.io.*
......@@ -301,6 +300,7 @@ when (idHex.substring(0, 5)) {
internal object DebugNetworkHandler : BotNetworkHandler<DataPacketSocketAdapter>, CoroutineScope {
override val supervisor: CompletableJob = SupervisorJob()
override val socket: DataPacketSocketAdapter = object : DataPacketSocketAdapter {
override val serverIp: String
get() = ""
......@@ -320,10 +320,10 @@ internal object DebugNetworkHandler : BotNetworkHandler<DataPacketSocketAdapter>
get() = bot
}
override val bot: Bot = Bot(qq, "")
override val bot: Bot = Bot(qq, "", coroutineContext)
override val session = BotSession(bot, sessionKey, socket, this)
override suspend fun login(configuration: BotConfiguration): LoginResult = LoginResult.SUCCESS
override suspend fun login(): LoginResult = LoginResult.SUCCESS
override suspend fun addHandler(temporaryPacketHandler: TemporaryPacketHandler<*, *>) {
}
......@@ -336,4 +336,5 @@ internal object DebugNetworkHandler : BotNetworkHandler<DataPacketSocketAdapter>
override val coroutineContext: CoroutineContext
get() = GlobalScope.coroutineContext
}
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment