네트워크 TCP/IP 완벽 가이드: 웹 개발자가 알아야 할 네트워크 기초
1. 네트워크 기초 개념
네트워크(Network)란 두 대 이상의 컴퓨터가 데이터를 주고받을 수 있도록 연결된 통신 시스템을 의미한다. 현대 백엔드 개발에서 네트워크 지식은 필수적이다. API 서버를 개발하고, 마이크로서비스 간 통신을 구현하고, 성능 최적화를 하기 위해서는 네트워크의 동작 원리를 정확히 이해해야 한다.
LAN, WAN, 인터넷
LAN(Local Area Network)은 건물이나 캠퍼스 내부와 같은 소규모 지역을 연결하는 네트워크이다. 이더넷(Ethernet)이나 Wi-Fi가 대표적인 LAN 기술이다. 전송 속도가 빠르고(1Gbps~10Gbps), 오류율이 낮다.
WAN(Wide Area Network)은 도시, 국가, 대륙 간을 연결하는 대규모 네트워크이다. ISP(Internet Service Provider)가 제공하는 회선을 통해 LAN을 서로 연결한다. LAN에 비해 전송 속도가 느리고 비용이 높다.
인터넷(Internet)은 전 세계의 네트워크를 TCP/IP 프로토콜로 연결한 글로벌 네트워크이다. “네트워크의 네트워크”라고 불리며, 수십억 대의 장치가 연결되어 있다.
1
2
3
4
5
6
7
8
9
10
┌────────────┐ WAN ┌────────────┐
│ LAN A │◄───────────────────►│ LAN B │
│ (서울 사무실) │ ISP 회선 │ (부산 사무실) │
│ PC1 PC2 │ │ PC3 PC4 │
│ 서버1 │ │ 서버2 │
└────────────┘ └────────────┘
▲ ▲
│ 인터넷 │
└────────────────────────────────────┘
수많은 WAN/LAN 연결
2. OSI 7계층 모델
OSI(Open Systems Interconnection) 7계층 모델은 ISO(국제표준화기구)에서 제정한 네트워크 통신의 표준 참조 모델이다. 네트워크 통신 과정을 7개의 계층으로 나누어 각 계층의 역할과 책임을 명확히 구분한다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
┌─────────────────────────────────────────────────────────────┐
│ 계층 │ 이름 │ 역할 │ 프로토콜/장비 │
├──────┼───────────────┼───────────────────┼──────────────────┤
│ 7 │ 응용 계층 │ 사용자 인터페이스 │ HTTP, FTP, SMTP │
│ │ (Application) │ 네트워크 서비스 제공 │ DNS, SSH │
├──────┼───────────────┼───────────────────┼──────────────────┤
│ 6 │ 표현 계층 │ 데이터 형식 변환 │ JPEG, MPEG │
│ │ (Presentation)│ 암호화/복호화 │ SSL/TLS, ASCII │
├──────┼───────────────┼───────────────────┼──────────────────┤
│ 5 │ 세션 계층 │ 연결 관리 │ NetBIOS │
│ │ (Session) │ 동기화 │ RPC │
├──────┼───────────────┼───────────────────┼──────────────────┤
│ 4 │ 전송 계층 │ 종단간 신뢰성 보장 │ TCP, UDP │
│ │ (Transport) │ 흐름/오류 제어 │ 포트 번호 │
├──────┼───────────────┼───────────────────┼──────────────────┤
│ 3 │ 네트워크 계층 │ 라우팅, 논리적 주소 │ IP, ICMP, ARP │
│ │ (Network) │ 경로 결정 │ 라우터 │
├──────┼───────────────┼───────────────────┼──────────────────┤
│ 2 │ 데이터링크 계층 │ 물리적 주소(MAC) │ Ethernet, Wi-Fi │
│ │ (Data Link) │ 프레임 단위 전송 │ 스위치, 브리지 │
├──────┼───────────────┼───────────────────┼──────────────────┤
│ 1 │ 물리 계층 │ 비트 전송 │ 전기신호, 광신호 │
│ │ (Physical) │ 물리적 연결 │ 케이블, 허브 │
└──────┴───────────────┴───────────────────┴──────────────────┘
각 계층의 핵심 역할을 좀 더 상세히 살펴보자.
7계층 - 응용 계층(Application Layer)은 사용자가 직접 상호작용하는 네트워크 서비스를 제공한다. 웹 브라우저(HTTP), 이메일 클라이언트(SMTP/POP3/IMAP), 파일 전송(FTP), 도메인 이름 해석(DNS) 등이 이 계층에서 동작한다. 백엔드 개발자가 가장 많이 다루는 계층이다.
4계층 - 전송 계층(Transport Layer)은 종단 간(end-to-end) 통신을 담당한다. TCP는 연결 지향적이고 신뢰성 있는 전송을, UDP는 비연결형이고 빠른 전송을 제공한다. 포트 번호를 사용하여 같은 호스트 내에서 여러 프로세스를 구분한다.
3계층 - 네트워크 계층(Network Layer)은 패킷의 경로를 결정(라우팅)하고 IP 주소를 사용하여 논리적으로 네트워크를 구분한다. 라우터가 이 계층에서 동작하며, 서로 다른 네트워크를 연결한다.
2계층 - 데이터링크 계층(Data Link Layer)은 같은 네트워크 내에서 프레임 단위로 데이터를 전송한다. MAC 주소를 사용하여 장치를 식별하며, 스위치가 이 계층에서 동작한다.
데이터 캡슐화와 역캡슐화
데이터가 상위 계층에서 하위 계층으로 내려가면서 각 계층의 헤더가 추가되는 과정을 캡슐화(Encapsulation)라 하고, 수신 측에서 헤더를 제거하며 올라가는 과정을 역캡슐화(Decapsulation)라 한다.
1
2
3
4
5
6
7
8
9
10
11
12
송신측 (캡슐화) 수신측 (역캡슐화)
┌──────────┐ ┌──────────┐
│ Data │ 응용 계층 │ Data │
├──────────┤ ├──────────┤
│ H│ Data │ 전송 계층 (세그먼트) │ H│ Data │
├──────────┤ ├──────────┤
│H│H│ Data │ 네트워크 계층 (패킷) │H│H│ Data │
├──────────┤ ├──────────┤
│H│H│H│Data│T│ 데이터링크 계층 (프레임) │H│H│H│Data│T│
├──────────┤ ├──────────┤
│ 01101... │ 물리 계층 (비트) │ 01101... │
└──────────┘ └──────────┘
3. TCP/IP 4계층 모델
TCP/IP 모델은 실제 인터넷에서 사용되는 프로토콜 스택으로, OSI 7계층을 4개 계층으로 단순화한 것이다. 현대 네트워크의 사실상의 표준(de facto standard)이다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
┌──────────────────────────────┐
│ OSI 7계층 │ TCP/IP 4계층 │
├────────────────┼──────────────┤
│ 응용 계층 (7) │ │
│ 표현 계층 (6) │ 응용 계층 │ ← HTTP, DNS, FTP, SSH
│ 세션 계층 (5) │ │
├────────────────┼──────────────┤
│ 전송 계층 (4) │ 전송 계층 │ ← TCP, UDP
├────────────────┼──────────────┤
│ 네트워크 계층(3) │ 인터넷 계층 │ ← IP, ICMP, ARP
├────────────────┼──────────────┤
│ 데이터링크 (2) │ 네트워크 │
│ 물리 계층 (1) │ 인터페이스 계층│ ← Ethernet, Wi-Fi
└────────────────┴──────────────┘
4. IP 주소 체계
IPv4
IPv4는 32비트 주소 체계로, 약 43억 개의 주소를 제공한다. 8비트씩 4개로 나누어 점(.)으로 구분하여 표기한다. 예: 192.168.1.100
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
IP 주소 클래스:
┌─────────┬──────────────────┬────────────────────┬─────────────────┐
│ 클래스 │ 범위 │ 네트워크/호스트 │ 용도 │
├─────────┼──────────────────┼────────────────────┼─────────────────┤
│ A │ 0.0.0.0 ~ │ 8비트/24비트 │ 대규모 네트워크 │
│ │ 127.255.255.255 │ │ │
├─────────┼──────────────────┼────────────────────┼─────────────────┤
│ B │ 128.0.0.0 ~ │ 16비트/16비트 │ 중규모 네트워크 │
│ │ 191.255.255.255 │ │ │
├─────────┼──────────────────┼────────────────────┼─────────────────┤
│ C │ 192.0.0.0 ~ │ 24비트/8비트 │ 소규모 네트워크 │
│ │ 223.255.255.255 │ │ │
└─────────┴──────────────────┴────────────────────┴─────────────────┘
사설 IP 주소 대역:
- 10.0.0.0 ~ 10.255.255.255 (Class A)
- 172.16.0.0 ~ 172.31.255.255 (Class B)
- 192.168.0.0 ~ 192.168.255.255 (Class C)
서브넷 마스크와 CIDR
서브넷 마스크는 IP 주소에서 네트워크 부분과 호스트 부분을 구분하는 데 사용된다. CIDR(Classless Inter-Domain Routing)는 클래스 없이 유연하게 네트워크를 분할하는 방식이다.
1
2
3
4
5
6
7
8
9
10
11
12
IP 주소: 192.168.1.100
서브넷 마스크: 255.255.255.0
CIDR 표기: 192.168.1.100/24
네트워크 주소: 192.168.1.0 (AND 연산)
브로드캐스트: 192.168.1.255
사용 가능 호스트: 192.168.1.1 ~ 192.168.1.254 (254개)
/24 = 256개 IP (254개 호스트)
/25 = 128개 IP (126개 호스트)
/26 = 64개 IP (62개 호스트)
/32 = 1개 IP (단일 호스트)
IPv6
IPv4의 주소 고갈 문제를 해결하기 위해 등장한 128비트 주소 체계이다. 약 3.4 × 10^38개의 주소를 제공한다. 16비트씩 8개 그룹으로 나누어 콜론(:)으로 구분한다.
1
2
IPv6 예시: 2001:0db8:85a3:0000:0000:8a2e:0370:7334
축약형: 2001:db8:85a3::8a2e:370:7334
5. TCP vs UDP 비교
TCP (Transmission Control Protocol)
TCP는 연결 지향적(Connection-oriented) 프로토콜로, 신뢰성 있는 데이터 전송을 보장한다. 데이터가 순서대로 도착하며, 손실된 패킷은 재전송한다.
UDP (User Datagram Protocol)
UDP는 비연결형(Connectionless) 프로토콜로, 빠른 전송 속도를 제공하지만 신뢰성을 보장하지 않는다. 데이터의 순서 보장이나 재전송 메커니즘이 없다.
| 비교 항목 | TCP | UDP |
|---|---|---|
| 연결 방식 | 연결 지향적 (3-way handshake) | 비연결형 |
| 신뢰성 | 보장 (ACK, 재전송) | 보장하지 않음 |
| 순서 보장 | 보장 (Sequence Number) | 보장하지 않음 |
| 흐름 제어 | 있음 (슬라이딩 윈도우) | 없음 |
| 혼잡 제어 | 있음 | 없음 |
| 헤더 크기 | 20~60 바이트 | 8 바이트 |
| 속도 | 상대적으로 느림 | 빠름 |
| 용도 | HTTP, FTP, SMTP, SSH | DNS, DHCP, 스트리밍, 게임 |
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
// TCP 서버 예제
public class TcpServer {
public static void main(String[] args) throws Exception {
ServerSocket serverSocket = new ServerSocket(8080);
System.out.println("TCP 서버 시작 - 포트 8080");
while (true) {
Socket clientSocket = serverSocket.accept(); // 3-way handshake 완료 후
System.out.println("클라이언트 연결: " + clientSocket.getRemoteSocketAddress());
new Thread(() -> {
try (BufferedReader in = new BufferedReader(
new InputStreamReader(clientSocket.getInputStream()));
PrintWriter out = new PrintWriter(
clientSocket.getOutputStream(), true)) {
String request = in.readLine();
System.out.println("수신: " + request);
out.println("응답: " + request.toUpperCase());
} catch (IOException e) {
e.printStackTrace();
}
}).start();
}
}
}
// UDP 서버 예제
public class UdpServer {
public static void main(String[] args) throws Exception {
DatagramSocket socket = new DatagramSocket(9090);
System.out.println("UDP 서버 시작 - 포트 9090");
byte[] buffer = new byte[1024];
while (true) {
DatagramPacket receivePacket = new DatagramPacket(buffer, buffer.length);
socket.receive(receivePacket); // 연결 없이 바로 수신
String message = new String(receivePacket.getData(), 0, receivePacket.getLength());
System.out.println("수신: " + message);
// 응답 전송
byte[] responseData = message.toUpperCase().getBytes();
DatagramPacket sendPacket = new DatagramPacket(
responseData, responseData.length,
receivePacket.getAddress(), receivePacket.getPort()
);
socket.send(sendPacket);
}
}
}
6. TCP 3-way Handshake / 4-way Handshake
3-way Handshake (연결 설정)
TCP 연결을 수립할 때 클라이언트와 서버 간에 3번의 패킷 교환이 이루어진다.
1
2
3
4
5
6
7
8
9
10
11
12
클라이언트 서버
│ │
│ 1. SYN (seq=x) │
│ ──────────────────────────────────→ │ 서버: SYN_RECEIVED
│ │
│ 2. SYN + ACK (seq=y, ack=x+1) │
│ ←────────────────────────────────── │
│ │
│ 3. ACK (ack=y+1) │
│ ──────────────────────────────────→ │
│ │
│ 연결 수립 (ESTABLISHED) │
- SYN: 클라이언트가 서버에 연결 요청을 보낸다. 클라이언트의 초기 시퀀스 번호(ISN)를 포함한다.
- SYN+ACK: 서버가 클라이언트의 요청을 수락하고, 자신의 초기 시퀀스 번호와 함께 클라이언트의 SYN에 대한 확인 응답을 보낸다.
- ACK: 클라이언트가 서버의 응답에 대한 확인 응답을 보낸다. 이 시점부터 데이터 전송이 가능하다.
왜 2-way가 아닌 3-way인가? 양쪽 모두 데이터를 보내고 받을 수 있는 능력을 확인해야 하기 때문이다. SYN으로 “나는 보낼 수 있다”, ACK로 “나는 받을 수 있다”를 확인하므로 양방향 확인에 최소 3번이 필요하다.
4-way Handshake (연결 종료)
TCP 연결을 종료할 때는 4번의 패킷 교환이 이루어진다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
클라이언트 서버
│ │
│ 1. FIN (seq=u) │
│ ──────────────────────────────────→ │ 서버: CLOSE_WAIT
│ │
│ 2. ACK (ack=u+1) │
│ ←────────────────────────────────── │
│ 클라이언트: FIN_WAIT_2 │
│ │ (서버: 남은 데이터 전송)
│ 3. FIN (seq=w) │
│ ←────────────────────────────────── │
│ │
│ 4. ACK (ack=w+1) │
│ ──────────────────────────────────→ │ 서버: CLOSED
│ 클라이언트: TIME_WAIT │
│ (2MSL 대기 후 CLOSED) │
- FIN: 클라이언트가 연결 종료를 요청한다.
- ACK: 서버가 종료 요청을 확인한다. 아직 보낼 데이터가 남아 있을 수 있으므로 즉시 종료하지 않는다.
- FIN: 서버가 남은 데이터를 모두 전송한 후 연결 종료를 요청한다.
- ACK: 클라이언트가 서버의 종료 요청을 확인한다.
TIME_WAIT 상태가 필요한 이유는 두 가지이다. 첫째, 마지막 ACK가 손실될 경우 서버가 FIN을 재전송할 수 있는데, 이때 클라이언트가 이미 종료되면 서버가 영원히 대기하게 된다. 둘째, 이전 연결의 지연된 패킷이 새 연결에 영향을 주는 것을 방지한다.
7. TCP 흐름 제어 (Flow Control)
흐름 제어는 송신자가 수신자의 처리 능력을 초과하여 데이터를 보내지 않도록 조절하는 메커니즘이다.
슬라이딩 윈도우 (Sliding Window)
슬라이딩 윈도우는 TCP의 핵심 흐름 제어 메커니즘이다. 수신자가 자신의 수신 버퍼 크기(윈도우 크기)를 송신자에게 알려주면, 송신자는 그 크기만큼만 ACK 없이 연속으로 데이터를 전송한다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
윈도우 크기 = 4 (4개의 세그먼트를 ACK 없이 전송 가능)
송신 버퍼:
┌───┬───┬───┬───┬───┬───┬───┬───┬───┬───┐
│ 1 │ 2 │ 3 │ 4 │ 5 │ 6 │ 7 │ 8 │ 9 │10 │
└───┴───┴───┴───┴───┴───┴───┴───┴───┴───┘
✓ ✓ ┌──── 윈도우 ────┐
│ 3 │ 4 │ 5 │ 6 │ ← 전송 가능 범위
└─────────────────┘
ACK 3을 수신하면 윈도우가 오른쪽으로 슬라이딩:
┌───┬───┬───┬───┬───┬───┬───┬───┬───┬───┐
│ 1 │ 2 │ 3 │ 4 │ 5 │ 6 │ 7 │ 8 │ 9 │10 │
└───┴───┴───┴───┴───┴───┴───┴───┴───┴───┘
✓ ✓ ✓ ┌──── 윈도우 ────┐
│ 4 │ 5 │ 6 │ 7 │ ← 전송 가능 범위
└─────────────────┘
수신자의 버퍼가 가득 차면 윈도우 크기를 0으로 알려주어 송신을 일시 중지시킨다. 이후 버퍼에 여유가 생기면 윈도우 크기를 다시 알려주어 송신을 재개한다.
8. TCP 혼잡 제어 (Congestion Control)
혼잡 제어는 네트워크의 혼잡 상태를 감지하고 데이터 전송 속도를 조절하여 네트워크 전체의 성능 저하를 방지하는 메커니즘이다. 흐름 제어가 수신자 기준이라면, 혼잡 제어는 네트워크 기준이다.
Slow Start (느린 시작)
연결 초기에 작은 윈도우(보통 1 MSS)에서 시작하여 ACK를 받을 때마다 윈도우를 지수적으로 증가시킨다.
1
2
3
4
5
6
7
8
9
10
11
12
13
혼잡 윈도우 (cwnd) 변화:
cwnd │
│ * (임계값 도달)
16 │ * /
│ * / ← AIMD (선형 증가)
8 │ * /
│ *
4 │ *
│ *
2 │ *
1 │* ← Slow Start (지수 증가)
└───────────────────────── RTT
AIMD (Additive Increase / Multiplicative Decrease)
혼잡 윈도우가 임계값(ssthresh)에 도달하면 지수적 증가에서 선형적 증가로 전환한다. 매 RTT마다 윈도우를 1 MSS씩 증가시킨다. 패킷 손실(혼잡)이 감지되면 윈도우를 절반으로 줄인다.
Fast Retransmit (빠른 재전송)
일반적으로 패킷 손실은 타임아웃으로 감지하지만, Fast Retransmit는 중복 ACK 3개를 수신하면 타임아웃을 기다리지 않고 즉시 해당 패킷을 재전송한다.
1
2
3
4
5
6
7
8
9
송신: Seg1 Seg2 Seg3 Seg4 Seg5
│ │ ✗ │ │ ← Seg3 손실
│ │ │ │
수신: │ │ │ │
ACK1 ACK2 ACK2 ACK2 ACK2 ← 중복 ACK 3개
↑ ↑ ↑
Seg3을 기다리고 있음
3번째 중복 ACK 수신 시 → Seg3 즉시 재전송 (타임아웃 안 기다림)
9. HTTP/1.1 vs HTTP/2 vs HTTP/3 비교
HTTP/1.1
HTTP/1.1은 1997년에 발표된 프로토콜로, Keep-Alive를 통해 TCP 연결을 재사용할 수 있게 되었다. 하지만 Head-of-Line Blocking 문제가 있어, 하나의 연결에서 이전 요청이 완료되기 전에는 다음 요청을 보낼 수 없다.
1
2
3
4
5
HTTP/1.1 (파이프라이닝 없이):
요청1 ──────→ 응답1 ──────→ 요청2 ──────→ 응답2 ──────→
(순차적 처리)
HTTP/1.1의 한계를 우회하기 위해 브라우저는 보통 도메인당 6~8개의 TCP 연결을 동시에 열어 사용
HTTP/2
HTTP/2는 2015년에 발표되었으며, 멀티플렉싱(Multiplexing)을 통해 하나의 TCP 연결에서 여러 요청/응답을 동시에 처리할 수 있다.
1
2
3
4
5
6
7
8
9
10
11
12
HTTP/2 멀티플렉싱:
하나의 TCP 연결에서:
Stream 1: 요청1 ──→ 응답1
Stream 2: 요청2 ──→ 응답2 (동시 처리)
Stream 3: 요청3 ──→ 응답3
주요 특징:
- 바이너리 프레이밍 (텍스트 → 바이너리)
- 멀티플렉싱 (하나의 연결로 여러 스트림)
- 헤더 압축 (HPACK)
- 서버 푸시 (Server Push)
- 스트림 우선순위
HTTP/3
HTTP/3는 TCP 대신 QUIC(UDP 기반) 프로토콜을 사용한다. TCP의 Head-of-Line Blocking 문제를 근본적으로 해결하고, 연결 설정 시간을 단축한다.
1
2
3
4
연결 설정 시간 비교:
HTTP/1.1 (TCP + TLS 1.2): 3 RTT (TCP 1 + TLS 2)
HTTP/2 (TCP + TLS 1.3): 2 RTT (TCP 1 + TLS 1)
HTTP/3 (QUIC): 1 RTT (또는 0-RTT 재연결)
| 특성 | HTTP/1.1 | HTTP/2 | HTTP/3 |
|---|---|---|---|
| 전송 프로토콜 | TCP | TCP | QUIC (UDP) |
| 멀티플렉싱 | 없음 | 지원 | 지원 |
| 헤더 압축 | 없음 | HPACK | QPACK |
| HoL Blocking | TCP/HTTP 레벨 | TCP 레벨만 | 없음 |
| 연결 설정 | 2~3 RTT | 2 RTT | 1 RTT (0-RTT 가능) |
10. HTTPS와 TLS/SSL 동작 원리
HTTPS(HTTP Secure)는 HTTP에 TLS(Transport Layer Security)/SSL(Secure Sockets Layer) 암호화를 추가한 프로토콜이다. 데이터를 암호화하여 전송하므로 도청, 변조, 위장을 방지한다.
TLS Handshake 과정
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
클라이언트 서버
│ │
│ 1. ClientHello │
│ (지원하는 암호 목록, 랜덤 값) │
│ ────────────────────────────────────→ │
│ │
│ 2. ServerHello │
│ (선택한 암호, 랜덤 값) │
│ + 서버 인증서 (공개키 포함) │
│ ←──────────────────────────────────── │
│ │
│ 3. 인증서 검증 │
│ + 프리마스터 시크릿 생성 │
│ + 서버 공개키로 암호화하여 전송 │
│ ────────────────────────────────────→ │
│ │
│ 4. 양쪽 모두 세션 키 생성 │
│ (프리마스터 시크릿 + 랜덤 값들) │
│ │
│ 5. ChangeCipherSpec + Finished │
│ ←──────────────────────────────────→ │
│ │
│ 암호화된 통신 시작 (대칭키 사용) │
핵심 개념은 비대칭키(공개키/개인키)로 대칭키를 안전하게 교환하고, 이후 실제 데이터 통신은 대칭키로 암호화하는 것이다. 비대칭키 암호화는 느리지만 키 교환에 안전하고, 대칭키 암호화는 빠르지만 키 교환이 문제이므로 두 방식을 결합한 것이다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// Java에서 HTTPS 요청
import javax.net.ssl.*;
import java.net.http.*;
import java.net.URI;
public class HttpsExample {
public static void main(String[] args) throws Exception {
// Java 11+ HttpClient
HttpClient client = HttpClient.newBuilder()
.version(HttpClient.Version.HTTP_2)
.followRedirects(HttpClient.Redirect.NORMAL)
.build();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://api.example.com/data"))
.header("Content-Type", "application/json")
.GET()
.build();
HttpResponse<String> response = client.send(
request, HttpResponse.BodyHandlers.ofString()
);
System.out.println("상태 코드: " + response.statusCode());
System.out.println("프로토콜: " + response.version());
System.out.println("응답: " + response.body());
}
}
11. DNS 동작 원리
DNS(Domain Name System)는 도메인 이름(예: www.google.com)을 IP 주소(예: 142.250.196.68)로 변환해주는 분산 데이터베이스 시스템이다.
DNS 계층 구조
1
2
3
4
5
6
7
8
9
10
11
12
┌──────────────┐
│ 루트 DNS 서버 │ (전 세계 13개 그룹)
│ ( . ) │
└──────┬───────┘
┌──────────┼──────────┐
┌─────▼─────┐ ┌──▼───┐ ┌───▼────┐
│ .com TLD │ │ .kr │ │ .org │ ← 최상위 도메인 서버
└─────┬─────┘ └──┬───┘ └───┬────┘
┌─────▼─────┐ ┌──▼──────┐
│google.com │ │naver.kr │ ← 권한 있는 네임서버
│ DNS 서버 │ │ DNS 서버 │
└───────────┘ └─────────┘
DNS 질의 과정
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
사용자 → www.example.com 접속 시도
1. 브라우저 캐시 확인
2. OS 캐시 확인 (hosts 파일)
3. 로컬 DNS 서버(ISP 제공)에 질의
로컬 DNS 서버 (재귀적 질의):
┌──────────────┐
│ 로컬 DNS 서버 │
└──────┬───────┘
│ ① www.example.com의 IP는?
▼
┌──────────────┐
│ 루트 DNS 서버 │ → ".com은 이쪽으로" (TLD 서버 주소 반환)
└──────────────┘
│
▼ ② www.example.com의 IP는?
┌──────────────┐
│ .com TLD 서버 │ → "example.com은 이쪽으로" (권한 서버 주소 반환)
└──────────────┘
│
▼ ③ www.example.com의 IP는?
┌───────────────────┐
│ example.com │ → "93.184.216.34입니다" (최종 IP 반환)
│ 권한 있는 네임서버 │
└───────────────────┘
│
▼
로컬 DNS 서버 → 사용자에게 IP 반환 (캐시에 저장)
DNS 레코드 타입은 다양하다. A 레코드는 도메인을 IPv4 주소에 매핑한다. AAAA 레코드는 IPv6 주소에 매핑한다. CNAME 레코드는 도메인의 별칭을 설정한다. MX 레코드는 메일 서버를 지정한다. NS 레코드는 네임서버를 지정한다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// Java에서 DNS 조회
import java.net.InetAddress;
public class DnsLookup {
public static void main(String[] args) throws Exception {
// 도메인 → IP 변환
InetAddress address = InetAddress.getByName("www.google.com");
System.out.println("IP: " + address.getHostAddress());
// 모든 IP 주소 조회 (DNS Round Robin)
InetAddress[] allAddresses = InetAddress.getAllByName("www.google.com");
for (InetAddress addr : allAddresses) {
System.out.println("IP: " + addr.getHostAddress());
}
// 역방향 DNS 조회 (IP → 도메인)
InetAddress reverse = InetAddress.getByName("8.8.8.8");
System.out.println("호스트: " + reverse.getHostName());
}
}
12. 웹 브라우저에 URL을 입력하면 일어나는 일
이 질문은 네트워크 면접의 단골 질문이다. 전체 과정을 단계별로 정리하면 다음과 같다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
사용자가 https://www.example.com/page 를 입력
1단계: URL 파싱
→ 프로토콜(https), 호스트(www.example.com), 경로(/page) 분석
2단계: DNS 조회
→ 브라우저 캐시 → OS 캐시 → 로컬 DNS → 루트/TLD/권한 DNS 순서로 조회
→ IP 주소 획득 (예: 93.184.216.34)
3단계: TCP 연결 (3-way Handshake)
→ SYN → SYN+ACK → ACK
→ 포트 443 (HTTPS)
4단계: TLS Handshake
→ ClientHello → ServerHello + 인증서 → 키 교환 → 암호화 통신 준비
5단계: HTTP 요청 전송
→ GET /page HTTP/1.1
→ Host: www.example.com
→ 쿠키, User-Agent 등 헤더 포함
6단계: 서버 처리
→ 웹 서버(Nginx/Apache) → WAS(Tomcat/Spring) → DB 조회 → 응답 생성
7단계: HTTP 응답 수신
→ 200 OK
→ HTML 문서 수신
8단계: 브라우저 렌더링
→ HTML 파싱 → DOM 트리 생성
→ CSS 파싱 → CSSOM 트리 생성
→ JavaScript 실행
→ 렌더 트리 생성 → 레이아웃 → 페인트 → 화면 표시
9단계: 추가 리소스 요청
→ 이미지, CSS, JS 파일 등을 병렬로 요청
→ 필요 시 DNS 조회, TCP 연결 반복
13. Java 소켓 프로그래밍
소켓은 네트워크 통신의 끝점(endpoint)으로, IP 주소와 포트 번호의 조합이다. Java는 java.net 패키지를 통해 소켓 프로그래밍을 지원한다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
// 간단한 채팅 서버
import java.io.*;
import java.net.*;
import java.util.*;
import java.util.concurrent.*;
public class ChatServer {
private static final Set<PrintWriter> clients = ConcurrentHashMap.newKeySet();
public static void main(String[] args) throws Exception {
ServerSocket server = new ServerSocket(12345);
System.out.println("채팅 서버 시작 - 포트 12345");
ExecutorService pool = Executors.newCachedThreadPool();
while (true) {
Socket socket = server.accept();
pool.submit(new ClientHandler(socket));
}
}
static class ClientHandler implements Runnable {
private final Socket socket;
private PrintWriter out;
ClientHandler(Socket socket) { this.socket = socket; }
@Override
public void run() {
try {
BufferedReader in = new BufferedReader(
new InputStreamReader(socket.getInputStream())
);
out = new PrintWriter(socket.getOutputStream(), true);
clients.add(out);
String nickname = in.readLine();
broadcast(nickname + "님이 입장했습니다.");
String message;
while ((message = in.readLine()) != null) {
broadcast("[" + nickname + "] " + message);
}
} catch (IOException e) {
// 연결 종료
} finally {
clients.remove(out);
try { socket.close(); } catch (IOException e) {}
}
}
private void broadcast(String message) {
for (PrintWriter writer : clients) {
writer.println(message);
}
}
}
}
// 채팅 클라이언트
public class ChatClient {
public static void main(String[] args) throws Exception {
Socket socket = new Socket("localhost", 12345);
BufferedReader in = new BufferedReader(
new InputStreamReader(socket.getInputStream())
);
PrintWriter out = new PrintWriter(socket.getOutputStream(), true);
BufferedReader keyboard = new BufferedReader(
new InputStreamReader(System.in)
);
System.out.print("닉네임: ");
out.println(keyboard.readLine());
// 메시지 수신 스레드
new Thread(() -> {
try {
String line;
while ((line = in.readLine()) != null) {
System.out.println(line);
}
} catch (IOException e) { /* 종료 */ }
}).start();
// 메시지 송신
String message;
while ((message = keyboard.readLine()) != null) {
out.println(message);
}
}
}
NIO (Non-blocking I/O)
Java NIO는 채널(Channel)과 버퍼(Buffer) 기반의 비동기 I/O를 지원한다. Selector를 사용하면 하나의 스레드로 여러 연결을 처리할 수 있어 대규모 서버에 적합하다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
import java.nio.*;
import java.nio.channels.*;
import java.net.*;
import java.util.*;
public class NioServer {
public static void main(String[] args) throws Exception {
Selector selector = Selector.open();
ServerSocketChannel serverChannel = ServerSocketChannel.open();
serverChannel.bind(new InetSocketAddress(8080));
serverChannel.configureBlocking(false);
serverChannel.register(selector, SelectionKey.OP_ACCEPT);
System.out.println("NIO 서버 시작 - 포트 8080");
while (true) {
selector.select(); // 이벤트 대기
Set<SelectionKey> selectedKeys = selector.selectedKeys();
Iterator<SelectionKey> iter = selectedKeys.iterator();
while (iter.hasNext()) {
SelectionKey key = iter.next();
iter.remove();
if (key.isAcceptable()) {
// 새 연결 수락
SocketChannel client = serverChannel.accept();
client.configureBlocking(false);
client.register(selector, SelectionKey.OP_READ);
System.out.println("새 연결: " + client.getRemoteAddress());
} else if (key.isReadable()) {
// 데이터 수신
SocketChannel client = (SocketChannel) key.channel();
ByteBuffer buffer = ByteBuffer.allocate(1024);
int bytesRead = client.read(buffer);
if (bytesRead == -1) {
client.close();
} else {
buffer.flip();
byte[] data = new byte[buffer.remaining()];
buffer.get(data);
System.out.println("수신: " + new String(data));
// 에코 응답
buffer.flip();
client.write(buffer);
}
}
}
}
}
}
14. REST API와 HTTP 메서드
REST(Representational State Transfer)는 웹 서비스 설계의 아키텍처 스타일이다. HTTP 메서드를 사용하여 리소스에 대한 CRUD 작업을 표현한다.
| HTTP 메서드 | CRUD 연산 | 설명 | 멱등성 | 안전성 |
|---|---|---|---|---|
| GET | Read | 리소스 조회 | O | O |
| POST | Create | 리소스 생성 | X | X |
| PUT | Update | 리소스 전체 수정 | O | X |
| PATCH | Update | 리소스 부분 수정 | X | X |
| DELETE | Delete | 리소스 삭제 | O | X |
멱등성(Idempotency)이란 같은 요청을 여러 번 보내도 결과가 동일한 것을 의미한다. GET, PUT, DELETE는 멱등성을 가지지만, POST는 그렇지 않다(매번 새로운 리소스가 생성됨).
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
// Spring Boot REST API 예제
@RestController
@RequestMapping("/api/users")
public class UserController {
@GetMapping
public ResponseEntity<List<User>> getAllUsers() {
return ResponseEntity.ok(userService.findAll());
}
@GetMapping("/{id}")
public ResponseEntity<User> getUser(@PathVariable Long id) {
return userService.findById(id)
.map(ResponseEntity::ok)
.orElse(ResponseEntity.notFound().build());
}
@PostMapping
public ResponseEntity<User> createUser(@Valid @RequestBody UserDto dto) {
User user = userService.create(dto);
URI location = URI.create("/api/users/" + user.getId());
return ResponseEntity.created(location).body(user);
}
@PutMapping("/{id}")
public ResponseEntity<User> updateUser(
@PathVariable Long id, @Valid @RequestBody UserDto dto) {
return ResponseEntity.ok(userService.update(id, dto));
}
@DeleteMapping("/{id}")
public ResponseEntity<Void> deleteUser(@PathVariable Long id) {
userService.delete(id);
return ResponseEntity.noContent().build();
}
}
HTTP 상태 코드
| 코드 범위 | 의미 | 주요 코드 |
|---|---|---|
| 1xx | 정보 | 100 Continue, 101 Switching Protocols |
| 2xx | 성공 | 200 OK, 201 Created, 204 No Content |
| 3xx | 리다이렉션 | 301 Moved Permanently, 302 Found, 304 Not Modified |
| 4xx | 클라이언트 오류 | 400 Bad Request, 401 Unauthorized, 403 Forbidden, 404 Not Found |
| 5xx | 서버 오류 | 500 Internal Server Error, 502 Bad Gateway, 503 Service Unavailable |
15. 면접에서 자주 나오는 네트워크 질문과 답변
Q1: TCP와 UDP의 차이점을 설명하세요.
TCP는 연결 지향적 프로토콜로 3-way handshake를 통해 연결을 수립하고, 데이터의 순서 보장, 손실 시 재전송, 흐름/혼잡 제어를 통해 신뢰성 있는 전송을 보장합니다. UDP는 비연결형 프로토콜로 이러한 기능이 없어 빠르지만 신뢰성을 보장하지 않습니다. TCP는 HTTP, FTP 등에, UDP는 DNS, 스트리밍, 온라인 게임 등에 사용됩니다.
Q2: 3-way handshake는 왜 필요한가요?
양쪽 모두 데이터를 보내고 받을 수 있는 능력을 확인하기 위해서입니다. SYN으로 “나는 보낼 수 있다”, ACK로 “나는 받을 수 있다”를 확인해야 하므로, 양방향 통신 확인에 최소 3단계가 필요합니다. 2-way로는 클라이언트만 확인 가능하고 서버의 송신 능력을 확인할 수 없습니다. 또한 이전 연결의 지연된 SYN 패킷이 새 연결로 오인되는 것을 방지하는 역할도 합니다.
Q3: HTTP와 HTTPS의 차이점은?
HTTPS는 HTTP에 TLS/SSL 암호화를 추가한 것입니다. HTTP는 데이터가 평문으로 전송되어 도청이 가능하지만, HTTPS는 대칭키/비대칭키 암호화를 통해 기밀성, 무결성, 인증을 보장합니다. HTTPS는 포트 443을 사용하고, TLS Handshake 과정에서 서버 인증서를 검증하고 세션 키를 교환합니다.
Q4: 웹 브라우저에 URL을 입력하면 어떤 일이 일어나나요?
URL 파싱 → DNS 조회(브라우저/OS 캐시 → 로컬 DNS → 루트/TLD/권한 DNS) → TCP 3-way Handshake → TLS Handshake(HTTPS인 경우) → HTTP 요청 전송 → 서버 처리(웹서버 → WAS → DB) → HTTP 응답 수신 → 브라우저 렌더링(HTML 파싱 → DOM/CSSOM → 렌더 트리 → 레이아웃 → 페인트) → 추가 리소스 요청 순서로 진행됩니다.
Q5: HTTP/1.1과 HTTP/2의 차이점은?
HTTP/2는 바이너리 프레이밍으로 텍스트 기반의 HTTP/1.1보다 파싱이 효율적입니다. 가장 큰 개선은 멀티플렉싱으로, 하나의 TCP 연결에서 여러 요청/응답을 동시에 처리하여 HTTP 수준의 HOL Blocking을 해결했습니다. HPACK을 통한 헤더 압축, 서버 푸시, 스트림 우선순위 등의 기능도 제공합니다. 다만 TCP 수준의 HOL Blocking은 여전히 존재하며, 이는 HTTP/3(QUIC)에서 해결됩니다.
Q6: DNS의 동작 과정을 설명하세요.
클라이언트가 도메인 이름으로 접속할 때, 먼저 로컬 캐시(브라우저, OS)를 확인합니다. 캐시에 없으면 로컬 DNS 서버에 질의하고, 로컬 DNS 서버는 루트 DNS → TLD DNS → 권한 있는 DNS 서버 순서로 재귀적/반복적 질의를 수행하여 IP 주소를 받아옵니다. 결과는 TTL 기간 동안 캐시됩니다.
Q7: CORS란 무엇이며, 어떻게 해결하나요?
CORS(Cross-Origin Resource Sharing)는 브라우저가 다른 출처의 리소스 요청을 제한하는 보안 메커니즘입니다. 서버에서 Access-Control-Allow-Origin 등의 응답 헤더를 설정하여 허용할 출처를 명시하면 해결됩니다. Spring에서는 @CrossOrigin 어노테이션이나 WebMvcConfigurer의 addCorsMappings로 설정할 수 있습니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
// Spring CORS 설정
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/api/**")
.allowedOrigins("https://example.com")
.allowedMethods("GET", "POST", "PUT", "DELETE")
.allowedHeaders("*")
.allowCredentials(true)
.maxAge(3600);
}
}
정리
네트워크는 백엔드 개발의 근간이 되는 핵심 CS 지식이다. API 서버를 개발하고 운영하면서 마주치는 대부분의 이슈가 네트워크와 관련되어 있다. TCP의 3-way/4-way handshake, HTTP 프로토콜의 진화, HTTPS의 보안 메커니즘, DNS의 동작 원리를 정확히 이해하면 문제 해결 능력이 크게 향상된다.
핵심 포인트를 정리하면 다음과 같다. OSI 7계층과 TCP/IP 4계층의 각 역할을 이해해야 한다. TCP는 신뢰성, UDP는 속도가 강점이다. 3-way handshake는 양방향 통신 확인을 위해 필수적이다. HTTP/2의 멀티플렉싱은 성능을 크게 개선했고, HTTP/3의 QUIC는 TCP의 근본적 한계를 해결했다. HTTPS는 비대칭키로 대칭키를 교환하고, 실제 통신은 대칭키로 암호화한다. DNS는 분산 데이터베이스 시스템으로, 도메인을 IP로 변환하는 전체 과정을 이해해야 한다. 이 지식들은 면접에서도 빈번하게 출제되므로 확실하게 숙지하는 것이 중요하다.