https://kagan-draca.tistory.com/326
https://kagan-draca.tistory.com/327
https://kagan-draca.tistory.com/328
오늘은 위와 같이 TCP socket 통신을 배우고 정리해봤다.
이 중에서 가장 재미있는 내용은,
클라이언트가 보낸 packet (16진수 형태로 Buffer에 담겨, Header 정보와 Data가 붙어 있는 중)을 서버가 처리하는 로직이었다.
export const onData = (socket) => (data) => {
//console.log(data);
socket.buffer = Buffer.concat([socket.buffer, data]);
// 기본 버퍼에 새로 수신된 데이터 추가하기
const totalHeaderLength = config.packet.totalLength + config.packet.typeLength;
// 버퍼의 해버 크기 보다 큰 경우 => 그때부터 진짜 message, data가 오는 상황이 된다.
while (socket.buffer.length >= totalHeaderLength) {
const length = socket.buffer.readUInt32BE(0);
// 버퍼에 0 ~ 4Byte(4Byte 크기) 까지는 패킷 길이 정보
const packetType = socket.buffer.readUInt8(config.packet.totalLength);
// 4Byte ~ 5Byte(1Byte 크기) 만큼은 packet의 타입
if (socket.buffer.length >= length) {
// 퍼버의 현재 길이가 원하는 버퍼의 사이즈 보다 크거나 같을 경우
const packet = socket.buffer.slice(totalHeaderLength, length);
// 해더 부분을 제거해서 원하는 packet Data만 가져온다.
socket.buffer = socket.buffer.slice(length);
// 남은 해더 부분도 제거해서 다음 이벤트에 따른 Header와 packet Data 정보를 기다린다.
console.log(`length : ${length}, packetType : ${packetType}`);
console.log(`packet : ${packet}`);
} else break;
// 아직 전체 패킷이 도착하지 않았을 때
}
};
위와 같은 로직으로,
그림을 통해 이해하자면, 클라이언트가 Buffer로
20 Byte의 Packet을 보냈을 때,
(실질적인 내용물 => Packet Data는 15 Byte)
Packet Length로 클라이언트가 보낼 Packet의 전체 크기를 Buffer에 16진수 형태의 4Byte로 보내게 되고,
Packet Type을 Buffer에 담아 16진수 형태의 1Byte 크기로 보내게 된다.
이때, Packet은 잘게 쪼개져 서버로 보내게 되고, 이를 서버가
socket.buffer = Buffer.concat([socket.buffer, data]);
버퍼로 받게 됩니다.
const totalHeaderLength = config.packet.totalLength + config.packet.typeLength;
while (socket.buffer.length >= totalHeaderLength) {
while의 조건으로 socket.buffer.length >= totalHeaderLength(해더의 크기)(5 Byte)를 줘
현재 sock의 buffer가 해더의 크기 보다 클 경우 (=> 즉, 지금부터 받는 서버에 오는 data는 packet Data) 반복문을 동작 시킵다.
while 안에서는
const length = socket.buffer.readUInt32BE(0);
// 버퍼에 0 ~ 4Byte(4Byte 크기) 까지는 패킷 길이 정보
0 ~ 32 bit(4 Byte) 만큼 자르고 그 안의 16진수 값을 10진수 정수로 바꿉니다.
이를 바탕으로 Packet의 해더와 Packet Data의 크기 입니다. 즉, Packet의 전체 크기를 알 수 있게 됩니다.
const packetType = socket.buffer.readUInt8(config.packet.totalLength);
// 4Byte ~ 5Byte(1Byte 크기) 만큼은 packet의 타입
로 4Byte 위치에서 부터 8bit(1Byte)만큼 1Byte 크기를 잘라 안의 16진수 값을
10진수로 변환해 packet의 타입 정보를 알아냅니다.
그 후,
if (socket.buffer.length >= length) {
// 퍼버의 현재 길이가 원하는 버퍼의 사이즈 보다 크거나 같을 경우
socket.buffer.length >= length
즉, 현재 socket의 buffer에 들어온 data 크기가 해더로 알게 된 packet의 크기와 같거나 크다면
=> 받아야 할 data를 받았거나 더 받았다면,
const packet = socket.buffer.slice(totalHeaderLength, length);
// 해더 부분을 제거해서 원하는 packet Data만 가져온다.
현재 socket.buffer 안의 packetData를 slice로 해더에서부터 전체 길이만큼 자릅니다
=> 해더를 제외한 실제 packet Data만 남긴다.
(붉은 영역 네모 자르기)
socket.buffer = socket.buffer.slice(length);
// 남은 해더 부분도 제거해서 다음 이벤트에 따른 Header와 packet Data 정보를 기다린다.
socket.buffer.slice(length)로 0 ~ 해더 길이 만큼
남은 socket.buffer의 packet을 제거해 다음 이벤트에 따른 data를 받을 준비를 합니다.
} else break;
// 아직 전체 패킷이 도착하지 않았을 때
그리고, 위의 if문에 부합하지 않으면 break로 다음 패킷 조각이 올 때 까지 기다립니다.
위와 같은 과정을 잘 응용하면, 이미지, 그림, 텍스트, 동영상 등 모든 데이터를 클라이언트에게서 받아와 서버가 처리할 수 있을 것 같