TCP Stream과 Packet 차이: 메시지 분할·합쳐짐과 패킷 파싱 방법

TCP Stream과 Packet 차이: 메시지 분할·합쳐짐과 패킷 파싱 방법#

1장 TCP 연결은 살아 있는데 왜 명령을 못 읽을까#

현장에서 다음과 같은 상황을 만날 수 있습니다.

TCP Connected

장비 연결 상태
ONLINE

그런데

특정 Event 누락
Command Parsing Error
CRC Error

Network 연결 자체는 살아 있습니다.

ping도 되고 TCP Socket도 연결되어 있습니다.

그런데 Application에서는 특정 Message를 읽지 못합니다.

이런 경우 중요한 질문은:

TCP가 보내는 단위와 Application이 기대하는 Message 단위가 같은가?

입니다.

결론부터 말하면 같지 않습니다.


2장 TCP는 Message를 전달하는 Protocol이 아니다#

TCP는 Application에게 순서가 보장되는 Byte Stream을 제공합니다.

Application이 다음 Message를 보냈다고 하겠습니다.

MESSAGE-A

그리고 이어서:

MESSAGE-B

를 보냈습니다.

송신 Program 입장에서는 두 번 send()했습니다.

하지만 수신 Program이 반드시:

recv() 1
→ MESSAGE-A

recv() 2
→ MESSAGE-B

형태로 받는다는 보장은 없습니다.


3장 한 번 보낸 데이터가 여러 번 나뉘어 들어올 수 있다#

송신 측:

send()

A5 5A 00 05 10 11 12 13 14

수신 측에서는 다음처럼 받을 수 있습니다.

recv() #1

A5 5A 00

다음 호출:

recv() #2

05 10 11

그리고:

recv() #3

12 13 14

Application Message 하나가 여러 번의 recv()로 나뉜 것입니다.

이를 실무에서는 흔히:

Partial Read

Split Message

Message Fragmentation

등으로 표현합니다.

다만 정확히 구분하면 이것은 반드시 IP Fragmentation을 의미하는 것은 아닙니다.

TCP가 Byte Stream이기 때문에 Application에서 읽는 경계가 Message 경계와 일치하지 않는 현상입니다.


4장 반대로 여러 Message가 한 번에 들어올 수도 있다#

송신 측에서:

Message A

Message B

Message C

를 연속으로 전송했습니다.

수신 측의 한 번의 recv()에서는:

Message A | Message B | Message C

가 한꺼번에 들어올 수도 있습니다.

예:

A5 5A 00 03 01 02 03
A5 5A 00 02 10 20
A5 5A 00 01 FF

가 하나의 Buffer에 들어올 수 있습니다.

이런 현상을 흔히:

Packet Concatenation

Message Coalescing

Sticky Packet

등으로 표현합니다.

Sticky Packet은 특히 일부 개발 현장에서 자주 쓰는 비공식적인 표현입니다.


5장 send 한 번과 recv 한 번은 대응하지 않는다#

이 원칙을 반드시 기억해야 합니다.

send()
≠
recv()

즉:

send 1회
=
상대 recv 1회

가 아닙니다.

TCP가 보장하는 것은:

Byte 순서

손실된 TCP Segment의 재전송

중복 제거

등이지 Application Message의 경계가 아닙니다.


6장 Packet, Segment, Message를 구분하자#

현장에서 모두 Packet이라고 부르기 때문에 혼란이 생깁니다.

조금 더 정확하게 나누면 다음과 같습니다.

Application Message
↓
TCP Byte Stream
↓
TCP Segment
↓
IP Packet
↓
Ethernet Frame

예를 들어 장비 Protocol의:

[A5 5A][LENGTH][COMMAND][DATA][CRC]

는 Application Message 또는 Protocol Frame입니다.

TCP 내부에서는 이것이 하나 이상의 TCP Segment에 나뉘어 전달될 수 있습니다.


7장 TCP Segment와 IP Fragmentation은 다른 문제다#

Fragmentation이라는 단어를 사용할 때 특히 주의해야 합니다.

TCP Application Message가 여러 recv()로 나뉘는 현상과 IP Fragmentation은 다릅니다.

Application Message 분할#

하나의 Message

↓

여러 recv()

TCP Segmentation#

TCP가 Byte Stream을 여러 TCP Segment로 나누어 전달합니다.

IP Fragmentation#

IP Packet이 MTU보다 커서 IP 계층에서 Fragment로 분할되는 현상입니다.

IPv4에서는 발생할 수 있으며, IPv6에서는 Router가 중간에서 IPv4 방식처럼 Fragmentation하지 않습니다.

즉 이 세 가지를 하나의 Fragmentation으로 묶어 이해하면 장애 분석이 어려워집니다.


8장 MTU는 Message 경계를 보장하지 않는다#

현장에서 다음과 같은 생각을 할 수 있습니다.

MTU가 1500 Byte니까
1500 Byte 이하 Message는 한 번에 들어오겠지?

그렇지 않습니다.

MTU는 Network Packet 크기에 영향을 주는 값이지 Application의 recv() 경계를 보장하는 값이 아닙니다.

Message가 100 Byte뿐이어도:

20 Byte
+
30 Byte
+
50 Byte

처럼 나뉘어 Application에 전달될 수 있습니다.

반대로 여러 작은 Message가 한 번의 recv()에 들어올 수도 있습니다.


9장 TCP는 왜 Message 경계를 기억하지 않을까#

TCP의 기본 모델은 단순합니다.

송신 Application:

A B C D E F G H

TCP는 이를:

연속된 Byte Stream

으로 취급합니다.

수신 Application은 결국:

A B C D E F G H

라는 순서대로 Byte를 받을 수 있지만:

AB | CD | EFGH

로 읽을지:

ABCDE | FGH

로 읽을지는 보장되지 않습니다.

따라서 Message 경계는 Application Protocol이 직접 정의해야 합니다.


10장 Message 경계를 만드는 대표적인 세 가지 방법#

TCP 위에서 Message를 구분하는 대표적인 방법은 다음과 같습니다.

고정 길이

Delimiter

Length Field

각각 장단점이 있습니다.


11장 고정 길이 방식#

모든 Message가 항상 16 Byte라고 하겠습니다.

[16 Bytes]
[16 Bytes]
[16 Bytes]

Parser는 Buffer에:

16 Byte 이상

이 쌓이면 하나를 꺼내면 됩니다.

장점:

구현 단순

Parsing 빠름

단점:

가변 Data 처리 비효율

작은 Data에도 고정 공간 필요

입니다.


12장 Delimiter 방식#

Text Protocol에서 흔하게 사용하는 방법입니다.

예:

STATUS,DEV01\r\n

여기서:

\r\n

이 Message 종료 표시입니다.

Buffer에서 CRLF를 찾으면 그 앞까지 하나의 Message로 처리합니다.

예:

OPEN,01\r\nSTATUS,01\r\n

이라면 두 Message로 나눕니다.


12.1 Delimiter가 Payload에 들어가면 어떻게 할까#

Payload에도 같은 문자가 들어갈 수 있다면 문제가 됩니다.

예:

MESSAGE
HELLO\nWORLD

Protocol이 \n을 종료 문자로 사용하는데 Data 안에도 \n이 들어가면 Message 경계를 잘못 판단할 수 있습니다.

그래서:

Escape

Quote

Encoding

Length Field

등의 규칙이 필요합니다.


13장 Length Field 방식#

Binary Protocol에서 매우 많이 사용하는 구조입니다.

예:

[A5 5A][00 05][PAYLOAD 5 Bytes][CRC]

구조:

Signature
A5 5A

Length
00 05

Payload
5 Bytes

CRC
2 Bytes

Parser는 먼저 Header와 Length를 읽습니다.

그 다음 필요한 Byte가 모두 들어올 때까지 기다립니다.


14장 Length 기반 Parser의 기본 흐름#

Byte 수신
↓
Buffer 저장
↓
Header 검색
↓
Length 읽기
↓
전체 Frame 길이 계산
↓
Buffer에 충분한 Byte가 있는가?
├─ No → 더 기다림
└─ Yes
    ↓
    Frame 추출
    ↓
    CRC 검증
    ↓
    처리

이것이 TCP 기반 장비 Protocol Parser의 가장 기본적인 구조입니다.


15장 한 Message가 두 번에 나뉘어 들어오는 경우#

Protocol:

A5 5A 00 05 10 11 12 13 14 CRC1 CRC2

첫 번째 recv():

A5 5A 00 05 10 11

Parser는 Length를 읽습니다.

Length = 5

하지만 아직 필요한 Byte가 모두 없습니다.

따라서 처리하지 않습니다.

Buffer 유지

↓

다음 recv 대기

두 번째:

12 13 14 CRC1 CRC2

가 들어오면 Buffer에 합칩니다.

그제야 하나의 완전한 Frame을 추출합니다.


16장 여러 Message가 한 번에 들어온 경우#

Buffer:

A5 5A 00 02 10 20 CRC CRC
A5 5A 00 03 30 40 50 CRC CRC

첫 번째 Frame을 추출합니다.

A5 5A 00 02 10 20 CRC CRC

그리고 처리된 부분만 Buffer에서 제거합니다.

남은 데이터:

A5 5A 00 03 30 40 50 CRC CRC

다시 Parser를 실행합니다.

따라서 일반적인 Parser는:

한 번 Frame 처리 후 끝

이 아니라 Buffer에 완성된 Frame이 남아 있는 동안 반복해서 추출합니다.


17장 한 번의 recv에서 여러 Frame을 모두 처리해야 한다#

구조를 단순화하면:

recv()
↓
Buffer 추가
↓
while 완전한 Frame 존재
    ↓
    Frame 추출
    ↓
    처리
    ↓
    다음 Frame 검사

입니다.

이 구조가 없으면:

Message A
Message B

가 한 번에 들어왔을 때 Message B를 잃어버릴 수 있습니다.


18장 Header 또는 Signature가 필요한 이유#

Binary Protocol에서는 다음처럼 특정 Pattern을 Message 시작 표시로 사용할 수 있습니다.

A5 5A

Stream:

11 22 77 A5 5A 00 05 ...

라면 Parser는:

11 22 77

을 불필요한 데이터로 보고:

A5 5A

부터 Frame Parsing을 시작할 수 있습니다.

이를 Synchronization이라고 볼 수 있습니다.


19장 Parser가 동기화를 잃는 경우#

정상:

A5 5A LEN DATA CRC

여야 하는데:

A5 XX LEN DATA CRC

처럼 Header 일부가 손상됐다고 하겠습니다.

Parser가 잘못된 위치에서 Length를 읽으면 이후 Stream 전체를 잘못 해석할 수도 있습니다.

따라서 잘못된 Frame을 발견하면 재동기화가 필요합니다.


20장 Resynchronization이란 무엇인가#

재동기화는 현재 Frame 해석을 포기하고 다음 정상 Header를 다시 찾는 과정입니다.

예:

77 11 35 A5 5A 00 05 ...
         ^^^^^

앞의:

77 11 35

를 버리고:

A5 5A

부터 다시 시작합니다.

이를 통해 하나의 깨진 Message 때문에 이후 모든 Message가 손상되는 것을 방지할 수 있습니다.


21장 Header 검색만으로는 충분하지 않다#

Payload 내부에도 우연히:

A5 5A

가 들어갈 수 있습니다.

그래서 Header를 발견했다고 바로 정상 Frame으로 인정하면 안 됩니다.

함께 확인합니다.

Header

Length

최대 Frame Size

CRC

Protocol Version

Command

등입니다.

여러 조건을 이용해 정상 Frame인지 검증해야 합니다.


22장 Length 값도 무조건 믿으면 안 된다#

잘못된 Frame에서 Length가:

65535

로 읽혔다고 하겠습니다.

Parser가:

65535 Byte가 올 때까지 기다린다.

고 하면 Memory가 계속 증가하거나 Connection이 사실상 멈출 수 있습니다.

따라서:

Maximum Frame Size

를 반드시 정해야 합니다.

예:

MAX_FRAME_SIZE = 4096

이고 Length가 이를 넘으면 Frame을 폐기하고 다시 동기화를 시도합니다.


23장 Buffer 크기도 제한해야 한다#

잘못된 Client가 계속 의미 없는 Data를 보낸다고 하겠습니다.

AAAA....
BBBB....
CCCC....

Header가 나오지 않는데 Buffer를 계속 쌓으면 Memory 사용량이 계속 증가합니다.

따라서:

Maximum Buffer Size

Read Timeout

Connection Timeout

같은 보호 장치가 필요합니다.


24장 TCP 자체가 Packet Loss를 Application에 그대로 보여주지는 않는다#

중요한 구분입니다.

TCP Segment가 Network에서 손실되면 TCP는 일반적으로 재전송을 통해 복구하려고 합니다.

Application에서는:

TCP Segment 하나가 없어졌다.

라고 직접 보는 것이 아니라:

Data 도착이 늦어짐

Connection Timeout

Connection Reset

등의 현상으로 보게 될 수 있습니다.

따라서 TCP 위에서 Application Message 일부가 정상적으로 전달된 Stream 중간에서 임의로 사라지는 것은 정상적인 TCP 동작 모델과는 다릅니다.


25장 TCP인데 CRC가 필요한가#

TCP 자체에도 오류 검출과 재전송 기능이 있습니다.

그렇다면 Application Protocol의 CRC가 필요 없을까요?

반드시 그렇지는 않습니다.

Application CRC는:

장비 내부 처리 오류

Serial-TCP Gateway 변환

Protocol Frame 자체 검증

저장·전달 과정 오류

Application 규격 호환성

등을 확인하는 목적으로 사용할 수 있습니다.

특히 기존 Serial Protocol을 TCP Gateway로 그대로 전달하는 시스템에서는 CRC Field가 유지되는 경우가 많습니다.


26장 주차관제 카메라 Event를 예로 들어보자#

LPR Camera가 Controller에 다음 Message를 보낸다고 하겠습니다.

[A5 5A]
[LENGTH]
[EVENT]
[PLATE]
[TIMESTAMP]
[CRC]

전체 Message가:

80 Bytes

입니다.

Controller의 첫 번째 recv()에는:

30 Bytes

만 들어왔습니다.

이때 Parser가 즉시:

Frame Error

를 발생시키면 안 됩니다.

아직 Message가 완전히 도착하지 않았기 때문입니다.


26.1 올바른 처리#

30 Bytes 수신
↓
Length 확인
↓
전체 Message 부족
↓
Buffer 유지
↓
추가 Data 수신
↓
80 Bytes 확보
↓
CRC 검증
↓
Event 처리

순서입니다.


27장 차량이 연속으로 들어오면 여러 Event가 붙어 올 수 있다#

차량 세 대가 빠르게 들어온다고 하겠습니다.

Camera:

EVENT 1

EVENT 2

EVENT 3

Controller의 한 번의 recv():

EVENT 1 + EVENT 2 + EVENT 3

일 수 있습니다.

Parser가 첫 Frame만 처리하고 Buffer를 초기화하면:

EVENT 2

EVENT 3

을 잃어버립니다.

따라서 처리한 Byte만 제거해야 합니다.


28장 TCP Parser의 핵심은 Buffer다#

잘 만든 Parser의 핵심은 간단합니다.

Network에서 받은 Data
↓
Buffer에 누적
↓
완전한 Message만 추출
↓
불완전한 Data는 남겨둠

즉:

recv 결과

를 Parsing하는 것이 아니라:

누적 Buffer

를 Parsing한다고 생각하는 편이 정확합니다.


29장 Python으로 간단한 Length 기반 Parser 만들기#

시험용 구조를 단순화하면 다음과 같습니다.

buffer = bytearray()

while True:
    data = recv_from_simulator()

    if not data:
        break

    buffer.extend(data)

    while True:
        header_pos = buffer.find(b"\xA5\x5A")

        if header_pos < 0:
            if len(buffer) > 1:
                del buffer[:-1]
            break

        if header_pos > 0:
            del buffer[:header_pos]

        if len(buffer) < 4:
            break

        payload_length = int.from_bytes(
            buffer[2:4],
            byteorder="big"
        )

        if payload_length > 4096:
            del buffer[0]
            continue

        frame_length = 4 + payload_length + 2

        if len(buffer) < frame_length:
            break

        frame = bytes(buffer[:frame_length])

        del buffer[:frame_length]

        payload = frame[4:4 + payload_length]
        received_crc = frame[-2:]

        if validate_crc(payload, received_crc):
            process_message(payload)

핵심은:

Data 부족
→ 기다림

Frame 완성
→ 추출

남은 Data
→ 그대로 유지

입니다.


30장 기존 예제에서 주의해야 할 점#

Length가 Payload 길이라고 가정한다면 다음처럼:

4 + length + 2

전체 Frame 길이를 계산해야 합니다.

즉:

Header 2 Byte
Length 2 Byte
Payload N Byte
CRC 2 Byte

입니다.

단순히:

len(buffer) < 4 + length

만 검사하고 CRC를 읽으면 Buffer 범위를 넘을 수 있습니다.

실제 Parser에서는 전체 Frame Length를 먼저 계산해야 합니다.


31장 Length Field의 의미부터 확인해야 한다#

다음 값:

00 05

가 있다고 하겠습니다.

이것이:

Payload 5 Bytes

라는 보장은 없습니다.

Protocol에 따라:

Payload만 포함

Command + Payload 포함

CRC 포함

전체 Frame 포함

일 수 있습니다.

Parser를 구현하기 전 제조사 Protocol 문서에서 Length 계산 범위를 확인해야 합니다.


32장 Endianness도 확인한다#

Length Byte:

00 10

을 Big Endian으로 읽으면:

16

입니다.

Little Endian으로 읽으면:

4096

입니다.

잘못 해석하면 Parser가 전혀 엉뚱한 크기의 Message를 기다리게 됩니다.

따라서:

Length Byte Order

를 반드시 확인합니다.


33장 Timeout으로 Message 경계를 판단하는 방식은 조심한다#

일부 Legacy Protocol에서는:

50ms 동안 Data가 없으면
Message 종료

처럼 Timing 기반으로 Frame을 구분하기도 합니다.

하지만 TCP에서는 Network Jitter 때문에 위험할 수 있습니다.

예:

Message 절반 수신
↓
일시적인 지연
↓
Timeout
↓
불완전한 Message를 하나로 판단

할 수 있기 때문입니다.

가능하다면 명확한 Length 또는 Delimiter 규칙을 사용하는 편이 안정적입니다.


34장 TCP 연결 상태와 Application Protocol 상태는 다르다#

Socket:

CONNECTED

라고 표시되어도 Application Protocol이 정상이라는 뜻은 아닙니다.

예:

TCP Connected

하지만

잘못된 Protocol Version

잘못된 Header

잘못된 Length

CRC Error

가 계속 발생할 수 있습니다.

따라서 Monitoring에서도:

TCP Connected

와:

Protocol Healthy

를 구분하는 것이 좋습니다.


35장 Heartbeat를 사용하면 Application 상태를 확인할 수 있다#

TCP Connection은 살아 있지만 상대 Application이 정상적으로 처리하지 못하는 경우도 있습니다.

Protocol에서:

PING

PONG

또는:

HEARTBEAT

를 사용할 수 있습니다.

예:

Controller
→ HEARTBEAT SEQ 15

Device
→ ACK 15

이렇게 하면 Application Level Communication 상태를 추가로 확인할 수 있습니다.


36장 TCP Keepalive와 Application Heartbeat는 다르다#

TCP Keepalive:

TCP Connection이 살아 있는지 확인

Application Heartbeat:

상대 Application이 Protocol에 정상 응답하는지 확인

입니다.

따라서 TCP Keepalive가 성공한다고 장비 업무 Logic이 정상이라고 단정할 수 없습니다.


37장 VLAN은 Parser 문제와 별개의 영역이다#

현장 Network에:

Camera VLAN

Controller VLAN

Management VLAN

이 있다고 하겠습니다.

VLAN 또는 Routing이 잘못되어 TCP Connection 자체가 만들어지지 않는다면 Parser 단계까지 가지 못합니다.

반대로 TCP Connection이 정상적으로 유지되고 실제 Byte가 들어오고 있다면 Parser 오류를 VLAN 문제로만 볼 수 없습니다.

장애를 계층별로 분리해야 합니다.


38장 Switch가 TCP 재전송을 하는 것은 아니다#

현장에서 흔히:

Switch가 TCP Packet을 다시 보내주나?

라는 질문이 나올 수 있습니다.

일반적인 Ethernet Switch는 TCP Connection과 TCP 재전송을 관리하지 않습니다.

TCP 재전송은 TCP Endpoint의 Transport Layer가 수행합니다.

즉:

Client TCP Stack
↔
Server TCP Stack

에서 관리합니다.

Switch는 주로 Ethernet Frame 전달 역할을 합니다.


39장 MTU와 MSS는 어디에 영향을 줄까#

MTU는 하나의 Network Packet에 실을 수 있는 크기에 영향을 줍니다.

TCP에서는 연결 과정에서 MSS를 이용해 TCP Payload 크기를 조절합니다.

하지만 중요한 것은:

MTU나 MSS가 Application Message 경계를 정의하지 않는다는 것입니다.

하나의 Application Message가 여러 TCP Segment에 들어갈 수도 있고 여러 Message의 Byte가 연속 Stream으로 전달될 수도 있습니다.


40장 Parser가 잘못된 Frame을 만났을 때 어떻게 할까#

대표적인 전략은:

Frame 폐기

Log 기록

재동기화

입니다.

예:

Header 정상

Length 정상

CRC 실패

라면 해당 Frame을 폐기하고 다음 Header를 찾을 수 있습니다.


40.1 잘못된 Message를 부분적으로 살리는 것은 조심한다#

손상된 Frame 일부를 임의로 복구하려고 하면 잘못된 업무 Data를 만들 수 있습니다.

특히:

차량번호

금액

장비 Command

Sensor 상태

와 같은 Data는 추측해서 복원해서는 안 됩니다.

Protocol이 별도의 Recovery 규칙을 제공하지 않는다면 검증에 실패한 Frame은 폐기하는 편이 안전할 수 있습니다.


41장 CRC가 실패했다고 다음 Byte를 모두 버리지 않는다#

Buffer:

[손상 Frame][정상 Frame][정상 Frame]

이라고 하겠습니다.

첫 Frame CRC가 실패했다고 Buffer 전체를 초기화하면 뒤의 정상 Frame도 잃어버립니다.

가능하면:

현재 Frame 폐기
↓
다음 Header 탐색
↓
정상 Frame 복원

하는 방식이 좋습니다.


42장 잘못된 Header가 계속 들어오면 DoS 문제가 될 수 있다#

공격자 또는 잘못된 장비가 다음처럼 계속 보낸다고 하겠습니다.

A5
A5
A5
A5
...

Parser가 무한정 Buffer를 검색하거나 계속 Memory를 늘리면 서비스가 느려질 수 있습니다.

따라서:

Maximum Buffer

Maximum Frame

Read Timeout

Connection Rate Limit

Parsing Error Limit

등을 둘 수 있습니다.


43장 CPU 사용량도 고려해야 한다#

Buffer가 매우 큰데 매번:

buffer.find(header)

를 처음부터 반복하면 CPU 비용이 증가할 수 있습니다.

대량 장비를 처리한다면:

State Machine

Ring Buffer

Incremental Parser

같은 구조를 고려할 수 있습니다.

다만 단순한 장비 몇 대를 다루는 시스템에서는 지나친 최적화보다 정확한 Parser 구현이 먼저입니다.


44장 State Machine으로 Parser를 구성할 수도 있다#

예:

WAIT_HEADER
↓
READ_LENGTH
↓
READ_PAYLOAD
↓
READ_CRC
↓
VALIDATE
↓
PROCESS

오류 발생:

ERROR
↓
RESYNC
↓
WAIT_HEADER

이 방식은 복잡한 Streaming Protocol에서 상태를 명확하게 관리하는 데 도움이 됩니다.


45장 Packet Capture를 보면 TCP Segment 경계가 보인다#

Wireshark 같은 도구에서는 TCP Segment 단위의 Packet을 볼 수 있습니다.

예:

Segment 1
A5 5A 00 10 01 02

Segment 2
03 04 05 06 ...

Segment 3
...

하지만 Application에서는 TCP Stack이 이를 순서대로 재조립해 Byte Stream으로 전달합니다.

따라서 Packet Capture에서 보이는 TCP Segment 경계와 Application Parser의 Message 경계를 동일하게 생각하면 안 됩니다.


46장 Wireshark의 Follow TCP Stream은 왜 유용할까#

Packet Capture에서는 여러 TCP Segment로 나뉘어 있는 Data를 볼 수 있습니다.

Follow TCP Stream 같은 기능을 사용하면 해당 Connection의 Data 흐름을 연속된 Stream 관점에서 확인할 수 있습니다.

Protocol 분석에서는:

TCP Segment 하나

보다:

전체 Byte Stream

을 보는 것이 Message 경계를 찾는 데 더 유용한 경우가 많습니다.


47장 로그에는 recv 단위보다 Message 단위를 남기는 것이 좋다#

좋지 않은 Log:

RX 10 Bytes
RX 5 Bytes
RX 28 Bytes

이것만으로는 실제 Message를 이해하기 어렵습니다.

더 좋은 Log:

RAW RX 43 Bytes

FRAME #128
Length = 18
Command = EVENT
CRC = OK

FRAME #129
Length = 17
Command = STATUS
CRC = OK

처럼 Raw Stream과 Parsed Frame을 함께 기록하는 것입니다.


48장 Timestamp도 두 종류로 볼 수 있다#

Network Data를 처음 받은 시점:

RECEIVED_AT

과 완전한 Application Message를 재조립한 시점:

FRAME_COMPLETED_AT

이 다를 수 있습니다.

정밀한 Latency 분석에서는 이 차이가 중요할 수 있습니다.


49장 개인정보가 포함된 Stream은 주의해서 Capture한다#

주차관제 Packet에는:

차량번호

카드 ID

회원 ID

입출차 시각

등이 들어갈 수 있습니다.

따라서 Packet Capture나 Raw Stream Log를 저장할 때는:

Masking

Encryption

Access Control

Retention Policy

를 적용해야 합니다.


50장 장애 증상별 판단표#

증상 우선 확인
TCP 연결은 정상인데 Event 누락 Stream Parser·Message Boundary
간헐적 Parsing Error Partial Read·Length·Buffer
여러 Event 중 첫 번째만 처리 Concatenated Message 처리
Length가 비정상적으로 큼 Endianness·손상 Frame
CRC가 연속 실패 Parsing Offset·Protocol·Data 손상
Buffer가 계속 증가 Header 미검출·Size Limit
연결 후 한동안 정상, 이후 Parser 실패 재동기화·Frame Boundary
Capture에는 Data가 있는데 Application은 없음 Parser·Buffer Logic
Connection 자체가 안 됨 IP·VLAN·Routing·Port·Service

51장 현장 진단 순서#

TCP 장비에서 Message가 정상적으로 처리되지 않는다면 다음 순서로 확인합니다.

1. IP 연결 가능한가?
↓
2. TCP Connection이 만들어지는가?
↓
3. 실제 Byte가 수신되는가?
↓
4. Protocol Header가 보이는가?
↓
5. Length를 올바르게 읽는가?
↓
6. 전체 Message가 Buffer에 있는가?
↓
7. 여러 Message가 붙어 있지는 않은가?
↓
8. CRC가 정상인가?
↓
9. Command Parsing이 정상인가?
↓
10. Application Logic이 처리하는가?

Network와 Protocol Parser 문제를 한꺼번에 섞지 않는 것이 중요합니다.


52장 흔히 하는 잘못된 판단#

52.1 send 한 번은 recv 한 번으로 들어온다#

TCP에서는 보장되지 않습니다.

52.2 TCP Packet 하나가 Application Message 하나다#

TCP는 Byte Stream이므로 Message Boundary를 보장하지 않습니다.

52.3 하나의 Message가 나뉘면 Network Packet Loss다#

Partial Read나 TCP Segmentation 때문일 수 있으며 반드시 손실은 아닙니다.

52.4 Fragmentation은 모두 같은 현상이다#

Application Message 분할, TCP Segmentation, IP Fragmentation을 구분해야 합니다.

52.5 MTU보다 작은 Message는 한 번에 recv된다#

보장되지 않습니다.

52.6 TCP를 사용하면 Application CRC는 무조건 필요 없다#

Protocol 요구사항에 따라 Application CRC를 사용할 수 있습니다.

52.7 TCP Connection이 살아 있으면 장비 Application도 정상이다#

Application Protocol이 멈췄을 수도 있습니다.

52.8 Switch가 TCP 재전송을 처리한다#

일반적인 Switch가 아니라 TCP Endpoint가 재전송을 관리합니다.


53장 Parser 구현 체크리스트#

□ TCP를 Byte Stream으로 처리하고 있는가?

□ recv 한 번을 하나의 Message라고 가정하지 않는가?

□ Receive Buffer를 가지고 있는가?

□ Header 또는 Message 시작 규칙을 확인했는가?

□ Length Field의 Endianness를 확인했는가?

□ Length가 무엇을 포함하는지 확인했는가?

□ Maximum Frame Size를 제한했는가?

□ Maximum Buffer Size를 제한했는가?

□ Message가 분할되어 들어와도 처리 가능한가?

□ 여러 Message가 한꺼번에 들어와도 처리 가능한가?

□ 한 Frame 처리 후 남은 Buffer를 유지하는가?

□ CRC 실패 후 재동기화할 수 있는가?

□ 잘못된 Header가 계속 들어올 때 보호 장치가 있는가?

□ TCP 연결 상태와 Protocol 상태를 구분하는가?

□ Raw Stream과 Parsed Frame을 모두 진단할 수 있는가?

54장 자기 점검#

54.1 TCP Stream과 Application Message의 가장 큰 차이는 무엇인가#

TCP는 Message 경계를 제공하지 않는 연속된 Byte Stream입니다.

Application은 별도의 Protocol 규칙으로 Message 경계를 만들어야 합니다.

54.2 하나의 Message가 여러 recv로 나뉘어 들어올 수 있는가#

가능합니다.

따라서 완전한 Message가 모일 때까지 Buffer에 누적해야 합니다.

54.3 여러 Message가 한 번의 recv에 들어올 수 있는가#

가능합니다.

따라서 Buffer 안에 완성된 Message가 여러 개 있는지 반복해서 검사해야 합니다.

54.4 Length 기반 Parser에서 가장 중요한 것은 무엇인가#

Length Field의 의미와 Endianness를 정확히 알고 비정상적으로 큰 Length를 제한하는 것입니다.

54.5 TCP 연결이 유지되면 Application 통신도 정상인가#

아닙니다.

Socket은 연결돼 있어도 Protocol Parser나 상대 Application이 비정상일 수 있습니다.


55장 이 글을 마치며#

TCP 장비 통신에서 가장 중요한 원칙은 다음 한 줄로 정리할 수 있습니다.

TCP는 Message가 아니라 Byte Stream을 전달합니다.

송신 Application이:

MESSAGE 1

MESSAGE 2

라고 보냈다고 해서 수신 Application이 동일한 경계로 받는 것은 아닙니다.

실제 수신은:

MES

SAGE 1MESSAGE

2

처럼 어떤 크기로든 나뉠 수 있습니다.

따라서 Application Protocol이:

Header

Length

Delimiter

Fixed Size

CRC

등을 사용해 Message 경계를 다시 찾아야 합니다.

전체 흐름은:

TCP Byte Stream
↓
Receive Buffer
↓
Header 탐색
↓
Length 확인
↓
완전한 Frame 대기
↓
Frame 추출
↓
CRC 검증
↓
Command Parsing
↓
Application 처리

로 이해하면 됩니다.

특히 다음 다섯 가지를 기억하면 됩니다.

TCP의 한 번의 send와 한 번의 recv는 서로 대응하지 않습니다.

하나의 Application Message는 여러 번의 recv로 나뉠 수 있고 여러 Message가 한 번의 recv에 합쳐질 수도 있습니다.

TCP Segmentation, Application Message 분할, IP Fragmentation은 서로 다른 개념입니다.

안정적인 Parser는 수신 데이터를 Buffer에 누적하고 완전한 Frame만 하나씩 추출해야 합니다.

TCP 연결 성공과 Application Protocol 정상 동작은 별개이므로 Connection 상태와 Message 처리 상태를 따로 관찰해야 합니다.

결국 TCP 기반 장비 통신에서 Packet Parsing을 이해한다는 것은 recv()로 들어온 데이터를 바로 Message라고 믿지 않는 데서 시작합니다.

네트워크가 전달한 Byte Stream에서 제조사가 정의한 Protocol 규칙을 이용해 원래 Message의 경계를 다시 복원하는 것이 핵심입니다.

이 페이지의 목차