TCP 서버 연결 기초

변우영·2024년 10월 18일

프로젝트 로직 분석 및 흐름 정리

TCP 클라이언트-서버 기반의 게임 애플리케이션입니다. 클라이언트(client.js)와 서버(server.js), 그리고 여러 유틸리티 모듈로 구성되어 있습니다.

디렉토리 및 파일 구조

src.zip 파일에는 다음과 같은 구성 요소가 포함되어 있습니다:

  1. config: 프로젝트 설정 파일.

    • config.js: 서버 포트, 호스트, 환경 변수와 같은 전역 설정을 관리합니다.
  2. constants: 애플리케이션에서 사용되는 상수 값 정의.

    • env.js: 개발, 프로덕션 등 환경별 상수를 정의합니다.
    • header.js: 패킷 포맷 및 분석에 사용되는 헤더 상수를 정의합니다.
  3. event: 클라이언트-서버 간 이벤트 기반 통신을 관리.

    • onConnection.js: 클라이언트가 서버에 연결될 때 초기 설정을 처리합니다.
    • onData.js: 클라이언트로부터 받은 데이터를 처리하고 응답을 결정합니다.
    • onEnd.js: 클라이언트 연결 종료 시 동작을 정의합니다.
    • onError.js: 오류 발생 시 이를 기록하고 처리합니다.
  4. init: 초기화 로직을 처리.

    • assets.js: 초기화 시 필요한 게임 자산 로드.
    • index.js: 초기화 작업을 총괄하는 진입점.
    • loadProtos.js: Protocol Buffers 정의를 로드하여 메시지 인코딩/디코딩에 사용합니다.
  5. protobuf: Protocol Buffers 정의 파일.

    • packetNames.js: 통신에 사용되는 패킷 이름을 정의합니다.
    • request, response: 클라이언트 요청 및 서버 응답을 위한 Protocol Buffers 정의.
  6. server.js: 서버 로직.

    • 클라이언트 연결을 처리하고, 요청을 받고, 응답을 전송합니다. 모듈의 재사용성과 확장성을 위해 init, event, utils 모듈을 사용합니다.
  7. utils: 유틸리티 함수 모음.

    • parser.js: 메시지 분석 및 응답 구성 유틸리티 함수.

코드 블록 및 분석

클라이언트 측 로직 - client.js

모듈 가져오기 및 상수 정의
import net from 'net';
import { getProtoMessages, loadProtos } from './src/init/loadProtos.js';

const TOTAL_LENGTH = 4; // 전체 길이를 나타내는 4바이트
const PACKET_TYPE_LENGTH = 1; // 패킷 타입을 나타내는 1바이트
  • net 모듈을 가져와 TCP 연결을 설정합니다.
  • Protocol Buffers 처리를 위해 getProtoMessagesloadProtos를 가져옵니다.
  • TOTAL_LENGTHPACKET_TYPE_LENGTH는 패킷 헤더의 길이를 정의합니다.
유틸리티 함수 정의
const readHeader = (buffer) => {
  return {
    length: buffer.readUInt32BE(0),
    packetType: buffer.writeUInt8(TOTAL_LENGTH),
  };
};
  • readHeader 함수는 버퍼에서 패킷 헤더를 읽어냅니다.
  • 전체 길이(length)와 패킷 유형(packetType)을 추출합니다.
패킷 전송
const sendPacket = (socket, packet) => {
  const protoMessages = getProtoMessages();
  const Packet = protoMessages.common.Packet;
  if (!Packet) {
    console.error('Packet 메시지를 찾을 수 없습니다.');
    return;
  }

  const buffer = Packet.encode(packet).finish();

  const packetLength = Buffer.alloc(TOTAL_LENGTH);
  packetLength.writeUInt32BE(buffer.length + TOTAL_LENGTH + PACKET_TYPE_LENGTH, 0);

  const packetType = Buffer.alloc(PACKET_TYPE_LENGTH);
  packetType.writeUInt8(1, 0);

  const packetWithLength = Buffer.concat([packetLength, packetType, buffer]);

  socket.write(packetWithLength);
};
  • sendPacket 함수는 서버로 패킷을 전송합니다.
  • 패킷의 길이와 유형을 버퍼에 기록한 후, 인코딩된 메시지와 함께 소켓을 통해 전송합니다.
서버에 연결 설정
const HOST = 'localhost';
const PORT = 5555;

const client = new net.Socket();

client.connect(PORT, HOST, async () => {
  console.log('Connected to server');
  await loadProtos();

  const message = {
    handlerId: 2,
    userId: 'xyz',
    payload: {},
    clientVersion: '1.0.0',
    sequence: 0,
  };

  sendPacket(client, message);
});
  • client.connect(PORT, HOST)로 서버에 연결합니다.
  • 연결 후 loadProtos()를 호출하여 Protocol Buffers 정의를 로드하고, 메시지를 생성하여 서버로 전송합니다.
서버로부터 데이터 수신
client.on('data', (data) => {
  const buffer = Buffer.from(data);
  const { handlerId, length } = readHeader(buffer);
  console.log(`handlerId: ${handlerId}`);
  console.log(`length: ${length}`);

  const headerSize = TOTAL_LENGTH + PACKET_TYPE_LENGTH;
  const message = buffer.slice(headerSize);
  console.log(`server 에게 받은 메세지: ${message}`);
});
  • 서버로부터 받은 데이터를 버퍼로 변환하고, readHeader를 통해 헤더 정보를 추출합니다.
  • 메시지 내용은 헤더를 제거한 나머지 부분입니다.
연결 이벤트 처리
client.on('close', () => {
  console.log('Connection closed');
});

client.on('error', (err) => {
  console.error('Client error:', err);
});
  • 연결 종료와 오류 이벤트를 처리합니다.

서버 측 로직 - server.js

const net = require('net');
const { onConnection, onData, onEnd, onError } = require('./event');
const { loadProtos } = require('./init/loadProtos');

const PORT = 5555;
const server = net.createServer(async (socket) => {
  await loadProtos();
  console.log('Client connected');

  socket.on('data', (data) => onData(socket, data));
  socket.on('end', () => onEnd(socket));
  socket.on('error', (err) => onError(err));
});

server.listen(PORT, () => {
  console.log(`Server listening on port ${PORT}`);
});
  • TCP 서버를 설정하고 포트 5555에서 클라이언트 연결을 수신합니다.
  • 연결, 데이터 수신, 연결 종료, 오류 이벤트를 처리합니다.

상세 개념 분석

  1. 버퍼(Buffer): 바이너리 데이터를 임시로 저장하는 공간입니다. 버퍼를 통해 패킷의 길이와 유형을 관리하고 메시지를 처리합니다.

  2. Protocol Buffers (protobufjs): 데이터 직렬화를 위해 사용되는 도구입니다. 이 프로젝트에서는 메시지 인코딩과 디코딩에 사용됩니다.

  3. TCP 소켓 통신: 신뢰성 있는 데이터 전송을 보장하는 프로토콜입니다. Node.js의 net 모듈을 사용해 TCP 연결을 관리합니다.

  4. 헤더 정보: 메시지의 길이와 유형을 포함하는 정보로, 클라이언트와 서버 간의 통신을 원활하게 관리하기 위해 사용됩니다.

profile
개발자로 한걸음!

0개의 댓글