diff --git a/build.gradle.kts b/build.gradle.kts index e9ef4d6..f87ebb8 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -20,6 +20,7 @@ dependencies { implementation(compose.desktop.currentOs) implementation(compose.material) implementation(compose.materialIconsExtended) + implementation("io.ktor:ktor-client-okhttp-jvm:2.3.11") // Ktor (Network) val ktorVersion = "2.3.11" diff --git a/src/main/kotlin/Main.kt b/src/main/kotlin/Main.kt index 7affab1..7b720c1 100644 --- a/src/main/kotlin/Main.kt +++ b/src/main/kotlin/Main.kt @@ -149,6 +149,7 @@ fun main() = application { CoroutineScope(Dispatchers.Default).launch { AutoTradingManager.startAutoDiscoveryLoop() KisWebSocketManager.onExecutionReceived = AutoTradingManager.onExecutionReceived + KisWebSocketManager.connect() } if (config.modelPath.isNotEmpty()) { diff --git a/src/main/kotlin/network/KisWebSocketManager.kt b/src/main/kotlin/network/KisWebSocketManager.kt index ca2f0ed..94d079c 100644 --- a/src/main/kotlin/network/KisWebSocketManager.kt +++ b/src/main/kotlin/network/KisWebSocketManager.kt @@ -2,7 +2,14 @@ package network import androidx.compose.runtime.mutableStateOf import io.ktor.client.* +import io.ktor.client.engine.cio.CIO +import io.ktor.client.engine.okhttp.OkHttp +import io.ktor.client.engine.config +import io.ktor.client.plugins.HttpTimeout import io.ktor.client.plugins.websocket.* +import io.ktor.client.engine.cio.* +import io.ktor.client.engine.okhttp.* +import io.ktor.client.plugins.* import io.ktor.http.* import io.ktor.websocket.* import kotlinx.coroutines.* @@ -12,21 +19,55 @@ import kotlinx.serialization.json.jsonPrimitive import model.KisSession import model.RealTimeTrade import util.AesCrypto +import java.util.concurrent.TimeUnit import java.util.concurrent.atomic.AtomicBoolean object KisWebSocketManager { - private val client = HttpClient { + + val os = System.getProperty("os.name").lowercase() + val arch = System.getProperty("os.arch").lowercase() + val isWin = os.contains("win") + + private val client = HttpClient(if(isWin){ OkHttp } else { CIO }) { install(WebSockets) { - pingInterval = 20_000 // 20초마다 표준 웹소켓 핑 전송 (서버-클라이언트 연결 유지 도움) + pingInterval = 20_000 + } + install(HttpTimeout) { + requestTimeoutMillis = 30_000 // 전체 요청 시간 + connectTimeoutMillis = 10_000 // 서버 연결 시간 + socketTimeoutMillis = 30_000 // 데이터 패킷 간격 시간 + } + + // 엔진별 상세 설정 (config 대신 해당 엔진 명칭 사용) + if (isWin) { + engine { + // OkHttp 환경 특화: 윈도우 네트워크 유휴 상태 방지 + this as OkHttpConfig + config { + retryOnConnectionFailure(true) + connectTimeout(15, TimeUnit.SECONDS) + readTimeout(0, TimeUnit.SECONDS) // 무제한 대기 + } + } + } else { + engine { + // CIO 엔진 설정 + this as CIOEngineConfig + endpoint { + connectTimeout = 15_000 + requestTimeout = 30_000 + } + } } } + private var session: DefaultClientWebSocketSession? = null private val isConnected = AtomicBoolean(false) private val scope = CoroutineScope(Dispatchers.IO + SupervisorJob()) private var connectJob: Job? = null + // 콜백 리스너 var onPriceUpdate: ((RealTimeTrade) -> Unit)? = null - var onExecutionReceived: ((String, String, String, String, Boolean) -> Unit)? = null suspend fun connect() { @@ -72,7 +113,7 @@ object KisWebSocketManager { } -// private val _currentPrice = mutableStateOf("0") + // private val _currentPrice = mutableStateOf("0") private val currentPrice = androidx.compose.runtime.mutableStateMapOf() // val currentPrice = _currentPrice diff --git a/src/main/kotlin/ui/SettingsScreen.kt b/src/main/kotlin/ui/SettingsScreen.kt index e3f7799..60beeb2 100644 --- a/src/main/kotlin/ui/SettingsScreen.kt +++ b/src/main/kotlin/ui/SettingsScreen.kt @@ -164,15 +164,20 @@ fun SettingsScreen(onAuthSuccess: () -> Unit) { scope.launch { var retryCount = 0 val maxRetries = 3 - val retryDelay = 90_000L // 1분 30초 + val totalDelaySeconds = 90 // 1분 30초 = 90초 var isAuthCompleted = false while (retryCount <= maxRetries && !isAuthCompleted) { + // 재시도 시 대기 및 카운트다운 표시 if (retryCount > 0) { - statusMessage = "⏳ 인증 재시도 중... (${retryCount}/${maxRetries}) - 1분 30초 후 재시작" - delay(retryDelay) + for (secondsLeft in totalDelaySeconds downTo 1) { + statusMessage = "⚠️ 인증 실패. ${secondsLeft}초 후 자동으로 다시 시도합니다. (시도 ${retryCount}/${maxRetries})" + delay(1000L) // 1초 대기 + } } + statusMessage = if (retryCount == 0) "⏳ 인증 시도 중..." else "⏳ ${retryCount}차 재시도 중..." + // 1. 설정값 저장 KisSession.config = config DatabaseFactory.saveConfig(config) @@ -194,9 +199,8 @@ fun SettingsScreen(onAuthSuccess: () -> Unit) { retryCount++ if (retryCount > maxRetries) { statusMessage = "❌ 인증 실패. 3회 재시도 후 중단되었습니다. 키 정보를 확인하세요." - } else { - statusMessage = "⚠️ 인증 실패. 잠시 후 자동으로 다시 시도합니다. (시도 $retryCount)" } + // 다음 루프에서 카운트다운 진입 } } }