Programming/Client

메타버스는 어떻게 구현할까? (WebSocket, 서버 분할, 멀티 스레드)

Jiwoo 2023. 5. 3. 11:17

🌳 들어가며

과거 메타버스라는 말이 없을 때 퍼피레드, 팝플의 헤비 유저였고

성인이 되고서는 제페토와 본디 등을 해봤다.

가상의 공간이지만 나만의 공간을 마련하고, 꾸미고,

사람들과 관계를 맺으면서 어떠한 갈증이 충족되는 것을 느꼈기에

그런 게임이나 공간이 있다면 이끌려 시작했었다.

현재는 모각코를 위해 zep, 게더타운과 같은 서비스를 주로 이용하고 있는데,

전부터 메타버스가 어떤 기술로 구현되는지 궁금했기에 이번 기회에 공부해봤다!!


유저끼리 소통하고, 움직임을 감지하려면 어떻게 해야할까?
다른 웹 사이트와 같이 HTTP 프로토콜을 이용해서 요청을 보내고 받아야 할까?
아니다. 실시간으로 양방향 소통을 위해서는 웹 소켓을 이용해야 한다.

📌 웹 소켓

서버와 클라이언트 간의 양방향 통신을 지원하는 컴퓨터 통신 프로토콜

HTML5 표준에 포함되어 있어서 대부분의 최신 브라우저에서 지원된다.

라이브러리를 통하면 보다 쉽게 사용 가능하다!

  • HTTP와 같이 OSI 모델 중 7계층인 어플리케이션 계층에 위치에 있으며 TCP에 의존함
    (네트워크 시스템의 계층 구조는 전에 정리한 글을 참고)

  • 하지만 HTTP와 달리 웹 소켓을 사용해 통신하면 실시간 통신이 가능하다.

작동 원리
첫 연결은 HTTP 프로토콜을 통해 이뤄짐
-> 연결이 정상적으로 이루어진다면 websocket 연결(TCP/IP 기반)까지 이뤄짐
-> 일정 시간이 지나면 HTTP 연결은 자동으로 끊어짐

HTTP vs Socket 뭐가 다를까?

HTTP 연결

클라이언트에서 서버로 요청을 보내고, 서버는 요청에 대한 응답을 보낸 후 연결을 끊음

  • 클라이언트 요청시에만 서버가 응답, 서버가 데이터 보낼 수 없음 = 단방향 통신

Socket 연결

서버와 클라이언트가 특정 Port를 통해 연결을 유지하고 있어 실시간으로 통신 가능

  • 서버도 클라이언트로 요청 보낼 수 있음 = 양방향 통신
  • 실시간으로 변화하는 데이터 빠르게 처리, 사용자 경험 향상 가능
  • ex) 실시간 동영상 스트리밍, 온라인 게임, 채팅, 메타버스

📌 웹 소켓 서버 & 분할 서버

그럼 웹 소켓 서버는 어떤 식으로 구축해야 안정적으로 대량의 유저를 감당할 수 있을까?
멀티 스레드를 기반한 서버를 여러 개로 분할해서 운영해야 한다!

웹 소켓 서버

웹 소켓 프로토콜을 기반으로 양방향 통신을 지원하는 서버

클라이언트로부터 웹 소켓 연결 요청을 받으면 해당 요청을 수락하고,
클라이언트와 웹 소켓 연결을 생성함

이후 클라이언트와 서버 간에는 웹 소켓 프로토콜을 사용하여 양방향 통신 수행 가능


수행 기능

  • 웹 소켓 연결 수락 및 관리
  • 데이터 송수신
  • 이벤트 처리: 클라이언트와 서버 간에 발생하는 이벤트 처리 ex) 클라이언트 연결 및 해제, 오류 발생
  • 보안 기능: SSL/TLS 암호화 같은 보안 기능 제공

분할 서버 환경

대규모 사용자를 지원하기 위해 일반 API 요청을 처리하는 서버, 웹 소켓 서버를 분리하고
필요에 따라 웹 소켓 서버도 여러 개로 분할하여 운영한다.

이를 통해 각 서버는 특정 범위의 클라이언트 연결을 처리하고,
서버 간의 연결과 부하를 분산시키는 것이 가능해짐.
=> 이를 Load Balancing 이라고 하며, 로드 밸런서를 사용함!


로드 밸런서(Load Balancer)
클라이언트의 연결 요청을 웹 소켓 서버 중 하나에 연결함
이때 로드 밸런서는 각 웹 소켓 서버의 부하 상태를 모니터링하여 부하 분산을 최적화하고,
서버의 장애 발생 시 다른 서버로 연결을 전환할 수 있는 기능도 제공함

📌 멀티스레드

다수의 유저의 동시 접속에 대응하기 위해 웹 소켓 서버에서 멀티스레드를 사용한다

서버는 각 유저에 대한 소켓 연결을 각각의 쓰레드로 처리하여
동시에 다수의 요청에 대응할 수 있기 때문이다.

그럼 왜 멀티 프로세스가 아닌 멀티스레드를 사용할까?

프로세스 vs 스레드 (그래서 둘이 뭔데?)


프로세스

컴퓨터에서 실행되고 있는 하나의 작업 덩어리

  • 각각 독립된 메모리 영역(code, data, stack, heap)을 할당받음
  • 기본적으로 프로세스당 최소 1개의 스레드를 가짐
  • 각 프로세스는 별도의 주소 공간을 가지고, 다른 프로세스의 공간에 접근 불가 (접근하려면 IPC라는 프로세스간의 통신을 해야함)

스레드

프로세스 내에서 실행되는 여러 흐름의 단위 / 프로세스의 특정한 수행 경로

  • 프로세스 내에서 stack만 따로 할당받고 나머지 영역은 스레드끼리 서로 공유함
    (프로세스와 다르게 스레드는 각자의 힙 공간을 공유하며 읽고 쓸 수 있음)
  • 한 스레드가 프로세스 자원을 변경하면 다른 이웃 스레드도 변경 결과 즉시 볼 수 있음

멀티 프로세스 vs 멀티 스레드 (둘 중 뭘 선택해야 할까?)

장단점이 있으니 프로젝트마다 적절히 선택해서 구현해야함!


멀티 프로세스 멀티 스레드
작업 방식 프로세스들이 협력적으로 하나 이상의 작업을 동시에 처리 하나의 프로세스에 여러 스레드로 자원을 공유하며 작업 수행
필요한 메모리 / 시간 큼 / 오래 걸림

- CPU가 프로세스를 왔다갔다하며 프로그램을 실행하는 context switching은 둘다 필요하나, 프로세스가 더 느림
적음 / 빠름

- 운영체제가 시스템 자원을 효율적으로 관리 가능
안전성 높음 (각 프로세스가 독립된 구조)

- 하나의 프로세스 에러가 다른 프로세스에 영향 X
낮음 (각 스레드가 자원 공유함)

- 전역 변수를 이용하므로 동기화 문제 & 충돌 가능성 높음 - 한 스레드의 에러가 다른 스레드에도 영향 미침
통신 비용

- 프로세스 간의 데이터를 주고받는 통신에 부담이 큼
적음

- 스레드 간 자원을 공유하기 때문에 통신하기 쉬움
사용되는 예시 윈도우, 리눅스가 지원은 함 윈도우, 리눅스, 웹 서버

📌 웹 소켓 통신 맛보기

1. 프로젝트 생성

$ npm init
$ npm i express ws

우리는 간단하게 웹 소켓이 실행되는 모습을 보기 위해
node.js 의 ExpressJS 프레임워크와 ws 라는 웹소켓 라이브러리를 사용하여 통신해 볼 것이다.
node.js는 싱글 스레드 기반이기 멀티 스레드를 구현하기는 어렵다.
멀티스레드 구현 가능한 언어는 저수준 언어인 C, C++, 어셈블리어부터
고수준 언어인 Java, Python, Ruby, Go 등이 있으니 참고!

2. 클라이언트 코드 세팅 & 서버 만들기

<!-- index.html -->
<!DOCTYPE html>
<html lang="ko">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Websocket Chatting</title>
  </head>
  <body>
    <h1>채팅 페이지</h1>
    <button id="send">메세지 보내기</button>
  </body>
</html>
// app.js
const express = require("express")
const app = express()

app.use("/", function(req, res) {
    res.sendFile(__dirname + '/index.html');
})

app.listen(8000)

정적 파일을 서빙할 node.js 서버도 생성해주자.
이 서버는 HTML 파일을 서빙함과 동시에 웹소켓 서버역할도 해준다.
접속 포드는 8000번이며 아래 명령으로 서버를 실행하면 된다.

$ node app.js

3. 웹 소켓 서버 만들기 & 클라이언트의 메세지 받기

<!-- index.html -->
<!DOCTYPE html>
<html lang="ko">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Websocket Chatting</title>
  </head>
  <body>
    <h1>채팅 페이지</h1>
    <button id="send" onclick="socket.send('hello')">메세지 보내기</button>
    <!-- 버튼을 누르면 서버로 메세지 전송됨 -->
    <script>
        let socket = new WebSocket("ws://localhost:8081")
    </script>
  </body>
</html>
// app.js
// 위 코드 아래에 추가

// ws 모듈 불러오기
const WebSocket = require('ws');
// 웹 소켓 서버 열어주기 (포트는 8081번)
const socket = new WebSocket.Server({ port: 8081 })

socket.on("connection", (ws, req) => {
    ws.on("message", msg => {
      // 클라이언트로부터 메세지 받아서 콘솔 출력
      console.log(`Received from user: ${msg}`)
    })
})

socket은 웹소켓 서버를 의미하고,
콜백함수로 받아온 ws는 연결된 클라이언트를 의미한다.
위 코드는 socket에 연결 이벤트를 바인딩하고,
연결된 클라이언트의 메세지를 콘솔에 출력하는 동작을 수행한다.


node app.js로 서버를 실행하고 버튼을 누르면
서버 콘솔에 메세지가 전달된다!

4. 서버에서 메세지 전달하기

// app.js
socket.on("connection", (ws, req) => {
    ws.on("message", msg => {
      // 클라이언트로부터 메세지 받아서 콘솔 출력
      console.log(`Received from user: ${msg}`)
      // 서버가 메세지 전송
      ws.send('나도 안녕')
    })
})

이렇게 send 메서드를 사용하여 클라이언트에게 메세지를 전달할 수 있다.

🌳 마치며

node.js를 사용하여 웹 소켓을 맛보기 해봤다.
이를 활용하여 간단한 채팅앱부터 메타버스 서비스까지 구현할 수 있따.
앞으로도 메타버스를 활용한 서비스들은 더 많아질 것으로 보이니
이를 알아놓고 활용해보는 것이 중요할 듯 하다.


참고

위키백과: 웹 소켓
Http 프로그래밍과 Socket 프로그래밍 차이
웹소켓으로 개발하기 전 알아야 할 것들