DATA/STOCK/daemon/korea/0100/stock_total_striker_ws.php
<?php
// ============================================================
// 웹소켓 타격기 : stock_total_striker_ws.php
// 환경기가 준 $unit_packs를 순차로 박고 퇴근
// ============================================================

if (empty($unit_packs) || empty($WS_APPROVAL_KEY)) return;

if (!function_exists('ws_send_frame')) {
    function ws_send_frame($fp, $payload) {
        $len  = strlen($payload);
        $mask = random_bytes(4);
        $masked = '';
        for ($i = 0; $i < $len; $i++) { $masked .= $payload[$i] ^ $mask[$i % 4]; }
        return @fwrite($fp, pack('CCn', 0x81, 0xFE, $len) . $mask . $masked);
    }
}

$total_received = 0;

foreach ($unit_packs as $index => $target_symbols) {

    echo "[타격기] 세트 #" . ($index + 1) . " 시작 (종목수: " . count($target_symbols) . ")\n";

    // 1. 소켓 열기 (세트마다 새로 열기)
    $fp = @stream_socket_client($WS_URL, $errno, $errstr, 10);
    if (!$fp) {
        error_log("[WS] 연결 실패: $errstr ($errno)");
        sleep(1);
        continue;
    }
    stream_set_timeout($fp, 10);

    // 2. 핸드쉐이크
    $ws_key = base64_encode(random_bytes(16));
    $header = "GET / HTTP/1.1\r\nHost: ops.koreainvestment.com:21000\r\nUpgrade: websocket\r\nConnection: Upgrade\r\nSec-WebSocket-Key: {$ws_key}\r\nSec-WebSocket-Version: 13\r\n\r\n";
    fwrite($fp, $header);
    fread($fp, 2048);

    // 3. 종목 구독
    foreach ($target_symbols as $sym) {
        $sub = json_encode([
            "header" => ["approval_key" => $WS_APPROVAL_KEY, "custtype" => "P", "tr_type" => "1", "content-type" => "utf-8"],
            "body"   => ["input" => ["tr_id" => "H0STCNT0", "tr_key" => $sym]]
        ]);
        ws_send_frame($fp, $sub);
        usleep(50000);
    }

    // 4. 수신 및 DB 박기
    $received = 0;
    $target   = count($target_symbols);
    $deadline = time() + 5;

    while ($received < $target && time() < $deadline) {
        $head = @fread($fp, 2);
        if (!$head || strlen($head) < 2) continue;

        $payload_len = ord($head[1]) & 0x7F;
        if ($payload_len == 126)      $payload_len = unpack('n', fread($fp, 2))[1];
        elseif ($payload_len == 127)  $payload_len = unpack('J', fread($fp, 8))[1];

        $payload = @fread($fp, $payload_len);
        if (empty($payload) || strpos($payload, 'H0STCNT0') === false) continue;

        $parts = explode('|', $payload);
        if (count($parts) < 4) continue;

        $data = explode('^', $parts[3]);

        // 아까 모니터로 확인한 실제 웹소켓 인덱스 기준
        $code           = $data[0]  ?? '';  // 종목코드
        $mksc_shrn_iscd = $data[0]  ?? '';  // 0번
        $stck_cntg_hour = $data[1]  ?? '';  // 1번
        $stck_prpr      = $data[2]  ?? '';  // 2번
        $prdy_vrss_sign = $data[3]  ?? '';  // 3번
        $prdy_vrss      = $data[4]  ?? '';  // 4번
        $prdy_ctrt      = $data[5]  ?? '';  // 5번
        $wghn_avrg_prce = $data[6]  ?? '';  // 6번
        $stck_oprc      = $data[7]  ?? '';  // 7번
        $stck_hgpr      = $data[8]  ?? '';  // 8번
        $stck_lwpr      = $data[9]  ?? '';  // 9번
        $askp1          = $data[10] ?? '';  // 10번
        $bidp1          = $data[11] ?? '';  // 11번
        $cntg_vol       = $data[12] ?? '';  // 12번
        $acml_vol       = $data[13] ?? '';  // 13번
        $acml_tr_pbmn   = $data[14] ?? '';  // 14번
        $seln_cntg_smtn = $data[15] ?? '';  // 15번
        $buyt_cntg_smtn = $data[16] ?? '';  // 16번
        $ctg_vol_intn   = $data[17] ?? '';  // 17번
        $seln_dgre_rate = $data[18] ?? '';  // 18번
        $stck_sdpr      = $data[19] ?? '';  // 19번
        $hour_cls_code  = $data[20] ?? '';  // 20번
        $virt_cntg_qty  = $data[21] ?? '';  // 21번
        $vi_stnd_prce   = $data[22] ?? '';  // 22번
        $prdy_vol       = $data[23] ?? '';  // 23번

        if ($code && $stck_prpr && !empty($pdo)) {
            try {
                $stmt = $pdo->prepare("
                    UPDATE daemon_stock_Ticker_0100
                    SET mksc_shrn_iscd = :mksc_shrn_iscd,
                        stck_cntg_hour = :stck_cntg_hour,
                        stck_prpr      = :stck_prpr,
                        prdy_vrss_sign = :prdy_vrss_sign,
                        prdy_vrss      = :prdy_vrss,
                        prdy_ctrt      = :prdy_ctrt,
                        wghn_avrg_prce = :wghn_avrg_prce,
                        stck_oprc      = :stck_oprc,
                        stck_hgpr      = :stck_hgpr,
                        stck_lwpr      = :stck_lwpr,
                        askp1          = :askp1,
                        bidp1          = :bidp1,
                        cntg_vol       = :cntg_vol,
                        acml_vol       = :acml_vol,
                        acml_tr_pbmn   = :acml_tr_pbmn,
                        seln_cntg_smtn = :seln_cntg_smtn,
                        buyt_cntg_smtn = :buyt_cntg_smtn,
                        ctg_vol_intn   = :ctg_vol_intn,
                        seln_dgre_rate = :seln_dgre_rate,
                        stck_sdpr      = :stck_sdpr,
                        hour_cls_code  = :hour_cls_code,
                        virt_cntg_qty  = :virt_cntg_qty,
                        vi_stnd_prce   = :vi_stnd_prce,
                        prdy_vol       = :prdy_vol
                    WHERE stck_shrn_iscd = :code
                ");
                $stmt->execute([
                    ':mksc_shrn_iscd' => $mksc_shrn_iscd,
                    ':stck_cntg_hour' => $stck_cntg_hour,
                    ':stck_prpr'      => $stck_prpr,
                    ':prdy_vrss_sign' => $prdy_vrss_sign,
                    ':prdy_vrss'      => $prdy_vrss,
                    ':prdy_ctrt'      => $prdy_ctrt,
                    ':wghn_avrg_prce' => $wghn_avrg_prce,
                    ':stck_oprc'      => $stck_oprc,
                    ':stck_hgpr'      => $stck_hgpr,
                    ':stck_lwpr'      => $stck_lwpr,
                    ':askp1'          => $askp1,
                    ':bidp1'          => $bidp1,
                    ':cntg_vol'       => $cntg_vol,
                    ':acml_vol'       => $acml_vol,
                    ':acml_tr_pbmn'   => $acml_tr_pbmn,
                    ':seln_cntg_smtn' => $seln_cntg_smtn,
                    ':buyt_cntg_smtn' => $buyt_cntg_smtn,
                    ':ctg_vol_intn'   => $ctg_vol_intn,
                    ':seln_dgre_rate' => $seln_dgre_rate,
                    ':stck_sdpr'      => $stck_sdpr,
                    ':hour_cls_code'  => $hour_cls_code,
                    ':virt_cntg_qty'  => $virt_cntg_qty,
                    ':vi_stnd_prce'   => $vi_stnd_prce,
                    ':prdy_vol'       => $prdy_vol,
                    ':code'           => $code,
                ]);
                $received++;
                $total_received++;
                echo "[" . date('H:i:s') . "] {$code} | {$stck_prpr}\n";
            } catch (Throwable $e) {
                error_log("[WS] DB 에러 ({$code}): " . $e->getMessage());
            }
        }
    }

    // 5. 소켓 닫기
    fclose($fp);
    echo "[타격기] 세트 #" . ($index + 1) . " 완료. 1초 휴식...\n";
    sleep(1);
}

echo "[결과] 총 {$total_received}개 종목 업데이트 완료. 퇴근!\n";