
TCP/IP 4계층: OSI 7계층은 잊어라
학교에서는 OSI 7계층을 배우지만, 실제 인터넷은 TCP/IP 4계층으로 돌아갑니다. 이론과 실제 차이.

학교에서는 OSI 7계층을 배우지만, 실제 인터넷은 TCP/IP 4계층으로 돌아갑니다. 이론과 실제 차이.
내 서버는 왜 걸핏하면 뻗을까? OS가 한정된 메모리를 쪼개 쓰는 처절한 사투. 단편화(Fragmentation)와의 전쟁.

미로를 탈출하는 두 가지 방법. 넓게 퍼져나갈 것인가(BFS), 한 우물만 팔 것인가(DFS). 최단 경로는 누가 찾을까?

이름부터 빠릅니다. 피벗(Pivot)을 기준으로 나누고 또 나누는 분할 정복 알고리즘. 왜 최악엔 느린데도 가장 많이 쓰일까요?

매번 3-Way Handshake 하느라 지쳤나요? 한 번 맺은 인연(TCP 연결)을 소중히 유지하는 법. HTTP 최적화의 기본.

백엔드 서버를 만들면서 계속 마주치는 용어들이 있었다. "TCP 연결", "IP 주소", "HTTP 요청", "포트 번호". 이게 대체 뭐고, 어떻게 연결되는지 도통 그림이 안 그려졌다. 학교 다닐 때 잠깐 본 OSI 7계층은 너무 복잡했고, 실제로는 아무도 그 얘기를 안 했다. 그러다가 "실제로는 TCP/IP 4계층만 알면 된다"는 말을 듣고 뒤통수를 맞은 기분이었다.
결국 이거였다. 내가 필요한 건 이론으로만 남은 지식이 아니라, 지금 돌아가고 있는 인터넷의 실제 작동 원리였다. 그래서 TCP/IP 모델을 제대로 파보기로 했다.
처음에 가장 헷갈렸던 건 "왜 모델이 두 개냐"는 거였다. OSI 7계층은 네트워크 책마다 나오는데, 실제 문서는 전부 TCP/IP 4계층으로 설명한다. "그냥 하나로 통일하면 안 되나?" 싶었다.
두 번째로 혼란스러웠던 건 계층 간의 관계다. "Application Layer에서 만든 데이터가 Transport Layer로 내려간다"는 말은 이해했는데, 그게 구체적으로 어떻게 "포장"되고 "전달"되는지 감이 안 왔다. 특히 "캡슐화(Encapsulation)"라는 단어는 객체지향에서만 쓰는 줄 알았는데 네트워크에도 나오니까 더 혼란스러웠다.
세 번째는 실제 코드와의 연결이었다. fetch() 함수 하나 부르면 끝인데, 이게 대체 언제 4개 계층을 다 거치는 건지, 내 코드 어디에서 계층이 바뀌는 건지 알 수가 없었다.
결정적으로 이해가 된 순간은 우편 배송 시스템에 비유하면서였다.
편지를 부칠 때를 생각해보면, 내가 하는 일(편지 쓰기)과 우체국이 하는 일(분류·배송)과 배달원이 하는 일(최종 전달)이 완전히 분리돼 있다. 나는 우체국 내부의 분류 시스템을 몰라도 되고, 배달 트럭의 엔진 구조를 몰라도 편지를 보낼 수 있다. 각 단계는 자기 일만 하고, 윗 단계한테 "이거 포장해서 넘길게요" 하면 끝이다.
TCP/IP 모델도 똑같다는 걸 깨달았다.
각 계층은 바로 위·아래 계층하고만 대화하고, 건너뛰어서 대화하지 않는다. 이게 계층화(Layering)의 핵심이었다. 덕분에 Application 개발자는 TCP 내부 구현을 몰라도 되고, 라우터 제조사는 HTTP가 뭔지 몰라도 된다.
OSI 7계층 모델은 1970년대 후반 ISO(국제표준화기구)가 "완벽한 네트워크 표준"을 만들겠다며 설계했다. Session Layer, Presentation Layer처럼 이론적으로는 깔끔하게 분리된 계층들이었다. 문제는 너무 복잡했고, 표준 회의만 계속하다가 실제 구현은 늦어졌다는 점이다.
반면 TCP/IP는 1960년대 말 미 국방부 DARPA가 "일단 되게 만들자"는 실용주의로 접근했다. 완벽한 분리보다는 "실제로 작동하는 것"이 중요했다. 그래서 OSI의 5(Session), 6(Presentation), 7(Application)을 하나로 합쳐버렸다. 대부분의 애플리케이션은 세션 관리와 데이터 표현을 자체적으로 처리하니까, 굳이 별도 계층으로 나눌 필요가 없었다.
결과적으로 TCP/IP가 먼저 실제 인터넷에 깔렸고, OSI는 "이론적 참고 모델"로만 남았다. 지금도 OSI 7계층을 배우는 이유는, 네트워크를 개념적으로 이해하기에는 좋은 프레임워크이기 때문이다. 하지만 실제 구현은 TCP/IP 4계층이 표준이다.
가장 아래 계층. 물리적인 비트 전송을 담당한다.
여기서는 Ethernet, Wi-Fi, PPP 같은 프로토콜들이 작동한다. 랜선에 전기 신호를 흘리거나, 무선으로 전파를 쏘거나, 광케이블로 빛을 쏘는 것이 이 계층의 일이다. MAC 주소(물리 주소)도 여기서 사용된다.
비유하자면, 편지를 실어 나르는 "도로"다. 도로가 흙길인지 고속도로인지(Ethernet vs Wi-Fi)에 따라 속도는 다르지만, 윗 계층은 그걸 신경 쓰지 않는다. 그냥 "아래로 내려보내면 물리적으로 전달되겠지" 하고 맡긴다.
개발자로서 직접 건드릴 일은 거의 없다. 운영체제와 네트워크 드라이버가 알아서 처리해준다. 다만 패킷 캡처 툴(tcpdump, Wireshark)을 쓸 때 이 계층의 Ethernet Frame을 볼 수 있다.
네트워크를 "넘나드는" 계층. 여기서 IP(Internet Protocol)가 작동한다.
IP의 핵심 역할은 라우팅(Routing)이다. 출발지 IP 주소(예: 192.168.0.5)에서 목적지 IP 주소(예: 8.8.8.8)까지 어떤 경로로 갈지 결정한다. 인터넷은 수천만 개의 라우터로 연결된 거대한 네트워크인데, 각 라우터는 "다음 홉(Next Hop)"을 결정하며 패킷을 릴레이 방식으로 전달한다.
비유하자면, 택배 상자에 "서울 강남구 XX동"이라는 주소를 쓰는 것이다. 중간 집배소(라우터)들은 이 주소를 보고 "이건 서울 방면이니까 저쪽 트럭에 실어야지" 하고 판단한다.
여기서 중요한 개념이 NAT(Network Address Translation)다. 집에 있는 여러 기기(노트북, 휴대폰, TV)가 모두 사설 IP(192.168.x.x)를 쓰는데, 실제 인터넷으로 나갈 때는 공인 IP 하나로 "변환"된다. 이때 포트 번호를 같이 바꾸는 걸 NAPT(Network Address Port Translation) 또는 PAT(Port Address Translation)라고 부른다.
예를 들어, 내 노트북(192.168.0.10:5000)과 휴대폰(192.168.0.20:6000)이 동시에 구글에 접속하면, 공유기는 이걸 공인 IP(예: 203.x.x.x:10001, 203.x.x.x:10002)로 바꿔서 내보낸다. 응답이 돌아오면 다시 원래 사설 IP로 되돌려준다. 이게 바로 "집에서 공유기 하나로 여러 기기를 쓸 수 있는" 이유다.
"어떻게" 데이터를 전달할지 결정하는 계층. TCP와 UDP 두 가지가 대표적이다.
TCP(Transmission Control Protocol)는 "신뢰성" 중심이다. 3-way handshake로 연결을 맺고, 패킷 순서를 보장하고, 손실되면 재전송한다. 웹 브라우징(HTTP/HTTPS), 이메일(SMTP), 파일 전송(FTP) 같은 곳에서 쓴다. "이 편지는 반드시 도착해야 해!"라는 상황에 적합하다.
UDP(User Datagram Protocol)는 "속도" 중심이다. 연결 과정 없이 그냥 던지고, 도착 확인도 안 한다. 대신 빠르다. 실시간 스트리밍(YouTube, Netflix), 온라인 게임, DNS 조회 같은 곳에서 쓴다. "몇 개 패킷 손실돼도 괜찮아, 어차피 다음 프레임 올 거야"라는 상황에 적합하다.
여기서 포트 번호(Port Number)가 등장한다. IP 주소는 "어느 컴퓨터"를 지정하고, 포트 번호는 "그 컴퓨터의 어느 프로그램"을 지정한다. 예를 들어:
142.250.207.46:443 = 구글 서버의 HTTPS 프로그램192.168.0.10:3000 = 내 노트북의 개발 서버포트 번호 덕분에 한 컴퓨터에서 여러 네트워크 프로그램이 동시에 돌 수 있다.
사용자가 실제로 쓰는 프로그램이 작동하는 계층.
HTTP/HTTPS(웹), SMTP(이메일 전송), IMAP/POP3(이메일 수신), FTP(파일 전송), SSH(원격 접속), DNS(도메인 조회) 등이 모두 여기 속한다.
개발자가 fetch('https://api.example.com/users')를 부르면, 이게 Application Layer에서 시작한다. HTTP 요청 메시지를 만들고, 그걸 아래 계층으로 내려보낸다.
OSI 모델에서는 Session Layer(연결 관리), Presentation Layer(암호화·압축·인코딩)를 따로 뗐지만, TCP/IP에서는 이 모든 걸 Application Layer에 때려 넣었다. HTTPS는 자체적으로 TLS 암호화를 하고, HTTP/2는 자체적으로 압축을 한다. 별도 계층으로 분리할 필요가 없다는 실용주의다.
TCP/IP 모델의 핵심은 캡슐화(Encapsulation)다. 각 계층은 윗 계층에서 받은 데이터를 "짐"으로 취급하고, 자기 계층의 헤더를 붙인다.
[Application Data]
↓ (Transport Layer가 TCP 헤더 추가)
[TCP Header | Application Data] ← TCP Segment
↓ (Internet Layer가 IP 헤더 추가)
[IP Header | TCP Header | Application Data] ← IP Packet
↓ (Network Interface Layer가 Ethernet 헤더·트레일러 추가)
[Eth Header | IP Header | TCP Header | Application Data | Eth Trailer] ← Ethernet Frame
비유하자면, 편지를 봉투에 넣고(Transport), 그 봉투를 박스에 넣고(Internet), 박스를 택배 차에 싣는(Network Interface) 것과 같다. 각 단계에서 "포장"이 추가된다.
받는 쪽에서는 정반대로 역캡슐화(Decapsulation)가 일어난다. Ethernet 헤더를 벗기고, IP 헤더를 벗기고, TCP 헤더를 벗기고, 최종적으로 원본 데이터를 꺼낸다.
터미널에서 curl https://example.com을 치면 무슨 일이 일어날까?
curl이 HTTP 요청 메시지를 만든다.
GET / HTTP/1.1
Host: example.com
User-Agent: curl/7.88.1
Accept: */*
이게 Application Layer의 데이터다. 이제 이걸 아래 계층으로 내려보낸다.
2단계: Transport Layer운영체제의 TCP 스택이 받아서 TCP 헤더를 붙인다.
[SRC PORT: 54321 | DST PORT: 443 | SEQ: 1000 | ACK: 0 | FLAGS: SYN | ... | HTTP Request]
이게 TCP Segment다.
3단계: Internet Layer운영체제의 IP 스택이 IP 헤더를 붙인다.
[SRC IP: 192.168.0.10 | DST IP: 93.184.216.34 | TTL: 64 | PROTOCOL: TCP | ... | TCP Segment]
이게 IP Packet이다.
4단계: Network Interface Layer네트워크 드라이버가 Ethernet 헤더와 트레일러를 붙인다.
[SRC MAC: AA:BB:CC:DD:EE:FF | DST MAC: 11:22:33:44:55:66 | TYPE: IPv4 | IP Packet | CRC]
이게 Ethernet Frame이고, 실제로 랜선이나 Wi-Fi로 전송되는 최종 형태다.
이후 과정Frame은 공유기(NAT 라우터)를 거치면서 Source IP가 공인 IP로 바뀌고, 여러 중간 라우터들을 홉-바이-홉으로 거쳐서 example.com 서버에 도착한다. 서버는 역캡슐화를 해서 최종적으로 HTTP 요청을 받아낸다.
보통은 fetch()나 axios 같은 고수준 라이브러리를 쓰지만, 더 아래 계층인 TCP 소켓을 직접 다룰 수도 있다.
const net = require('net');
// TCP 클라이언트 생성 (Transport Layer 직접 제어)
const client = net.createConnection({ port: 80, host: 'example.com' }, () => {
console.log('TCP 연결 성공!');
// HTTP 요청 메시지를 직접 작성 (Application Layer)
client.write('GET / HTTP/1.1\r\n');
client.write('Host: example.com\r\n');
client.write('Connection: close\r\n');
client.write('\r\n');
});
// 응답 받기
client.on('data', (data) => {
console.log('받은 데이터:', data.toString());
});
client.on('end', () => {
console.log('연결 종료');
});
이 코드는 net.createConnection()으로 Transport Layer의 TCP 연결을 직접 만든다. 그리고 client.write()로 Application Layer의 HTTP 메시지를 직접 보낸다.
보통은 HTTP 라이브러리가 이 과정을 다 숨겨주지만, 여기서는 계층 분리가 명확하게 드러난다. TCP는 "신뢰성 있는 바이트 스트림"만 제공할 뿐, HTTP가 뭔지는 모른다. HTTP 메시지 포맷(GET / HTTP/1.1\r\n)은 Application Layer의 책임이다.
실제 패킷을 만드는 건 운영체제가 하지만, 개념을 이해하기 위해 Python으로 "가짜 패킷"을 만들어보자.
# 각 계층이 헤더를 추가하는 과정 시뮬레이션
def application_layer(data):
"""Application Layer: HTTP 요청 생성"""
http_request = f"GET /api/users HTTP/1.1\r\nHost: api.example.com\r\n\r\n{data}"
print(f"[Application] Data: {http_request[:50]}...")
return http_request
def transport_layer(data):
"""Transport Layer: TCP 헤더 추가"""
tcp_header = "[TCP: SRC=5000 DST=443 SEQ=1000]"
segment = tcp_header + data
print(f"[Transport] Segment: {segment[:60]}...")
return segment
def internet_layer(data):
"""Internet Layer: IP 헤더 추가"""
ip_header = "[IP: SRC=192.168.0.10 DST=93.184.216.34]"
packet = ip_header + data
print(f"[Internet] Packet: {packet[:70]}...")
return packet
def network_interface_layer(data):
"""Network Interface Layer: Ethernet 헤더 추가"""
eth_header = "[ETH: SRC_MAC=AA:BB:CC DST_MAC=11:22:33]"
frame = eth_header + data
print(f"[Network Interface] Frame: {frame[:80]}...")
return frame
# 실행
original_data = "user_id=123"
app_data = application_layer(original_data)
transport_data = transport_layer(app_data)
internet_data = internet_layer(transport_data)
final_frame = network_interface_layer(internet_data)
print("\n최종 전송 데이터:")
print(final_frame[:100] + "...")
출력 결과:
[Application] Data: GET /api/users HTTP/1.1
Host: api.example.com...
[Transport] Segment: [TCP: SRC=5000 DST=443 SEQ=1000]GET /api/users HTTP/1.1...
[Internet] Packet: [IP: SRC=192.168.0.10 DST=93.184.216.34][TCP: SRC=5000 DST=...
[Network Interface] Frame: [ETH: SRC_MAC=AA:BB:CC DST_MAC=11:22:33][IP: SRC=192.168.0.10 DST=...
최종 전송 데이터:
[ETH: SRC_MAC=AA:BB:CC DST_MAC=11:22:33][IP: SRC=192.168.0.10 DST=93.184.216.34][TCP: SRC=5000...
각 함수가 하나의 계층을 대표하고, 위에서 아래로 내려가면서 헤더가 점점 추가되는 모습이 보인다. 실제로는 바이너리 형태지만, 개념은 똑같다.
이론만으로는 와닿지 않으니, 실제 패킷을 캡처해보자. Wireshark(패킷 분석 툴)를 설치하고, curl https://example.com을 실행하면서 캡처를 켜면 다음과 같은 구조가 보인다.
Frame 1: 66 bytes on wire
Ethernet II
Destination: 11:22:33:44:55:66
Source: AA:BB:CC:DD:EE:FF
Type: IPv4 (0x0800)
Internet Protocol Version 4
Source: 192.168.0.10
Destination: 93.184.216.34
Protocol: TCP (6)
Transmission Control Protocol
Source Port: 54321
Destination Port: 443
Flags: SYN
[Application Data: TLS Client Hello]
실제로 네 계층이 명확하게 분리돼 있다.
Wireshark에서 각 계층을 펼쳐보면 헤더의 모든 필드(TTL, Sequence Number, Checksum 등)를 볼 수 있다. 이게 바로 "책에서만 보던 이론"이 실제로 작동하는 모습이다.
각 계층이 독립적이라서, 한 계층만 바꿔도 나머지는 그대로 쓸 수 있다.
Application 개발자는 TCP가 어떻게 재전송을 하는지 몰라도 된다. 그냥 "믿고 보내면 도착한다"는 인터페이스만 쓰면 된다. 마찬가지로 TCP 개발자는 IP가 어떻게 라우팅하는지 몰라도 되고, IP 개발자는 Ethernet이 어떻게 전기 신호를 보내는지 몰라도 된다.
계층별로 표준 프로토콜이 정해져 있어서, 서로 다른 회사의 장비도 호환된다. Apple 노트북과 Samsung 스마트폰과 Cisco 라우터가 모두 TCP/IP를 따르기 때문에, 서로 통신할 수 있다.
실제로는 TCP/IP 4계층만 쓰는데, 왜 학교에서는 OSI 7계층을 가르칠까?
답은 간단하다. 개념적 이해에는 OSI가 더 좋기 때문이다.
OSI는 각 계층의 역할을 더 세밀하게 나눴다. Session Layer(연결 관리), Presentation Layer(데이터 포맷 변환), Application Layer(실제 애플리케이션 로직)를 구분하면, "암호화는 어디서 일어나지?", "연결 상태는 누가 관리하지?" 같은 질문에 답하기 쉽다.
또한 네트워크 엔지니어들 사이에서는 "Layer 3 스위치", "Layer 7 로드밸런서" 같은 용어를 쓴다. 이건 OSI 모델 기준이다. 그래서 두 모델을 모두 알아두면, 실제 대화에서 혼란이 줄어든다.
결론적으로, OSI는 배우기 위한 모델, TCP/IP는 쓰기 위한 모델이라고 받아들였다.
graph LR
subgraph OSI["OSI 7 Layers"]
O7[7. Application]
O6[6. Presentation]
O5[5. Session]
O4[4. Transport]
O3[3. Network]
O2[2. Data Link]
O1[1. Physical]
end
subgraph TCP["TCP/IP 4 Layers"]
T4[4. Application]
T3[3. Transport]
T2[2. Internet]
T1[1. Network Interface]
end
O7 --> T4
O6 --> T4
O5 --> T4
O4 --> T3
O3 --> T2
O2 --> T1
O1 --> T1
OSI의 5·6·7계층이 TCP/IP의 Application Layer로 합쳐졌고, OSI의 1·2계층이 TCP/IP의 Network Interface Layer로 합쳐졌다. 중간 계층(Transport, Network/Internet)은 거의 동일하다.
TCP/IP 4계층 모델을 이해하고 나니, 이전에 막연하게 쓰던 개념들이 다 연결됐다.
그리고 curl 하나 치는 순간, 내 데이터가 네 계층을 순차적으로 거쳐서 "포장"되고, 인터넷을 건너고, 서버에서 "포장 해제"되는 과정이 눈에 그려진다.
결국 핵심은 계층 분리(Layering)와 캡슐화(Encapsulation)였다. 각 계층은 자기 일만 하고, 윗 계층의 데이터를 "짐"으로 취급하며 자기 헤더를 붙인다. 이 단순한 원리로 전 세계 인터넷이 돌아간다.
OSI 7계층은 이론으로만 남은 지식인 줄 알았는데, 개념 이해에는 여전히 유용했다. 하지만 실제 코드를 짜고 패킷을 분석할 때는 TCP/IP 4계층이 훨씬 직관적이다. 이론과 실제 차이를 이해하게 된 순간이었다.