이 사이트의 실습은 자바스크립트를 켜야 움직입니다. 아래는 같은 내용을 글로 적은 것입니다.
공부 › 네트워크 › 전송 계층
소켓과 포트
한 컴퓨터의 여러 프로그램 가운데 누가 이 데이터를 받나
IP 주소는 컴퓨터 한 대까지만 찾아갑니다. 그 컴퓨터에서 돌고 있는 여러 프로그램 가운데 누가 데이터를 받을지는 포트 번호가 정합니다. 프로그램은 소켓을 만들어 포트 번호를 붙이고, 운영체제는 그 포트로 온 데이터를 그 소켓에 넘깁니다. 서버가 여는 포트와 클라이언트가 연결하는 포트에 따라 연결이 어느 프로그램에 닿는지 봅니다.
실행으로 대조 · Node.js 24의 net 모듈로 127.0.0.1에서 스물일곱 가지 조합을 실행해 대조
이 실습에 나오는 개념
- 포트
- 한 컴퓨터에서 돌고 있는 여러 프로그램 가운데 누가 데이터를 받을지 구분하는 0부터 65535까지의 번호다. IP 주소가 컴퓨터를 가리킨다면 포트는 그 컴퓨터 안의 프로그램을 가리키므로, 접속할 곳은 "IP 주소:포트"로 적는다. 서버 프로그램은 정해 둔 포트에서 연결을 기다리며, 웹 서버의 80번(HTTP)과 443번(HTTPS)처럼 널리 약속된 번호도 있다. 한 포트에서 연결을 기다릴 수 있는 프로그램은 보통 하나뿐이어서, 이미 쓰이는 포트에 서버를 또 띄우면 "주소가 이미 사용 중"이라는 오류가 난다. TCP의 포트와 UDP의 포트는 따로 관리되므로 번호가 같아도 서로 다른 것이다.
- 소켓
- 프로그램이 네트워크로 데이터를 주고받을 때 쓰는 통신의 끝점이며, 운영체제가 만들어 프로그램에 건네준다. 프로그램은 소켓에 쓰고 소켓에서 읽기만 하고, 실제로 데이터를 나누어 보내고 받는 일은 운영체제가 맡는다. TCP 연결 하나는 양쪽 끝의 소켓 한 쌍으로 이루어지고, 운영체제는 보내는 IP 주소, 보내는 포트, 받는 IP 주소, 받는 포트의 네 값으로 연결을 구분한다. 그래서 서버는 포트 하나로 여러 클라이언트의 연결을 함께 받을 수 있고, 연결마다 소켓이 하나씩 따로 생긴다. 다 쓴 소켓을 닫지 않으면 운영체제의 자원이 계속 묶인다.
- 임시 포트
- 클라이언트가 서버에 연결할 때 운영체제가 자동으로 골라 주는 보내는 쪽 포트 번호다. 클라이언트 코드는 받는 쪽(서버)의 포트만 적고, 자기 쪽 포트는 운영체제가 비어 있는 번호 가운데 하나를 빌려준다. 연결이 끝나면 그 번호는 반납되어 다른 연결에 다시 쓰인다. 번호를 고르는 범위는 운영체제와 설정에 따라 다르므로 특정 번호가 나온다고 가정하면 안 된다. 짧은 시간에 연결을 아주 많이 새로 맺으면 빌려줄 번호가 모자라 새 연결이 실패할 수 있다.
- TCP와 UDP
- 인터넷에서 데이터를 전송하는 두 가지 대표적인 방식이다. TCP는 통신 전에 연결을 맺고, 데이터가 빠짐없이 순서대로 도착하도록 보장하며, 문제가 생기면 다시 보낸다. UDP는 연결 없이 데이터를 보내며 도착 여부나 순서를 보장하지 않는 대신 가볍고 빠르다. HTTP/1.1과 HTTP/2, 데이터베이스 연결은 TCP를, 실시간 영상·음성이나 DNS는 UDP를 주로 쓴다.
이렇게 바꿔 보세요
- 처음 설정으로 — 같은 포트에 서버 둘은 안 되는데, 클라이언트 셋은 한 포트에 함께 붙습니다.
- 포트로 프로그램을 고른다 — 같은 컴퓨터인데 9001번으로 보내면 서버 B가 받습니다.
- 듣는 서버가 없는 포트 — 컴퓨터는 켜져 있어도 연결이 거절됩니다.
- 서버 B가 뜨지 못한 뒤 9001번으로 — B는 9000번을 얻지 못했고 9001번에는 아무도 없습니다.
- 서버 하나, 클라이언트 하나 — 가장 단순한 경우부터 봅니다.
처음 설정으로 실행하면
- serverA.listen(9000): 서버 A가 소켓을 만들어 9000번 포트에 붙이고, 운영체제에 "9000번 포트로 들어오는 연결은 이 소켓으로 달라"고 등록합니다. 9000번이 비어 있었으므로 성공합니다. 이제 서버 A는 연결을 기다립니다.
- serverB.listen(9000): 서버 B도 9000번을 등록하려 합니다. 이미 서버 A가 쓰고 있으므로 운영체제가 거절하고, 서버 B는 EADDRINUSE(주소가 이미 사용 중) 오류를 받습니다. 한 포트로 들어오는 연결을 받을 프로그램은 하나여야 누구에게 줄지 정할 수 있기 때문입니다.
- 클라이언트 1의 net.connect(9000): 코드에는 받는 포트만 적었습니다. 보내는 포트는 운영체제가 비어 있는 번호 가운데 하나(여기서는 50001번)를 골라 줍니다. 9000번에서 서버 A가 듣고 있으므로 연결이 맺어집니다.
- 클라이언트 2의 net.connect(9000): 운영체제가 이번에는 보내는 포트로 50002번을 골라 줍니다. 서버 A가 듣고 있으므로 연결이 맺어집니다. 앞의 연결과 받는 포트는 똑같이 9000번이고, 보내는 포트만 다릅니다. 운영체제는 네 값(보내는 IP, 보내는 포트, 받는 IP, 받는 포트)이 하나라도 다르면 다른 연결로 봅니다.
- 클라이언트 3의 net.connect(9000): 운영체제가 이번에는 보내는 포트로 50003번을 골라 줍니다. 서버 A가 듣고 있으므로 연결이 맺어집니다. 앞의 연결과 받는 포트는 똑같이 9000번이고, 보내는 포트만 다릅니다. 앞의 두 연결은 끊기지 않고 그대로 열려 있습니다.
- socket.write: 클라이언트 3개가 저마다 데이터를 보냅니다. 모두 9000번 포트로 가지만, 운영체제가 보내는 포트를 보고 어느 연결의 데이터인지 가려서 서버 A에게 연결별로 따로 넘깁니다. 연결마다 소켓이 하나씩 있기 때문입니다.
실행 결과
- 이미 쓰이는 포트에 서버를 또 띄우면 EADDRINUSE로 실패합니다. 개발 중에 "포트가 이미 사용 중"이라는 오류를 만나면, 먼저 띄워 둔 서버가 아직 살아 있는 경우가 대부분입니다. 그 프로그램을 끝내거나 다른 포트를 씁니다.
- 서버 포트 하나에 클라이언트 여럿이 함께 연결됩니다. 연결이 늘어도 서버 쪽 포트는 9000번 하나뿐이고, 연결은 (보내는 IP, 보내는 포트, 받는 IP, 받는 포트) 네 값의 묶음으로 구분됩니다. 웹 서버가 443번 포트 하나로 수많은 사용자를 받는 것도 같은 원리입니다.
코드
// Node.js (net 모듈). 서버와 클라이언트를 모두 이 컴퓨터(127.0.0.1)에서 실행한다
const net = require('net');
const serverA = net.createServer(conn => conn.on('data', d => conn.write('A')));
serverA.listen(9000, '127.0.0.1'); // 9000번 포트에서 듣기 시작
const serverB = net.createServer(conn => conn.on('data', d => conn.write('B')));
serverB.on('error', e => console.log(e.code)); // 실패하면 오류 이름이 찍힌다
serverB.listen(9000, '127.0.0.1'); // 서버 A가 이미 쓰는 포트
for (let i = 1; i <= 3; i++) {
const socket = net.connect(9000, '127.0.0.1'); // 받는 포트만 적는다. 보내는 포트는 적지 않는다
socket.on('connect', () => {
console.log(socket.localPort); // 운영체제가 골라 준 보내는 포트
socket.write('c' + i);
});
socket.on('data', d => console.log(d.toString())); // 어느 서버가 받았는지
socket.on('error', e => console.log(e.code));
}
이 모형의 전제
스물일곱 가지 조합을 이 PC(윈도, Node.js 24)의 127.0.0.1에서 net 모듈로 실제로 실행해 대조했습니다. 대조한 것은 서버 B의 listen 결과(성공 또는 EADDRINUSE), 클라이언트 연결의 성공 수와 거절 수(ECONNREFUSED), 데이터를 받은 서버, 서버가 본 연결 수, 서버 쪽 소켓의 포트가 모두 듣는 포트 하나와 같은지, 클라이언트 쪽 포트가 서로 다른지입니다. 실행에서는 9000, 9001, 9002 대신 그때 비어 있던 포트 세 개를 썼고, 클라이언트 쪽 포트 번호(화면의 50001 등)는 실행할 때마다 달라지므로 "서로 다르다"는 것만 결과로 삼았습니다. 운영체제가 임시 포트를 고르는 범위는 운영체제와 설정에 따라 다릅니다(IANA가 권하는 범위는 49152~65535, RFC 6335 6절). 연결을 양쪽의 IP 주소와 포트로 구분한다는 것은 TCP 표준(RFC 9293 4절 용어 풀이의 connection과 socket)의 정의입니다. 클라이언트를 다른 컴퓨터에 두는 경우와, 같은 포트를 여러 프로그램이 함께 쓰게 하는 특수한 소켓 옵션(SO_REUSEPORT 등)은 다루지 않았습니다.
앞 실습: 스위치와 라우터 · 다음 실습: TCP 연결과 재전송