Khi nào cần có tùy chọn TCP SO_LINGER (0)?


95

Tôi nghĩ rằng tôi hiểu ý nghĩa chính thức của tùy chọn. Trong một số mã kế thừa mà tôi đang xử lý bây giờ, tùy chọn được sử dụng. Khách hàng phàn nàn về RST là phản hồi đối với FIN từ phía họ khi kết nối gần với phía họ.

Tôi không chắc mình có thể gỡ bỏ nó một cách an toàn, vì tôi không hiểu khi nào thì nên sử dụng nó.

Bạn có thể vui lòng cho một ví dụ về thời điểm tùy chọn sẽ được yêu cầu?


1
Bạn nên loại bỏ nó. Nó không nên được sử dụng trong mã sản xuất. Lần duy nhất tôi từng thấy nó được sử dụng là do điểm chuẩn không hợp lệ.
Marquis of Lorne,

Câu trả lời:


82

Lý do điển hình để đặt SO_LINGERthời gian chờ bằng 0 là để tránh số lượng lớn các kết nối đang ở TIME_WAITtrạng thái, buộc tất cả các tài nguyên có sẵn trên một máy chủ.

Khi kết nối TCP được đóng hoàn toàn, phần cuối bắt đầu quá trình đóng ("hoạt động đóng") sẽ kết thúc với việc kết nối ở trong TIME_WAITvài phút. Vì vậy, nếu giao thức của bạn là một trong đó máy chủ khởi tạo kết nối đóng và liên quan đến số lượng rất lớn các kết nối tồn tại trong thời gian ngắn, thì nó có thể dễ bị sự cố này.

Tuy nhiên, đây không phải là một ý tưởng hay - TIME_WAITtồn tại là có lý do (để đảm bảo rằng các gói tin lạc từ các kết nối cũ không gây trở ngại cho các kết nối mới). Tốt hơn hết là bạn nên thiết kế lại giao thức của mình thành một giao thức mà máy khách bắt đầu đóng kết nối, nếu có thể.


3
Tôi hoàn toàn đồng ý. Tôi đã thấy một ứng dụng giám sát đang khởi tạo nhiều (vài nghìn kết nối tồn tại ngắn ngủi sau mỗi X giây) và nó có khả năng mở rộng quy mô lớn hơn (thêm một nghìn kết nối). Tôi không biết tại sao, nhưng ứng dụng không phản hồi. Ai đó đã đề xuất SO_LINGER = true, TIME_WAIT = 0 để giải phóng tài nguyên hệ điều hành một cách nhanh chóng và sau khi điều tra ngắn, chúng tôi đã thử giải pháp này với kết quả rất tốt. TIME_WAIT không còn là vấn đề đối với ứng dụng này.
bartosz.r

24
Tôi không đồng ý. Một giao thức cấp ứng dụng nằm trên TCP phải được thiết kế theo cách mà máy khách luôn khởi tạo kết nối đóng. Bằng cách đó, ý TIME_WAITchí ngồi vào khách hàng không gây hại gì. Hãy nhớ như nó nói trong "Lập trình mạng UNIX" ấn bản thứ ba (Stevens và cộng sự) trang 203: "Trạng thái TIME_WAIT là bạn của bạn và ở đó để giúp chúng tôi. Thay vì cố gắng tránh trạng thái, chúng ta nên hiểu nó (Phần 2.7) . "
mgd

8
Điều gì sẽ xảy ra nếu một khách hàng muốn mở 4000 kết nối sau mỗi 30 giây (ứng dụng giám sát này là một ứng dụng khách! Vì nó khởi tạo kết nối)? Có, chúng tôi có thể thiết kế lại ứng dụng, thêm một số đại lý địa phương trong cơ sở hạ tầng, thay đổi mô hình để thúc đẩy. Nhưng nếu chúng ta đã có một ứng dụng như vậy và nó phát triển, thì chúng ta có thể làm cho nó hoạt động bằng cách điều chỉnh tweet kéo dài. Bạn thay đổi một tham số và bạn đột nhiên có ứng dụng hoạt động mà không cần đầu tư ngân sách để triển khai kiến ​​trúc mới.
bartosz.r

3
@ bartosz.r: Tôi chỉ nói rằng sử dụng SO_LINGER với thời gian chờ 0 thực sự nên là phương sách cuối cùng. Một lần nữa, trong ấn bản thứ ba "Lập trình mạng UNIX" (Stevens và cộng sự) trang 203 nó cũng nói rằng bạn có nguy cơ bị hỏng dữ liệu. Hãy cân nhắc đọc RFC 1337 nơi bạn có thể biết lý do TIME_WAIT là bạn của bạn.
mgd 30/10/12

7
@caf Không, giải pháp cổ điển sẽ là một nhóm kết nối, như đã thấy trong mọi API TCP hạng nặng, ví dụ như HTTP 1.1.
Marquis of Lorne,

188

Đố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, ACKRST

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

  1. Một cuộc gọi close()
    • A gửi FINcho B
    • A đi vào FIN_WAIT_1trạng thái
  2. B nhận FIN
    • B gửi ACKcho A
    • B đi vào CLOSE_WAITtrạng thái
  3. A nhận được ACK
    • A đi vào FIN_WAIT_2trạng thái
  4. B cuộc gọi close()
    • B gửi FINcho A
    • B đi vào LAST_ACKtrạng thái
  5. A nhận được FIN
    • A gửi ACKcho B
    • A đi vào TIME_WAITtrạng thái
  6. B nhận ACK
    • B chuyển sang CLOSEDtrạ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_WAITtrạng thái.

Để hiểu tại sao TIME_WAITnhà 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_WAITtrạ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-Lengthtiê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_LINGERthờ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_LINGERthờ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_WAIThoặc kết thúc ở TIME_WAITtrạ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_WAITcố 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ó RSTquyề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.


6
TIME_WAITchỉ là bạn khi nó không bắt đầu gây ra vấn đề: stackoverflow.com/questions/1803566/…
Pacerier

2
vậy nếu bạn đang viết một máy chủ web thì sao? làm thế nào để bạn "yêu cầu khách hàng bắt đầu đóng cửa"?
Shaun Neal

2
@ShaunNeal bạn rõ ràng là không. Nhưng một ứng dụng / trình duyệt được viết tốt sẽ bắt đầu quá trình đóng. Nếu máy khách không hoạt động tốt, thật may mắn là chúng tôi đã có TIME_WAIT ám sát để đảm bảo chúng tôi không hết bộ mô tả ổ cắm và cổng tạm thời.
mgd

16

Khi nán lại được bật nhưng thời gian chờ bằng 0, ngăn xếp TCP không đợi dữ liệu đang chờ được gửi trước khi đóng kết nối. Dữ liệu có thể bị mất do điều này nhưng bằng cách cài đặt kéo dài theo cách này, bạn đang chấp nhận điều này và yêu cầu kết nối được đặt lại ngay lập tức thay vì đóng một cách dễ dàng. Điều này khiến RST được gửi thay vì FIN thông thường.

Cảm ơn EJP đã bình luận, xem chi tiết tại đây .


1
Tôi hiểu điều này. những gì tôi yêu cầu là cho ví dụ "thực tế" khi chúng tôi muốn sử dụng khôi phục cài đặt gốc.
dimba

5
Bất cứ khi nào bạn muốn hủy bỏ một kết nối; vì vậy nếu giao thức của bạn không hợp lệ và bạn có một khách hàng nói chuyện rác tại bạn đột nhiên bạn muốn hủy bỏ kết nối với một RST vv
Len Holgate

5
Bạn đang nhầm lẫn giữa thời gian chờ kéo dài bằng 0 với thời gian chờ kéo dài. Nán lại có nghĩa là close () không chặn. Giữ nguyên với thời gian chờ dương nghĩa là đóng () chặn đến hết thời gian chờ. Tiếp tục với thời gian chờ bằng 0 gây ra RST, và đây là câu hỏi đặt ra.
Marquis of Lorne,

2
Vâng, bạn chính xác. Tôi sẽ điều chỉnh câu trả lời để sửa lại thuật ngữ của mình.
Len Holgate

6

Việc bạn có thể loại bỏ phần còn sót lại trong mã của mình một cách an toàn hay không phụ thuộc vào loại ứng dụng của bạn: đó là “máy khách” (mở kết nối TCP và chủ động đóng nó trước) hay là “máy chủ” (nghe TCP mở và đóng nó sau khi bên kia bắt đầu đóng)?

Nếu ứng dụng của bạn có hương vị của một “máy khách” (đóng trước) VÀ bạn khởi tạo và đóng một số lượng lớn các kết nối đến các máy chủ khác nhau (ví dụ: khi ứng dụng của bạn là một ứng dụng giám sát giám sát khả năng truy cập của một số lượng lớn các máy chủ khác nhau) thì ứng dụng của bạn có vấn đề là tất cả các kết nối máy khách của bạn bị kẹt ở trạng thái TIME_WAIT. Sau đó, tôi khuyên bạn nên rút ngắn thời gian chờ xuống giá trị nhỏ hơn giá trị mặc định để vẫn tắt một cách duyên dáng nhưng giải phóng tài nguyên kết nối máy khách sớm hơn. Tôi sẽ không đặt thời gian chờ thành 0, vì 0 không tắt một cách duyên dáng với FIN nhưng hủy bỏ với RST.

Nếu ứng dụng của bạn có hương vị của một “máy khách” và phải tìm nạp một lượng lớn tệp nhỏ từ cùng một máy chủ, bạn không nên khởi tạo kết nối TCP mới trên mỗi tệp và kết thúc bằng một lượng lớn kết nối máy khách trong TIME_WAIT, nhưng giữ cho kết nối luôn mở và tìm nạp tất cả dữ liệu qua cùng một kết nối. Tùy chọn nán lại có thể và nên được loại bỏ.

Nếu ứng dụng của bạn là “máy chủ” (đóng thứ hai khi phản ứng với ứng dụng đóng của ứng dụng ngang hàng), khi đóng () kết nối của bạn sẽ tắt một cách duyên dáng và tài nguyên được giải phóng khi bạn không vào trạng thái TIME_WAIT. Nán lại không nên được sử dụng. Nhưng nếu ứng dụng sever của bạn có một quy trình giám sát phát hiện các kết nối mở không hoạt động ở chế độ chờ trong một thời gian dài (định nghĩa là "dài"), bạn có thể tắt kết nối không hoạt động này từ phía mình - xem nó như một loại xử lý lỗi - với việc tắt tạm thời. Điều này được thực hiện bằng cách đặt thời gian chờ kéo dài thành 0. close () sau đó sẽ gửi RST cho khách hàng, nói với anh ta rằng bạn đang tức giận :-)


1

Trong máy chủ, bạn có thể muốn gửi RSTthay vì FINkhi ngắt kết nối các máy khách hoạt động sai. Điều đó bỏ qua FIN-WAITtheo sau là TIME-WAITtrạng thái ổ cắm trong máy chủ, điều này ngăn chặn việc làm cạn kiệt tài nguyên máy chủ và do đó, bảo vệ khỏi loại tấn công từ chối dịch vụ này.


0

Tôi thích quan sát của Maxim rằng các cuộc tấn công DOS có thể làm cạn kiệt tài nguyên máy chủ. Nó cũng xảy ra mà không có kẻ thù thực sự độc hại.

Một số máy chủ phải đối phó với 'cuộc tấn công DOS không chủ ý' xảy ra khi ứng dụng khách có lỗi rò rỉ kết nối, nơi chúng tiếp tục tạo kết nối mới cho mọi lệnh mới mà chúng gửi đến máy chủ của bạn. Và sau đó có lẽ cuối cùng sẽ đóng các kết nối của họ nếu họ gặp phải áp lực GC, hoặc có lẽ các kết nối cuối cùng đã hết thời gian.

Một kịch bản khác là khi "tất cả các máy khách có cùng một địa chỉ TCP". Sau đó, các kết nối máy khách chỉ có thể phân biệt được bằng số cổng (nếu chúng kết nối với một máy chủ duy nhất). Và nếu các máy khách bắt đầu xoay vòng nhanh chóng các kết nối mở / đóng vì bất kỳ lý do gì, chúng có thể làm cạn kiệt bộ không gian (cổng thêm + cổng, IP máy chủ + cổng).

Vì vậy, tôi nghĩ rằng các máy chủ có thể được khuyên tốt nhất nên chuyển sang chiến lược Linger-Zero khi chúng thấy số lượng ổ cắm cao ở trạng thái TIME_WAIT - mặc dù nó không khắc phục được hành vi của máy khách, nhưng nó có thể làm giảm tác động.


0

Ổ cắm lắng nghe trên máy chủ có thể sử dụng kéo dài với thời gian 0 để có quyền truy cập liên kết trở lại ổ cắm ngay lập tức và để đặt lại bất kỳ máy khách nào có kết nối chưa kết nối xong. TIME_WAIT là một cái gì đó chỉ thú vị khi bạn có một mạng đa đường và có thể kết thúc với các gói không có thứ tự hoặc nếu không thì đang xử lý thứ tự gói / thời gian đến của mạng lẻ.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.