FireDrago

[치지직 채팅] 1. 치지직 채팅 웹소켓 프로토콜 분석 본문

프로젝트

[치지직 채팅] 1. 치지직 채팅 웹소켓 프로토콜 분석

화이용 2025. 12. 5. 21:07

본 포스팅은 개인적인 학습 목적으로 작성되었으며, 분석된 내용은 서비스 업데이트에 따라 언제든 변경될 수 있습니다

1. 채팅창 연결

 

  • 치지직 방송에 입장하면 채팅 연결을 위한 웹소켓 연결 통신이 이루어진다.
GET wss://kr-ss3.chat.naver.com/chat HTTP/1.1
Host: kr-ss3.chat.naver.com
Connection: Upgrade
Pragma: no-cache
Cache-Control: no-cache
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/142.0.0.0 Safari/537.36
Upgrade: websocket
Origin: https://chzzk.naver.com
Sec-WebSocket-Version: 13
Accept-Encoding: gzip, deflate, br, zstd
Accept-Language: ko-KR,ko;q=0.9,en-US;q=0.8,en;q=0.7
Sec-WebSocket-Key: 3/az/P5eagleGlTMBVxrjQ==
Sec-WebSocket-Extensions: permessage-deflate; client_max_window_bits

1. Request Headers

  • URL: wss://kr-ss3.chat.naver.com/chat
  • Connection: Upgrade & Upgrade: websocket :
    HTTP 프로토콜을 WebSocket 프로토콜로 전환하겠다는 표준 핸드쉐이크 요청이다.
  • Sec-WebSocket-Key :
    브라우저가 생성한 임의의 키값이다.
    서버는 이 키를 바탕으로 올바른 웹소켓 연결인지 검증하고 응답(Accept) 헤더를 내려준다.

2. Response Headers

  • HTTP/1.1 101 Switching Protocols :
    가장 중요한 응답 코드. 서버가 클라이언트의 Upgrade 요청을 승인했으며,
    지금부터 이 TCP 연결을 HTTP 프로토콜이 아닌 WebSocket 프로토콜로 전환하여 사용하겠다는 의미다.
    이 응답을 받은 직후 onOpen 이벤트가 발생한다.
  • Connection: upgrade & upgrade: websocket :
    프로토콜 전환이 확정되었음을 명시한다.
  • Sec-WebSocket-Accept :
    클라이언트가 보낸 Sec-WebSocket-Key에 특정 매직 스트링(GUID)을 붙여 SHA-1로 해싱한 결과다.
    클라이언트는 이 값을 검증하여 자신이 보낸 요청에 대한 올바른 응답인지 확인한다. (핸드쉐이크 무결성 검증)
  • Server: nginx :
    치지직 채팅 서버 앞단에 Nginx가 리버스 프록시(Reverse Proxy)로 배치되어 있음을 알 수 있다.
  • Cache-Control / Pragma / Expires :
    실시간 양방향 통신이므로 브라우저나 프록시 서버가
    데이터를 캐싱(저장)하지 않도록 막는 헤더들이다. (no-cache, max-age=0)
  • X-Frame-Options / X-XSS-Protection :
    보안 관련 헤더. DENY는 이 페이지(소켓 엔드포인트)를 <iframe> 등으로 다른 사이트에서 불러오지 못하게 막는 설정이다.

3. Socket 프로토콜 명세

3-1. 인증 및 접속 요청 (Handshake Request)

  • WebSocket 프로토콜로 전환된 직후 클라이언트는 서버에게 요청을 보낸다.
{
  "ver": "3",                            // 통신규약 버전 
  "cmd": 100,                            // 패킷 식별자, 핵심명령 
  "svcid": "game",                       // 네이버 라이브 채팅 서버중 '치지직' 식별자
  "cid": "{CHANNEL_ID}",                 // 스트리머의 고유 id
  "tid": 1,                              // 요청과 응답을 짝지어주는 일련번호
  "bdy": {
    "uid": "{USER_ID}",                  // 유저 고유 식별자, 토큰(accTkn)과 일치해야함
    "devType": 2001,                     // 접속한 기기의 종류 
    "devName": "Google Chrome/142.0.0.0" // 브라우저의 종류
    "accTkn": "{ACCESS_TOKEN}",          // 가장 중요한 토큰값 요청을 통해 미리 받아와야함
    "libVer": "4.9.3",                   // 웹 자바스크립트 SDK 버전 (주기적 업데이트)
    "locale": "ko",                      // 위치
		"osVer": "macOS/10.15.7"             // os 정보
		"timezone": "Asia/Seoul"             // 시간정보
    "auth": "SEND"                       // 권한 요청
  }
}

토큰을 어떻게 받아올까?

  • 채팅방 최초 접속인데도 불구하고,
    accessToken 이 포함되어있다.
    어떻게 클라이언트는 토큰을 발급받을까?
  • 방송에 최초입장하면, 채팅방 입장전에 이런 요청을 먼저 보내는 것을 볼 수 있다.
    응답 형식은 다음과 같은데 토큰을 전송받고, 이 토큰을 그대로 채팅방 입장에 사용한다.
  • 채팅창 입장은 chat.naver.com / 토큰발급은 comm-api.game.naver.com 인걸로 보아
    기존의 네이버 게임 플랫폼의 모듈을 일부 사용하고 있는것 같다. (추측)
{
    "code": 200,
    "message": null,
    "content": {
        "accessToken": "{token}",
        "temporaryRestrict": {
            "temporaryRestrict": false,
            "times": 0,
            "duration": null,
            "createdTime": null
        },
        "realNameAuth": false,
        "extraToken": "{token}"
        "chatTime": null
    }
}

3-2 . 접속 승인 응답 (Handshake Response)

// [Server -> Client] 접속 요청(cmd: 100)에 대한 응답
{
  "svcid": "game",
  "cmd": 10100,         // [중요] 100번 요청에 대한 승인 코드 (100 + 10000)
  "retCode": 0,         // [중요] 0이면 성공. 그 외 숫자는 에러(차단/만료 등)
  "retMsg": "SUCCESS",  // 결과 메시지
  "tid": "1",           // 내가 보냈던 요청의 tid와 일치하는지 확인
  "cid": "N29nlK",      // 채널 ID
  "bdy": {
    "sid": "TwyKl...",  // [★핵심] 세션 ID. 이 값을 변수에 저장해야 함 (이후 모든 요청에 필수)
    "uuid": "eb4a...",  // 유저 식별값 (크게 중요치 않음)
    "accTkn": "...",    // 내가 보낸 토큰 확인용
    "auth": "SEND"      // 부여된 권한 (SEND or READ)
  }
}
  • retCode : (0) : 가장 먼저 체크해야 한다.
  • bdy.sid : 이 값을 저장하여 채팅 요청을 보내거나 향후 모든 요청에 필수적으로 사용된다.
  • 서버는 접속 허가하는 응답을 내려준다. 세션 정보(sid) 를 주목하자

3-3. 최근 채팅 내역 조회 (Request Recent Messages)

// [Client -> Server] 접속 성공 직후, 이전 대화 내용을 불러옴
{
  "ver": "3",
  "cmd": 5101,          // [중요] 최근 메시지 조회 명령어
  "svcid": "game",
  "cid": "N29nlK",
  "sid": "TwyKl...",    // [★필수] 위(10100)에서 받은 Session ID를 여기에 넣어야 함
  "tid": 2,             // [중요] 두 번째 요청이므로 1 증가시킴 (순서 보장)
  "bdy": {
    "recentMessageCount": 50  // 가져올 이전 채팅 개수 (보통 50 사용)
  }
}
  • sid : 만약 이 필드가 null 이거나, 틀린 값을 보내면, 서버는 401 Unathorized 에러를 반환하고 연결을 종료한다.
  • tid : 비동기 통신에서 요청의 순서를 맞추는 키다. 1번 요청이 끝나야 2번 요청이 가능하다.

2. 상태 유지

  • WebSocket 연결은 영구적이지 않다.
    Nginx 프록시나 서버 설정에 의해 일정시간 데이터가 오가지 않으면 연결이 끊긴다.
    이를 위해 주기적으로 살아있다는 신호를 보내야한다.
  • 치지직 같은 대형 서버는 수많은 sleep 유저를 즉시 연결 끊어야함 (웹소켓 연결 비용)
    서버가 먼저 핑을 전송 하고 대답이 없을시 바로 연결 종료

2-1. Ping(Server → Client)

  • 1분에 한번씩 서버가 먼저 보냄
{
  "ver": "2",
  "cmd": 0 // 살아있니?
}

2-2. Pong(Client → Server)

  • 거의 딜레이없이 클라이언트가 응답
{
  "ver": "3",
  "cmd": 1000 // [중요] 10000번은 핑(Ping) 명령어를 의미함
}

2- 3. 특수 케이스

  • 장시간 모니터링 결과, 대부분의 경우 서버가 먼저 핑을 보내지만,
    극히 드물게 클라이언트가 먼저 핑을 보내는 경우가 있었다.
  • 추정 원인: 네트워크 일시적 지연으로 서버의 핑이 늦게 도착했거나,
    클라이언트 내부의 안전장치(Fallback) 로직이 트리거 된 것으로 추정됨.

3. 채팅 패킷 

  • 핸드쉐이크가 끝나면 서버는 실시간으로 발생하는 이벤트를 전송한다.
    가장 빈번하게 발생하는 일반 채팅과 수익 모델인 후원(도네이션)으로 나뉜다.

3-1. 일반채팅

  • 유저들의 대화, 이모티콘 사용, 매니저 채팅 등이 포함된다.
  • Command Code : 93101
{
  "cmd": 93101,          // [식별자] 일반 채팅 커맨드 
  "cid": "{CHANNEL_ID}", // 
  "bdy": [               // [주의] 배열(Array) 형태. 한 패킷에 여러 메시지가 담길 수 있음 
    {
      "uid": "{USER_ID_HASH}",    // 유저 ID 해시값 
      "msg": "안녕하세요 {:emoji_key:}", // 메시지 본문 (이모티콘 코드가 포함될 수 있음) 
      "msgTypeCode": 1,           // [구분] 1 = 일반 텍스트 채팅 
      "msgStatusType": "NORMAL",  // 메시지 상태 
      "ctime": 1764923581686,     // 메시지 생성 타임스탬프 (Epoch Time) 

      // [★주의] JSON Object가 아니라 'String'임. 이중 파싱(Double Parsing) 필요 
      "profile": "{\"nickname\": \"닉네임\", \"userRoleCode\": \"common_user\", ...}", 

      // [★주의] 역시 JSON String 형태임. 파싱 후 이모티콘 URL 매핑 정보 확인 
      "extras": "{\"emojis\": {\"emoji_key\": \"HTTP_URL...\"}}" 
    }
  ]
}

3-2. 후원 채팅

  • 치즈 후원 메시지이다.
  • Command Code : 93102
{
  "cmd": 93102,          // [식별자] 후원 채팅 커맨드 
  "cid": "{CHANNEL_ID}", // 
  "bdy": [
    {
      "uid": "anonymous",         // [특이사항] 익명 후원일 경우 'anonymous'로 옴 
      "msg": "리액션 해주세요!",      // 후원 메시지 
      "msgTypeCode": 10,          // [구분] 10 = 후원(Donation) 

      // [주의] 익명 후원일 경우 profile 필드가 null임 
      "profile": null,

      // [★핵심] 후원 금액과 정보는 여기에 들어있음 (JSON String 파싱 필요) 
      "extras": "{\"payAmount\": 1000, \"payType\": \"CURRENCY\", \"weeklyRankList\": [...]}" 
    }
  ]
}