이 사이트의 실습은 자바스크립트를 켜야 움직입니다. 아래는 같은 내용을 글로 적은 것입니다.
공부 › 네트워크 › 전송 계층
TCP와 UDP
세 번에 나눠 보낸 데이터는 세 번에 나뉘어 도착하나
TCP와 UDP는 프로그램의 데이터를 상대 프로그램까지 실어 나르는 두 가지 방식입니다. TCP는 먼저 연결을 맺고 바이트를 순서대로 빠짐없이 전하지만, 어디서 끊어 보냈는지는 전해 주지 않습니다. UDP는 연결 없이 덩어리째 그냥 보내고, 도착했는지는 책임지지 않습니다.
일부만 실행으로 확인 · Node.js 24의 net, dgram 모듈로 127.0.0.1에서 여덟 가지 조합 가운데 여섯 가지를 실행해 대조. TCP의 유실과 순서 바뀜 두 가지는 RFC 9293 기준
이 실습에 나오는 개념
- TCP와 UDP
- 인터넷에서 데이터를 전송하는 두 가지 대표적인 방식이다. TCP는 통신 전에 연결을 맺고, 데이터가 빠짐없이 순서대로 도착하도록 보장하며, 문제가 생기면 다시 보낸다. UDP는 연결 없이 데이터를 보내며 도착 여부나 순서를 보장하지 않는 대신 가볍고 빠르다. HTTP/1.1과 HTTP/2, 데이터베이스 연결은 TCP를, 실시간 영상·음성이나 DNS는 UDP를 주로 쓴다.
- 바이트 스트림과 데이터그램
- 데이터를 실어 나르는 두 가지 모양이다. 바이트 스트림은 바이트가 순서대로 끊김 없이 이어지는 흐름으로, TCP가 이 방식이다. 보내는 쪽이 몇 번에 나눠 썼는지는 전달되지 않아서, 세 번에 나눠 보낸 것이 한 번에 읽히거나 한 번에 보낸 것이 여러 번에 나뉘어 읽힐 수 있다. 데이터그램은 한 번에 보낸 덩어리가 그대로 하나의 단위로 전달되는 방식으로, UDP가 이 방식이며 덩어리끼리 합쳐지거나 쪼개져 읽히지 않는다. 그래서 TCP 위에서 메시지를 주고받는 프로그램은 길이를 먼저 적거나 구분 글자를 넣어 메시지의 끝을 스스로 표시해야 한다.
- 소켓
- 프로그램이 네트워크로 데이터를 주고받을 때 쓰는 통신의 끝점이며, 운영체제가 만들어 프로그램에 건네준다. 프로그램은 소켓에 쓰고 소켓에서 읽기만 하고, 실제로 데이터를 나누어 보내고 받는 일은 운영체제가 맡는다. TCP 연결 하나는 양쪽 끝의 소켓 한 쌍으로 이루어지고, 운영체제는 보내는 IP 주소, 보내는 포트, 받는 IP 주소, 받는 포트의 네 값으로 연결을 구분한다. 그래서 서버는 포트 하나로 여러 클라이언트의 연결을 함께 받을 수 있고, 연결마다 소켓이 하나씩 따로 생긴다. 다 쓴 소켓을 닫지 않으면 운영체제의 자원이 계속 묶인다.
- 포트
- 한 컴퓨터에서 돌고 있는 여러 프로그램 가운데 누가 데이터를 받을지 구분하는 0부터 65535까지의 번호다. IP 주소가 컴퓨터를 가리킨다면 포트는 그 컴퓨터 안의 프로그램을 가리키므로, 접속할 곳은 "IP 주소:포트"로 적는다. 서버 프로그램은 정해 둔 포트에서 연결을 기다리며, 웹 서버의 80번(HTTP)과 443번(HTTPS)처럼 널리 약속된 번호도 있다. 한 포트에서 연결을 기다릴 수 있는 프로그램은 보통 하나뿐이어서, 이미 쓰이는 포트에 서버를 또 띄우면 "주소가 이미 사용 중"이라는 오류가 난다. TCP의 포트와 UDP의 포트는 따로 관리되므로 번호가 같아도 서로 다른 것이다.
- 3-way handshake
- TCP 연결을 맺을 때 거치는 세 단계의 과정이다. 클라이언트가 연결 요청(SYN)을 보내고, 서버가 수락과 함께 자신의 연결 요청(SYN+ACK)을 보내며, 클라이언트가 이를 확인(ACK)한다. 양쪽이 서로 보내고 받을 수 있는 상태임을 확인한 뒤 데이터 전송을 시작한다.
이렇게 바꿔 보세요
- 처음 설정으로 — TCP. 세 번에 나눠 보냈는데 받는 쪽은 한 덩어리로 읽습니다.
- 같은 것을 UDP로 — 보낸 덩어리 그대로 세 개가 도착합니다.
- 받는 프로그램 없이 UDP로 — 보내기는 세 번 다 성공합니다. 데이터는 사라집니다.
- 받는 프로그램 없이 TCP로 — 연결부터 실패해서 아무것도 보내지 못합니다.
- UDP에서 하나가 사라지면 — 빠진 채로 받고, 보낸 쪽은 모릅니다.
- UDP에서 순서가 바뀌면 — 도착한 순서대로 받습니다.
- TCP에서 하나가 사라지면 — 운영체제가 다시 보내서 채웁니다. 프로그램은 모릅니다.
처음 설정으로 실행하면
- net.connect(9000, '127.0.0.1'): TCP는 데이터를 보내기 전에 먼저 연결을 맺습니다(3-way handshake). 받는 프로그램이 9000번 포트에서 기다리고 있으므로 성공합니다. 이제 양쪽 운영체제가 이 연결로 오가는 바이트에 번호를 붙여 관리합니다.
- socket.write('AB'): 2바이트가 받는 쪽 운영체제에 도착해 수신 버퍼에 쌓입니다. 받는 프로그램은 아직 읽지 않았으므로 운영체제가 맡아 둡니다. TCP의 버퍼는 바이트가 차례로 쌓이는 한 줄입니다.
- socket.write('CD'): 2바이트가 받는 쪽 운영체제에 도착해 수신 버퍼에 쌓입니다. 앞의 바이트 바로 뒤에 이어 붙습니다. 어디까지가 한 번의 write였는지는 기록되지 않습니다.
- socket.write('EF'): 2바이트가 받는 쪽 운영체제에 도착해 수신 버퍼에 쌓입니다. 역시 앞의 바이트 뒤에 이어 붙어, 버퍼에는 6바이트가 한 줄로 놓입니다.
- 받는 프로그램이 읽습니다: 'data' 이벤트 한 번에 6바이트 "ABCDEF"가 모두 나옵니다. 세 번에 나눠 보냈다는 흔적은 없습니다. TCP가 전하는 것은 순서가 맞는 바이트의 흐름이지, 덩어리의 경계가 아닙니다.
실행 결과
- TCP는 바이트의 흐름(스트림)입니다. 순서와 내용은 지켜 주지만 write를 몇 번에 나눠 했는지는 전하지 않습니다. 그래서 TCP 위에서 메시지를 주고받는 프로그램은 "어디까지가 메시지 하나인지"를 스스로 정합니다. 길이를 먼저 적어 보내거나(HTTP의 Content-Length), 줄바꿈 같은 구분 글자를 씁니다.
- 이 실습은 받는 프로그램이 늦게 읽는 경우입니다. 도착하자마자 읽으면 보낸 대로 세 번에 나뉘어 읽히기도 하지만, 그렇게 된다는 보장은 없습니다. "write 한 번이 read 한 번"이라고 가정한 코드는 내 컴퓨터에서는 돌다가 실제 환경에서 깨집니다.
- TCP는 하나라도 빠지거나 순서가 바뀌면 안 되는 곳에 씁니다. 웹(HTTP/1.1, HTTP/2), DB 연결, 파일 전송, 메일이 TCP 위에서 동작합니다.
코드
// Node.js (net 모듈): TCP
const socket = net.connect(9000, '127.0.0.1'); // 먼저 연결을 맺는다
socket.on('connect', async () => { // 연결된 뒤에야 보낼 수 있다
socket.write('AB'); await sleep(50); // 0.05초 간격으로 세 번
socket.write('CD'); await sleep(50);
socket.write('EF');
});
socket.on('error', e => console.log(e.code)); // 연결 실패는 여기로 온다
// 받는 프로그램: 세 번이 다 도착한 뒤에 읽기 시작한다
net.createServer({ pauseOnConnect: true }, conn => {
conn.on('data', d => console.log(d.toString()));
setTimeout(() => conn.resume(), 500);
}).listen(9000, '127.0.0.1');
이 모형의 전제
"AB", "CD", "EF"를 0.05초 간격으로 보내고 받는 프로그램은 세 번이 다 도착한 뒤에 읽는 상황입니다. 여덟 가지 조합 가운데 여섯 가지를 이 PC(윈도, Node.js 24)의 127.0.0.1에서 net과 dgram 모듈로 실제로 실행해 대조했습니다(조합마다 다섯 번 실행해 매번 같은 결과). UDP의 유실과 순서 바뀜은 보내는 쪽과 받는 쪽 사이에 데이터그램을 버리거나 늦게 넘기는 중계 프로그램을 두어 만들었고, 양 끝의 UDP 소켓이 이를 바로잡지 않는다는 것을 확인했습니다. TCP에서 유실과 순서 바뀜이 일어난 두 가지 조합은 이 PC에서 만들 수 없어 실행하지 않았으며, TCP 표준(RFC 9293 3.4절의 순서 번호, 3.8절의 확인 응답과 재전송)에 따라 "프로그램이 읽는 결과는 아무 일 없을 때와 같다"고 적었습니다. TCP에서 한 번의 읽기로 6바이트가 모두 나온 것은 이 PC에서 본 결과이고, 표준이 보장하는 것은 바이트의 순서와 내용뿐입니다. 읽기가 몇 번으로 나뉘는지는 상황에 따라 달라집니다. UDP가 데이터그램 단위로 전하고 도착을 보장하지 않는다는 것은 RFC 768과 RFC 1122 4.1절의 내용입니다. UDP 헤더에는 순서를 나타내는 번호가 없고(RFC 768), 그 아래의 IP도 순서를 지켜 주지 않습니다(RFC 791 1.2절). 받는 프로그램이 없을 때 운영체제가 ICMP 메시지로 알려 주기도 하지만, 이 실행에서 send의 결과나 오류 이벤트로 돌아온 것은 없었습니다. 운영체제의 버퍼가 가득 차서 버려지는 경우는 다루지 않았습니다.
앞 실습: TCP 연결과 재전송 · 다음 실습: 흐름 제어와 혼잡 제어