ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 스파르타) The Last Rollback (D-22, Node.js 게임 서버 최종 프로젝트) - buffer, bull queue, JSON circular structure
    TIL 2024. 8. 6. 12:05


     > 과제 진행 간 완료한 사항 및 문제점과 해결 과정을 정리해보았다.

     

    학습 키워드: Node.js, C#, Unity, Protobuf, Buffer

     

    1. 진행 사항

    1) 낮/밤 라운드 전환 기능 구현 완료:

     

    56 update 낮 밤 라운드 기능 2 by donkim1212 · Pull Request #60 · eliotjang/the-last-rollback-server

    관련 Issue close #56 작업 내용 #56 에서 명시한 작업 완료 참고 사항 첫 번째 index의 몬스터 버그 확인하기

    github.com

     (상세 작업 내역은 위 PR 및 연결된 Issue 참고)

     

     팀원의 코드가 dev로 merge됨에 따라 구현해둔 낮 밤 라운드의 전환에 관련된 기능들을 필요한 위치에 추가하고 합치는 과정을 진행했다. 몬스터가 나오는 전투 라운드인 밤 라운드는 모든 몬스터가 사망하게되면 종료되는데, 이 부분에 대한 코드가 미완성이어서 새롭게 작성하여 추가했다.

     

      /**
       *
       * @param {number} monsterIndex 몬스터 인덱스
       * @param {string} accountId
       * @description updatePlayerAttackMonster 내부에서 사용
       */
      killMonster(monsterIndex, accountId) {
        // this.roundMonsters.delete(monsterIndex); // 기존 코드
        this.roundKillCount++;
        const playerInfo = this.playerInfos.get(accountId);
        playerInfo.killed.push(monsterIndex);
        this.playerInfos.set(accountId, playerInfo);
        if (this.roundMonsters.size === this.roundKillCount) {
          // 밤 round 종료
          this.roundKillCount = 0;
          this.endNightRound(accountId);
        }
      }

     

     기존에는 단순히 몬스터를 Map에서 제거하는 작업만 해줬는데, 마지막 몬스터가 죽는 시점이 라운드의 종료 시점이기 때문에 이 위치에서 해당 state를 판단할 수 있어야한다. 현재 코드는 판정에 문제가 있는데, 막타를 동시에 때린 경우에 대한 핸들링을 위해 bull queue를 적용할 생각이다.

     

    2. 트러블 슈팅

    1) 간헐적으로 잘 못된 패킷을 수신하는 문제 (해결):

    Figure 1. 잘 못된 패킷 확인

     

     MVP 구현이 거의 완료되어서 EC2 배포 시 어느정도의 latency가 나올지 확인하기 위해 배포를 진행해보았다. 그런데 막상 매칭 큐에 들어가려고 보니 몇 명은 던전 세션에 들어가지고 나머지는 타운 세션에 남겨지는 일이 발생했다. 최초에는 NullReferenceException이 발생했는데, 해당 위치에 Debug.Log를 찍어보니 패킷의 MsgId가 0번으로 찍히고 내용물이 없는 문제가 있는 것을 확인했다. 그런데 실제로 서버에서는 0번 패킷을 보내고 있지 않기 때문에 원인 파악에 어려움을 겪었다.

     

     Debug.Log를 여기저기 추가하고, 서버 코드도 수정해보던 와중에 클라이언트에서 response 패킷만이 갈 수 있는 위치에 보낸적 없는 에러코드(5058 , 4406등)가 찍히는 것을 확인했다.

    export const ErrorCodes = {
      REQUEST_NOT_SUCCESS: 1,
      SOCKET_ERROR: 10000,
      CLIENT_VERSION_MISMATCH: 10001,
      UNKNOWN_HANDLER_ID: 10002,
      PACKET_DECODE_ERROR: 10003,
      PACKET_STRUCTURE_MISMATCH: 10004,
      MISSING_FIELDS: 10005,
      USER_NOT_FOUND: 10006,
      INVALID_PACKET: 10007,
      INVALID_SEQUENCE: 10008,
      GAME_NOT_FOUND: 10009,
      GAME_DATA_MISMATCH: 10010,
      UNKNOWN_HANDLER_ID: 10011,
      MISSING_PATH: 10012,
      SERIALIZE_ERROR: 10013,
      USER_NOT_FOUND: 10014,
      MISSING_LOGIN_FIELDS: 10015,
      TOKEN_EXPIRED_ERROR: 10016,
      JSON_WEB_TOKEN_ERROR: 10017,
      UNUSUAL_REQUEST: 10018,
      CLIENT_VERSION_MISMATCH: 10019,
      MISSING_SIGNUP_FIELDS: 10020,
      EXISTED_USER: 10021,
      SIGNUP_ERROR: 10022,
      GAME_ASSETS_LOAD_ERROR: 10023,
      SESSION_USER_NOT_FOUND: 10024,
      GAME_REDIS_DATA_ERROR: 10025,
      TRANSFORM_CASE_ERROR: 10026,
      TOKEN_VERIFY_ERROR: 10027,
      EXISTED_NICKNAME: 10028,
      MONSTER_NOT_FOUND: 10029,
      INVALID_ARGUMENT: 11111,
      PROBABILITY_ERROR: 11112,
    };

     실제로 매핑된 값에는 포함되어있지 않은 값이기 때문에 앞선 문제와 합쳐서 생각해보니 통지 패킷이 응답 패킷으로, 응답 패킷이 통지 패킷으로 잘 못 들어간 것이라고 판단되었다. 하지만 역시나 앞서 언급했듯이 실제로 서버에서는 잘 못 보내주고 있는 패킷이 없는데, 그렇다면 에러 코드가 잘못 들어가는 상황은 수신 버퍼가 어긋나는 상황이 아니라면 불가능하게 된다.

     

     결국 패킷 수신 부분의 코드를 다시 들여다보게 되었다. 클라이언트 코드를 수정하던 와중 뭘 잘 못 건드린건가 싶어서 정상 작동하던 팀원이 한명 존재한다는 사실을 알고 해당 팀원이 빌드한 클라이언트를 다 같이 공유하여 실행해보니 해당 문제가 사라진 것을 확인했다. Unity Version Control로 코드를 가져와 합치는 과정에서 문제 위치를 발견하게 되었다.

     

    public void OnRecvPacket(PacketSession session, ArraySegment<byte> buffer)
    {
        int size = BitConverter.ToInt32(buffer.Array, buffer.Offset);
        byte id = buffer.Array[buffer.Offset + 4]; // 4 -> buffer.Offset + 4
    
        Action<PacketSession, ArraySegment<byte>, ushort> action = null;
        if (_onRecvOuter.TryGetValue((ushort) id, out action)){
            action.Invoke(session, buffer, (ushort) id);
        }
    }

     

     지난번에 수정된 문제 중 buffer를 가져올 때 Offset을 적용하지 않으면 여러 패킷을 동시에 수신할 때 버퍼를 잘 못 잘라서 내용물이 섞이게 되는 문제가 있었는데, 이전에 ClientPacketManager에서도 동일한 문제가 있을것 같아 수정하던 중에 id 부분만 빼먹은 것이 원인이었다. 로컬 환경에서 테스트할 때는 해당 문제가 왜 없었는지는 아직까지 파악하지 못했지만, 버퍼가 막히는 문제는 해결되어 게임이 정상 동작했다.

     

    Figure 2. 정상적으로 연결됨

     

     

    2) Bull queue TypeError 발생 (해결):

    Figure 3. Bull queue 에러

     기존 코드에서 bull queue에 User 클래스의 인스턴스를 stringify하여 넣어주고 있었는데, user 클래스 많이 수정되어서 기존에 발생하지 않던 오류가 간헐적으로 발생하게 되었다. Circular structure를 JSON으로 변환할 수 없다는 것은 user 클래스의 어느 property의 어느 위치에 user 자신에 대한 reference 가 들어있어서 무한으로 순환하는 구조라는 뜻이다. 초기에 작성할 때는 어떤 값을 넣을지 명확하지 않아서 user 자체를 사용했지만 실제로는 user의 accountId만 넣어도 문제 없이 동작하는 구조기 때문에 accountId 만 넣어주는 구조로 바꿔서 문제를 해결했다. 추가로 순환 구조가 발생하는 부분을 찾아보도록 해야겠다.

     

    --


    REFERENCES:

     

     

    GitHub - eliotjang/the-last-rollback-server: 액션 MORPG

    액션 MORPG. Contribute to eliotjang/the-last-rollback-server development by creating an account on GitHub.

    github.com

     > 프로젝트 repo

    728x90
Designed by Tistory.