TIL

TIL_2025-01-09

explosion149 2025. 1. 10.

목차

 

심각한 오류

오류인데 정상적으로 작동함

오류인데 정상인척?! 비정상적으로 작동함

 

서버에서 보내준 위치와 입력 동기화 <- 상하가 좌우 이동으로 변경되어있음

그리고 심지어 좌우 이동은 적용되지 않음

 

우선 서버부터 훑어보자

 

 

 

 

기초 부터 순서대로 훑어보기

 

1.번

더보기
// src/server.js

//1번
// 해당 코드는 가장 처음 실행되며 기본 적인 기능들을 불러온다.

/* 
이 코드는 TCP 서버를 초기화하고 실행하는 기능을 제공합니다.
서버는 클라이언트의 연결을 처리하기 위해 `onConnection` 이벤트 핸들러를 사용하며,
서버가 성공적으로 시작되면 실행 중인 주소를 로그로 출력합니다.
초기화 과정에서 오류가 발생할 경우, 오류 메시지를 출력하고 프로세스를 종료합니다.
*/

import net from 'net';  // net 모듈 임포트 (TCP 소켓 기능 제공)
import initServer from './init/index.js';  // 서버 초기화 함수 임포트
import { config } from './config/config.js';  // 설정 파일 임포트 (서버 설정 정보)
import { onConnection } from './events/onConnection.js';  // 연결 이벤트 핸들러 임포트
import { createGameSession } from './session/game.session.js';  // 게임 세션 생성 함수 임포트

const server = net.createServer(onConnection);  // 서버 생성 및 연결 이벤트 핸들러 등록

// 서버 주소 정보를 출력하는 함수
const logServerAddress = function () {
  console.log(`서버가 ${config.server.host}:${config.server.port}에서 실행 중입니다.`);  // 서버 실행 중인 주소 출력
  console.log(server.address()); // 서버 주소 정보 출력
};

// 서버 초기화 및 실행
const startServer = async () => {
  try {
    await initServer(); // 서버 초기화 (필요한 설정 및 리소스 로드)

    // 서버 시작 시 게임 인스턴스 생성
    const gameSession = createGameSession();  // 새로운 게임 세션 생성
    console.log('Initial game session created:', gameSession.id);  // 생성된 게임 세션 ID 출력

    // 서버를 지정된 호스트와 포트에서 실행
    server.listen(config.server.port, config.server.host, logServerAddress); // 서버 시작 및 주소 출력
  } catch (err) {
    console.error(err); // 오류 발생 시 오류 메시지 출력
    process.exit(1); // 오류 발생 시 프로세스 종료
  }
};

// 서버 초기화 및 실행
startServer();  // 서버 시작

 

 

2.번

더보기
// src/events/onConnection.js

// 2번째로 실행된다.

/* 
이 코드는 클라이언트 소켓 연결을 처리하는 함수를 정의합니다.
'buffer' 속성을 추가하여 각 클라이언트에 대해 고유한 버퍼를 유지하고,
데이터 수신, 연결 종료, 오류 발생 시 각각의 핸들러를 설정합니다.
*/

import { onEnd } from './onEnd.js';  // 연결 종료 처리 함수
import { onError } from './onError.js';  // 오류 처리 함수
import { onData } from './onData.js';  // 데이터 수신 처리 함수

// 클라이언트 소켓이 연결될 때 호출되는 함수
export const onConnection = (socket) => {
  console.log('클라이언트가 연결되었습니다:', socket.remoteAddress, socket.remotePort);  // 연결된 클라이언트 정보 로그

  // 소켓 객체에 buffer 속성을 추가하여 각 클라이언트에 고유한 버퍼를 유지
  socket.buffer = Buffer.alloc(0);

  // 데이터 수신, 연결 종료, 오류 발생 시 핸들러 설정
  socket.on('data', onData(socket));  // 데이터 수신 이벤트 핸들러
  socket.on('end', onEnd(socket));  // 연결 종료 이벤트 핸들러
  socket.on('error', onError(socket));  // 오류 이벤트 핸들러
};

 

3.번

더보기
// src/events/onData.js


//3번째 실행

/* 
이 코드는 소켓에서 수신된 데이터를 처리하는 함수를 정의합니다.
버퍼에 데이터를 추가하고, 패킷 헤더를 파싱하여 다양한 패킷 타입에 따라 적절한 핸들러를 호출합니다.
오류 발생 시 오류를 처리하는 로직도 포함되어 있습니다.
*/

import { config } from '../config/config.js';  // 설정 파일 가져오기
import { PACKET_TYPE } from '../constants/header.js';  // 패킷 타입 상수 가져오기
import { packetParser } from '../utils/parser/packetParser.js';  // 패킷 파서 유틸리티 가져오기
import { getHandlerById } from '../handlers/index.js';  // 핸들러 가져오기
import { getUserById, getUserBySocket } from '../session/user.session.js';  // 사용자 세션 관리 관련 함수 가져오기
import { handleError } from '../utils/error/errorHandler.js';  // 오류 처리 유틸리티 가져오기
import CustomError from '../utils/error/customError.js';  // 사용자 정의 오류 클래스 가져오기
import { ErrorCodes } from '../utils/error/errorCodes.js';  // 오류 코드 상수 가져오기
import { getProtoMessages } from '../init/loadProtos.js';  // 프로토 메시지 로드 함수 가져오기

// 핑 패킷 처리 함수
const handlePing = (socket, packet) => {
  const protoMessages = getProtoMessages();  // 프로토 메시지 로드
  const Ping = protoMessages.common.Ping;  // 핑 프로토 메시지 가져오기
  const pingMessage = Ping.decode(packet);  // 패킷 디코딩
  const user = getUserBySocket(socket);  // 소켓에 연결된 사용자 가져오기
  if (!user) {
    throw new CustomError(ErrorCodes.USER_NOT_FOUND, '유저를 찾을 수 없습니다.');  // 사용자 미발견 오류 처리
  }
  user.handlePong(pingMessage);  // 핑 응답 처리
};

// 일반 패킷 처리 함수
const handleNormalPacket = async (socket, packet) => {
  const { handlerId, sequence, payload, userId } = packetParser(packet);  // 패킷 파싱
  const user = getUserById(userId);  // 사용자 ID로 사용자 가져오기

  // 유저가 접속해 있는 상황에서 시퀀스 검증
  if (handlerId !== 0 && user) {
    // 시퀀스 업데이트
    user.sequence = sequence;  // 사용자 시퀀스 업데이트
  }

  if (user && user.sequence !== sequence) {
    throw new CustomError(ErrorCodes.INVALID_SEQUENCE, '잘못된 호출 값입니다. ');  // 잘못된 시퀀스 오류 처리
  }

  const handler = getHandlerById(handlerId);  // 핸들러 가져오기
  await handler({  // 핸들러 호출
    socket,  // 소켓 정보
    userId,  // 사용자 ID
    payload,  // 패킷 페이로드 데이터
  });
};

// 소켓에서 수신된 데이터를 처리하는 함수
export const onData = (socket) => async (data) => {
  // 기존 버퍼에 새로 수신된 데이터를 추가
  socket.buffer = Buffer.concat([socket.buffer, data]);

  // 패킷의 총 헤더 길이 (패킷 길이 정보 + 타입 정보)
  const totalHeaderLength = config.packet.totalLength + config.packet.typeLength;

  // 버퍼에 최소한 전체 헤더가 있을 때만 패킷을 처리
  while (socket.buffer.length >= totalHeaderLength) {
    // 1. 패킷 길이 정보 수신 (4바이트)
    const length = socket.buffer.readUInt32BE(0);

    // 2. 패킷 타입 정보 수신 (1바이트)
    const packetType = socket.buffer.readUInt8(config.packet.totalLength);

    // 3. 패킷 전체 길이 확인 후 데이터 수신
    if (socket.buffer.length >= length) {
      // 패킷 데이터를 자르고 버퍼에서 제거
      const packet = socket.buffer.slice(totalHeaderLength, length);
      socket.buffer = socket.buffer.slice(length);

      try {
        switch (packetType) {
          case PACKET_TYPE.PING:  // 핑 패킷 처리
            await handlePing(socket, packet);  // 핑 패킷 처리 함수 호출
            break;
          case PACKET_TYPE.NORMAL:  // 일반 패킷 처리
            await handleNormalPacket(socket, packet);  // 일반 패킷 처리 함수 호출
            break;
        }
      } catch (error) {
        handleError(socket, error);  // 오류 발생 시 처리
      }
    } else {
      // 아직 전체 패킷이 도착하지 않음
      break;  // 루프 종료
    }
  }
};

 

 

 

더보기
// src/utills/parser/packetParser.js

// 4번째로 onEvent 실행될때 같이 실행됨 여기엔 기능만 제공할뿐 직접 실행되는것은 없음

/* 
이 코드는 수신한 데이터 패킷을 파싱하여 유효성을 검사하는 기능을 제공합니다.
패킷을 디코딩하고, 클라이언트 버전 및 핸들러 ID를 검증하며, 필요한 필드의 존재를 확인합니다.
오류가 발생할 경우 사용자 정의 오류를 생성하여 적절한 오류 코드를 반환합니다.
*/

import { getProtoMessages } from '../../init/loadProtos.js';  // 프로토 메시지 로드 함수 임포트
import { getProtoTypeNameByHandlerId } from '../../handlers/index.js';  // 핸들러 ID에 대한 프로토 타입 이름 가져오기
import { config } from '../../config/config.js';  // 설정 파일 임포트
import CustomError from '../error/customError.js';  // 사용자 정의 오류 클래스 임포트
import { ErrorCodes } from '../error/errorCodes.js';  // 오류 코드 상수 임포트

// 버퍼를 16진수 문자열로 변환하는 함수
function bufferToHex(buffer) {
  return Array.prototype.map.call(new Uint8Array(buffer), x => ('00' + x.toString(16)).slice(-2)).join(' ');
}

// 패킷 디코딩 함수
const decodePacket = (Packet, data) => {
  try {
    // 패킷을 디코딩
    const packet = Packet.decode(data);
    // 디코딩된 패킷 정보 로그 출력
    console.log("Decoded packet:", {
      handlerId: packet.handlerId,
      userId: packet.userId,
      version: packet.version,
      sequence: packet.sequence,
      payloadLength: packet.payload ? packet.payload.length : 0
    });
    return packet;  // 디코딩된 패킷 반환
  } catch (error) {
    // 패킷 디코딩 오류 처리
    console.error("Packet decode error:", error);
    throw new CustomError(ErrorCodes.PACKET_DECODE_ERROR, '패킷 디코딩 중 오류가 발생했습니다.');
  }
};

// 클라이언트 버전 검증 함수
const validateClientVersion = (clientVersion) => {
  // 클라이언트 버전이 서버 설정과 일치하는지 검증
  if (clientVersion !== config.client.version) {
    console.error(`Version mismatch - Client: ${clientVersion}, Server: ${config.client.version}`);
    throw new CustomError(
      ErrorCodes.CLIENT_VERSION_MISMATCH,
      '클라이언트 버전이 일치하지 않습니다.'
    );
  }
};

// 핸들러 ID 검증 함수
const validateHandlerId = (handlerId) => {
  // 핸들러 ID에 따른 프로토타입 이름 가져오기
  const protoTypeName = getProtoTypeNameByHandlerId(handlerId);
  if (!protoTypeName) {
    console.error(`Unknown handler ID: ${handlerId}`);
    throw new CustomError(
      ErrorCodes.UNKNOWN_HANDLER_ID,
      `알 수 없는 핸들러 ID: ${handlerId}`
    );
  }
  return protoTypeName;  // 유효한 프로토타입 이름 반환
};

// 페이로드 디코딩 함수
const decodePayload = (PayloadType, payload) => {
  try {
    // 페이로드를 디코딩
    const decodedPayload = PayloadType.decode(payload);
    console.log("Decoded payload:", decodedPayload);
    return decodedPayload;  // 디코딩된 페이로드 반환
  } catch (error) {
    // 페이로드 디코딩 오류 처리
    console.error("Payload decode error:", error);
    throw new CustomError(
      ErrorCodes.PACKET_STRUCTURE_MISMATCH,
      '패킷 구조가 일치하지 않습니다.'
    );
  }
};

// 필수 필드 검증 함수
const validateRequiredFields = (PayloadType, payload) => {
  // 기대되는 필드와 실제 필드 목록을 비교
  const expectedFields = Object.keys(PayloadType.fields);
  const actualFields = Object.keys(payload);
  const missingFields = expectedFields.filter(
    (field) => !actualFields.includes(field)
  );

  // 누락된 필드가 있는지 확인
  if (missingFields.length > 0) {
    console.error("Missing fields:", missingFields);
    throw new CustomError(
      ErrorCodes.MISSING_FIELDS,
      `필수 필드가 누락되었습니다: ${missingFields.join(', ')}`
    );
  }
};

// 패킷을 파싱하는 함수
export const packetParser = (data) => {
  try {
    // 수신한 원시 패킷 데이터 로그 출력
    console.log("Received raw packet data:", bufferToHex(data));

    // Proto 메시지 가져오기
    const protoMessages = getProtoMessages();
    const Packet = protoMessages.common.Packet;

    // 패킷 디코딩
    const packet = decodePacket(Packet, data);

    // 핸들러 ID, 사용자 ID, 클라이언트 버전, 시퀀스 정보 추출
    const handlerId = packet.handlerId;
    const userId = packet.userId;
    const clientVersion = packet.clientVersion;
    const sequence = packet.sequence;

    // 클라이언트 버전 검증
    validateClientVersion(clientVersion);

    // 핸들러 ID에 따른 Payload 구조 확인
    const protoTypeName = validateHandlerId(handlerId);
    const [namespace, typeName] = protoTypeName.split('.');
    const PayloadType = protoMessages[namespace][typeName];

    // Payload 디코딩
    const payload = decodePayload(PayloadType, packet.payload);

    // 필수 필드 검증
    validateRequiredFields(PayloadType, payload);

    // 성공적으로 패킷 파싱 로그 출력
    console.log("Successfully parsed packet:", {
      handlerId,
      userId,
      payload,
      sequence
    });

    // 파싱된 결과 반환
    return { handlerId, userId, payload, sequence };

  } catch (error) {
    // 이미 CustomError인 경우 그대로 전파
    if (error instanceof CustomError) {
      throw error;
    }
    // 그 외의 경우 새로운 CustomError로 래핑
    console.error("Unexpected error in packet parser:", error);
    throw new CustomError(
      ErrorCodes.PACKET_DECODE_ERROR,
      '패킷 처리 중 예상치 못한 오류가 발생했습니다.'
    );
  }
};

'TIL' 카테고리의 다른 글

TIL_2025-01-14  (1) 2025.01.14
TIL_2025-01-10  (0) 2025.01.10
TIL_2025-01-08  (0) 2025.01.08
TIL_2025-01-07  (0) 2025.01.07
TIL_2025-01-06  (0) 2025.01.06

댓글