[TIL] 24.11.22 FRI

GDORI·2024년 11월 22일
0

TIL

목록 보기
110/143
post-thumbnail

Mutex

뮤텍스가 무엇인지 이해하기 쉽게 말하면 하나의 길이 있는데, 이 길에는 하나의 방향으로 오직 한 대의 차만이 들어올 수 있다고 치자. 이 때, 서로 다른 방향에서 두 대의 차가 마주 달려온다면 정면충돌 사고가 나거나 서로 오가지 못하여 둘 중 하나는 후진하여 원래 출발 지점으로 돌아가야 한다. 그렇다면 차 두 대가 모두 사고 안 나게 통행하려면 둘 중의 한 대가 먼저 통과하도록 우선권을 부여해야 할 것이다. 이 때에는 한 쪽 끝에서 출발하는 차에게 녹색 불을 켜주고, 반대쪽 끝에 있는 차에게는 빨간 불이 들어와서 길로 들어오지 못하게 하면 된다.
[출처 - 나무위키]

패킷 처리

보통 멀티스레드 환경에서 공유자원을 접근하는 로직에서 레이스 컨디션 상황을 방지하기 위해 사용한다고 알고있다.
근데 나는 싱글스레드 환경인 자바스크립트에서 사용을 해보고자 한다.
기존 코드에서는 bool 변수로 스위치 역할을 하여 작업중엔 접근을 하지 못하게 막았었는데 프레임워크 중 async-mutex 라는 뮤텍스를 쉽게 구현할 수 있는 친구가 있어 사용하였다.

JS 코드

class PacketManager {
  // 싱클톤 패턴
  constructor() {
    if (PacketManager.instance instanceof PacketManager) return PacketManager.instance;
    PacketManager.instance = this;
    this.sendQueue = [];
    this.recvQueue = [];
    this.sendLock = new Mutex();
    this.recvLock = new Mutex();
  }

  // sendPacket을 위한 enQueue
  async enQueueSend(socket, packet) {
    // 뮤텍스 실행
    await this.sendLock.runExclusive(() => {
      // 큐 삽입
      this.sendQueue.push({ socket, packet });
      // sendQueue에 들어있는 친구들 프로세싱
      this.processSendPacket();
    });
  }

  // receivePacket을 위한 enQueue
  async enQueueRecv(socket, packet) {
    await this.recvLock.runExclusive(() => {
      this.recvQueue.push({ socket, packet });
      this.processRecvPacket();
    });
  }

  // sendPacket을 위한 deQueue
  async deQueueSend() {
    return await this.sendLock.runExclusive(() => {
      return this.sendQueue.shift();
    });
  }

  // receivePacket을 위한 deQueue
  async deQueueRecv() {
    return await this.recvLock.runExclusive(() => {
      return this.recvQueue.shift();
    });
  }

  // 보낼 패킷들을 큐에서 하나씩 꺼내 소켓전송
  async processSendPacket() {
    if (!this.sendQueue.length) return;
    while (this.sendQueue.length) {
      const { socket, packet } = await this.deQueueSend();
      if (!socket || !packet) throw new Error('패킷 보내기 오류');
      socket.write(packet);
    }
  }

  // 들어오는 패킷을 큐에서 하나씩 꺼내 처리 후 핸들러 연동
  async processRecvPacket() {
    if (!this.recvQueue.length) return;
    while (this.recvQueue.length) {
      const { socket, packet } = await this.deQueueRecv();
      if (!socket || !packet) throw new Error('패킷 보내기 오류');

      const { version, packetType, payload } = packet;

      if (version !== config.client.version) {
        throw new CustomErr(ERR_CODES.CLIENT_VERSION_MISMATCH, 'Check to version');
      }
      if (!PACKET_TYPE_REVERSED[packetType]) {
        throw new CustomErr(ERR_CODES.UNKNOWN_PACKET_TYPE, 'Unknown packet type');
      }
      if (!payload || !GamePacket.decode(payload)) {
        throw new CustomErr(ERR_CODES.PACKET_DECODE_ERR, 'Packet decode error');
      }

      const payloadName = snakeToCamel(PACKET_TYPE_REVERSED[packetType]);
      const handlers = getHandlers();
      const handler = handlers[payloadName];

      if (!handler) {
        throw new CustomErr(ERR_CODES.HANDLER_NOT_FOUND, 'Handler not found');
      }

      const decodedPayload = { ...GamePacket.decode(payload)[payloadName] };
      handler(socket, decodedPayload);
    }
  }
}

export default PacketManager;
profile
하루 최소 1시간이라도 공부하자..

0개의 댓글