-
[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 코드 값
다음은 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
> 강의 노트, "[게임서버개발 플러스]TCP Echo 서버 만들기 (1)" by 조호영 튜터님
https://teamsparta.notion.site/TCP-Echo-2-dee9024d343044d8b0d94bc88a875c23
> 강의 노트, "[게임서버개발 플러스]TCP Echo 서버 만들기 (2)" by 조호영 튜터님
728x90'TIL-sparta' 카테고리의 다른 글
강의 과제) 삼각 함수, 역 삼각 함수 (0) 2024.06.27 강의 과제) 로드 밸런싱, 대칭 키 / 공개 키 암호화 방식 (0) 2024.06.26 [TIL] 스파르타) Node.js 게임서버 주특기 플러스 1주차 강의 수강, 프로그래머스 - 오프라인/온라인 판매 데이터 통합하기 풀이 (MySQL) (0) 2024.06.24 프로그래머스) 그룹별 조건에 맞는 식당 출력하기 풀이 (MySQL) (0) 2024.06.23 프로그래머스) 대여 횟수가 많은 자동차들의 월별 대여 횟수 구하기 풀이 (MySQL) (0) 2024.06.22