Study/TIL(Today I Learned)

24.10.10 게임서버

에린_1 2024. 10. 10. 23:22
728x90

게임 서버

게임 서버를 C++로 만들어보고 있다.

저번에 만들어 본 서버를 토대로 만들다가 너무 지저분하게 코드를 쓰고 있어서 다시 자료를 찾고 재시작중이다. 어렵지만 해야디..해야디.. 게임 서버하고 언리얼, 언리얼 데디서버까지 봐야해서 쉽지는 않을것같다. 그래도 고다고

  • C++로 게임 서버를 개발할 때는, 클라이언트와 서버 간의 네트워크 통신을 처리하고, 게임의 상태를 서버에서 관리한다. 서버는 클라이언트의 요청을 받고, 게임 로직을 처리한 후, 그 결과를 다시 클라이언트에 전달한다. 서버는 게임의 중요한 상태를 유지하고, 여러 클라이언트 간에 동기화를 책임진다.

소켓(Socket)과 통신

  • C++ 서버는 소켓(Socket)을 이용해 클라이언트와 통신한다. 소켓은 네트워크를 통해 데이터를 주고받을 수 있는 일종의 엔드포인트다. 서버는 소켓을 열어 클라이언트로부터 연결 요청을 받아들이고, 데이터를 주고받을 수 있다. 소켓은 주로 TCP나 UDP 프로토콜을 사용한다.
    • TCP (Transmission Control Protocol)
      • 신뢰성 있는 연결을 보장하며, 데이터가 순서대로 전달되도록 한다.
    • UDP (User Datagram Protocol)
      • 빠른 전송이 가능하지만, 데이터가 유실될 수 있고 순서가 보장되지 않는다. 실시간성이 중요한 게임에서는 주로 UDP를 사용한다.

기본적인 서버 구조

  • 서버는 일반적으로 메인 루프에서 클라이언트의 연결을 기다리고, 클라이언트의 요청을 처리한 후, 해당 데이터를 다른 클라이언트에게 전송하는 구조로 동작한다.
int main() 
{
    InitializeSocket();

    int serverSocket = OpenServerSocket(PORT);

    while (true) 
    {
        int clientSocket = AcceptClientConnection(serverSocket);
        char buffer[1024];
        int bytesRead = recv(clientSocket, buffer, sizeof(buffer), 0);
        ProcessGameLogic(buffer);
        send(clientSocket, buffer, bytesRead, 0);
    }

    CloseSocket(serverSocket);
}

#include <winsock2.h>
#include <ws2tcpip.h>
#pragma comment(lib, "Ws2_32.lib")

void InitializeSocket() 
{
    WSADATA wsaData;
    int result = WSAStartup(MAKEWORD(2, 2), &wsaData); 
    if (result != 0) 
    {
        printf("WSAStartup 실패: %d\\n", result);
        exit(1);
    }
}

멀티스레딩 (Multi-threading)

  • 여러 플레이어가 동시에 접속하는 상황에서는 멀티스레드로 서버를 구현하여 각 클라이언트의 요청을 독립적으로 처리하는 것이 일반적이다. 이를 통해 한 클라이언트의 요청이 다른 클라이언트의 처리에 영향을 미치지 않도록 한다.
void HandleClient(int clientSocket) 
{
    char buffer[1024];
    while (true) 
    {
        int bytesRead = recv(clientSocket, buffer, sizeof(buffer), 0);
        ProcessGameLogic(buffer);
        send(clientSocket, buffer, bytesRead, 0);
    }
}

서버의 역할

  • 게임 상태 관리
    • 서버는 모든 플레이어의 게임 상태(위치, 점수 등)를 관리하고, 클라이언트 간의 일관성을 유지한다.
  • 동기화
    • 서버는 각 클라이언트의 행동을 중앙에서 처리하고, 그 결과를 다른 클라이언트에게 전달하여 게임을 동기화한다.
  • 보안
    • 서버는 게임 로직을 관리하기 때문에, 클라이언트가 임의로 조작할 수 없도록 해야 한다. 따라서 중요한 게임 로직은 서버에서 처리된다.

UE5 데디서버 게임서버 통신

1. 소켓 프로그래밍으로 통신

  • 언리얼 엔진의 데디케이티드 서버와 별도의 게임 서버 간에 소켓 프로그래밍을 사용하면, 양방향 통신을 할 수 있다. 양쪽 모두 클라이언트와 서버 역할을 할 수 있고, TCP 또는 UDP 프로토콜을 사용하여 데이터를 주고받을 수 있다.

주요 단계

  1. 언리얼 데디케이티드 서버에서 소켓 연결: 소켓을 열고, 별도의 게임 서버와 연결.
  2. 게임 서버에서 소켓 리스닝: 게임 서버에서 소켓을 열어, 언리얼 데디케이티드 서버로부터 연결을 받아들임.
  3. 데이터 전송: 서로 데이터를 주고받으며, 게임 상태를 동기화하거나 필요한 요청/응답을 처리.

언리얼 엔진에서 소켓 연결을 위한 주요 코드 예시

언리얼 엔진에서 소켓 통신을 구현하려면, FSocket과 ISocketSubsystem을 사용해 네트워크 통신을 할 수 있다.

소켓 클라이언트 (언리얼 서버가 클라이언트 역할):

FSocket* Socket = ISocketSubsystem::Get(PLATFORM_SOCKETSUBSYSTEM)->CreateSocket(NAME_Stream, TEXT("default"), false);

FIPv4Address IP;
FIPv4Address::Parse(TEXT("127.0.0.1"), IP);
TSharedRef<FInternetAddr> Addr = ISocketSubsystem::Get(PLATFORM_SOCKETSUBSYSTEM)->CreateInternetAddr();
Addr->SetIp(IP.Value);
Addr->SetPort(12345);

bool bConnected = Socket->Connect(*Addr);
if (bConnected) {
    FString Message = TEXT("Hello Server!");
    int32 Sent;
    Socket->Send((uint8*)TCHAR_TO_UTF8(*Message), Message.Len(), Sent);
}

소켓 서버 (별도의 게임 서버)

  • 게임 서버는 소켓을 열어 클라이언트(언리얼 서버)로부터 연결을 받아들이고 데이터를 처리한다. 이는 일반적인 C++ 소켓 서버 코드를 참고할 수 있다.
  • 여기서는 socket(), bind(), listen(), accept() 등을 사용해 소켓 서버를 구현할 수 있다.

소켓 프로그래밍 장점

  • 실시간 통신
    • 데이터를 실시간으로 주고받을 수 있어, 빠르고 자유로운 통신이 가능하다.
  • 유연한 통신
    • 원하는 데이터 형식을 자유롭게 정의할 수 있다.

소켓 프로그래밍 단점

  • 구현 복잡성
    • 소켓 통신은 상대적으로 구현이 복잡하며, 데이터 전송의 오류 처리나 패킷 관리가 필요하다.

2. HTTP/REST API 통신

  • 서버 간의 통신을 좀 더 쉽게 구현하고 싶다면, HTTP/REST API를 통해 통신하는 방법도 있다. 이 방식에서는 별도의 게임 서버가 RESTful 웹 서비스를 제공하고, 언리얼 서버가 이를 호출하여 데이터 요청을 처리할 수 있다.

주요 단계

  1. 게임 서버에서 REST API 제공
    • 게임 서버는 웹 서버를 통해 REST API를 제공한다. 예를 들어, 게임 상태를 가져오거나, 업데이트하는 API 엔드포인트를 생성할 수 있다.
  2. 언리얼 엔진에서 HTTP 요청 보내기
    • 언리얼 엔진의 FHttpModule을 사용해 HTTP 요청을 보내고, 게임 서버에서 데이터를 받는다.

언리얼 엔진에서 HTTP 요청 예시

  • 언리얼 엔진에서는 FHttpModule을 사용해 쉽게 HTTP 요청을 보낼 수 있다.
void SendHttpRequest()
{
    TSharedRef<IHttpRequest> Request = FHttpModule::Get().CreateRequest();
    Request->OnProcessRequestComplete().BindUObject(this, &MyClass::OnResponseReceived);

    Request->SetURL(TEXT("<http://localhost:8000/api/gamestate>"));
    Request->SetVerb(TEXT("GET"));
    Request->SetHeader(TEXT("Content-Type"), TEXT("application/json"));
    Request->ProcessRequest();
}

void OnResponseReceived(FHttpRequestPtr Request, FHttpResponsePtr Response, bool bWasSuccessful)
{
    if (bWasSuccessful)
    {
        FString ResponseString = Response->GetContentAsString();
    }
}

REST API 장점

  • 구현이 상대적으로 간단
    • HTTP 프로토콜을 사용하기 때문에, 이미 존재하는 라이브러리나 도구를 쉽게 활용할 수 있다.
  • 확장성
    • API를 사용해 쉽게 서버 간의 통신을 확장할 수 있다.

REST API 단점

  • 실시간 통신 한계
    • HTTP 요청/응답 방식은 실시간으로 데이터를 주고받는 데는 한계가 있다.
  • 네트워크 오버헤드
    • HTTP는 TCP보다 상대적으로 오버헤드가 크기 때문에, 대용량 데이터를 빈번하게 주고받는 경우 성능 이슈가 발생할 수 있다.

결론

  • 실시간 통신이 중요하고, 데이터 전송을 빠르게 해야 한다면 소켓 프로그래밍이 더 적합하다.
  • 구현이 간단하고, 서버 간에 상태나 설정을 주고받는 정도라면 HTTP/REST API를 사용하는 것도 좋은 선택이다.
728x90

'Study > TIL(Today I Learned)' 카테고리의 다른 글

24.10.14 게임 서버  (0) 2024.10.14
24.10.11 게임 서버  (0) 2024.10.11
24.10.08 UE5, CS  (8) 2024.10.08
24.10.02 UE5, 알고리즘  (1) 2024.10.02
24.09.30 UE5  (1) 2024.09.30