ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [TIL] 스파르타) Node.js 게임서버 주특기 플러스 1주차 강의 내용 정리, 2주차 강의 수강 시작
    TIL-sparta 2024. 6. 25. 19:08

     

     > 주특기 플러스 강의 2주차를 수강하고 어제 정리하지 못한 1주차 내용을 적어보았다.

     

    학습 키워드: Node.js, net, socket

     

    1. net 라이브러리를 이용한 socket echo 서버 만들기

    1) 서버 생성:

    // server.js
    
    import net from 'net';
    
    const PORT = 3000;
    
    const server = net.createServer((socket) => {
      console.log(`Client connected: ${socket.remoteAddress}:${socket.remotePort}`);
      // socket.on(...
    });
    
    server.listen(PORT, () => {
      console.log(`Echo Server listening on port ${PORT}`);
      console.log(server.address());
    });

     구조 자체는 express, socket.io로 구현했던 웹소켓 서버와 크게 다르지 않다. Node의 기본 네트워크 모듈인 net에 있는 createServer 함수로 TCP 혹은 IPC (Inter-Process Communication) 서버를 생성할 수 있고, parameter의 callback 함수로 socket을 받아서 처리하면 된다.

     

    // client.js
    
    import net from "net";
    
    const HOST = "localhost";
    const PORT = 3000;
    
    new net.Socket();
    
    client.connect(PORT, HOST, () => {
    	console.log("Connected to the server...");
    }
    
    // client.on(...

     

     위 코드는 클라이언트 연결 예시다. 서버와 동일한 위치에서 실행했기 때문에 HOST가 localhost로 세팅되어 있지만, 실제 서비스에서는 이 위치에 TCP 서버의 IP 주소 혹은 DNS 주소가 들어가게 된다.

     

     

    2) socket 이벤트 예시:

    socket.on("data", (data) => {}
    socket.on("end", () => {}
    socket.on("error", (err) => {}

     웹 소켓(socket.io)과는 다르게 net socket.on의 이벤트 이름은 정해진 것을 사용한다.

     'data' : socket에 write를 이용한 쓰기가 이루어진 경우 'data' 이벤트로 전송된다. 

     'end' : 한 쪽에서 FIN 패킷을 전송한 경우 발생한다. 보통 이 이벤트 내에서 console.log를 남겨 연결 종료를 확인한다.

     'close' : 양 쪽 모두에서 연결이 종료되었을 때 발생한다. 리소스 자원 해제 등 후처리에 주로 사용된다고 한다.

     'error' : 에러 발생 시 발생하는 이벤트로, error 이벤트 종료 직후 close 이벤트가 발생한다.

     나열된 것 외에도 몇 가지 이벤트가 더 존재한다. 강의에서 제시된 이벤트로는 connect, ready, lookup, drain, timeout 등 이 있다. 자세한 사항은 강의 노트 혹은 공식 documentation을 확인하자.

     

     

    3) 데이터의 교환 (Buffer):

     TCP 소켓 서버에서는 데이터를 바이트 (1 Byte = 8 bit = 0~255 = 00~FF) 배열로 교환한다. 바이트 배열은 JS 기본 Buffer 객체를 사용한다. 각 바이트에는 전송되는 문자의 ASCII 코드 값이 16진수 형태로 담기게 된다. 아래 예시를 참고하자.

    const buffer = Buffer.from("Hello");
    
    console.log(buffer); // 48 65 6c 6c 6f    16진수로 변환된 ASCII 코드 값
    console.log(buffer[0]); // 72             10진수, ASCII 코드 값

    ASCII 테이블. 문자 'H'는 10진수로 72, 16진수로 48이다.

     

    다음은 1주차 강의에서 등장한 버퍼(Buffer)의 method 목록이다.

    Buffer.alloc(NUM_OF_BYTES) // 버퍼 할당
    Buffer.from(SOME_STRING) // 문자열을 버퍼 배열로 치환
    Buffer.concat([bufferA, bufferB]) // bufferA와 bufferB를 이어붙임
    Buffer.slice(START, END) // (deprecated) slice되어 반환된 buffer도 같은 메모리 주소를 사용
    Buffer.subarray(START, END) // slice와 동일, 이 버퍼를 수정하면 원본 버퍼도 수정됨
    
    // buffer가 Buffer 객체일 때
    buffer.readIntBE(OFFSET, BYTE_LENGTH) // 읽기 시작 위치 OFFSET, 읽을 길이 BYTE_LENGTH
    buffer.readIntLE(OFFSET, BYTE_LENGTH) // 끝 위치부터 시작 위치 순으로 뒤집어 읽음
    
    // BE = Big Endian (정방향), LE = Little Endian (역방향)
    
    // Offset 위치 부터 length 까지 범위 안에 value 정수 쓰기
    buffer.writeIntBE(VALUE, OFFSET, BYTE_LENGTH) // 정방향
    buffer.writeIntLE(VALUE, OFFSET, BYTE_LENGTH) // 역방향

     

     

    4) 사용 예시:

    // client.js
    
    ...
    
    client.on("data", (data) => {
      // data : Buffer, Buffer 객체는 Byte 배열: 56 30 1c ff 9a ...
      const { length, handlerId } = readHeader(data);
      console.log("length: ", length);
      console.log("handlerId: ", handlerId);
      const buffer = Buffer.from(data);
    
      const headerSize = TOTAL_LENGTH_SIZE + HANDLER_ID_SIZE;
      const message = buffer.subarray(headerSize);
      console.log(`server에게 받은 메세지: ${message}`);
    });
    
    client.on("close", () => {
      // 양 쪽의 연결이 완전히 끝났을 때
      console.log(`Connection closed.`);
    });
    
    client.on("error", (err) => {
      console.log(`Client error: ${err}`);
    });

     

    // server.js
    
    ...
    
    socket.on("data", (data) => {
      const { length, handlerId } = readHeader(data);
      console.log("length: ", length);
      console.log("handlerId: ", handlerId);
      
      if (length > MAX_MESSGAE_LENGTH) {
        console.error(`Error: Message length ${length}`);
        socket.write(`Error: Message too long`);
        socket.end();
        return;
      }
      
      if (!handlers[handlerId]) {
        console.error(`Error: No handler found for ID ${handlerId}`);
        socket.write(`Error: Invalid handler ID ${handlerId}`);
        socket.end();
        return;
      }
      
      const buffer = Buffer.from(data);
      
      const headerSize = TOTAL_LENGTH_SIZE + HANDLER_ID_SIZE;
      const message = buffer.subarray(headerSize);
      
      console.log("Client message: ", message);
      
      const responseMessage = "Hi, there";
      const responseBuffer = Buffer.from(responseMessage);
      
      const header = writeHeader(responseBuffer.length, handlerId);
      const packet = Buffer.concat([header, responseBuffer]);
      
      socket.write(packet); // 받은 data 다시 전송
    });
    
    socket.on("end", () => {
      // 한 쪽의 연결이 끝났을 때
      console.log(`Client disconnected: ${socket.remoteAddress}:${socket.remotePort}`);
    });
    
      socket.on("error", (err) => {
      console.log(`Socket error: ${err}`);
    });

     

     

    --

     

    REFERENCES:

     

    https://nodejs.org/api/net.html#netcreateserveroptions-connectionlistener

     > NodeJS, "Net" - official documentation

    https://teamsparta.notion.site/TCP-Echo-1-d474eea01ab04dc39997796f2c514d9c#eb9c77a35fc046b1a06ac4f6e882bf60

     > 강의 노트, "[게임서버개발 플러스]TCP Echo 서버 만들기 (1)" by 조호영 튜터님

    https://teamsparta.notion.site/TCP-Echo-2-dee9024d343044d8b0d94bc88a875c23

     > 강의 노트, "[게임서버개발 플러스]TCP Echo 서버 만들기 (2)" by 조호영 튜터님

    728x90
Designed by Tistory.