package com.xw.yunwei.websocket import com.xw.common.utils.LogUtils import com.xw.yunwei.BuildConfig import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job import kotlinx.coroutines.delay import kotlinx.coroutines.isActive import kotlinx.coroutines.launch import okhttp3.OkHttpClient import okhttp3.Request import okhttp3.Response import okhttp3.WebSocket import okhttp3.WebSocketListener import okhttp3.logging.HttpLoggingInterceptor import okio.ByteString import org.json.JSONObject import java.util.LinkedList import java.util.concurrent.TimeUnit //WebSocket客户端 class WebSocketClient( private val config: WebSocketConfig = WebSocketConfig ) { private var webSocket: WebSocket? = null private var okHttpClient: OkHttpClient? = null private var isConnecting = false private var isConnected = false private var reconnectCount = 0 private val ioCoroutineScope = CoroutineScope(Dispatchers.IO) // 监听器列表 private val listeners = mutableListOf() // 心跳相关 private var heartbeatJob: Job? = null // 消息队列(连接失败时缓存) private val messageQueue = LinkedList() private val maxQueueSize = 50 companion object { private const val TAG = "WebSocketClient" } /** * 初始化WebSocket客户端 */ private fun initClient(): OkHttpClient { return OkHttpClient.Builder() .connectTimeout(config.WS_TIMEOUT, TimeUnit.SECONDS) .writeTimeout(config.WS_TIMEOUT, TimeUnit.SECONDS) .readTimeout(config.WS_TIMEOUT, TimeUnit.SECONDS) .pingInterval(config.PING_INTERVAL, TimeUnit.SECONDS) // 自动Ping .retryOnConnectionFailure(true) .addInterceptor(HttpLoggingInterceptor().apply { level = if (BuildConfig.DEBUG) { HttpLoggingInterceptor.Level.BODY } else { HttpLoggingInterceptor.Level.NONE } }) .addInterceptor { chain -> val original = chain.request() val request = original.newBuilder() .header("User-Agent", "Android-App/${BuildConfig.VERSION_NAME}") .header("Connection", "Upgrade") .header("Upgrade", "websocket") .header("Sec-WebSocket-Version", "13") .apply { // 添加认证token val token = getAuthToken() if (token.isNotBlank()) { header("Authorization", "Bearer $token") } } .build() chain.proceed(request) } .build() } /** * 连接WebSocket */ fun connect(isFirstConnect: Boolean) { if (isConnecting || isConnected) { LogUtils.d(TAG, "WebSocket正在连接或已连接") return } isConnecting = true reconnectCount = 0 ioCoroutineScope.launch { try { val request = Request.Builder() .url(config.WS_URL) .build() okHttpClient = initClient() okHttpClient?.newWebSocket(request, createWebSocketListener()) LogUtils.i(TAG, "开始连接WebSocket: ${config.WS_URL}") } catch (e: Exception) { isConnecting = false LogUtils.e(TAG, "连接WebSocket失败: ${e.message}", e) notifyConnectFailed(e) } } } /** * 创建WebSocket监听器 */ private fun createWebSocketListener(): WebSocketListener { return object : WebSocketListener() { override fun onOpen(webSocket: WebSocket, response: Response) { LogUtils.i(TAG, "WebSocket连接成功") isConnecting = false isConnected = true reconnectCount = 0 this@WebSocketClient.webSocket = webSocket // 启动心跳 startHeartbeat() // 发送积压的消息 flushMessageQueue() // 通知监听器 notifyConnected() } override fun onMessage(webSocket: WebSocket, text: String) { LogUtils.d(TAG, "收到文本消息: $text") handleReceivedMessage(text) } override fun onMessage(webSocket: WebSocket, bytes: ByteString) { LogUtils.d(TAG, "收到二进制消息, 长度: ${bytes.size}") handleReceivedMessage(bytes) } override fun onClosing(webSocket: WebSocket, code: Int, reason: String) { LogUtils.w(TAG, "WebSocket正在关闭: code=$code, reason=$reason") isConnected = false stopHeartbeat() } override fun onClosed(webSocket: WebSocket, code: Int, reason: String) { LogUtils.w(TAG, "WebSocket已关闭: code=$code, reason=$reason") isConnected = false stopHeartbeat() notifyDisconnected(code, reason) // 根据关闭码决定是否重连 if (shouldReconnect(code)) { scheduleReconnect() } } override fun onFailure(webSocket: WebSocket, t: Throwable, response: Response?) { LogUtils.e(TAG, "WebSocket连接失败: ${t.message}") isConnecting = false isConnected = false stopHeartbeat() notifyConnectFailed(t) // 失败时尝试重连 if (reconnectCount < config.MAX_RECONNECT_COUNT) { scheduleReconnect() } } } } /** * 发送消息 */ fun sendMessage(message: String): Boolean { return if (isConnected) { try { webSocket?.send(message) LogUtils.d(TAG, "发送消息成功: $message") notifySendMessageSuccess(message) true } catch (e: Exception) { LogUtils.e(TAG, "发送消息失败: ${e.message}", e) cacheMessage(message) notifySendMessageFailed(message, e) false } } else { LogUtils.w(TAG, "WebSocket未连接,缓存消息: $message") cacheMessage(message) false } } /** * 发送JSON消息 */ fun sendJsonMessage(type: String, data: Any = emptyMap()): Boolean { val json = try { val jsonObject = JSONObject().apply { put("type", type) put("data", JSONObject(data.toString())) put("timestamp", System.currentTimeMillis()) } jsonObject.toString() } catch (e: Exception) { LogUtils.e(TAG, "构建JSON失败: ${e.message}", e) return false } return sendMessage(json) } /** * 断开连接 */ fun disconnect(code: Int = 1000, reason: String = "normal closure") { LogUtils.i(TAG, "主动断开WebSocket连接") stopHeartbeat() isConnecting = false isConnected = false reconnectCount = config.MAX_RECONNECT_COUNT // 阻止重连 webSocket?.close(code, reason) webSocket = null try { okHttpClient?.dispatcher?.executorService?.shutdown() okHttpClient?.connectionPool?.evictAll() } catch (e: Exception) { LogUtils.e(TAG, "清理资源失败: ${e.message}", e) } } /** * 重新连接 */ fun reconnect() { if (isConnecting) { LogUtils.d(TAG, "已经在重连中...") return } disconnect() LogUtils.d(TAG, "开始重连...") ioCoroutineScope.launch { delay(1000) // 延迟1秒重连 connect(false) } } /** * 添加监听器 */ fun addListener(listener: IWebSocketListener) { if (!listeners.contains(listener)) { listeners.add(listener) } } /** * 移除监听器 */ fun removeListener(listener: IWebSocketListener) { listeners.remove(listener) } /** * 清除所有监听器 */ fun clearListeners() { listeners.clear() } /** * 获取连接状态 */ fun getConnectionState(): ConnectionState { return when { isConnected -> ConnectionState.CONNECTED isConnecting -> ConnectionState.CONNECTING else -> ConnectionState.DISCONNECTED } } // 私有方法 private fun startHeartbeat() { stopHeartbeat() heartbeatJob = ioCoroutineScope.launch { while (isActive && isConnected) { delay(config.PING_INTERVAL * 1000) if (isConnected) { sendJsonMessage(WebSocketConfig.MessageType.HEARTBEAT) } } } } private fun stopHeartbeat() { heartbeatJob?.cancel() heartbeatJob = null } private fun scheduleReconnect() { if (reconnectCount >= config.MAX_RECONNECT_COUNT) { LogUtils.w(TAG, "已达到最大重连次数: ${config.MAX_RECONNECT_COUNT}") return } reconnectCount++ val delay = config.RECONNECT_DELAY * reconnectCount // 退避策略 LogUtils.i(TAG, "计划 ${reconnectCount} 秒后重连") ioCoroutineScope.launch { delay(delay * 1000) if (!isConnected && !isConnecting) { connect(false) } } } private fun cacheMessage(message: String) { synchronized(messageQueue) { if (messageQueue.size >= maxQueueSize) { messageQueue.poll() // 移除最旧的消息 } messageQueue.offer(message) } } private fun flushMessageQueue() { synchronized(messageQueue) { while (messageQueue.isNotEmpty()) { messageQueue.poll()?.also { sendMessage(it) } } } } private fun handleReceivedMessage(text: String) { try { val json = JSONObject(text) val type = json.optString("type") when (type) { "pong" -> { // 心跳响应 LogUtils.d(TAG, "收到心跳响应") } else -> { listeners.forEach { it.onMessage(text) } } } } catch (e: Exception) { LogUtils.e(TAG, "解析消息失败: ${e.message}", e) listeners.forEach { it.onMessage(text) } } } private fun handleReceivedMessage(bytes: ByteString) { listeners.forEach { it.onMessage(bytes) } } private fun notifyConnected() { listeners.forEach { it.onConnected() } } private fun notifyConnectFailed(t: Throwable) { listeners.forEach { it.onConnectFailed(t) } } private fun notifyDisconnected(code: Int, reason: String) { listeners.forEach { it.onDisconnected(code, reason) } } private fun notifySendMessageSuccess(message: String) { listeners.forEach { it.onSendMessageSuccess(message) } } private fun notifySendMessageFailed(message: String, t: Throwable) { listeners.forEach { it.onSendMessageFailed(message, t) } } private fun shouldReconnect(code: Int): Boolean { // 正常关闭不重连 return false } private fun getAuthToken(): String { // 从SharedPreferences或安全存储获取 return "" } enum class ConnectionState { CONNECTING, CONNECTED, DISCONNECTED } }