심각한 오류
오류인데 정상적으로 작동함
오류인데 정상인척?! 비정상적으로 작동함
서버에서 보내준 위치와 입력 동기화 <- 상하가 좌우 이동으로 변경되어있음
그리고 심지어 좌우 이동은 적용되지 않음
우선 서버부터 훑어보자
기초 부터 순서대로 훑어보기
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 |
댓글