Port & socket — localhost:8080 thực sự là gì
Port định danh ứng dụng trên một máy; socket là điểm cuối kết nối; 5-tuple định danh duy nhất một kết nối. Bài này giải thích vì sao một server lắng nghe một cổng mà phục vụ được hàng nghìn client, ý nghĩa 127.0.0.1 vs 0.0.0.0, và lỗi EADDRINUSE.
TL;DR: Tới đúng máy (IP) chưa đủ — một máy chạy nhiều dịch vụ, nên cần port (số 0–65535) để vào đúng ứng dụng. Một socket là điểm cuối giao tiếp gắn với (IP, port). Điều khiến một web server lắng nghe một cổng 443 mà phục vụ được hàng nghìn client cùng lúc là 5-tuple: mỗi kết nối được định danh duy nhất bởi (protocol, src IP, src port, dst IP, dst port) — client khác nhau ở IP/port nguồn nên không lẫn. Bind 127.0.0.1 = chỉ nội bộ, 0.0.0.0 = mọi interface. Hai tiến trình không thể bind cùng IP:port → EADDRINUSE. Soi bằng ss/lsof.
npm run dev báo "listening on localhost:3000", ssh user@host đi tới cổng 22, EADDRINUSE: address already in use :::8080 — bạn gặp port mỗi ngày. Nhưng localhost:8080 thực sự nghĩa là gì? Và bí ẩn lớn hơn: một web server chỉ lắng nghe cổng 443, làm sao nó phục vụ hàng nghìn người dùng đồng thời mà không lẫn lộn ai với ai?
Bài này giải thích port, socket, và khái niệm 5-tuple — chìa khoá trả lời cả hai câu hỏi.
1. Analogy — Số điện thoại công ty + máy lẻ
Một công ty có một số điện thoại (như IP của máy), nhưng hàng trăm máy lẻ (extension) bên trong. Bạn gọi số công ty rồi bấm "108" để vào đúng phòng. Tổng đài phân biệt hàng trăm cuộc gọi đồng thời nhờ biết ai gọi từ đâu tới máy lẻ nào.
| Công ty | Mạng |
|---|---|
| Số điện thoại công ty | Địa chỉ IP (đúng máy) |
| Máy lẻ "108" | Port (đúng ứng dụng) |
| Tổng đài phân biệt từng cuộc | 5-tuple phân biệt từng kết nối |
IP đưa bạn tới đúng toà nhà; port đưa tới đúng phòng. localhost:8080 = "máy này, ứng dụng đang nghe ở cổng 8080".
2. Port — số định danh ứng dụng
Port là số nguyên 0–65535 ở tầng Transport, cho biết gói dữ liệu thuộc về ứng dụng nào trên máy. Chia ba nhóm:
| Nhóm | Dải | Ví dụ |
|---|---|---|
| Well-known | 0–1023 | 80 (HTTP), 443 (HTTPS), 22 (SSH), 53 (DNS) |
| Registered | 1024–49151 | 5432 (PostgreSQL), 3306 (MySQL), 6379 (Redis) |
| Ephemeral (tạm) | 49152–65535 | port nguồn OS tự cấp cho client |
Khi bạn mở một web, đích là IP:443 (cổng cố định của server), còn nguồn là IP_của_bạn:<port tạm> — OS tự chọn một ephemeral port cho kết nối đó.
3. Socket — điểm cuối kết nối
Socket là giao diện hệ điều hành cấp cho ứng dụng để gửi/nhận qua mạng — gắn với một cặp (IP, port). Có hai vai trò:
- Listening socket: server gọi
bind(IP, port)rồilisten()— "tôi nhận kết nối ở cổng này". Ví dụ web server bind0.0.0.0:443. - Connection socket: mỗi client kết nối tới tạo ra một socket riêng cho kết nối đó (qua
accept()ở server,connect()ở client).
Một server có một listening socket nhưng nhiều connection socket — mỗi client một cái.
4. 5-tuple — vì sao một cổng phục vụ nghìn client
Đây là điểm cốt lõi. Một kết nối TCP được định danh duy nhất bởi 5 thành phần (5-tuple):
Hai client cùng kết nối tới server:443. Chúng trùng dst IP và dst port — nhưng khác src IP hoặc src port. Vậy 5-tuple của hai kết nối khác nhau → OS coi là hai kết nối tách biệt, không lẫn. Đó là lý do một cổng 443 phục vụ được vô số client:
TCP 203.0.113.7:51001 -> 10.0.0.5:443 (client A)
TCP 203.0.113.7:51002 -> 10.0.0.5:443 (client A, ket noi 2)
TCP 198.51.100.9:60200 -> 10.0.0.5:443 (client B)
Cả ba cùng 10.0.0.5:443 nhưng khác nguồn → ba kết nối độc lập.
5. localhost, 127.0.0.1 và 0.0.0.0
Khi bind, địa chỉ quyết định ai kết nối được (nối lại bài IP):
127.0.0.1(localhost) — chỉ nhận kết nối từ chính máy này. An toàn cho dịch vụ nội bộ (vd database dev).0.0.0.0— lắng nghe trên mọi interface, kể cả từ máy khác trong LAN / Internet (nếu có đường vào).
Bind nhầm 0.0.0.0 cho thứ đáng lẽ chỉ nội bộ là một lỗi bảo mật phổ biến — vô tình mở dịch vụ ra ngoài.
6. EADDRINUSE — vì sao "address already in use"
Hai tiến trình không thể cùng bind một cặp IP:port. Chạy hai server trên :8080 → cái thứ hai báo EADDRINUSE. Tìm thủ phạm:
ss -tlnp # liet ke socket dang LISTEN + tien trinh
# LISTEN 0 511 0.0.0.0:8080 ... users:(("node",pid=4321,...))
lsof -i :8080 # cach khac, theo cong
Đôi khi cổng vẫn "bận" một lúc sau khi tắt server do trạng thái TIME_WAIT của TCP — chi tiết ở course TCP. Tạm thời: chờ vài giây hoặc dùng option tái sử dụng địa chỉ.
7. Pitfall — hiểu nhầm thường gặp
❌ Nhầm 1: "Một cổng chỉ phục vụ được một client một lúc." ✅ Một listening socket trên cổng 443 đẻ ra nhiều connection socket, mỗi client một cái, phân biệt bằng 5-tuple. Một cổng phục vụ vô số kết nối đồng thời.
❌ Nhầm 2: "localhost:3000 thì ai cũng vào được."
✅ Bind 127.0.0.1:3000 chỉ chấp nhận kết nối từ chính máy đó. Muốn máy khác vào phải bind 0.0.0.0 (và có đường mạng/port forwarding).
❌ Nhầm 3: "EADDRINUSE là bug của code tôi."
✅ Thường là một tiến trình khác (hoặc lần chạy trước chưa thoát hẳn) đang giữ cổng, hoặc TIME_WAIT. Dùng ss -tlnp/lsof -i tìm tiến trình giữ cổng trước khi nghi code.
8. 📚 Deep Dive — tài liệu gốc
Đọc khi muốn tới gốc port/socket:
- RFC 9293 — TCP — khái niệm port và cách một kết nối được định danh.
- IANA Service Name and Port Registry — danh sách well-known port chính thức.
- Berkeley sockets API —
socket(),bind(),listen(),accept(),connect().
Ghi chú: "Socket" vừa là khái niệm (điểm cuối (IP, port)) vừa là API hệ điều hành. Chi tiết vòng đời socket TCP sẽ học ở course TCP.
9. Tóm tắt
- Port (0–65535) định danh ứng dụng trên một máy; well-known (0–1023), registered, ephemeral (OS cấp cho client).
- Socket là điểm cuối
(IP, port): một listening socket ở server, nhiều connection socket cho từng client. - 5-tuple
(protocol, src IP, src port, dst IP, dst port)định danh duy nhất mỗi kết nối — nên một cổng 443 phục vụ được vô số client (khác src). - Bind
127.0.0.1= chỉ nội bộ;0.0.0.0= mọi interface (cẩn thận lộ dịch vụ). - Hai tiến trình không bind cùng
IP:port→EADDRINUSE; soi bằngss -tlnp/lsof -i.
10. Tự kiểm tra
Q1Tới đúng máy bằng IP rồi, vì sao vẫn cần port?▸
Q2Một web server chỉ lắng nghe cổng 443 nhưng phục vụ hàng nghìn client cùng lúc mà không lẫn. Cơ chế nào cho phép?▸
Nhờ 5-tuple: mỗi kết nối được định danh bởi (protocol, src IP, src port, dst IP, dst port). Các client tới cùng server:443 trùng dst IP + dst port, nhưng khác src IP hoặc src port → 5-tuple khác nhau → OS coi là các kết nối tách biệt.
Về socket: server có một listening socket trên 443, nhưng mỗi client tạo một connection socket riêng. Một cổng, vô số kết nối.
Q3Bind server vào 127.0.0.1 khác 0.0.0.0 thế nào, và vì sao nhầm lẫn có thể thành lỗ hổng?▸
127.0.0.1 (loopback) chỉ chấp nhận kết nối từ chính máy này — hợp cho dịch vụ nội bộ như database dev. 0.0.0.0 lắng nghe trên mọi interface, tức máy khác trong LAN (hoặc Internet nếu có port forwarding) cũng kết nối được. Bind nhầm 0.0.0.0 cho thứ đáng lẽ chỉ nội bộ vô tình phơi dịch vụ ra ngoài — một lỗi bảo mật phổ biến.Q4Bạn chạy server thì gặp `EADDRINUSE`. Nên kiểm tra gì trước khi nghi code?▸
EADDRINUSE nghĩa là cặp IP:port đã bị một socket khác giữ — hai tiến trình không thể bind cùng địa chỉ:cổng. Trước khi nghi code, dùng ss -tlnp hoặc lsof -i :PORT để tìm tiến trình nào đang giữ cổng (có thể là lần chạy trước chưa thoát, hoặc app khác). Cũng có thể cổng còn vướng TIME_WAIT sau khi server tắt — chờ vài giây hoặc bật tái sử dụng địa chỉ.Q5Phân biệt listening socket và connection socket.▸
bind() + listen() để "nhận kết nối ở cổng này" (vd 0.0.0.0:443). Connection socket: với mỗi client kết nối tới, accept() tạo một socket riêng đại diện cho kết nối đó. Một server có một listening socket nhưng nhiều connection socket — mỗi client một cái, phân biệt bằng 5-tuple.Q6Khi bạn mở một kết nối tới `server:443`, vì sao client không cần tự chọn một cổng cố định, và cổng nguồn đó từ đâu ra?▸
server:443 không lẫn. Nếu client bị buộc dùng một cổng cố định, nó chỉ mở được một kết nối tới mỗi đích cùng lúc; ephemeral port cho phép mở nhiều kết nối song song tới cùng server.Bài tiếp theo: DNS — phân giải tên miền hoạt động thế nào
Bài này có giúp bạn hiểu bản chất không?
Hỏi đáp về bài này
Chưa có câu hỏi
Có gì chưa rõ trong bài? Đặt câu hỏi đầu tiên — câu trả lời từ cộng đồng giúp bạn (và người sau).
Đặt câu hỏi đầu tiên