전송 계층, 호스트를 넘어, 정확한 프로세스에 데이터를 전달하는 법
지난번에 우리는 Network layer(IP)에 대해 깊이 파고들었다. 수많은 라우터를 거쳐 패킷이 목적지 호스트(컴퓨터)에 도착하는 과정은 여러 물류센터를 거쳐 택배가 아파트 단지 앞까지 도착하는 것과 같았다.
하지만 여기서 이런 의문이 든다. "아파트 단지(IP)까지 도착한 건 알겠는데, 이 패킷이 101동 202호의 철수(특정 앱)에게 가야 하는지, 505호의 영희(다른 앱)에게 가야 하는지는 누가 결정하지?"
컴퓨터 안에는 웹 브라우저, 카카오톡, 이메일 등 수많은 프로세스가 동시에 실행되고 있다. IP 주소만으로는 이 중 누구에게 데이터를 줘야 할지 알 수 없다. 여기서 바로 Transport Layer(전송 계층)의 역할이 시작된다는 것을 깨달았다. 오늘은 이에 관해 UDP, TCP, 신뢰성을 확보하기 위한 각종 장치들에 대해 정리해보고자 한다.
| Introduction |
1. Transport Layer: Process-to-Process Communication
Network layer가 'Host-to-Host' 통신을 담당한다면, Transport layer는 'Process-to-Process' 통신을 담당한다. 물리적인 기계 사이의 연결을 넘어, 실제 실행 중인 프로그램(Process) 간의 논리적인 연결을 만들어주는 것이다.
| Process to Process |
이 계층의 핵심 기능은 크게 세 가지로 요약할 수 있었다.
주소 지정(Addressing): 누구에게 보낼 것인가? (Port Number)
캡슐화(Encapsulation): 데이터를 어떻게 포장할 것인가?
다중화/역다중화(Multiplexing/Demultiplexing): 여러 프로세스의 데이터를 어떻게 관리할 것인가?
2. Addressing
IP 주소만으로는 부족했다. 프로세스를 식별하기 위한 새로운 식별자가 필요했고, 그것이 바로 포트 번호(Port Number)다.
| Addressing |
포트 번호의 범위: 16비트 정수를 사용하여 0부터 65,535까지 존재한다.
Well-known (0~1,023): HTTP(80), DNS(53) 등 전 세계적으로 약속된 서버용 포트.
Registered (1,024~49,151): 특정 용도로 등록된 포트.
Dynamic/Private (49,152~65,535): 클라이언트가 임시로 사용하는 포트(Ephemeral port).
서버를 개발할 때, 우리는 보통 Well-known 포트를 리스닝하고 있고, 클라이언트는 OS로부터 임시 포트를 할당받아 통신을 시작한다. 이 과정에서 소켓 주소(Socket Address)라는 개념이 등장한다.
Socket Address = IP Address + Port Number
결국 인터넷 상에서 유니크한 통신 끝점(Endpoint)을 정의하려면 IP와 Port가 모두 필요하다는 결론에 도달했다. IP가 '집 주소'라면, Port는 '방 번호'인 셈이다.
3. Multiplexing과 Demultiplexing
전송 계층을 공부하며 흥미롭게 본 부분은 Multiplexing(다중화)과 Demultiplexing(역다중화)의 개념이었다.
| Multiplexing/Demultiplexing |
- Multiplexing (Sender): 여러 애플리케이션(프로세스)에서 보낸 데이터를 전송 계층이 받아 헤더를 붙여 하나의 링크로 내보내는 과정.
Demultiplexing (Receiver): 도착한 데이터들의 헤더(포트 번호)를 확인하여, 올바른 프로세스의 "우편함"에 넣어주는 과정.
마치 우체국에서 여러 사람의 편지를 모아(Mux) 트럭에 싣고, 도착지에서 다시 각 수취인에게 분류(Demux)하는 과정과 완벽히 일치한다. (통신할 땐 역시 비유가 택배, 우체국 비유가 와닿는다)
4. UDP (User Datagram Protocol)
전송 계층의 프로토콜은 크게 TCP와 UDP로 나뉜다. 그중 먼저 살펴본 것은 UDP다. UDP는 게으른 프로토콜이 아니라, 미니멀리즘을 추구하는 프로토콜이다라고 보았던 기억이 난다.
| UDP |
UDP는 비연결형(Connectionless)이며 신뢰성이 없는(Unreliable) 프로토콜이다.
Connectionless: 연결 설정(Handshake) 과정이 없다. 그냥 보낸다.
Unreliable: 데이터가 잘 도착했는지 확인(ACK)하지 않는다. 순서가 뒤바뀌거나 유실되어도 신경 쓰지 않는다.
No Flow/Error Control: 흐름 제어나 오류 제어 메커니즘이 거의 없다.
"그럼 이걸 도대체 왜 쓰지?"라는 의문이 들 수 있다. 하지만 실시간 스트리밍 서비스의 사례를 통해 그 이유를 깨달았다. TCP의 복잡한 확인 과정은 지연(Latency)을 유발한다. 이처럼 실시간성이 중요한 서비스(영상이 초당 30 frame이라고 했을 때 1~2개의 frame drop 정도는 모를 수 있음), DNS처럼 단발성 질문/답변이 오가는 서비스에서는 '속도'와 '가벼움'이 신뢰성보다 우선순위가 높기 때문이다.
5. UDP Header 구조 분석
UDP가 얼마나 가벼운지는 헤더를 보면 명확해진다. TCP 헤더가 최소 20바이트인 반면(자세한 건 아래에), UDP 헤더는 고작 8바이트다.
0 7 8 15 16 23 24 31
+--------+--------+--------+--------+
| Source | Destination |
| Port Number | Port Number |
+--------+--------+--------+--------+
| Total | |
| Length | Checksum |
+--------+--------+--------+--------+
Source Port (16 bit): 보내는 프로세스의 포트.
Destination Port (16 bit): 받는 프로세스의 포트.
Total Length (16 bit): 헤더 + 데이터의 전체 길이.
Checksum (16 bit): 최소한의 오류 검출을 위한 필드.
재미있는 점은 Checksum이다. UDP는 신뢰성을 보장하지 않지만, 최소한 "이 데이터가 깨지지는 않았는지", 그리고 "Pseudoheader(IP 정보 포함)를 통해 엉뚱한 호스트에 온 것은 아닌지" 정도는 검사한다. 만약 Checksum이 맞지 않으면? 가차 없이 폐기한다. 재전송 요청 따위는 없다. 이것이 UDP의 쿨함이다.
6. TCP (Transmission Control Protocol)
앞서 우리는 보내고 잊어버리는(Fire and Forget) 쿨한 프로토콜, UDP를 만났다. 하지만 세상의 모든 일이 쿨할 수는 없다. 송금 내역이 중간에 사라지거나, 전송받은 파일의 순서가 뒤죽박죽 섞여버린다면 그것은 '쿨한' 게 아니라 '재앙'이다. 이걸 방지하기 위해 신뢰성을 기술적으로 구현한 게 바로 오늘 다룰 TCP(Transmission Control Protocol)다.
TCP의 핵심 특징
연결 지향(Connection-oriented): 데이터를 주고받기 전에 먼저 논리적인 연결(Logical Connection)을 맺는다. 물리적인 회선이 생기는 건 아니지만, 서로의 상태를 기억하는 가상의 파이프를 뚫는 셈이다.
신뢰성(Reliable): 데이터가 중간에 유실되거나 손상되면 재전송한다. 순서가 바뀌면 원래대로 재조립한다.
스트림 전달(Stream Delivery): 데이터를 덩어리가 아닌, 끝없이 이어지는 바이트의 흐름(Stream of bytes)으로 취급한다. 내부적으로는 데이터를 보내고 받기가 같은 속도로 이루어지지 않기 때문에, Buffer를 만들어 처리한다. (이후에 다룰 Flow, Error control을 위해 필수!)
7. Segment (세그먼트)
TCP는 응용 프로그램(Application)으로부터 끊임없이 흘러들어오는 바이트 스트림을 적절한 크기로 잘라서 포장하는데, 이 포장된 패킷을 세그먼트(Segment)라고 부른다.
| Segment |
UDP 헤더가 고작 8바이트였던 것에 비해, TCP 헤더는 최소 20바이트에서 최대 60바이트에 이른다. 이 헤더에는 신뢰성을 지키기 위한 수많은 장치들이 빼곡히 들어차 있다.
TCP 헤더 해부 (Anatomy of TCP Header)
여기서 내가 주목한 필드는 다음 세 가지다.
Sequence Number (순서 번호): "이 데이터가 전체 흐름에서 몇 번째 조각인지"를 나타낸다. 이를 통해 수신 측은 뒤죽박죽 도착한 패킷을 올바른 순서로 조립할 수 있다. 재미있는 건 0, 1, 2 순서가 아니라, 난수(ISN)로 시작하는 바이트 단위 번호를 쓴다는 점이다.
Acknowledgment Number (확인 응답 번호): "여기까지 잘 받았으니, 다음엔 이 번호부터 줘"라는 뜻이다. TCP는 누적 확인(Cumulative ACK) 방식을 사용하므로,
AckNum: 1001은 1000번까지 완벽하게 받았다는 영수증과 같다.Control Flags (6 bits): TCP의 상태와 목적을 알리는 신호등이다.
SYN: 연결 요청 (Synchronize)ACK: 응답 확인 (Acknowledgment)FIN: 연결 종료 (Finish)RST: 연결 재설정 (Reset)PSH: 데이터 즉시 전달 (Push)URG: 긴급 데이터 (Urgent)
8. 연결 시작: 3-Way Handshaking
TCP 통신의 가장 유명한 개념이자, 면접 단골 질문이기도 한 3-Way Handshake. 단순히 "3번 왔다 갔다 한다"라고 외우기보다, "왜 굳이 3번일까?"를 고민했던 흔적을 남긴다.
이 과정은 클라이언트와 서버가 서로 "나 너한테 데이터 보낼 건데, 내 시작 번호(Sequence Number)는 이거야. 준비됐니?"라고 확인하는 과정이다. 양쪽 모두 자신의 시작 번호(ISN)를 알려주고(SYN), 상대방의 번호를 확인했다는 대답(ACK)을 해야 하므로 최소 3번의 단계가 필요하다.
단계별 시나리오
Client → Server [SYN]:
"똑똑, 대화 좀 합시다. 제 대화 시작 번호는 1000번입니다."
Seq: 8000,SYN=1
Server → Client [SYN + ACK]:
"네, 들립니다(ACK 8001). 저도 준비됐어요. 제 시작 번호는 5000번입니다."
Seq: 15000,Ack: 8001,SYN=1, ACK=1
Client → Server [ACK]:
"확인했습니다(ACK 15001). 이제 진짜 데이터 보냅니다."
Seq: 8001,Ack: 15001,ACK=1
이 세 번의 과정이 끝나야만 비로소 ESTABLISHED 상태가 되고, 본격적인 데이터 전송이 시작된다.
생각해보기: 만약 2-way로 끝낸다면? 서버가 수락(SYN+ACK)을 보냈는데 그게 길에서 사라졌다면, 서버는 연결된 줄 알고 혼자 기다리지만 클라이언트는 연결 안 된 줄 알고 포기하는 상황이 발생할 수 있다. 3-Way는 이런 불확실성을 제거하는 최소한의 장치다.
데이터 전송 단계에서는 마찬가지로 Sequence Number와 Acknowledgement Number를 받은 데이터에 따라 변경하며 이루어지고, Control Flag는 PSH를 사용한다.
| Data Transfer |
9. 연결 종료: 4-Way Handshaking (Connection Termination)
Connection만큼이나 중요한 것이 Termination이다. TCP의 연결 종료는 시작보다 조금 더 신중하다. 이를 4-Way Handshake라고 한다. 한쪽이 데이터를 다 보냈다고 해서(FIN) 바로 끊어버리면, 반대쪽에서 아직 보내고 있던 데이터가 유실될 수 있기 때문이다.
이러한 상태를 Half-Close(반만 닫힘)라고 하는데, 마치 전화 통화에서 "나 할 말 다 했어"라고 말하고 나서도, 상대방이 "어, 나도 끊을게"라고 할 때까지 수화기를 들고 기다려주는 것과 같다.
종료 시나리오
Client → Server [FIN]: "저 이제 보낼 데이터 없어요. 끝낼게요."
Server → Client [ACK]: "알겠습니다. 잠시만요." (아직 서버가 보낼 데이터가 남았을 수 있음)
Server → Client [FIN]: (남은 데이터를 다 보내고) "저도 이제 끝났습니다. 안녕히 계세요."
Client → Server [ACK]: "네, 수고하셨습니다."
클라이언트는 마지막 ACK를 보내고 나서도 혹시나 서버가 못 들었을까 봐 잠시 대기하는 시간(TIME_WAIT)을 갖는다. 끝까지 책임을 다하는 TCP의 꼼꼼함이 돋보이는 대목이다.
이렇게 해서 우리는 UDP의 간결함과 TCP의 연결 지향적 특성을 이해했다. 3-Way Handshake를 통해 클라이언트와 서버 사이에는 보이지 않는 '신뢰의 파이프'가 뚫렸다.
하지만 파이프가 뚫렸다고 해서 무작정 물을 콸콸 쏟아부을 수는 없는 노릇이다. 받는 쪽의 양동이가 넘칠 수도 있고(Flow Control 이슈), 파이프 중간이 어딘가 새거나 막힐 수도 있으며(Error/Congestion 이슈), 너무 많은 물을 한꺼번에 보내다가 파이프가 터질 수도 있다. 다음은 그 정교한 메커니즘인 흐름 제어, 오류 제어, 그리고 혼잡 제어에 대해 정리하며 Transport Layer 학습을 마무리하고자 한다.
10. Flow Control: 상대방의 속도에 맞춰주기
데이터를 보내는 사람(Sender)과 받는 사람(Receiver)의 처리 속도는 다를 수밖에 없다. 나는 신나서 초당 100개의 메시지를 보내는데, 상대방은 초당 10개밖에 읽지 못한다면? 나머지 90개는 버퍼(Buffer)에 쌓이다가 결국 넘쳐서 사라질 것이다(Buffer Overflow).
TCP는 이를 막기 위해 Sliding Window라는 기법을 사용한다.
Sliding Window와 rwnd (Receive Window)
핵심은 간단하다. "내가 받을 수 있는 만큼만 보내!"라고 Receiver가 Sender에게 알려주는 것이다.
| Flow Control |
Send Window: Sender가 확인 응답(ACK)을 받지 않고도 한 번에 보낼 수 있는 데이터의 양.
rwnd (Receive Window Size): Receiver가 현재 자신의 버퍼에 남은 공간(여유분)을 TCP 헤더에 담아 보낸다. (총 버퍼의 크기 - 대기중인 bytes로 계산됨)
Sender는 이 rwnd 값을 보고, 딱 그만큼만 데이터를 보낸다. 만약 Receiver의 버퍼가 꽉 차서 rwnd=0을 보내면? Sender는 그 즉시 전송을 멈추고 기다린다. 이것이 바로 TCP의 Flow Control(흐름 제어)이다. 단순히 빠르기만 한 게 아니라, 상대방을 배려하는 '속도 조절'의 미학이 담겨 있다.
11. Error Control: 잃어버린 데이터를 되살리는 법
네트워크는 불완전하다. 패킷은 가다가 사라지기도 하고(Loss), 깨지기도 한다(Corrupted). TCP는 신뢰성을 보장해야 하므로, 이 오류들을 감지하고 복구해야 한다.
재전송(Retransmission)의 두 가지 트리거
TCP는 "데이터를 보냈는데 대답(ACK)이 없으면 안 간 것으로 간주"하고 다시 보낸다. 이때 재전송을 결정하는 기준은 크게 두 가지다.
RTO (Retransmission Time-Out):
타이머가 울릴 때까지 ACK가 안 오면 패킷이 유실된 것으로 판단하고 재전송한다.
이 RTO 시간은 고정된 게 아니라, 네트워크 상황(RTT)에 따라 유동적으로 변한다.
Lost acknowledgement 3 Duplicate ACKs (3개의 중복 ACK):
이건 좀 더 똑똑한 방법이다. 예를 들어 1, 2, 3, 4, 5번 패킷을 보냈는데, 2번이 사라졌다고 치자.
Receiver는 3, 4, 5번을 받을 때마다 "나 2번 기다리고 있어!"라며 계속 2번을 달라는 ACK를 보낸다.
Sender가 똑같은 ACK를 3번 연속 받으면, "아, 타임아웃은 아직 안 됐지만 2번이 확실히 없어졌구나"라고 판단하고 즉시 재전송한다. 이를 Fast Retransmission(빠른 재전송)이라고 한다.
12. Congestion Control: 도로 사정 살피기
Flow Control이 '상대방(Receiver)'을 배려하는 것이라면, Congestion Control(혼잡 제어)은 '네트워크(Network)' 전체를 배려하는 것이다.
나와 상대방의 컴퓨터가 아무리 좋아도, 그 사이를 잇는 인터넷 회선(라우터들)이 막히면 소용없다. 모두가 데이터를 콸콸 쏟아내면 인터넷 망 자체가 마비될 수 있다(Congestion Collapse). 이를 막기 위해 TCP는 cwnd (Congestion Window)라는 변수를 하나 더 사용하여 스스로 속도를 조절한다.
보낼 수 있는 데이터 양 = Min(rwnd, cwnd) (상대방이 받을 수 있는 양과 네트워크가 감당할 수 있는 양 중 작은 쪽을 따른다.)
TCP의 혼잡 제어 정책 (Congestion Policy)
교수님께서 보여주신 그래프는 마치 톱니바퀴 같았다. 올라갔다 뚝 떨어지고, 다시 올라가는 과정의 반복. 이 과정은 크게 세 단계로 나뉜다.
Slow Start (느린 시작):
Congestion Avoidance (혼잡 회피):
Congestion Detection & Recovery:
마치며
이렇게 Transport Layer를 정리해 보았다. UDP의 단순함도 매력적이지만, TCP가 보여주는 '연결을 맺고(Connection)', '상대방을 배려하며(Flow Control)', '전체 상황을 살피며(Congestion Control)', '실수를 바로잡는(Error Control)' 태도는 가히 삶의 지혜와도 밀접하게 맞닿아 있는 것 같아 인상적이었다.
단순한 프로토콜이 아니라, 복잡한 세상에서 신뢰를 구축하고 유지하는 시스템의 철학이었다. 이제 이 탄탄한 전송 계층 위에서 동작할 마지막 주제, 애플리케이션 계층(Application Layer)에 대해 알아볼 준비가 되었다.
추천글
[컴퓨터 네트워크] 데이터 통신의 기본 요소와 TCP/IP 5계층
[컴퓨터 네트워크] Data-link layer | Frameing, Error Control methods, MAC Protocols
[컴퓨터 네트워크] Physical Layer | 데이터 전송 속도의 한계, 아날로그의 디지털화 & 디지털의 아날로그화
[컴퓨터네트워크] Network Layer: Data transfer | IPv4 datagram, Fragmentation, ICMP, Mobile IP, IPv6