오늘은 클라이언트에서 패킷으로 보내는 sequence값을 체크해보는 로직을 작성해 보았습니다.
기존 onData
- 기존 코드에선 sequence값을 체크하고 있지 않았습니다.
export const onData = (socket) => async (data) => { try { socket.buffer = Buffer.concat([socket.buffer, data]); while (socket.buffer.length >= PACKET_TYPE_LENGTH + VERSION_LENGTH) { // 패킷 타입 읽어오기 const packetType = socket.buffer.readUInt16BE(0); const versionLength = socket.buffer.readUInt8(PACKET_TYPE_LENGTH); const totalHeaderLength = PACKET_TYPE_LENGTH + VERSION_LENGTH + versionLength + SEQUENCE_LENGTH + PAYLOAD_LENGTH; ... // 시퀀스 const sequence = socket.buffer.readUInt32BE( PACKET_TYPE_LENGTH + VERSION_LENGTH + versionLength, ); // 시퀀스 체크 TODO ... // payload const payload = socket.buffer.subarray(totalHeaderLength, totalPacketLength); // console.log('payload: ', payload); // 이후의 데이터 다시 저장 socket.buffer = socket.buffer.subarray(totalPacketLength); const data = packetParser(packetType, payload); const handler = getHandlerById(packetType); // console.log(handler);a await handler({ packetType, data, socket }); } } catch (e) { console.error('onData error: ', e); } };
기존 createResponse
- 기존 버퍼객체를 만들어 응답으로 보낼 데이터를 만드는 함수입니다.
- 현재 sequence를 일단 임시방편으로 0으로해서 진행해 왔습니다.
export const createResponse = (packetType, responseData, socket) => { try { const protoMessages = getProtoMessages(); const gamePacket = protoMessages['protoPacket']['GamePacket']; // 따로 저장한 responseProto에서 protoType을 가져옴 const [namespace, typeName] = responseProto[packetType].protoType.split('.'); const response = protoMessages[namespace][typeName]; const responseInstance = response.create(responseData); const gamePacketFieldName = responseProto[packetType].fieldName; const gamePacketInstance = gamePacket.create({ [gamePacketFieldName]: responseInstance }); const sequence = 0; const buffer = gamePacket.encode(gamePacketInstance).finish(); const headerPacket = createHeader(packetType, sequence, buffer.length); return Buffer.concat([headerPacket, buffer]); } catch (e) { console.error(e); } };
먼저 해당 클라이언트를 정보를 가질 class를 하나 만들고 클라이언트 전용 세션 관리 파일을 만들어 클라이언트와 서버가 연결될때 해당 클라이언트를 새로 세션에 저장하게 설계해 봤습니다.
class Client
- 클라이언트의 간단한 정보와 여기서 각 클라이언트의 sequence값을 관리할 수 있게 만들어 보았습니다.
let clientCounter = 1; class Client { constructor(socket) { this.id = this.getClientId(); this.socket = socket; this.sequence = 1; } getSequence() { return this.sequence; } updateSequence() { this.sequence++; } getClientId() { return clientCounter++; } } export default Client;
client 세션 관리 파일
- 세션에 저장 삭제 등의 관리를 해줍니다.
import Client from '../classes/models/client.class.js'; import { clientSessions } from './sessions.js'; export const addClient = (socket) => { const client = new Client(socket); clientSessions.push(client); return client; }; export const removeClient = (socket) => { const index = clientSessions.findIndex((client) => client.socket === socket); if (index !== -1) { return clientSessions.splice(index, 1)[0]; } }; export const getAllClient = () => { return clientSessions; }; export const getClientBySocket = (socket) => { return clientSessions.find((client) => client.socket === socket); };
onConnection
- 클라이언트와 연결이 될떄 해당 클라이언트를 세션에 추가 저장합니다.
addClient(socket)
export const onConnection = (socket) => { console.log(`Client connected from: ${socket.remoteAddress}:${socket.remotePort}`); socket.buffer = Buffer.alloc(0); addClient(socket); socket.on('data', onData(socket)); socket.on('end', onEnd(socket)); socket.on('error', onError(socket)); };
수정된 OnData
- 해당 세션에 저장된 클라이언트를 찾아 sequence값을 올려줍니다.(이 부분이 사실 애매한면이 있습니다.)
- 이 sequence값이 서버에서 응답을 보내줄때 올려주는 것이 맞을 지에 대해 고민해 보았습니다.
- 근데 일단 이번 과제에서 여러 패킷을 교환되는 중 여러 sequence값이 올바르게 오르고 가는 부분에서 여러 에러 사항들이 생겨서 패킷(데이터)를 받을때 sequence값을 올리게 되었습니다.
export const onData = (socket) => async (data) => { try { socket.buffer = Buffer.concat([socket.buffer, data]); const client = getClientBySocket(socket); client.updateSequence(); // 시퀀스 업데이트 while (socket.buffer.length >= PACKET_TYPE_LENGTH + VERSION_LENGTH) { // 패킷 타입 읽어오기 const packetType = socket.buffer.readUInt16BE(0); const versionLength = socket.buffer.readUInt8(PACKET_TYPE_LENGTH); .... .... // 시퀀스 const sequence = socket.buffer.readUInt32BE( PACKET_TYPE_LENGTH + VERSION_LENGTH + versionLength, ); // 시퀀스 체크 if (sequence !== client.getSequence()) { throw new Error('시퀀스가 일치하지 않습니다.'); } .... .... const handler = getHandlerById(packetType); // console.log(handler);a await handler({ packetType, data, socket }); } } catch (e) { console.error('onData error: ', e); } };
수정된 createResponse
- 해당 클라이언트를 찾아 sequence값을 찾아오게 변경하였습니다.
export const createResponse = (packetType, responseData, socket) => { try { const protoMessages = getProtoMessages(); const gamePacket = protoMessages['protoPacket']['GamePacket']; ... const client = getClientBySocket(socket); let sequence; if (client) { sequence = client.getSequence(); } else { sequence = 0; } const buffer = gamePacket.encode(gamePacketInstance).finish(); const headerPacket = createHeader(packetType, sequence, buffer.length); return Buffer.concat([headerPacket, buffer]); } catch (e) { console.error(e); } };
벌써 또 팀과제 제출이네요 이번엔 진짜 시간이 촉박하다고 느껴졌지만 어찌저찌 거의 완성이 된것 같습니다. 제출까지 화이팅할 생각합니다.
오늘도 화이팅!!