Đối với đề xuất của tôi, vui lòng đọc phần cuối cùng: “Khi nào sử dụng SO_LINGER với thời gian chờ 0” .
Trước khi chúng ta đến với bài giảng nhỏ về:
- Kết thúc TCP bình thường
TIME_WAIT
FIN
, ACK
vàRST
Kết thúc TCP bình thường
Trình tự kết thúc TCP bình thường trông giống như sau (đơn giản hóa):
Chúng tôi có hai đồng nghiệp: A và B
- Một cuộc gọi
close()
- A gửi
FIN
cho B
- A đi vào
FIN_WAIT_1
trạng thái
- B nhận
FIN
- B gửi
ACK
cho A
- B đi vào
CLOSE_WAIT
trạng thái
- A nhận được
ACK
- A đi vào
FIN_WAIT_2
trạng thái
- B cuộc gọi
close()
- B gửi
FIN
cho A
- B đi vào
LAST_ACK
trạng thái
- A nhận được
FIN
- A gửi
ACK
cho B
- A đi vào
TIME_WAIT
trạng thái
- B nhận
ACK
- B chuyển sang
CLOSED
trạng thái - tức là bị xóa khỏi bảng socket
THỜI GIAN CHỜ ĐỢI
Vì vậy, peer bắt đầu kết thúc - tức là các cuộc gọi close()
trước - sẽ kết thúc ở TIME_WAIT
trạng thái.
Để hiểu tại sao TIME_WAIT
nhà nước là bạn của chúng ta, vui lòng đọc phần 2.7 trong "Lập trình mạng UNIX" ấn bản thứ ba của Stevens và cộng sự (trang 43).
Tuy nhiên, nó có thể là một vấn đề với nhiều ổ cắm ở TIME_WAIT
trạng thái trên một máy chủ vì nó cuối cùng có thể ngăn các kết nối mới được chấp nhận.
Để giải quyết vấn đề này, tôi đã thấy nhiều người đề xuất đặt tùy chọn ổ cắm SO_LINGER với thời gian chờ 0 trước khi gọi close()
. Tuy nhiên, đây là một giải pháp không tốt vì nó khiến kết nối TCP bị lỗi.
Thay vào đó, hãy thiết kế giao thức ứng dụng của bạn để việc kết thúc kết nối luôn được bắt đầu từ phía máy khách. Nếu máy khách luôn biết khi nào nó đã đọc tất cả dữ liệu còn lại, nó có thể bắt đầu trình tự kết thúc. Ví dụ, một trình duyệt biết từ Content-Length
tiêu đề HTTP khi nó đã đọc tất cả dữ liệu và có thể bắt đầu đóng. (Tôi biết rằng trong HTTP 1.1, nó sẽ giữ nó mở trong một thời gian để có thể sử dụng lại và sau đó đóng nó.)
Nếu máy chủ cần đóng kết nối, hãy thiết kế giao thức ứng dụng để máy chủ yêu cầu máy khách gọi close()
.
Khi nào sử dụng SO_LINGER với thời gian chờ 0
Một lần nữa, theo "Lập trình mạng UNIX" phiên bản thứ ba trang 202-203, việc thiết lập SO_LINGER
thời gian chờ 0 trước khi gọi close()
sẽ khiến trình tự kết thúc bình thường không được bắt đầu.
Thay vào đó, thiết lập ngang hàng tùy chọn này và gọi close()
sẽ gửi RST
(đặt lại kết nối) cho biết điều kiện lỗi và đây là cách nó sẽ được nhận biết ở đầu bên kia. Bạn thường sẽ thấy các lỗi như "Thiết lập lại kết nối bởi ngang hàng".
Do đó, trong tình huống bình thường, thực sự là một ý tưởng tồi nếu đặt SO_LINGER
thời gian chờ là 0 trước khi gọi close()
- từ bây giờ được gọi là đóng hủy bỏ - trong một ứng dụng máy chủ.
Tuy nhiên, dù sao thì một số tình huống nhất định cũng đảm bảo làm như vậy:
- Nếu một ứng dụng khách của ứng dụng máy chủ của bạn hoạt động sai (hết thời gian, trả về dữ liệu không hợp lệ, v.v.) thì việc hủy bỏ là hợp lý để tránh bị mắc kẹt
CLOSE_WAIT
hoặc kết thúc ở TIME_WAIT
trạng thái.
- Nếu bạn phải khởi động lại ứng dụng máy chủ của mình hiện có hàng nghìn kết nối máy khách, bạn có thể cân nhắc đặt tùy chọn ổ cắm này để tránh hàng nghìn ổ cắm máy chủ trong
TIME_WAIT
(khi gọi close()
từ cuối máy chủ) vì điều này có thể ngăn máy chủ nhận được các cổng khả dụng cho các kết nối máy khách mới sau khi được khởi động lại.
- Tại trang 202 trong cuốn sách nói trên, nó nói cụ thể: "Có một số trường hợp đảm bảo sử dụng tính năng này để gửi một kết thúc hủy bỏ. Một ví dụ là máy chủ đầu cuối RS-232, có thể treo mãi mãi khi
CLOSE_WAIT
cố gắng cung cấp dữ liệu đến một thiết bị đầu cuối bị kẹt nhưng sẽ đặt lại đúng cách cổng bị kẹt nếu nó có RST
quyền loại bỏ dữ liệu đang chờ xử lý. "
Tôi muốn giới thiệu này bài viết dài mà tôi tin rằng đưa ra một câu trả lời rất tốt cho câu hỏi của bạn.