Lockless 게임서버 설계와 구현
잠금 없는




김 성 익
noerror@softnette.com
소프트네트
개요
• 동기화(Synchronication)의 어려움을 극복하기 위해,
  구조적으로 잠금(Lock)을 최소화하는
  게임 서버 설계 제안
• 게임 서버 개발에서의 동기화
• 직렬화를 통한 잠금 없는 서버 프레임워크
• 잠금 없는 서버 구현 사례
멀티쓰레드 게임서버 모델
• IOCP를 사용하는 윈도우 기반의 서버모델

 RecvAsync       GQCS         GQCS        GQCS



                패킷처리         패킷처리        패킷처리



               RecvAsync     RecvAsync   RecvAsync


 GQCS : GetQueuedCompletionStatus
코드#1
void CSession::EnterRoom(int roomid)
{
    m_Sector = m_Server->FindRoom(roomid);

    m_Sector->list.push_back(this);

}


void CSession::UpdatePosition(int roomid, const Vec3& pos)
{
    m_Pos = pos;

    CPacket p(UPDATEPOS)
    p << m_ID << m_Pos;


    for(unsigned int i=0; i<m_MyRoom->list.size(); i++)
    {
        if (m_Sector->list[i] != this)
            Send(m_Sector->list[i], p);
    }

}
코드#1            Room1     Room1   Room2




 Enter     Attack      Move       Move   Move   Move     Attack




• 멀티쓰레드 모델
   Enter        Move          Attack

   Move         Move

   Move                       Attack
코드#2
void CSession::EnterRoom(int roomid)
{
    m_Sector = m_Server->FindRoom(roomid);
    ENTER(m_Sector->cs);
    m_Sector->list.push_back(this);
    LEAVE(m_Sector->cs);
}


void CSession::UpdatePosition(int roomid, const Vec3& pos)
{
    m_Pos = pos;

    CPacket p(UPDATEPOS)
    p << m_ID << m_Pos;

    ENTER(m_Sector->cs);
    for(unsigned int i=0; i<m_MyRoom->list.size(); i++)
    {
        if (m_Sector->list[i] != this)
            Send(m_Sector->list[i], p);
    }
    LEAVE(m_Sector->cs);
}
동기화
• 동기화(Synchronization)
  – 동시 접근 시 생기는 오류 막기 위해 크리티컬 섹
    션(Critical section), 뮤텍스(Mutex), 이벤트(Event),
    세마포어(Semaphore)등의 동기화 객체를 이용하
    여 잠금(lock)처리 Barrier Synchronization
    • 범위가 클 수록 동시성(concurrency) 감소
      Linear <-> Recursive
동기화
• 동기화를 올바르게 하지 않으면(#1)
 – (운이 좋다면) 즉시 다운
 – 잘못된 메모리 참조
  • 잘못된 값이 복사되서 전파
 – 잘못된 결과값
  • 트랜젝션(Transaction) 처리
 – 전혀 엉뚱한 곳에서 예측이 어려운 형태의 오류로
   나타남
동기화
• 동기화를 올바르게 하지 않으면(#2)
 – 잠금 상태에서 IO사용으로 인한 치명적인 성능 저
   하 유발
 – 데드락(deadlock) 발생
동기화
• 동기화 정책의 완전성 요구
 – 안정성 측면
 – 성능 측면
• 지속적인 개발 스트레스 요소
  새로운 콘텐츠 도입시 부담감
  =>방어적 프로그래밍
  =>개발 퍼포먼스 저하
• 서버 개발자의 숙명, 실력
• 게임 서버 개발에서의 동기화
• 직렬화를 통한 잠금 없는 서버 프레임워크
• 잠금 없는 서버 구현 사례
싱글 쓰레드 게임 서버 모델
• 고정관념을 버려보면
  “시스템을 최대한 활용하기 위해서 게임서버
  는 멀티 쓰레드 모델이어야 한다”
• 게임에 따라서는 꽤 괜찮은 모델
싱글 쓰레드 게임 서버 모델
• 단점
 – 시스템 활용도 낮음
  동시처리가 안되기 때문에 멀티쓰레드 서버 대비 처리량이 적기 때문
  에 응답성이 다소 떨어질 수 있다 (latency↑)
 – 확장성(Scalibility)의 한계
  CPU 코어 증가 등 하드웨어 개선을 통한 성능 향상은 미비
  하지만 게임의 성격과 맞다면 여러 개의 서버 프로세스 실행은 가능
 – 성능 때문에 DB등의 IO작업을 메인 쓰레드에서 직
   접 처리할 수 없음
  서버를 기능별로 분산(예 DB쿼리서버) 운영 / 비동기 (non-blocking)함
  수 사용
싱글 쓰레드 게임 서버 모델
• 단점
 – 서버를 기능별로 분산한다면
   •   처리가 선형적이지 않아 코드가 비직관적
   •   알고리즘이 여러 공간으로 분산
   •   서버간 통신을 위한 기계적인 코드 작업
   •   서버간의 시차로 인한 기계적인 예외 처리
싱글 쓰레드 게임 서버 모델
• 장점
 – 잠금 처리가 불필요
   • 원자성(atomicity), 격리성(isolation) 완벽 보장
 – 안정적, 예측 가능
   • 메모리가 침범되거나, 알고리즘이 교차 처리되어 꼬이
     거나, 데드락 걸릴 확률이 없다
기본 아이디어
• 멀티 쓰레드 게임 서버 모델에 싱글 쓰레드 게
  임 서버 모델의 장점을 가져온다
• 싱글 쓰레드 게임 서버 모델을 멀티쓰레드로
  구동
기본 아이디어
• 멀티 쓰레드 게임 서버 모델에 싱글 쓰레드 게
  임 서버 모델의 장점을 가져온다
• 싱글 쓰레드 게임 서버 모델을 멀티쓰레드로
  구동

          “직렬화”
      선형화, 파이프라인, 시퀀스, …
직렬화
• 잠금이 필요한 단위로 실행하는 쓰레드를 지
  정한다
• 캐주얼 게임을 예로 들면
  – 홀수 게임방 처리는 Thread#1 에서
  – 짝수 게임방 처리는 Thread#2 에서 실행

• 주) 여기서 사용하는 직렬화, 선형화는 일반적인 병렬프로그래밍에서 사용
  되는 용어(직렬화-배리어, 선형화-원자화)와는 다른 의미입니다. 오해를 없
  애기 위해 영문 표기 하지 않습니다.
직렬화                Room1       Room1   Room2




 Enter     Attack      Move       Move    Move      Move       Attack




• 멀티쓰레드 모델                               • 직렬화한 모델
   Enter        Move          Attack       Enter      Move

   Move         Move                       Attack

   Move                       Attack       Move

                                           Move

                                           Move       Attack
코드#3
void CSession::EnterRoom(int roomid)
{

    m_Sector = m_Server->FindRoom(roomid);
    ENTER(m_Sector->cs);
    m_Sector->list.push_back(this);
    LEAVE(m_Sector->cs);
}

void CSession::UpdatePosition(int roomid, const Vec3& pos)
{

    m_Pos = pos;

    CPacket p(UPDATEPOS)
    p << m_ID << m_Pos;

    ENTER(m_Sector->cs);
    for(unsigned int i=0; i<m_MyRoom->list.size(); i++)
    {
        if (m_Sector->list[i] != this)
            Send(m_Sector->list[i], p);
    }
    LEAVE(m_Sector->cs);
}
코드#4
void CSession::EnterRoom(int roomid)
{
    _ASSERT(GetThreadID(roomid) == GetCurrentThreadID());
    m_Sector = m_Server->FindRoom(roomid);

    m_Sector->list.push_back(this);

}

void CSession::UpdatePosition(int roomid, const Vec3& pos)
{
    _ASSERT(GetThreadID(m_Room->id) == GetCurrentThreadID());
    m_Pos = pos;

    CPacket p(UPDATEPOS)
    p << m_ID << m_Pos;


    for(unsigned int i=0; i<m_MyRoom->list.size(); i++)
    {
        if (m_Sector->list[i] != this)
            Send(m_Sector->list[i], p);
    }

}
장점
• 잠금 처리 불필요
 – 동시성(concurrency) 증가
 – 동기화 처리의 스트레스 일부 해방
  • 개발 퍼포먼스 증가
  • 새로운 시스템 시도와 도입에 관대해짐 희망사항
• 멀티 쓰레드 모델 사용
 – 성능, 반응성
 – 확장성
구현
• 쓰레드 별로 IOCP생성, 소켓은 필요한 쓰레드의
  IOCP에 등록
RecvAsync



            GQCS#1       GQCS#2      GQCS#3



            패킷처리         패킷처리        패킷처리



            RecvAsync    RecvAsync   RecvAsync

            쓰레드#1        쓰레드#2       쓰레드#3
구현
• 치명적인 문제점
 – 소켓을 CreateIoCompletionPort 함수를 이용하여 IOCP핸
   들에 등록을 한 후에는 다른 IOCP핸들에 등록이 불가능하
   다
 – 연결 중에는 방 이동이 불가능 ???
구현
• 치명적인 문제점
 – 소켓을 CreateIoCompletionPort 함수를 이용하여 IOCP핸
   들에 등록을 한 후에는 다른 IOCP핸들에 등록이 불가능하
   다
 – 연결 중에 방 이동이 불가능 ???
• 가장 간단한 해결 방법
 – 힌트 PostQueuedCompletionStatus
PostQueuedCompletionStatus
• IOCP핸들에 결과를 통보하는 함수
 – 소켓 메시지 수신해서 GQCS에서 결과를 받는 것
   과 동일한 결과
   완료 큐에 결과를 쌓고 쓰레드가 대기 중이면 깨
   우기
PostQueuedCompletionStatus
• Demultiplexer 역할



 while(m_bTerminate == false)
 {
   unsigned long dwReadbytes = -1;
   ULONG_PTR dwCompKey = 0;                                              이미지 출처 : 위키피디아
   OVERLAPPED* s;

     if (::GetQueuedCompletionStatus(m_hIOCP, &dwReadbytes, &dwCompKey, &s, 1000))
     {
       int idx = GetThreadIndex(s);
       ::PostQueuedCompletionStatus(m_hThreadIOCP[idx], dwReadbytes, dwCompKey, s);
     }
 }
RecvAsync               GQCS




            GQCS#1             GQCS#2      GQCS#3



            패킷처리               패킷처리        패킷처리



            RecvAsync          RecvAsync   RecvAsync

            쓰레드#1              쓰레드#2       쓰레드#3
• 게임 서버 개발에서의 동기화
• 직렬화를 통한 잠금 없는 서버 프레임워크
• 잠금 없는 서버 구현 사례
개발 중인 서버 모델 소개
•   IOCP 멀티쓰레드 모델 기반
•   직렬화를 통한 Lockless 서버 아키텍처
•   DB 동기(blocking)함수 사용
•   게임 오브젝트 프레임 워크
    – 클라이언트와 유사한 객체 관리
      충돌처리, 아이템사용, ...
• Http 서버스
• UDP P2P 통신
IO 이슈
• IO 처리
 – 처리 방식
   • 동기(blocking) 함수
   • 비동기(non-blocking) 함수
 – 종류
   • DB쿼리, 로그 남기기, 파일 읽기 등등
 – 병목!!
   • 느리다, 예측 불가능하다
   • 잠금 상태에서 처리하면 불행한 사태 발생
IO 이슈
• 동기함수 vs 비동기 함수
 – 결과
   • 바로 얻으냐
   • 다른 위치에서 얻느냐
 – 대기
   • 완료 될 때까지 쓰레드가 대기 상태에서 기다리냐
   • 완료와 상관없이 다음 처리로 넘어가느냐
IO 이슈
• 일반적인 싱글 쓰레드 모델에서
 – DB처리는 별도의 서버 혹은 쓰레드에서
 – 아이템 리스트 얻기 절차
  • 1. DB서버에 아이템 요청
       – 보낸 패킷에 받을 세션 정보를 추가
  •   2.   DB서버에서 처리
  •   3.   결과를 게임서버로 전달
  •   4.   게임서버에서 DB세션 처리 부에서 전달 받음
  •   5.   해당 세션을 찾은 후에 결과를 처리
       – 해당 세션의 상태를 고려
IO 이슈
• 일반적인 멀티 쓰레드 모델이라면
 – 처리가 지연되더라도 다른 처리에 영향을 안 주기
   때문에 IO 처리는 바로 처리해도 됨
 – 바로 처리 <= 단순하고, 직관적인 처리
  void CSession::EnumItem()
  {
      CDatabase::CRecordSet r;
      if (CGameDB::Query(&r, L"exec w_enum_item") == true)
      {
          for(int i=0; i<r.Count(); i++)
          {
              CPacket item(_SC_LOGIN_SUCCESS);
              SendPacket(this, item << r.GetString(i, 0) << r.GetInt(i, 1));
          }
      }
      CPacket p(_SC_END_OF_ITEM);
      SendPacket(this, p);
  }
정책
• 명세
 – IO를 동기(blocking) 방식으로 직접 처리한다
• 하지만 싱글 쓰레드와 마찬가지로 직렬화한
  상태에서 동기(blocking)함수를 사용하면 불행
  한 일이 생김
정책
• 패킷을 구분
 – 게임 관련된 패킷 (직렬화함)
   • 연관 쓰레드에서 실행
 – 아닌 패킷
   • 일반 멀티쓰레드 모델처럼 처리
• 처리 중에
 – 패킷 타입이 다르면 분기
 – 쓰레드 할당이 바뀌면 분기
RecvAsync    GQCS        GQCS



            패킷처리        패킷처리



            RecvAsync   RecvAsync
RecvAsync    GQCS        GQCS



            패킷처리        패킷처리
                                        게임패킷

            RecvAsync   RecvAsync




                            GQCS#1     GQCS#2



                            패킷처리       패킷처리



                           RecvAsync   RecvAsync

                            쓰레드#1      쓰레드#2
구조
• 쓰레드내에서 게임 업데이트 처리
 – 완료 큐의 내용을 처리하면서 주기적으로 할당된
   Room의 Update함수 호출
  • GQCS 타임아웃 활용
 – 클라이언트와 유사한 씬 처리
  • 동기화 할 필요가 없으므로 클라이언트 만들듯 쉽게~
  • 프레임 베이스로 서버 사이드 충돌, 액션, 물리 처리
RecvAsync    GQCS        GQCS



            패킷처리        패킷처리
                                        게임패킷

            RecvAsync   RecvAsync




            오브젝트
                            GQCS#1     GQCS#2
            업데이트


                            패킷처리       패킷처리



                           RecvAsync   RecvAsync

                            쓰레드#1      쓰레드#2
도전과제
• Many Integrated Core




• 로드 밸런싱 (Load Balancing)
정리
• 직렬화를 통해서 잠금 없는 서버 프레임워크
  구성 가능
 – 주) 직렬화로 동기화가 필요 없는 부분에 한정
   그 외의 부분은 일반 멀티 쓰레드 모델처럼 동기
   화 처리 필요
   예) 캐주얼 게임의 경우 게임방안의 처리는 잠금
   없이 처리 가능하지만 방의 생성과 삭제는 잠금
   처리가 필요하다
정리
• 잠금 없는 서버 구조로 얻을 수 있는 것들
 – 개발 퍼포먼스↑
 – 안정성↑
 – 동기화로 인한 복잡도↓
 – 직관적인 코드 관리
• 감사합니다
tip
• __declspec(thread) 키워드
  – 전역처럼 사용하지만 쓰레드마다 할당됨
  – 동기화할 필요가 없음
  – 패킷 처리시 함수마다 session을 넘기고 있다면 드
    라마틱하게 간결한 코드로 만들 수 있음
tip
• PQCS 함수 활용
 – 별도 메시지 큐와 동기화 처리 없는 비동기(non-
   blocking) 로그서비스
   • GQCS로 받아서 저장하는 쓰레드 하나 운영
   • PQCS로 로그 정보 전달
     – 몰려도 시스템의 완료 큐에 안전하게 쌓여 있음
tip
• PQCS 함수 활용
 – 접속이나 종료 등 사후 처리도 쓰레드풀(Thread
   pool)에서 처리
   • 완료키 값을 사전에 정의한 값으로 전달
     – 세션정보를 완료키로 전달한다면 Bytes에 비상식적인 큰 값으
       로, 혹은 OVERLAPPED 포인터를 임의의 전역변수 주소로
     – #define _DESTROY -1234
       //::PostQueuedCompletionStatus(handle, _DESTROY, key, NULL);
       //::PostQueuedCompletionStatus(handle, 0, _DESTROY, &overlapped);
       //static OVERLAPPED _destroy;
       //::PostQueuedCompletionStatus(handle, 0, key, &_destroy);

   • 서버 흐름 관리가 단순 명료해짐

NDC12_Lockless게임서버설계와구현

  • 1.
    Lockless 게임서버 설계와구현 잠금 없는 김 성 익 noerror@softnette.com 소프트네트
  • 2.
    개요 • 동기화(Synchronication)의 어려움을극복하기 위해, 구조적으로 잠금(Lock)을 최소화하는 게임 서버 설계 제안
  • 3.
    • 게임 서버개발에서의 동기화 • 직렬화를 통한 잠금 없는 서버 프레임워크 • 잠금 없는 서버 구현 사례
  • 4.
    멀티쓰레드 게임서버 모델 •IOCP를 사용하는 윈도우 기반의 서버모델 RecvAsync GQCS GQCS GQCS 패킷처리 패킷처리 패킷처리 RecvAsync RecvAsync RecvAsync GQCS : GetQueuedCompletionStatus
  • 5.
    코드#1 void CSession::EnterRoom(int roomid) { m_Sector = m_Server->FindRoom(roomid); m_Sector->list.push_back(this); } void CSession::UpdatePosition(int roomid, const Vec3& pos) { m_Pos = pos; CPacket p(UPDATEPOS) p << m_ID << m_Pos; for(unsigned int i=0; i<m_MyRoom->list.size(); i++) { if (m_Sector->list[i] != this) Send(m_Sector->list[i], p); } }
  • 6.
    코드#1 Room1 Room1 Room2 Enter Attack Move Move Move Move Attack • 멀티쓰레드 모델 Enter Move Attack Move Move Move Attack
  • 7.
    코드#2 void CSession::EnterRoom(int roomid) { m_Sector = m_Server->FindRoom(roomid); ENTER(m_Sector->cs); m_Sector->list.push_back(this); LEAVE(m_Sector->cs); } void CSession::UpdatePosition(int roomid, const Vec3& pos) { m_Pos = pos; CPacket p(UPDATEPOS) p << m_ID << m_Pos; ENTER(m_Sector->cs); for(unsigned int i=0; i<m_MyRoom->list.size(); i++) { if (m_Sector->list[i] != this) Send(m_Sector->list[i], p); } LEAVE(m_Sector->cs); }
  • 8.
    동기화 • 동기화(Synchronization) – 동시 접근 시 생기는 오류 막기 위해 크리티컬 섹 션(Critical section), 뮤텍스(Mutex), 이벤트(Event), 세마포어(Semaphore)등의 동기화 객체를 이용하 여 잠금(lock)처리 Barrier Synchronization • 범위가 클 수록 동시성(concurrency) 감소 Linear <-> Recursive
  • 9.
    동기화 • 동기화를 올바르게하지 않으면(#1) – (운이 좋다면) 즉시 다운 – 잘못된 메모리 참조 • 잘못된 값이 복사되서 전파 – 잘못된 결과값 • 트랜젝션(Transaction) 처리 – 전혀 엉뚱한 곳에서 예측이 어려운 형태의 오류로 나타남
  • 10.
    동기화 • 동기화를 올바르게하지 않으면(#2) – 잠금 상태에서 IO사용으로 인한 치명적인 성능 저 하 유발 – 데드락(deadlock) 발생
  • 11.
    동기화 • 동기화 정책의완전성 요구 – 안정성 측면 – 성능 측면 • 지속적인 개발 스트레스 요소 새로운 콘텐츠 도입시 부담감 =>방어적 프로그래밍 =>개발 퍼포먼스 저하 • 서버 개발자의 숙명, 실력
  • 12.
    • 게임 서버개발에서의 동기화 • 직렬화를 통한 잠금 없는 서버 프레임워크 • 잠금 없는 서버 구현 사례
  • 13.
    싱글 쓰레드 게임서버 모델 • 고정관념을 버려보면 “시스템을 최대한 활용하기 위해서 게임서버 는 멀티 쓰레드 모델이어야 한다” • 게임에 따라서는 꽤 괜찮은 모델
  • 14.
    싱글 쓰레드 게임서버 모델 • 단점 – 시스템 활용도 낮음 동시처리가 안되기 때문에 멀티쓰레드 서버 대비 처리량이 적기 때문 에 응답성이 다소 떨어질 수 있다 (latency↑) – 확장성(Scalibility)의 한계 CPU 코어 증가 등 하드웨어 개선을 통한 성능 향상은 미비 하지만 게임의 성격과 맞다면 여러 개의 서버 프로세스 실행은 가능 – 성능 때문에 DB등의 IO작업을 메인 쓰레드에서 직 접 처리할 수 없음 서버를 기능별로 분산(예 DB쿼리서버) 운영 / 비동기 (non-blocking)함 수 사용
  • 15.
    싱글 쓰레드 게임서버 모델 • 단점 – 서버를 기능별로 분산한다면 • 처리가 선형적이지 않아 코드가 비직관적 • 알고리즘이 여러 공간으로 분산 • 서버간 통신을 위한 기계적인 코드 작업 • 서버간의 시차로 인한 기계적인 예외 처리
  • 16.
    싱글 쓰레드 게임서버 모델 • 장점 – 잠금 처리가 불필요 • 원자성(atomicity), 격리성(isolation) 완벽 보장 – 안정적, 예측 가능 • 메모리가 침범되거나, 알고리즘이 교차 처리되어 꼬이 거나, 데드락 걸릴 확률이 없다
  • 17.
    기본 아이디어 • 멀티쓰레드 게임 서버 모델에 싱글 쓰레드 게 임 서버 모델의 장점을 가져온다 • 싱글 쓰레드 게임 서버 모델을 멀티쓰레드로 구동
  • 18.
    기본 아이디어 • 멀티쓰레드 게임 서버 모델에 싱글 쓰레드 게 임 서버 모델의 장점을 가져온다 • 싱글 쓰레드 게임 서버 모델을 멀티쓰레드로 구동 “직렬화” 선형화, 파이프라인, 시퀀스, …
  • 19.
    직렬화 • 잠금이 필요한단위로 실행하는 쓰레드를 지 정한다 • 캐주얼 게임을 예로 들면 – 홀수 게임방 처리는 Thread#1 에서 – 짝수 게임방 처리는 Thread#2 에서 실행 • 주) 여기서 사용하는 직렬화, 선형화는 일반적인 병렬프로그래밍에서 사용 되는 용어(직렬화-배리어, 선형화-원자화)와는 다른 의미입니다. 오해를 없 애기 위해 영문 표기 하지 않습니다.
  • 20.
    직렬화 Room1 Room1 Room2 Enter Attack Move Move Move Move Attack • 멀티쓰레드 모델 • 직렬화한 모델 Enter Move Attack Enter Move Move Move Attack Move Attack Move Move Move Attack
  • 21.
    코드#3 void CSession::EnterRoom(int roomid) { m_Sector = m_Server->FindRoom(roomid); ENTER(m_Sector->cs); m_Sector->list.push_back(this); LEAVE(m_Sector->cs); } void CSession::UpdatePosition(int roomid, const Vec3& pos) { m_Pos = pos; CPacket p(UPDATEPOS) p << m_ID << m_Pos; ENTER(m_Sector->cs); for(unsigned int i=0; i<m_MyRoom->list.size(); i++) { if (m_Sector->list[i] != this) Send(m_Sector->list[i], p); } LEAVE(m_Sector->cs); }
  • 22.
    코드#4 void CSession::EnterRoom(int roomid) { _ASSERT(GetThreadID(roomid) == GetCurrentThreadID()); m_Sector = m_Server->FindRoom(roomid); m_Sector->list.push_back(this); } void CSession::UpdatePosition(int roomid, const Vec3& pos) { _ASSERT(GetThreadID(m_Room->id) == GetCurrentThreadID()); m_Pos = pos; CPacket p(UPDATEPOS) p << m_ID << m_Pos; for(unsigned int i=0; i<m_MyRoom->list.size(); i++) { if (m_Sector->list[i] != this) Send(m_Sector->list[i], p); } }
  • 23.
    장점 • 잠금 처리불필요 – 동시성(concurrency) 증가 – 동기화 처리의 스트레스 일부 해방 • 개발 퍼포먼스 증가 • 새로운 시스템 시도와 도입에 관대해짐 희망사항 • 멀티 쓰레드 모델 사용 – 성능, 반응성 – 확장성
  • 24.
    구현 • 쓰레드 별로IOCP생성, 소켓은 필요한 쓰레드의 IOCP에 등록 RecvAsync GQCS#1 GQCS#2 GQCS#3 패킷처리 패킷처리 패킷처리 RecvAsync RecvAsync RecvAsync 쓰레드#1 쓰레드#2 쓰레드#3
  • 25.
    구현 • 치명적인 문제점 – 소켓을 CreateIoCompletionPort 함수를 이용하여 IOCP핸 들에 등록을 한 후에는 다른 IOCP핸들에 등록이 불가능하 다 – 연결 중에는 방 이동이 불가능 ???
  • 26.
    구현 • 치명적인 문제점 – 소켓을 CreateIoCompletionPort 함수를 이용하여 IOCP핸 들에 등록을 한 후에는 다른 IOCP핸들에 등록이 불가능하 다 – 연결 중에 방 이동이 불가능 ??? • 가장 간단한 해결 방법 – 힌트 PostQueuedCompletionStatus
  • 27.
    PostQueuedCompletionStatus • IOCP핸들에 결과를통보하는 함수 – 소켓 메시지 수신해서 GQCS에서 결과를 받는 것 과 동일한 결과 완료 큐에 결과를 쌓고 쓰레드가 대기 중이면 깨 우기
  • 28.
    PostQueuedCompletionStatus • Demultiplexer 역할 while(m_bTerminate == false) { unsigned long dwReadbytes = -1; ULONG_PTR dwCompKey = 0; 이미지 출처 : 위키피디아 OVERLAPPED* s; if (::GetQueuedCompletionStatus(m_hIOCP, &dwReadbytes, &dwCompKey, &s, 1000)) { int idx = GetThreadIndex(s); ::PostQueuedCompletionStatus(m_hThreadIOCP[idx], dwReadbytes, dwCompKey, s); } }
  • 29.
    RecvAsync GQCS GQCS#1 GQCS#2 GQCS#3 패킷처리 패킷처리 패킷처리 RecvAsync RecvAsync RecvAsync 쓰레드#1 쓰레드#2 쓰레드#3
  • 30.
    • 게임 서버개발에서의 동기화 • 직렬화를 통한 잠금 없는 서버 프레임워크 • 잠금 없는 서버 구현 사례
  • 31.
    개발 중인 서버모델 소개 • IOCP 멀티쓰레드 모델 기반 • 직렬화를 통한 Lockless 서버 아키텍처 • DB 동기(blocking)함수 사용 • 게임 오브젝트 프레임 워크 – 클라이언트와 유사한 객체 관리 충돌처리, 아이템사용, ... • Http 서버스 • UDP P2P 통신
  • 32.
    IO 이슈 • IO처리 – 처리 방식 • 동기(blocking) 함수 • 비동기(non-blocking) 함수 – 종류 • DB쿼리, 로그 남기기, 파일 읽기 등등 – 병목!! • 느리다, 예측 불가능하다 • 잠금 상태에서 처리하면 불행한 사태 발생
  • 33.
    IO 이슈 • 동기함수vs 비동기 함수 – 결과 • 바로 얻으냐 • 다른 위치에서 얻느냐 – 대기 • 완료 될 때까지 쓰레드가 대기 상태에서 기다리냐 • 완료와 상관없이 다음 처리로 넘어가느냐
  • 34.
    IO 이슈 • 일반적인싱글 쓰레드 모델에서 – DB처리는 별도의 서버 혹은 쓰레드에서 – 아이템 리스트 얻기 절차 • 1. DB서버에 아이템 요청 – 보낸 패킷에 받을 세션 정보를 추가 • 2. DB서버에서 처리 • 3. 결과를 게임서버로 전달 • 4. 게임서버에서 DB세션 처리 부에서 전달 받음 • 5. 해당 세션을 찾은 후에 결과를 처리 – 해당 세션의 상태를 고려
  • 35.
    IO 이슈 • 일반적인멀티 쓰레드 모델이라면 – 처리가 지연되더라도 다른 처리에 영향을 안 주기 때문에 IO 처리는 바로 처리해도 됨 – 바로 처리 <= 단순하고, 직관적인 처리 void CSession::EnumItem() { CDatabase::CRecordSet r; if (CGameDB::Query(&r, L"exec w_enum_item") == true) { for(int i=0; i<r.Count(); i++) { CPacket item(_SC_LOGIN_SUCCESS); SendPacket(this, item << r.GetString(i, 0) << r.GetInt(i, 1)); } } CPacket p(_SC_END_OF_ITEM); SendPacket(this, p); }
  • 36.
    정책 • 명세 –IO를 동기(blocking) 방식으로 직접 처리한다 • 하지만 싱글 쓰레드와 마찬가지로 직렬화한 상태에서 동기(blocking)함수를 사용하면 불행 한 일이 생김
  • 37.
    정책 • 패킷을 구분 – 게임 관련된 패킷 (직렬화함) • 연관 쓰레드에서 실행 – 아닌 패킷 • 일반 멀티쓰레드 모델처럼 처리 • 처리 중에 – 패킷 타입이 다르면 분기 – 쓰레드 할당이 바뀌면 분기
  • 38.
    RecvAsync GQCS GQCS 패킷처리 패킷처리 RecvAsync RecvAsync
  • 39.
    RecvAsync GQCS GQCS 패킷처리 패킷처리 게임패킷 RecvAsync RecvAsync GQCS#1 GQCS#2 패킷처리 패킷처리 RecvAsync RecvAsync 쓰레드#1 쓰레드#2
  • 40.
    구조 • 쓰레드내에서 게임업데이트 처리 – 완료 큐의 내용을 처리하면서 주기적으로 할당된 Room의 Update함수 호출 • GQCS 타임아웃 활용 – 클라이언트와 유사한 씬 처리 • 동기화 할 필요가 없으므로 클라이언트 만들듯 쉽게~ • 프레임 베이스로 서버 사이드 충돌, 액션, 물리 처리
  • 41.
    RecvAsync GQCS GQCS 패킷처리 패킷처리 게임패킷 RecvAsync RecvAsync 오브젝트 GQCS#1 GQCS#2 업데이트 패킷처리 패킷처리 RecvAsync RecvAsync 쓰레드#1 쓰레드#2
  • 42.
    도전과제 • Many IntegratedCore • 로드 밸런싱 (Load Balancing)
  • 43.
    정리 • 직렬화를 통해서잠금 없는 서버 프레임워크 구성 가능 – 주) 직렬화로 동기화가 필요 없는 부분에 한정 그 외의 부분은 일반 멀티 쓰레드 모델처럼 동기 화 처리 필요 예) 캐주얼 게임의 경우 게임방안의 처리는 잠금 없이 처리 가능하지만 방의 생성과 삭제는 잠금 처리가 필요하다
  • 44.
    정리 • 잠금 없는서버 구조로 얻을 수 있는 것들 – 개발 퍼포먼스↑ – 안정성↑ – 동기화로 인한 복잡도↓ – 직관적인 코드 관리
  • 45.
  • 46.
    tip • __declspec(thread) 키워드 – 전역처럼 사용하지만 쓰레드마다 할당됨 – 동기화할 필요가 없음 – 패킷 처리시 함수마다 session을 넘기고 있다면 드 라마틱하게 간결한 코드로 만들 수 있음
  • 47.
    tip • PQCS 함수활용 – 별도 메시지 큐와 동기화 처리 없는 비동기(non- blocking) 로그서비스 • GQCS로 받아서 저장하는 쓰레드 하나 운영 • PQCS로 로그 정보 전달 – 몰려도 시스템의 완료 큐에 안전하게 쌓여 있음
  • 48.
    tip • PQCS 함수활용 – 접속이나 종료 등 사후 처리도 쓰레드풀(Thread pool)에서 처리 • 완료키 값을 사전에 정의한 값으로 전달 – 세션정보를 완료키로 전달한다면 Bytes에 비상식적인 큰 값으 로, 혹은 OVERLAPPED 포인터를 임의의 전역변수 주소로 – #define _DESTROY -1234 //::PostQueuedCompletionStatus(handle, _DESTROY, key, NULL); //::PostQueuedCompletionStatus(handle, 0, _DESTROY, &overlapped); //static OVERLAPPED _destroy; //::PostQueuedCompletionStatus(handle, 0, key, &_destroy); • 서버 흐름 관리가 단순 명료해짐