[C#] 게임 서버에서 세션(Session)이란?

Arthur·2023년 9월 5일
0
post-thumbnail

공부하게 된 계기


세션이라는 단어는 웹 서비스 개발을 할 때 '쿠키와 세션' 이라는 키워드로 처음 접했었습니다.
세션은 클라이언트 인증을 통해 누군지 식별하고, 그에 따른 권한을 주는 기술로 사용되었습니다.
인증은 대부분 로그인을 할 때 진행됩니다.

게임 서버를 공부하면서도 세션이라는 키워드를 동일하게 사용하는 것을 알게되었습니다.

단순히 인증과 인가를 위해 세션을 사용하는게 아니라 다른 의미가 있을 것 같다고 생각했습니다.
그래서 이번 게임 서버를 공부하면서 세션에 대한 개념도 확립하기 위해 이렇게 공부하게 되었습니다.



세션(Session)이란?


컴퓨터 과학에서, 특히 네트워크 분야에서 반영구적이고 상호작용적인 정보 교환을 전제하는 둘 이상의 통신 장치나 컴퓨터와 사용자 간의 대화나 송수신 연결상태를 의미하는 보안적인 다이얼로그(dialogue) 및 시간대를 가리킨다. 따라서 세션은 연결상태를 유지하는 것보다 연결상태의 안정성을 더 중요시 하게 된다.
<위키백과 - 세션 (컴퓨터 과학)>

게임 서버에서 세션은 클라이언트와 서버 간의 상호작용을 추적하고 유지하기 위한 개념입니다.
세션은 주로 멀티플레이어 게임 및 온라인 게임에서 사용됩니다.

세션의 중요한 의미는 아래와 같습니다.

  • 인증 및 접속 관리
    • 플레이어가 게임에 접속하면, 세션은 인증 정보를 저장하고 게임 서버와의 통신을 가능하게 합니다.
  • 게임 상태 저장
    • 플레이어의 위치, 게임 진행 상황, 보유 아이템 등을 추적하고 저장합니다.
    • 플레이어가 게임을 재접속할 때 이전 상태로 복구할 수 있습니다.
  • 통신 및 데이터 교환
    • 플레이어의 입력 및 동작이 세션을 통해 서버로 전달되며, 서버는 다른 플레이어와 상호작용하거나 게임 이벤트를 처리하기 위해 다시 클라이언트로 데이터를 보냅니다.
  • 보안
    • 인증 및 권한 부여를 통해 플레이어가 게임 내에서 허용된 작업만 수행할 수 있도록 보호합니다.

제가 세션을 이해했던 부분은 인증과 인가(권한 부여)만 있었다는 것을 알게되었습니다.
게임 서버를 강의를 보며 구축하면서 게임 상태 저장과 통신 및 데이터 교환을 주로 다뤄보고 있습니다.
이런 클라와 서버의 상호작용을 유지하고 추적하는 것이 세션이라는 것을 알게되었습니다.

그렇다면 게임 서버에서 이 세션을 담당하는 예제 코드는 어떻게 되어있는지 알아봅시다.
개념을 보기만 하는 것보다는 코드를 보는게 더 기억에 남고 응용할 수 있기 때문에 정리해봤습니다.



C# 예제 코드


public abstract class Session
{
    Socket _socket;
    int _disconnected = 0;

    public void Start(Socket socket)
    {
    	// 세션 소켓을 초기화 합니다.
        _socket = socket;
        
        // 비동기 소켓 통신을 위해 이벤트를 생성합니다.
        SocketAsyncEventArgs recvArgs = new SocketAsyncEventArgs();
        
        // 비동기 소켓 작업이 완료되었을 때 호출될 이벤트를 설정합니다.
        // 콜백 함수 => OnRecvCompleted
        recvArgs.Completed += new EventHandler<SocketAsyncEventArgs>(OnRecvCompleted);

		// 버퍼의 사이즈를 세팅합니다.
        recvArgs.SetBuffer(new byte[1024], 0, 1024]);

        RegisterRecv(recvArgs);
    }
        
    // Buff를 Send합니다.
    public void Send(byte[] sendBuff)
    {
        _socket.Send(sendBuff);
    }

	// 소켓을 셧다운하고 Close 합니다.
    public void Disconnect()
    {
    	// 멀티 스레드 환경에서의 안정성을 위해 Lock 설정을 추가합니다.
        // _disconnected 플래그 값을 활용
        if (Interlocked.Exchange(ref _disconnected, 1) == 1)
            return;

        _socket.Shutdown(SocketShutdown.Both);
        _socket.Close();
    }

    #region 네트워크 통신
    void RegisterRecv(SocketAsyncEventArgs args)
    {
    	// 비동기로 소켓 Receive를 처리합니다.
        // args 객체에 결과를 저장하고 작업이 완료되면 Completed 이벤트를 트리거하여 완료된 상태를 알립니다.
        // 비동기 소켓 작업이 실패하면 직업 OnRecvCompleted 함수를 호출합니다.
        bool pending = _socket.ReceiveAsync(args);
        if (pending == false)
            OnRecvCompleted(null, args);
    }

	// 비동기 소켓 통신에서 데이터 수신 작업이 완료되면 호출되는 이벤트 핸들러
    void OnRecvCompleted(Object sender, SocketAsyncEventArgs args)
    {
    	// 수신된 데이터의 바이트 수가 0보다 크면 성공
        if (args.BytesTransferred > 0 && args.SocketError == SocketError.Success)
        {
            // TODO
            try
            {
                string recvData = Encoding.UTF8.GetString(args.Buffer, args.Offset, args.BytesTransferred);
                Console.WriteLine($"[From Client] {recvData}");
                RegisterRecv(args);
            }
            catch (Exception e)
            {
                Console.WriteLine($"OnRecvCompleted Failed {e}");
            }
        }
        else
        {
            // TODO Disconnect
        }
    }
    #endregion
}

Session은 Listner에서 Accept 이 후에 진행되는 작업입니다.
(Listener 작업에 대한 내용은 링크를 누르시면 볼 수 있습니다.)



Receive와 Send의 차이


둘 다 네트워크 통신에서 데이터를 전송하는 데 사용됩니다.

직역하면 Receive는 받다, Send는 보내다가 됩니다.

  • Send(송신)
    • 데이터를 소켓에서 다른 장치 또는 소켓으로 보내는 작업
    • 보내려는 데이터의 크기와 종류를 지정할 수 있습니다.
  • Receive(수신)
    • 다른 소켓에서 데이터를 받아들이는 작업
    • 데이터를 수신하고 이를 소켓에서 읽어올 수 있게 합니다.


송신(Send) 버퍼와 수신(Receive) 버퍼


소켓 통신에서 두 버퍼는 데이터의 임시 저장 및 전송 관련 작업에 사용됩니다.
개발자는 각 용도에 맞게 버퍼의 사이즈와 주고 받을 데이터를 미리 정하고 코드를 작성합니다.

송신 버퍼(Send Buffer)

  • 큐와 마찬가지로 FIFO(선입선출) 형태로 작동
  • 데이터를 전송하기 전에 데이터가 임시로 저장되는 공간
  • 데이터를 보내려고 하는 소켓은 데이터를 송신 버퍼에 쓰고, 송신 버퍼가 가득 차면 데이터가 네트워크로 전송됩니다.
  • 데이터를 효율적으로 전송하기 위해 패킷으로 나뉘거나 조각내는 데 사용됩니다.
  • 데이터가 송신 버퍼에 들어간 후에도 프로그램은 계속해서 다른 작업을 수행할 수 있으며, 데이터가 실제로 전송되는 동안 대기하지 않아도 됩니다.

수신 버퍼(Receive Buffer)

  • 수신된 데이터가 일시적으로 저장되는 공간
  • 수신한 데이터는 네트워크에서 소켓으로 도착한 후에 수신 버퍼에 저장됩니다.
  • 프로그램은 필요할 때마다 수신 버퍼에서 데이터를 읽어올 수 있습니다.
  • 수신 버퍼의 크기는 일반적으로 송신자와 수신자 간의 데이터 흐름을 조절하기 위해 사용됩니다.

소켓 통신에서 버퍼를 사용하는 이유는 데이터 전송의 효율성과 안정성을 높이기 위해서입니다.
작은 데이터 패킷을 하나씩 전송하는 것보다, 데이터를 버퍼에 쌓아서 한 번에 보내는 경우 레이턴시(지연 시간)를 줄일 수 있습니다.
이런 데이터 패킷은 블록 단위로 처리하기 때문에 효율성을 올려주고 패킷 손실 또는 오류를 처리하기 쉽습니다.
(재전송이나 오류 복구 메커니즘을 통한 데이터 손실 최소화)

이런 버퍼의 사이즈는 개발자가 직접 설정할 수 있는데,
어떻게 설정 하는지에 따라서도 성능 차이가 발생 한다고 합니다.



참고 자료


  • 위키백과 - 세션 (컴퓨터 과학) => 링크
  • 인프런 - [C#과 유니티로 만드는 MMORPG 게임 개발 시리즈] Part4: 게임 서버 => 링크
  • 인프런 - 식당으로 비유? (Receive, Send와 Asynchronization) => 링크
  • [C/C++] Socket Send/Receive Buffer에 대한 고찰 => 링크
  • chatGPT
profile
기술에 대한 고민과 배운 것을 회고하는 게임 서버 개발자의 블로그입니다.

0개의 댓글