월드컵 생중계 보면서 개발자 모드 켜기

Broccolism·2022년 12월 4일
82
post-custom-banner

네이버 스포츠에서 개발자 모드 켜보기

"네이버 축구 생중계 그거.. 폴링 방식 쓰는거같던데요?"

때는 형제의 나라 가나와 붙었던 11월 28일...
후반전 시작하고 얼마 안 돼서 연속 2골이나 넣어버린 조규성 선수를 침흘리면서 보다가 문득 스터디원이 말했던게 생각났다.

동점 상황

제발 딱 한 골만 더!!! 하는 심정으로 네이버 생중계를 띄워놓고 F12를 눌러 크롬 개발자 모드를 켰다.
롱폴링도 아니고 그냥 폴링을 쓴다니, 왜지?

개발자 도구로 네트워크를 살펴보니 진짜로 일정한 시간 간격을 두고 request-response가 전달되고 있었다.

어떤 response가 들어왔나?

크게 2가지 타입으로 나눌 수 있었다.

  • 실시간 스트리밍 데이터를 보내는 응답
  • 현재 게임 상황과 중계 데이터를 보내는 응답

실시간 스트리밍 데이터


잘 보면 이름에 stream 이 들어간 게 있다. chunklist_480.stream.m3u8의 response를 찍어보면 이렇게 알 수 없는 내용들이 가득 적혀있는데, 아무래도 이게 우리가 보는 생중계 영상인 것 같다. 이유는..

먼저 Response를 한 줄씩 뜯어보면 #EXTINF 라는 문구로 시작하는 줄이 규칙적으로 나오는데, 구글에 extinf 라고 검색하면 이런 위키 백과 페이지가 하나 나온다.

그리고 이 M3U 라는 파일 포맷의 확장판이 Extended M3U이고 확장정보를 담을 때 #EXTINF를 사용한다고 적혀있다.

다시 M3U를 검색해보면 'http live streaming'을 위해 사용되는 파일 포맷이라는 내용의 글들이 꽤 보인다. 그리고 앞서 찾았던 위키 백과의 외부 링크 하나를 타고 들어가면...

이렇게 #EXTINF 태그의 포맷도 친절히 알려주고 있다. 네이버 스포츠 캡처본을 보면 #EXTINF:2.000000,~~~ 라고 되어있는걸 보면 duration은 2초마다 끊은 것 같다.

참고로 480.stream_* 응답은 이렇게 생겼다. 스트리밍 데이터라 개발자 모드에서 제대로 변환을 못 하는 것 같다.

게임 현황 및 중계 데이터

이제 다른 유형의 응답을 보자. 이름부터 좀 다르다. game-polling 이다. 아마 스터디원은 이걸 보고 polling 방식을 사용한다고 말했던 것 같다.

이번 응답은 좀 더 인간 친화적이다. 무려 JSON 형태로 오고 있다!
그래서 포멧팅을 해서 구경하던 중에 이런걸 발견했다.

{
  "scorers": {
    "home": [
      {
        "time": 58,
        "addedTime": null,
        "playerName": "조규성",
        "ownGoal": false
      },
      {
        "time": 61,
        "addedTime": null,
        "playerName": "조규성",
        "ownGoal": false
      }
    ],
    "away": [
      {
        "time": 24,
        "addedTime": null,
        "playerName": "모하메드 살리수",
        "ownGoal": false
      },
      {
        "time": 34,
        "addedTime": null,
        "playerName": "쿠두스",
        "ownGoal": false
      }
    ]
  }
}

오호.. 골 넣은 사람들 목록이구만? addedTimeownGoal이 무슨 뜻인지는 모르겠다.
조금 더 내려보자.

{
  "textRelayData": {
    "homeScore": 2,
    "awayScore": 2,
    "textRelays": [
      {
        "no": 1746503169,
        "eventType": "Goal",
        "flag": "0",
        "homeOrAway": "Home",
        "time": "61",
        "normalTime": "61",
        "addedTime": "0",
        "homeScore": 2,
        "awayScore": 3,
        "ptHomeScore": 0,
        "ptAwayScore": 0,
        "homeScorePlayer": null,
        "awayScorePlayer": null,
        "half": "2",
        "playerId": "1996453",
        "playerName": "조규성",
        "videoMasterId": "",
        "text": "골! 조규성의 헤딩골로 경기는 2 - 2 동점이 됩니다.",
        "statusCode": 2,
        "statusInfo": "후반 16'",
        "eventName": "Goal",
        "eventText": "Goal",
        "eventClassName": "stat_goal"
      }
    ]
  }
}

보아하니 조규성 선수의 동점골이 터졌을 때인 것 같다. 그러고보니 실제 중계 웹사이트에서 스크롤을 조금만 내려보면 텍스트로 중계를 보여주는 부분이 있었다.

자동 업데이트를 켜놓으면 이 텍스트 중계 부분이 웹페이지를 새로고침하지 않아도 저절로 업데이트가 되는걸 발견했다. 그리고 끝까지 보면서 후반부에 가나가 마지막 골을 넣었을 때에도 새로고침 없이 저절로 점수가 업데이트 되었다. game-polling이라는 이름처럼 주기적으로 요청을 보내고 있다는걸 확인할 수 있었다. 스크린샷은 찍지 못했지만 polling-status라는 이름의 연결이 있는걸 보면 폴링 혹은 이와 유사한 방식을 썼을 것이라고 추측해볼 수 있다. (물론 네이버 스포츠에서 이름만 polling이고 사실은 웹소켓이나 다른 방식을 썼을 수도 있다.🧐)

폴링과 롱폴링, 웹소켓

서버와 클라이언트가 실시간으로 소통하(는 것처럼 보이)기 위해 고안된 기법이 있다. 간단하게 요약하자면 이렇다.

폴링

클라이언트: "내가 5초마다 요청 넣을게. 그 때마다 바로 보내줘."
서버: "그래. 나도 요청 받자마자 바로 응답 주고 연결 닫을게."

롱폴링

클라이언트: "내가 5초마다 요청 넣을게. 근데 보낼거 없으면 좀 더 있다가 보내줘."
서버: "그래. 요청 받고나서 새 데이터가 생기면 보내줄게. 대신 대기시간 지나면 연결 닫을거야."

웹소켓

클라이언트: "우리 오래오래 보자."
서버: "그래. 둘 중 하나가 연결 끊기로 하기 전까지 계속 연결되어 있는거야."

약간의 자료조사와 약간의 뇌피셜

먼저 웹소켓을 사용하지 않은 이유는 명백하다.

동접자가 200만명에 달하는 상황에서 서버가 클라이언트 하나마다 연결을 모두 유지해야 한다면..? 경험이 짧은 나로써도 쉽지 않을 것 같다는 생각이 든다. 또한 웹소켓은 보통 서버 to 클라이언트 및 클라이언트 to 서버, 양방향 소통이 모두 필요할 때 유용한 방식이기도 하다. 월드컵 생중계는 클라이언트에서 서버쪽으로 보낼 데이터가 없다. 굳이 웹소켓을 쓸 필요가 없는 것이다.

그렇다면 폴링과 롱폴링 중 어떤걸 썼을까? 에 대한 구체적인 단서는 찾지 못했다. 응답 시간이 아주 칼같이 일정한 간격으로 찍혔다면 폴링일 가능성이 높을텐데 이것도 정확하지 않다. 네트워크 상황에 따라 달라질 수 있는 요소가 많기 때문이다.

결론

  • API call은 2종류로 나뉘었다: 영상 스트리밍을 위한 것, 게임 현황 및 중계 정보를 위한 것.
  • 영상 스트리밍에서는 M3U 파일 포맷을 사용했다.
  • 게임 현황 및 중계 정보를 받아오기 위해서는 폴링(혹은 그와 유사한) 방식을 썼을 것이다.

혹시나 단서를 찾은 분이 계신다면 공유해주세요. 🤗

profile
설계를 좋아합니다. 코드도 적고 그림도 그리고 글도 씁니다. 넓고 얕은 경험을 쌓고 있습니다.
post-custom-banner

19개의 댓글

comment-user-thumbnail
2022년 12월 4일

진짜 축구보면서도 이런 걸 찾으시다니 대단합니다 :) 저희 회사도 비디오,오디오 스트리밍 서비스에 hls를 사용하고 있어요 ㅎㅎ 이미 아실 수도 있지만 참고 되시는 링크하나 남겨요! https://aws.amazon.com/ko/media/tech/video-latency-in-live-streaming/

1개의 답글
comment-user-thumbnail
2022년 12월 6일

오운골 선수 같은 레전드 선수를 모르시다니

2개의 답글
comment-user-thumbnail
2022년 12월 6일

잘 읽었습니다. 감사합니다.
저도 갑자기 궁금해져서 찾아봤는데 제 환경에서는 조금 다르게 보였습니다.
제 환경에서도 폴링은 계속 이뤄지고 있었지만 네이버 스포츠 플러그인이라는 크롬 익스텐션을 설치한 사용자와 그렇지 않은 사용자가 분기되는 것 같습니다.
480p 이하의 화질 동영상 서비스에서는 말씀 주신대로 cdn 서버에서 짧은 동영상을 폴링 받아오는 것으로 추측됩니다.
화질이 720p 이상이 되면 (720, 1080) 네이버 스포츠 플러그인을 거쳐서,
똑같이 짧은 길이의 동영상을 폴링하되 주소가 로컬의 17080번 포트로 변경되었습니다.
480p에서 720p로 화질을 변경하게 되면 cdn서버에 폴링을 종료한다는 메시지를 보낸 다음, localhost로 폴링을 개시한다는 메시지를 보내 그 위치를 바꾸게 됩니다.
이 정보 조각들로 미루어보아 아마 이런 방식으로 동작하지 않을까? 하는 생각을 했습니다.
1) 경기 요약 (골, 옐로/레드 카드 등)은 지속적으로 HTTP 폴링 서버에서 가져온다
2) 게임 동영상 자체는 ffmpeg을 사용하여 짧은 길이의 동영상으로 자른 영상을 폴링한다.
3) 이 때, 고화질의 동영상을 많은 사용자가 폴링할 경우 리소스가 많이 소모되므로 별도의 플러그인을 사용한다.

플러그인의 역할이 무엇인지 정확히 알지는 못하겠는데, 서버 자체의 커넥션 비용을 줄이기 위한 모종의 방법을 사용하지 않을까요? ㅎㅎ
wireshark로 더 찾아보니 특정 위치의 IDC로 종종 TCP 커넥션을 재연결하는 모습도 보입니다. 로컬에서는 또 10080 포트를 사용해서 통신하네요.
월드컵 시즌인만큼 저도 관련된 포스트를 조만간 써보겠습니다!

1개의 답글
comment-user-thumbnail
2022년 12월 7일

OwnGoal은 아마 자책골을 지칭하는 것 같습니다

1개의 답글
comment-user-thumbnail
2022년 12월 8일

addedTime은 추가시간인 것 같아요 (추가시간에 골 넣으면 90+2분 이렇게 나왔던 걸 본 것 같네요.)

1개의 답글
comment-user-thumbnail
2022년 12월 12일

음.. 그러면 발로란트플랜 사이트도 롱풀링 방식인건가 .. (게임 전략짜는 사이트인데 피그마느낌입니다)

1개의 답글
comment-user-thumbnail
2022년 12월 12일

우왕 잘봤습니다.

1개의 답글
comment-user-thumbnail
2022년 12월 12일

우와 재미있어요 ㅋㅋㅋ 잘 읽었습니다

1개의 답글