TCP 통신
TCP 통신이란?
- TCP(Transmission Control Protocol) 통신은 데이터 전송을 신뢰성 있게 처리하기 위한 프로토콜로, 주로 인터넷과 네트워크에서 사용된다.
TCP 통신의 주요 개념
- 연결 기반 통신
- TCP는 데이터 전송 전에 송신자와 수신자 간에 연결을 설정하는 3-way handshake 과정을 거친 후 이를 통해 양쪽이 서로 연결되었음을 확인하고, 데이터 전송을 준비합니다.
- 3-way handshake 과정:
1. SYN: 클라이언트가 서버에게 연결 요청을 보냄.
2. SYN-ACK: 서버가 요청을 수락하고 응답.
3. ACK: 클라이언트가 서버의 응답을 확인하고 연결이 성립됨.
- 흐름 제어 (Flow Control)
- TCP는 수신자의 버퍼가 넘치지 않도록 흐름 제어 기능을 지원합니다. 송신자는 수신자의 처리 속도를 고려하여 데이터를 전송하므로, 네트워크 혼잡을 방지할 수 있습니다.
- 혼잡 제어 (Congestion Control)
- 네트워크 혼잡이 발생하지 않도록 TCP는 전송 속도를 조절합니다. 네트워크 상황에 따라 전송 속도를 동적으로 조정하여 데이터 전송의 효율성을 극대화합니다.
정도로 정리할 수 있다.
TCP 통신에서 buffer 사용 이유
- 바이너리 데이터 처리:
- TCP 통신에서 전송되는 데이터는 바이너리 형태(이진데이터)로 전송됩니다. JavaScript의 기본 데이터 타입인 string이나 number는 바이너리 데이터로 바로 변환할 수 없으므로, 이를 처리하기 위해 Buffer 객체를 사용해야 합니다. 버퍼는 바이너리 형태를 효율적으로 다룰 수 있도록 설계된 객체입니다.
? 바이너리 형태란 ?
- 바이너리 형태(Binary Form): 데이터를 2진수(0과 1)로 표현한 것.
- 즉 버퍼를 사용하는 이유는 컴퓨터가 읽을 수 있도록 데이터를 이진 형태로 변경해주기 위해서 이다.
TCP 통신 사용 코드 분석
- net 모듈을 통해 TCP 서버 생성 (server측)
import net from 'net'
const server = net.createServer((socket) => {
}
- net 모듈이란?
- net 모듈은 Node.js에서 TCP 서버나 클라이언트를 쉽게 생성하고 관리할 수 있도록 제공되는 기본 모듈. 이 모듈은 저수준의 네트워킹 작업을 자동으로 처리해 주기 때문에, 개발자는 복잡한 TCP 연결 설정 과정을 신경 쓰지 않고도 간단히 TCP 통신을 구현할 수 있다.
- net.createServer()를 호출하면, TCP 서버가 생성되어 특정 포트에서 클라이언트의 연결 요청을 기다리게 됩니다.
- TCP 연결이 설정되면, 서버는 연결된 클라이언트를 나타내는 소켓 객체(socket)를 생성 한 후 이 소켓 객체는 연결된 클라이언트와의 통신을 담당합니다.
- 클라이언트 접속 후 buffer 에 정보를 담아 서버에 전송
client.connect(PORT, HOST, () => {
console.log('Connected to the server...');
const message = 'Hello';
const buffer = Buffer.from(message);
const header = writeHeader(buffer.length, 11);
const packet = Buffer.concat([header, buffer]);
client.write(packet);
});
- client가 connect를 통해 연결.
- message라는 변수에 문자열 'Hello'를 담아
buffer라는 변수에 Buffer.from( )을 통해 message를 Buffer 형태로 포장 한 후 header라는 변수를 writeHeader함수에게 전달한다.
- 그리고 client.write를 통하여 서버로 다시 전송한다.
export const TOTAL_LENGTH_SIZE = 4;
export const HANDLER_ID = 2;
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;
};
- writeHeader 함수는 length와 handlerId를 매개 변수로 받아 실행된다.
- 그러므로 length에는 message의 버퍼 길이, handlerId는 11이 현재는 들어 와 있다.
- headerSize는 우리가 전달하고자 하는 데이터의 헤더부분을 설정하기 위해서 작성한 것이다 6바이트를 의미하게 된다.
- buffer = Buffer.alloc(headerSize)에서 alloc( )은 괄호 안에 있는 크기 만큼 새로운 버퍼를 생성한다는 의미이다.
- 즉, buffer라는 변수에 6바이트라는 새로운 버퍼를 생성한다.
- 그 이후 writeUInt32BE와 writeUInt16BE를 통해 새롭게 생성된 buffer에 값을 저장시킨다.
- writeUInt32BE와 writeUInt16BE란?
빅인디안 형태를 의미하고 32BE는 32비트를 의미한다.
8비트가 1바이트가 되므로 즉 4바이트를 의미하고
버퍼의 첫 번째 4바이트에 전체 패킷의 길이(length + headerSize) 를 저장한다는 의미이고 0은 저장할 첫 시작점(여기서는 버퍼의 첫바이 트)을 의미한다.
- 그렇다면 wirteUInt16BE는 2바이트에 handlerId라는 값을 저장하고 저장을 시작하는 지점은 TOTAL_LENGTH_SIZE 뒤 부터 저장한다!.
- 클라이언트에게 받은 정보로 서버에서 출력
socket.on('data', (data) => {
const buffer = Buffer.from(data);
const { length, handlerId } = readHeader(buffer);
console.log(`handlerId: ${handlerId}`);
console.log(`length: ${length}`);
const handler = handlers[handlerId];
const headerSize = TOTAL_LENGTH_SIZE + HANDLER_ID;
const message = buffer.slice(headerSize);
console.log(`client에게 받은 메세지: ${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);
});
- 이렇게 클라이언트가 전달한 정보는 data로 전달되고 이미 버퍼 형태로 전달되었지만 안정성을 위해 다시 한번 버퍼 형태로 저장해준다.
- 그리고 현재 저 data안에는 불필요한 header에 관한 정보들이 포함되어 있기에 그 부분을 slice를 통해 제거 한 후 메세지로 출력해준다!
정리 끝