Nó luôn làm tôi thất vọng về việc có bao nhiêu người quá đơn giản hóa sự khác biệt giữa TCP và UDP là "TCP đáng tin cậy nhưng chậm, trong khi UDP nhanh nhưng không đáng tin cậy".
Đây không phải là những gì TCP và UDP nói về. TCP và UDP là hai khái niệm trừu tượng khác nhau trên IP và được sử dụng cho những thứ khác nhau. Cả hai rất tốt trong lĩnh vực riêng của họ.
TCP là một giao thức định hướng luồng . Bạn sử dụng TCP khi bạn muốn chuyển nhiều dữ liệu tuần tự từ điểm này sang điểm khác, tối đa hóa thông lượng (nghĩa là giảm thiểu thời gian bạn phải gửi - tất cả dữ liệu). Việc tự động gửi lại các datagram bị mất và sắp xếp lại là một trong những tính năng của TCP, nhưng TCP cũng có các kết nối trạng thái, kiểm soát luồng và kiểm soát tắc nghẽn. Các tính năng này làm cho TCP trở thành một công cụ thực sự tốt để truyền một lượng lớn dữ liệu từ điểm này sang điểm khác. Các giao thức như HTTP và FTP nơi bạn muốn gửi khối dữ liệu lớn càng nhanh càng tốt để sử dụng TCP.
Mặt khác, UDP là một giao thức hướng thông điệp . Bạn sử dụng UDP khi bạn muốn gửi tin nhắn từ một điểm đến một hoặc nhiều điểm, giảm thiểu độ trễ (nghĩa là khoảng thời gian giữa một tin nhắn được gửi cho đến khi nhận được tin nhắn). Để giảm thiểu độ trễ, UDP không triển khai truyền lại hoặc sắp xếp lại datagram. Tuy nhiên, nếu bạn muốn ứng dụng dựa trên tin nhắn của mình hỗ trợ truyền lại và / hoặc sắp xếp lại, bạn có thể tự thực hiện điều đó và thực sự khá đơn giản! Ngoài ra, có một loạt những điều thú vị bạn có thể làm trong UDP mà bạn đơn giản không thể làm với TCP, như đục lỗ đa tuyến và UDP.
TCP và UDP đều là những công nghệ tuyệt vời dành cho những thứ khác nhau, vì vậy điều bạn muốn làm là tự hỏi liệu các giao tiếp trong chương trình của bạn được thể hiện tốt hơn dưới dạng một luồng byte hay một chuỗi các thông điệp và một quy tắc tốt Là:
Nếu chương trình của bạn là về việc gửi tin nhắn, hãy sử dụng UDP.
Nếu chương trình của bạn sắp gửi một luồng byte, hãy sử dụng TCP.
Đây là quyết định giữa TCP và UDP phải như thế nào, không phải về độ tin cậy, mà chỉ là một trong nhiều điểm khác biệt giữa chúng và theo tôi là quan trọng nhất trong tất cả chúng.
Giống như có thể thực hiện một sự trừu tượng hóa luồng trên UDP, có thể (và thật đáng buồn là rất phổ biến) để tạo một thông điệp trừu tượng hóa trên TCP.
Nếu bạn chọn tạo một thông điệp trừu tượng trên TCP, hãy nhớ rằng bạn sẽ phải sử dụng TCP theo cách không có nghĩa, điều đó có nghĩa là bạn sẽ gặp một số khó khăn. Đặc biệt:
Ranh giới thông báo: TCP coi dữ liệu là một luồng byte rất dài và do đó, không có khái niệm về gói hoặc thông điệp. Nếu bạn muốn gửi tin nhắn qua TCP, bạn sẽ phải tạo một số loại tiêu đề thư bao gồm độ dài của mỗi tin nhắn.
Bộ đệm khi gửi: TCP hy vọng bạn có thể tạo dữ liệu nhanh nhất có thể. Các datagram TCP có thể rất lớn và nếu điều kiện mạng tốt, một ngăn xếp TCP sẽ đệm các byte của bạn cho đến khi nó có thể gửi một datagram rất lớn do đó tối đa hóa thông lượng. Đây là một điều tốt cho các luồng, nhưng không quá nhiều cho các tin nhắn. Một khi quá nhiều lần mọi người kết thúc việc xóa bộ đệm trên mỗi lần gửi hoặc sử dụng các tùy chọn như TCP_NODELAY
, và thậm chí sau đó bạn không có gì đảm bảo. Để trích dẫn Richard Stevens quá cố :
2.11. Làm thế nào tôi có thể buộc một ổ cắm để gửi dữ liệu trong bộ đệm của nó?
Bạn không thể ép buộc nó. Giai đoạn = Stage. TCP tự quyết định khi nào nó có thể gửi dữ liệu. Bây giờ, thông thường khi bạn gọi write () trên ổ cắm TCP, TCP thực sự sẽ gửi một phân đoạn, nhưng không có gì đảm bảo và không có cách nào để buộc điều này. Có rất nhiều lý do khiến TCP sẽ không gửi một phân đoạn: một cửa sổ đóng và thuật toán Nagle là hai điều cần chú ý ngay lập tức.
Bộ đệm khi nhận: Ngay cả khi bạn quản lý để xóa bộ đệm trên mỗi lần gửi, không có gì đảm bảo rằng bạn sẽ nhận được một lần nhận cho mỗi lần gửi mà bạn gửi. Bạn hoàn toàn có thể nhận được một số tin nhắn trong một recv
cuộc gọi, bởi vì chúng được đệm ở đầu tiếp nhận (đặc biệt là trên các mạng có tốc độ mất gói cao). Sau đó, bạn phải triển khai hàng đợi nhận tin nhắn, đây là một việc khá ngu ngốc khi bạn sử dụng TCP.
Nhịp tim và phát hiện thả kết nối: TCP có cơ chế riêng để phát hiện các kết nối bị rớt, nhưng chúng thường quá chậm đối với các ứng dụng truyền tin nhắn (datagram giữ không thể cấu hình được gửi khoảng hai giờ một lần). Một khi có quá nhiều lần mọi người kết thúc việc thực hiện các giao thức tốc độ riêng của họ trong cùng một luồng TCP, và rồi cuối cùng họ lại hỏi "làm thế nào để đóng TIME_WAIT", "Cách phát hiện kết nối kín" hoặc đại loại như thế.
Không có vấn đề nào trong số này tồn tại nếu bạn tạo chương trình hướng thông điệp của mình bằng UDP:
Ranh giới tin nhắn: Mỗi tin nhắn là một datagram UDP riêng.
Bộ đệm khi gửi: Không tồn tại. Một sendto
cuộc gọi ngay lập tức gửi một datagram.
Bộ đệm khi nhận: Không tồn tại. Bạn sẽ chỉ nhận được một datagram cho mỗi recvfrom
cuộc gọi.
Phát hiện thả kết nối: UDP không được định hướng kết nối. Bạn có thể thực hiện thời gian chờ đơn giản với các tham số của riêng bạn để xem xét một khách hàng không còn ở đó nữa.
Nếu bạn muốn sử dụng TCP để truyền tin nhắn vì bạn nghĩ nó đơn giản, đừng.
Bây giờ, đối với "vấn đề" độ tin cậy đáng sợ, bạn có thể thực hiện độ tin cậy đơn giản bằng cách bao gồm hai trường trên mỗi tin nhắn: trường đầu tiên là ID tin nhắn tăng vĩnh viễn và trường thứ hai là ID tin nhắn lớn nhất bạn nhận được từ một khách hàng cụ thể. Nếu số lượng nội bộ của bạn khác quá nhiều so với những gì khách hàng nói rằng anh ta đã nhận được, bạn chỉ cần gửi lại bắt đầu từ tin nhắn đầu tiên mà khách hàng chưa nhận được. Với tính năng bao bọc, bạn có thể thực hiện việc này chỉ với hai byte bổ sung cho mỗi tin nhắn.
Điều tốt nhất về lớp độ tin cậy của riêng bạn là bạn có thể điều chỉnh nó cho bất cứ nhu cầu nào của bạn: bạn có thể có một số loại thông báo có độ tin cậy trong đó, trong khi các loại khác không có nó. Bạn có thể có nhiều kênh khác nhau, mỗi kênh có số thứ tự riêng, bạn có thể sử dụng các số đó để dự đoán độ trễ và rất nhiều nội dung tuyệt vời khác.
Đừng chạy trốn khỏi UDP vì những người khác đã nói với bạn "thật khó". Có lẽ họ chưa bao giờ (nghiêm túc) sử dụng UDP và có lẽ thậm chí chưa (nghiêm túc) sử dụng TCP. Thật ra UDP đơn giản và dễ hiểu hơn nhiều so với TCP.
Đọc lập trình mạng Unix của Stevens. Và sau đó đọc lại nó.