패킷 길이 프레이밍
-
내가 알기로 TCP 헤더에 이미 패킷의 크기가 있는 줄 알았는데… 아니었다!!!
-
게다가 TCP는 UDP와 다르게 메시지를 따로따로 보내는게 아니고, 바이트 스트림을 그냥 보냄!
-
TCP는 메시지 경계가 없다!!! 그저 스트림을 보낼 뿐이다!
-
TCP는 순서, 신뢰성, 흐름제어는 보장하나 메시지 경계를 보장하지 않는다
-
그래서 넣은게 패킷 길이 프레이밍!
-
참고 : 순서는 보장하기 때문에, 중간에 갑자기 다른 패킷이 앞에 길이 프레이밍 없이 들어오는 경우는 없음. 그건 걱정 안해도 됨
-
그래서, 패킷 구조가 이렇게 됨

코드로 구현하기
func SerializePacket(pkt *protocol.Packet) ([]byte, error) {
// 1. 패킷을 바이트 배열로 변경
packetBytes, err := proto.Marshal(pkt)
if err != nil {
return nil, err
}
// 2. 빈 바이트 배열을 만듬
// 이때 빈 바이트 배열의 길이는 4 + 바이트 배열로 변경한 패킷의 길이
frame := make([]byte, 4+len(packetBytes))
// 3. 빈 바이트 배열의 앞 4바이트를 바이트 배열로 변경한 패킷 길이로 정함
binary.BigEndian.PutUint32(frame[:4], uint32(len(packetBytes)))
// 4. 뒤에 바이트 배열로 변경한 패킷을 붙임
copy(frame[4:], packetBytes)
return frame, nil
}
func DeserializePacket(frame []byte) (*protocol.Packet, error) {
// 1. frame 자체의 길이가 4 바이트가 안되면 말이 안됨 (길이 프레이밍이 4 바이트임)
if len(frame) < 4 {
return nil, fmt.Errorf("Frame is too small")
}
// 2. 패킷의 길이를 받음
packetLen := binary.BigEndian.Uint32(frame[:4])
// 3. 전체 프레임 길이가 4 + 패킷 길이 보다 작으면 데이터가 오다 만거임
if len(frame) < int(4+packetLen) {
return nil, fmt.Errorf("Incomplate frame")
}
// 4. 실제 패킷 데이터 뽑기
packetBytes := frame[4 : 4+packetLen]
// 5. 역직렬화 해서 Packet 구조체로 변환 후, 문제 없으면 반환함
var pkt protocol.Packet
if err := proto.Unmarshal(packetBytes, &pkt); err != nil {
return nil, err
}
return &pkt, nil
}- 실행 결과

하나의 바이트 슬라이스에, 여러 개의 패킷이 섞여있다면?
- 결국 위에 만든 패킷 길이 프레이밍도 이걸 위해 했다.
- 위에서 만든 길이를 토대로 파서를 만들면 됨
- 아까 만든 역직렬화 로직에서 체크하던 부분을 파서로 옮김
// Buffer에 쌓인 byte 배열을 제대로 수신된 프레임들과 남은 프레임으로 쪼개서 반환
func ParseFrames(buffer []byte) ([][]byte, []byte) {
var frames [][]byte
offset := 0
for {
// 1. 앞에 붙는 길이 헤더를 검사함. 4보다 작으면 지금 처리 X
if len(buffer[offset:]) < 4 {
break
}
length := binary.BigEndian.Uint32(buffer[offset : offset+4])
frameSize := int(4 + length)
// 2. 프레임이 다 안왔으면 지금 처리 X
if len(buffer[offset:]) < frameSize {
break
}
// 3. 프레임 추출해서 프레임들에다가 넣음
frame := buffer[offset : offset+frameSize]
frames = append(frames, frame)
offset += frameSize
}
remain := buffer[offset:] // 처리 못한것들
return frames, remain
}
// 패킷 구조체를 바이트 배열로 변경
func SerializePacket(pkt *protocol.Packet) ([]byte, error) {
// 1. 패킷을 바이트 배열로 변경
packetBytes, err := proto.Marshal(pkt)
if err != nil {
return nil, err
}
// 2. 빈 바이트 배열을 만듬
// 이때 빈 바이트 배열의 길이는 4 + 바이트 배열로 변경한 패킷의 길이
frame := make([]byte, 4+len(packetBytes))
// 3. 빈 바이트 배열의 앞 4바이트를 바이트 배열로 변경한 패킷 길이로 정함
binary.BigEndian.PutUint32(frame[:4], uint32(len(packetBytes)))
// 4. 뒤에 바이트 배열로 변경한 패킷을 붙임
copy(frame[4:], packetBytes)
return frame, nil
}
// 역직렬화 가능한 byte 배열을 받고, 패킷으로 반환
func DeserializePacket(frame []byte) (*protocol.Packet, error) {
// 여기까지 왔으면 일단 역직렬화 가능한 바이트 배열이 들어왔다고 가정
// 역직렬화 해서 Packet 구조체로 변환 후, 문제 없으면 반환함
var pkt protocol.Packet
if err := proto.Unmarshal(frame[4:], &pkt); err != nil {
return nil, err
}
return &pkt, nil
}func main() {
// Frame 1
frame1, _ := SerializePacket(makePingPacket(1))
// Frame 2
frame2, _ := SerializePacket(makePingPacket(2))
// Frame 3 (일부만)
frame3, _ := SerializePacket(makePingPacket(3))
// 일부러 쪼개기
combined := append(frame1, frame2...)
combined = append(combined, frame3[:5]...) // 미완성
frames, remain := ParseFrames(combined)
for i, frame := range frames {
fmt.Println("\n\n---- Frame ", i, " ----")
fmt.Println(frame)
pkt, err := DeserializePacket(frame)
if err != nil {
fmt.Println("Deserialize Error : ", err)
continue
}
fmt.Printf("\nPacket Type : %v", pkt.Type)
switch pkt.Type {
case protocol.MessageType_PING:
var ping protocol.Ping
if err := proto.Unmarshal(pkt.Payload, &ping); err != nil {
fmt.Println("Ping Unmarshal Error : ", err)
}
fmt.Printf(" | Ping Timestamp : %v", ping.Timestamp)
}
}
fmt.Println("\n\nRemain Frames : ", remain)
}
- 해냈음!
- 두 개는 똑바로 보내고 하나는 보내다 마는걸 테스트했음
다음에 할 것
- 구조체를 만들어서 따로 정리하기
