Nền tảng Mạng máy tính/Port & socket — localhost:8080 thực sự là gì
14/17
Bài 14 / 17~22 phútPort, Socket & DNSMiễn phí lượt xem

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:portEADDRINUSE. 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 tyMạ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ộc5-tuple phân biệt từng kết nối
💡 Cách nhớ

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ómDảiVí dụ
Well-known0–102380 (HTTP), 443 (HTTPS), 22 (SSH), 53 (DNS)
Registered1024–491515432 (PostgreSQL), 3306 (MySQL), 6379 (Redis)
Ephemeral (tạm)49152–65535port nguồn OS tự cấp cho client

Khi bạn mở một web, đíchIP:443 (cổng cố định của server), còn nguồnIP_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ồi listen() — "tôi nhận kết nối ở cổng này". Ví dụ web server bind 0.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):

protocol
TCP
src IP
client
src port
tạm, mỗi kết nối khác
dst IP
server
dst port
443 (cố định)

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

📚 Spec & reference chính thức

Đọc khi muốn tới gốc port/socket:

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:portEADDRINUSE; soi bằng ss -tlnp / lsof -i.

10. Tự kiểm tra

Tự kiểm tra
Q1
Tới đúng máy bằng IP rồi, vì sao vẫn cần port?
Vì một máy chạy nhiều dịch vụ cùng lúc (web 443, SSH 22, database 5432). IP chỉ đưa gói tới đúng máy; port cho biết gói thuộc về ứng dụng nào trên máy đó. Không có port, OS không biết giao dữ liệu cho tiến trình nào. Ví như IP là số điện thoại công ty, port là máy lẻ phòng ban.
Q2
Mộ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.

Q3
Bind 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.
Q4
Bạ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ỉ.
Q5
Phân biệt listening socket và connection socket.
Listening socket: server tạo một lần qua 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.
Q6
Khi 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?
Phía client, OS tự cấp một ephemeral port (cổng tạm, dải 49152–65535) làm src port cho mỗi kết nối. Client không cần bind cổng cố định vì kết nối đã được định danh duy nhất bởi 5-tuple — chỉ cần src port khác nhau là hai kết nối tới cùng 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

Đặt 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