기초 TCP echo 서버 echo 연결 - 001

변우영·2024년 10월 17일

Node.js로 간단한 TCP 에코 서버 만들기

이번 프로젝트는 Node.js를 사용하여 TCP 에코 서버와 클라이언트를 구축하고, 네트워크를 통해 기본적인 문자열 데이터를 전송하고 처리하는 방법을 시도하였습니다.

프로젝트 구조

|-- handler10.js
|-- handler11.js
|-- index.js
|-- client.js
|-- server.js
|-- utils.js
|-- constants.js
|-- package.json

1. 핸들러 (Handlers)

서버는 들어오는 메시지를 처리하며, 각 메시지를 미리 정의된 핸들러에 따라 변환합니다.

  • handler10.js: 들어온 메시지를 모두 대문자로 변환합니다.

    const handler10 = (data) => {
        const processedData = data.toString().toUpperCase();
        return Buffer.from(processedData);
    };
    
    export default handler10;
  • handler11.js: 들어온 메시지의 문자를 역순으로 변환합니다.

    const handler11 = (data) => {
        const processedData = data.toString().split('').reverse().join('');
        return Buffer.from(processedData);
    };
    
    export default handler11;
  • index.js: 핸들러를 ID별로 관리합니다.

    import handler10 from "./handler10.js";
    import handler11 from "./handler11.js";
    
    const handlers = {
        10: handler10,
        11: handler11,
    };
    
    export default handlers;

2. 클라이언트

클라이언트는 서버에 연결하여 메시지를 보내고, 서버의 응답을 처리합니다.

  • client.js: 서버에 간단한 메시지를 보내고, 응답을 처리합니다.

    import net from 'net';
    import { readHeader, writeHeader } from './utils.js';
    import { HANDLER_ID, TOTAL_LENGTH_SIZE } from './constants.js';
    
    const HOST = 'localhost';
    const PORT = 5555;
    
    const client = new net.Socket();
    
    client.connect(PORT, HOST, () => {
        console.log('서버에 연결 중...');
    
        const message = "Hello";
        const buffer = Buffer.from(message);
    
        const header = writeHeader(buffer.length, 11); // 핸들러 ID 11은 문자열을 역순으로 변환
        const packet = Buffer.concat([header, buffer]);
        client.write(packet);
    });
    
    client.on('data', (data) => {
        const buffer = Buffer.from(data);
        const { length, handlerId } = readHeader(buffer);
        console.log(`Handler: ${handlerId}`);
        console.log(`Length: ${length}`);
    
        const headerSize = TOTAL_LENGTH_SIZE + HANDLER_ID;
        const message = buffer.slice(headerSize);
    
        console.log(`서버에서 받은 메시지: ${message}`);
    });
    
    client.on('close', () => {
        console.log(`연결이 종료되었습니다.`);
    });
    
    client.on('error', (err) => {
        console.log(`클라이언트 오류: ${err}`);
    });

3. 서버

서버는 연결을 수신하고, 메시지를 핸들러로 처리한 후 결과를 클라이언트에 반환합니다.

  • server.js: 핸들러 ID에 따라 클라이언트로부터 메시지를 처리하고, 응답을 반환합니다.

    import net from 'net';
    import { readHeader, writeHeader } from './utils.js';
    import { HANDLER_ID, MAX_MESSAGE_LENGTH, TOTAL_LENGTH_SIZE } from './constants.js';
    import handlers from './index.js';
    
    const PORT = 5555;
    
    const server = net.createServer((socket) => {
        console.log(`클라이언트 연결됨: ${socket.remoteAddress}:${socket.remotePort}`);
    
        socket.on('data', (data) => {
            const buffer = Buffer.from(data);
            const { length, handlerId } = readHeader(buffer);
            console.log(`Handler: ${handlerId}`);
            console.log(`Length: ${length}`);
    
            if (length > MAX_MESSAGE_LENGTH) {
                console.error(`Error: 메시지 길이 초과`);
                socket.write(`Error: 메시지 길이 초과`);
                socket.end();
                return;
            }
    
            const handler = handlers[handlerId];
            if (!handler) {
                console.error(`Error: 잘못된 핸들러 ID ${handlerId}`);
                socket.write(`Error: 잘못된 핸들러 ID`);
                socket.end();
                return;
            }
    
            const headerSize = TOTAL_LENGTH_SIZE + HANDLER_ID;
            const message = buffer.slice(headerSize);
    
            console.log(`클라이언트로부터 받은 메시지: ${message}`);
    
            const responseMessage = handler(message);
            const responseBuffer = Buffer.from(responseMessage);
    
            const header = writeHeader(responseBuffer.length, handlerId);
            const packet = Buffer.concat([header, responseBuffer]);
            socket.write(packet);
        });
    
        socket.on('end', () => {
            console.log(`클라이언트 연결 종료됨: ${socket.remoteAddress}:${socket.remotePort}`);
        });
    
        socket.on('error', (err) => {
            console.log(`소켓 오류: ${err}`);
        });
    });
    
    server.listen(PORT, () => {
        console.log(`서버가 포트 ${PORT}에서 대기 중입니다.`);
    });

4. 유틸리티 (Utilities)

  • utils.js: 메시지의 헤더를 읽고 작성하는 기능을 담당합니다.

    import { HANDLER_ID, TOTAL_LENGTH_SIZE } from './constants.js';
    
    export const readHeader = (buffer) => {
        return {
            length: buffer.readUInt32BE(0),
            handlerId: buffer.readUInt16BE(TOTAL_LENGTH_SIZE),
        };
    };
    
    export const writeHeader = (length, handlerId) => {
        const headerSize = TOTAL_LENGTH_SIZE + HANDLER_ID;
        const buffer = Buffer.alloc(headerSize);
        buffer.writeUInt32BE(length + headerSize, 0);
        buffer.writeUInt16BE(handlerId, TOTAL_LENGTH_SIZE);
    
        return buffer;
    };
  • constants.js: 서버와 클라이언트에서 사용되는 상수를 정의합니다.

    export const TOTAL_LENGTH_SIZE = 4;
    export const HANDLER_ID = 2;
    export const MAX_MESSAGE_LENGTH = 1024;

기본적인 TCP 서버-클라이언트 통신을 구축하는 방법이였습니다. 클라이언트가 메시지를 서버로 보내면, 서버는 해당 메시지를 적절한 핸들러를 통해 처리한 뒤, 처리된 메시지를 다시 클라이언트로 전송합니다.

이 프로젝트를 실행하려면 다음 명령어를 사용하세요

  1. 의존성 설치

    npm install
  2. 서버 시작

    npm run dev
  3. 별도의 터미널에서 클라이언트 실행

    npm run client
profile
개발자로 한걸음!

0개의 댓글