// Socket.IO 2.x / engine.io v3, YALNIZ websocket (sunucu: transports:['websocket']). // Akis (uzunluk oneki YOK, her websocket frame'i bir engine.io paketi): // GET ?EIO=3&transport=websocket (sid YOK) -> 101 // S->C 0{"sid":..,"pingInterval":25000,..} engine.io open // S->C 40 varsayilan namespace (yok say) // C->S 40/isans /isans namespace'ine baglan // S->C 40/isans onay -> merhaba gonder // C->S 42/isans,["merhaba",{...}] // S->C 42/isans,["lisans-karar",{...}] // C->S 2 (ping) S->C 3 (pong) | S->C 2 -> C->S 3 DWORD wsOturumu(const UrlParca& u) { HINTERNET oturum = WinHttpOpen(L"FikoYT-LisansCanli/1.0", WINHTTP_ACCESS_TYPE_NO_PROXY, WINHTTP_NO_PROXY_NAME, WINHTTP_NO_PROXY_BYPASS, 0); if (!oturum) { std::lock_guard lk(g_mtx); g_hata = "WinHttpOpen basarisiz"; return YENIDEN_BAGLANMA_MIN_MS; } // resolve, connect, send, receive(SONSUZ: websocket okuma bloklayici) WinHttpSetTimeouts(oturum, 8000, 8000, 12000, INFINITE); // std::wstring gecici nesnesi yerine sabit tampon (host ASCII varsayimi). wchar_t wbuf[256] = {0}; { const size_t n = u.host.size() < 255 ? u.host.size() : 255; for (size_t i = 0; i < n; ++i) wbuf[i] = (wchar_t)(unsigned char)u.host[i]; wbuf[n] = 0; } gunluk::g_teshisAdim = "LC:WinHttpConnect"; HINTERNET baglanti = WinHttpConnect(oturum, wbuf, (INTERNET_PORT)u.port, 0); gunluk::g_teshisAdim = "LC:baglanti-ok"; if (!baglanti) { std::lock_guard lk(g_mtx); g_hata = "HTTP baglantisi kurulamadi"; WinHttpCloseHandle(oturum); return YENIDEN_BAGLANMA_MIN_MS; } gunluk::uyari("lisans: websocket dogrudan baglaniliyor ({}:{})", u.host, (int)u.port); HINTERNET ws = nullptr; { const std::string wsPath = u.yol + "?EIO=3&transport=websocket"; const std::wstring wpath(wsPath.begin(), wsPath.end()); // ayni string: begin/end guvenli HINTERNET istek = WinHttpOpenRequest(baglanti, L"GET", wpath.c_str(), nullptr, WINHTTP_NO_REFERER, WINHTTP_DEFAULT_ACCEPT_TYPES, u.https ? WINHTTP_FLAG_SECURE : 0); if (istek) { if (u.https) { DWORD sec = SECURITY_FLAG_IGNORE_UNKNOWN_CA | SECURITY_FLAG_IGNORE_CERT_CN_INVALID | SECURITY_FLAG_IGNORE_CERT_DATE_INVALID; WinHttpSetOption(istek, WINHTTP_OPTION_SECURITY_FLAGS, &sec, sizeof(sec)); } // UPGRADE_TO_WEB_SOCKET tampon ALMAZ: (nullptr, 0) if (WinHttpSetOption(istek, WINHTTP_OPTION_UPGRADE_TO_WEB_SOCKET, nullptr, 0)) { if (WinHttpSendRequest(istek, WINHTTP_NO_ADDITIONAL_HEADERS, 0, WINHTTP_NO_REQUEST_DATA, 0, 0, 0) && WinHttpReceiveResponse(istek, nullptr)) { DWORD kod = 0, boyut = sizeof(kod); WinHttpQueryHeaders(istek, WINHTTP_QUERY_STATUS_CODE | WINHTTP_QUERY_FLAG_NUMBER, WINHTTP_HEADER_NAME_BY_INDEX, &kod, &boyut, WINHTTP_NO_HEADER_INDEX); if (kod == 101) { ws = WinHttpWebSocketCompleteUpgrade(istek, 0); if (ws) gunluk::uyari("lisans: websocket 101 OK"); else gunluk::uyari("lisans: websocket complete FAIL, error {}", GetLastError()); } else { gunluk::uyari("lisans: websocket upgrade HTTP {}", kod); } } else { gunluk::uyari("lisans: websocket send/recv FAIL, error {}", GetLastError()); } } else { gunluk::uyari("lisans: websocket setoption FAIL, error {}", GetLastError()); } WinHttpCloseHandle(istek); } } WinHttpCloseHandle(baglanti); // ws kendi baglantisini devraldi if (!ws) { std::lock_guard lk(g_mtx); g_hata = "websocket'e yukseltilemedi"; WinHttpCloseHandle(oturum); return YENIDEN_BAGLANMA_MIN_MS; } // Gonderimler tek seferde tek thread: ana dongu + ping thread'i mutex ile serilesir. std::mutex gonderMtx; auto gonder = [&](const std::string& s) -> DWORD { std::lock_guard lk(gonderMtx); return WinHttpWebSocketSend(ws, WINHTTP_WEB_SOCKET_UTF8_MESSAGE_BUFFER_TYPE, (PVOID)s.data(), (DWORD)s.size()); }; DWORD pingIntervalMs = 25000; std::atomic pingDur{false}; std::mutex pingMtx; std::condition_variable pingCv; std::thread pingIs; bool nsBagli = false; bool bitir = false; std::string birikim; for (; !bitir;) { char tampon[4096]; DWORD okunan = 0; WINHTTP_WEB_SOCKET_BUFFER_TYPE tur = WINHTTP_WEB_SOCKET_CLOSE_BUFFER_TYPE; const DWORD rc = WinHttpWebSocketReceive(ws, tampon, sizeof(tampon), &okunan, &tur); if (rc != NO_ERROR) { gunluk::uyari("lisans: ws okuma hatasi rc={} (12030=baglanti kesildi)", rc); std::lock_guard lk(g_mtx); g_hata = "baglanti koptu (okuma)"; break; } if (tur == WINHTTP_WEB_SOCKET_CLOSE_BUFFER_TYPE) { USHORT kapKod = 0; char kapNeden[123] = {0}; DWORD kapNedenUz = 0; WinHttpWebSocketQueryCloseStatus(ws, &kapKod, kapNeden, sizeof(kapNeden) - 1, &kapNedenUz); gunluk::uyari("lisans: sunucu ws kapatti kod={} neden={}", (int)kapKod, std::string(kapNeden, kapNedenUz < sizeof(kapNeden) ? kapNedenUz : 0)); std::lock_guard lk(g_mtx); g_hata = "sunucu baglantiyi kapatti"; break; } birikim.append(tampon, okunan); if (birikim.size() > (1u << 20)) { // 1 MB ust sinir: bellek korumasi gunluk::uyari("lisans: ws mesaji cok buyuk, baglanti kapatiliyor"); break; } if (tur != WINHTTP_WEB_SOCKET_UTF8_MESSAGE_BUFFER_TYPE) continue; // parca: devam const std::string frame = std::move(birikim); birikim.clear(); if (frame.empty()) continue; switch (frame[0]) { case '0': { // engine.io open long long v = 0; if (jsonSayiAl(frame, "pingInterval", v) && v >= 1000 && v < 600000) pingIntervalMs = (DWORD)v; gunluk::uyari("lisans: engine.io open, pingInterval={}", (int)pingIntervalMs); if (gonder("40/isans") != NO_ERROR) { bitir = true; break; } if (!pingIs.joinable()) { pingIs = std::thread([&] { try { std::unique_lock lk(pingMtx); while (!pingDur.load() && !g_durduruldu.load()) { if (pingCv.wait_for(lk, std::chrono::milliseconds(pingIntervalMs), [&] { return pingDur.load(); })) break; lk.unlock(); const DWORD prc = gonder("2"); lk.lock(); if (prc != NO_ERROR) break; } } catch (...) { } }); } break; } case '1': // engine.io close bitir = true; break; case '2': // sunucu ping -> pong if (gonder("3") != NO_ERROR) bitir = true; break; case '3': // pong break; case '4': { // engine.io message -> socket.io paketi if (frame.rfind("40/isans", 0) == 0) { // /isans namespace onayi ("40" tek basina yok sayilir) if (!nsBagli) { nsBagli = true; { std::lock_guard lk(g_mtx); g_durum = KanalDurum::Bagli; g_hata.clear(); g_sunucuAdi = u.host; } gunluk::bilgi("Canli lisans kanali baglandi (Socket.IO): {}", u.host); if (gonder(socketioEventUret("merhaba", merhabaMesajiUret())) != NO_ERROR) { bitir = true; } else { gunluk::uyari("lisans: merhaba gonderildi"); } } } else if (frame.rfind("41/isans", 0) == 0) { // sunucu namespace'i kapatti gunluk::uyari("lisans: sunucu /isans namespace'ini kapatti"); bitir = true; } else if (frame.rfind("44", 0) == 0) { // socket.io hata paketi gunluk::uyari("lisans: socket.io hata paketi: {}", frame.substr(0, 120)); } else { std::string ev, json; if (engineioPayloadCoz(frame, ev, json) && ev == "lisans-karar") { CanliKarar k; std::string hata; const UzakPolitikaDurum d = canliKararCoz(json, lisansAcikAnahtarHex(), k, hata); if (d == UzakPolitikaDurum::Gecerli) { kararUygula(k); } else if (d == UzakPolitikaDurum::Bicim && hata.find("tip") != std::string::npos) { // ping/pong benzeri -> sessizce yok say } else { gunluk::uyari("Canli kanal mesaji reddedildi ({}): {}", hata, json.substr(0, 120)); } } } break; } default: break; } } // Temizlik: once ping thread'ini durdur ve BEKLE (ws/oturum kapanmadan once). { std::lock_guard lk(pingMtx); pingDur = true; } pingCv.notify_all(); if (pingIs.joinable()) pingIs.join(); WinHttpWebSocketClose(ws, WINHTTP_WEB_SOCKET_SUCCESS_CLOSE_STATUS, nullptr, 0); WinHttpCloseHandle(ws); WinHttpCloseHandle(oturum); { std::lock_guard lk(g_mtx); if (g_durum == KanalDurum::Bagli) { g_durum = KanalDurum::Kopuk; g_sonBagliZaman = (i64)std::time(nullptr); } } gunluk::bilgi("Canli lisans kanali kapandi (Socket.IO)"); return YENIDEN_BAGLANMA_MIN_MS; }