FireDrago
[치지직 채팅] 1. 치지직 채팅 웹소켓 프로토콜 분석 본문
본 포스팅은 개인적인 학습 목적으로 작성되었으며, 분석된 내용은 서비스 업데이트에 따라 언제든 변경될 수 있습니다
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\": [...]}"
}
]
}
'프로젝트' 카테고리의 다른 글
| [치지직 채팅] 3. 웹소켓 클라이언트와 스레드 (0) | 2025.12.13 |
|---|---|
| [치지직 채팅] 2. 치지직 채팅 웹소켓 접속하기 (0) | 2025.12.09 |
| [Moment] 프로젝트 쿼리 실행속도 50초 단축하기 (0) | 2025.10.19 |
| [Moment] Spring Boot 로깅 전략: AOP, Logback, Docker Volume, AWS CloudWatch 적용기 (0) | 2025.08.17 |
| [Moment] GitHub Actions CI 9분에서 1분으로 단축하기 (Gradle, Docker 최적화) (0) | 2025.08.03 |
