Cách xây dựng máy chủ TCP thô khi mỗi mili giây đều quan trọng
TCP cực kỳ nhanh. Khi mỗi mili-giây đều quan trọng - trong các hệ thống thời gian thực, công cụ cơ sở dữ liệu, bộ nhớ đệm, message brokers, phần mềm quan trọng – các hệ thống này giao tiếp bằng TCP thô.
Bởi vì khi tốc độ và khả năng kiểm soát là ưu tiên hàng đầu, thì sự trừu tượng chính là kẻ thù.
HTTP, dù đáng tin cậy, nhưng đi kèm với chi phí. Nó hoạt động trên TCP và thêm nhiều lớp xử lý: headers, footers, mã hóa, giải mã – độ trễ bắt đầu xuất hiện.
Chi phí của HTTP Overhead
Hãy xem điều gì xảy ra bên trong khi một HTTP response được gửi đi:
StatusCode : 200
StatusDescription : OK
Content : {72, 101, 108, 108...}
RawContent : HTTP/1.1 200 OK
Content-Length: 12
Hello world!
Headers : {[Content-Length, 12]}
RawContentLength : 12
HTTP là giao thức dựa trên văn bản (text-based). Nó yêu cầu phân tích cú pháp, xử lý bổ sung và nhiều bước trước khi dữ liệu thực sự được truyền qua mạng.
So sánh với TCP thô:
const data = Buffer.from("hello")
tcpserverClientConnection.write(data)
Ta thấy rằng:
- Không có overhead.
- Không cần mã hóa hoặc giải mã thêm.
- Không có giao thức phức tạp.
- Chỉ đơn thuần là dữ liệu, đi từ điểm A đến điểm B nhanh nhất có thể.
UDP thậm chí còn nhanh hơn TCP – được sử dụng trong các ứng dụng thời gian thực như trò chơi P2P và phát trực tiếp video. Nhưng UDP đánh đổi độ tin cậy để lấy tốc độ.
TCP: Ba tầng dưới HTTP
Càng đi sâu vào hệ thống, bạn càng có nhiều quyền kiểm soát:
HTTP
└── Encoding/Decoding
└── Binary/Buffers
└── TCP
Nếu bạn muốn trở thành một kỹ sư hơn là chỉ xây dựng CRUD, nếu bạn muốn thiết kế các hệ thống mạng – thì việc hiểu TCP là điều bắt buộc.
TCP: Giới thiệu thực tiễn
Một trong những điều mạnh mẽ nhất về Node.js? Nó chính là những gì bạn tạo ra từ nó.
- Cần một CRUD API? ✅
- Cần một hệ thống xử lý dữ liệu? ✅
- Cần mở rộng nó bằng C++? ✅
Cách bạn nhìn nhận Node.js sẽ quyết định những gì bạn có thể xây dựng.
Bắt đầu đơn giản: Làm việc trực tiếp với bộ nhớ thô
const data = Buffer.alloc(4)
data.writeInt32BE(7) // Store the number 7 as raw binary
Chuyện gì đang xảy ra ở đây?
- Cấp phát 4 byte (32 bit) bộ nhớ.
- Ghi số nguyên 32-bit (7) vào bộ nhớ dưới dạng dữ liệu thô.
Đây là JavaScript ở cấp độ thấp hơn.
Bây giờ, hãy đi sâu hơn và xây dựng một Raw TCP Server!
Viết máy chủ TCP trong Node.js
Dưới đây là cách bạn có thể tạo một Raw TCP Server trong Node.js:
// server.js
const net = require("node:net")
const server = net.createServer((c) => {
c.on("data", (data) => console.log(data))
c.on("error", (err) => console.log(err))
c.write("hello world") // Implicitly converts string to a buffer
c.end()
})
server.listen(3000)
Chạy máy chủ và thử kết nối bằng curl
curl http://localhost:3000
ERROR: Lỗi có thể gặp phải
curl: The server committed a protocol violation.
Tại sao? Vì chúng ta đang giao tiếp bằng TCP thô, trong khi curl mong đợi một giao thức HTTP có headers, status codes và structured responses.
Dưới đây là một TCP Client để kết nối với máy chủ của chúng ta:
//client.js
const net = require("node:net")
const c = net.createConnection({ port: 3000, host: "localhost" })
c.on("data", (data) => console.log(data))
c.write("hello")
Chạy máy chủ và máy khách, và đây là kết quả:
<Buffer 48 65 6c 6c 6f 20 77 6f 72 6c 64 21>
Mặc dù dữ liệu được gửi đi dưới dạng raw bytes, nhưng vì chỉ là chuỗi (string), nên chúng ta có thể dễ dàng giải mã nó:
c.on("data", (data) => {
console.log(data.toString()) // "hello world"
})
Sức mạnh của Raw TCP
Tại sao điều này quan trọng?
Tại sao bạn nên quan tâm đến TCP thô?
Bởi vì một số hệ thống huyền thoại mà bạn sử dụng hàng ngày đều chạy trên TCP thuần túy, không cần HTTP:
- MySQL clients – Giao tiếp trực tiếp với cơ sở dữ liệu.
- RabbitMQ – Hệ thống hàng đợi tin nhắn (message queue) tốc độ cao.
- Redis – Bộ nhớ đệm hiệu suất cao, giao tiếp bằng TCP raw commands.
- Neo4j – Cơ sở dữ liệu đồ thị mạnh mẽ.
- Email clients (SMTP, IMAP, POP3) – Gửi và nhận email thông qua các giao thức TCP chuyên biệt.
Không có những thứ thừa thãi của HTTP:
✅ Không cookies ✅ Không headers ✅ Không cần JSON parsing
Chỉ là dữ liệu thô, nhanh chóng, giao thức tùy chỉnh, giúp tối ưu hóa hiệu suất.
Xây dựng một giao thức tùy chỉnh
Một giao thức đơn giản chỉ là một thỏa thuận giữa máy khách (client) và máy chủ (server): "Khi bạn gửi dữ liệu cho tôi, tôi mong đợi nó theo định dạng này, nếu không tôi sẽ từ chối."
Dưới đây là một HTTP request cơ bản:
HTTP/1.1 200 OK
Content-Length: 12
Hello world!
Hãy xây dựng giao thức nhị phân đơn giản của riêng mình dựa trên Raw TCP.
Xác định giao thức
Chúng ta sẽ cấu trúc bộ đệm của mình như thế này:
buffer = msg length (4 bytes) | msg (variable length) | metadata (variable length)
Trong đó:
- 4 byte đầu tiên → Độ dài của tin nhắn
- Các byte tiếp theo → Nội dung tin nhắn
- Các byte còn lại → Metadata (dữ liệu bổ sung)
Dưới đây là cách chúng ta có thể mã hóa dữ liệu trước khi gửi đi:
const server = net.createServer((c) => {
c.on("data", (data) => console.log(data.toString()))
const data = Buffer.from("hello world") // Encode message
const metadata = Buffer.from([0x00]) // No metadata
const len = Buffer.alloc(4) // Allocate 4 bytes for length
len.writeInt32BE(data.length, 0) // Store message length
const combinedBuffer = Buffer.concat([len, data, metadata])
c.write(combinedBuffer) // Send to client
c.end()
})
Bây giờ, ở phía máy khách , chúng ta sẽ giải mã thông điệp có cấu trúc này:
const c = net.createConnection({ port: 3000, host: "localhost" })
c.on("data", (data) => {
console.log(data)
const len = data.readInt32BE(0) // Read message length
console.log(len)
const msg = data.subarray(4, 4 + len) // Extract message
console.log(msg.toString())
const metadata = data.subarray(4 + len) // Extract metadata
console.log(metadata)
})
c.write("hello")
Và cứ như thế, chúng ta đã xây dựng được một hệ thống nhắn tin TCP có cấu trúc.
All rights reserved
Bình luận