2010-01-28 21 views
5

Tình huống mà tôi có trong Windows XP (SP3) đã khiến tôi phát điên, và tôi sắp hết hạn, vì vậy có thể ai đó có thể cung cấp một số nguồn cảm hứng.Tại sao cửa sổ chọn() không phải lúc nào cũng thông báo cho lựa chọn của luồng B() khi luồng A đóng đầu cuối của một cặp socket?

Tôi có chương trình nối mạng C++ (không phải GUI). Chương trình này được xây dựng để biên dịch và chạy dưới Windows, MacOS/X và Linux, do đó, nó sử dụng select() và non-blocking I/O làm cơ sở cho vòng lặp sự kiện của nó.

Ngoài nhiệm vụ mạng của mình, chương trình này cần phải đọc lệnh văn bản từ stdin và thoát ra một cách duyên dáng khi stdin bị đóng. Dưới Linux và MacOS/X, thật dễ dàng - tôi chỉ bao gồm STDIN_FILENO trong fd_set đã đọc của tôi để select(), và select() trả về khi stdin được đóng lại. Tôi kiểm tra xem FD_ISSET (STDIN_FILENO, & readSet) là đúng, cố gắng đọc một số dữ liệu từ stdin, recv() trả về 0/EOF, và vì vậy tôi thoát khỏi quá trình.

Trong Windows, mặt khác, bạn không thể chọn trên STDIN_FILE_HANDLE, vì nó không phải là ổ cắm thực. Bạn cũng không thể thực hiện các lần đọc không chặn trên STDIN_FILE_HANDLE. Điều đó có nghĩa là không có cách nào để đọc stdin từ chuỗi chính, vì ReadFile() có thể chặn vô thời hạn, khiến cho luồng chính ngừng phân phối chức năng mạng của nó.

Không sao, tôi nói, tôi sẽ chỉ sinh ra một sợi chỉ để xử lý stdin cho tôi. Chủ đề này sẽ chạy trong một vòng lặp vô hạn, chặn trong ReadFile (stdinHandle), và bất cứ khi nào ReadFile() trả về dữ liệu, stdin-thread sẽ ghi dữ liệu đó vào một socket TCP. Đầu kia của kết nối của socket đó sẽ được chọn() 'd trên luồng chính, vì vậy luồng chính sẽ thấy dữ liệu stdin đến qua kết nối, và xử lý "stdin" theo cùng cách nó sẽ theo bất kỳ hệ điều hành nào khác. Và nếu ReadFile() trả về false để chỉ ra rằng stdin đã đóng, stdin-thread chỉ đóng đầu cuối của cặp socket sao cho luồng chính sẽ được thông báo qua select(), như được mô tả ở trên. Tất nhiên, Windows không có chức năng socketpair() tốt, vì vậy tôi phải tự cuộn bằng cách sử dụng listen(), connect() và accept() (như đã thấy trong hàm CreateConnectedSocketPair() here. Nhưng tôi đã làm điều đó, và dường như nó hoạt động, nói chung:

Vấn đề là nó không hoạt động 100%. Cụ thể, nếu stdin được đóng trong vòng vài trăm mili giây khi chương trình khởi động, về một nửa thời gian chủ đề chính không nhận được bất kỳ thông báo rằng stdin-end của cặp socket đã được đóng lại, ý tôi là, tôi có thể thấy (bởi printf của tôi() - gỡ lỗi) rằng stdin-thread đã gọi closesocket() trên socket của nó, và tôi có thể thấy rằng thread chính là select() - ing trên socket được kết hợp (tức là đầu kia của socket- cặp), nhưng chọn() không bao giờ trả về vì nó nên ... và nếu nó trở lại, do một số lựa chọn ổ cắm khác sẵn sàng cho bất cứ điều gì, FD_ISSET (main_thread_socket_for_socket_pair, & readSet) trả về 0, như thể kết nối không đã đóng cửa. Tại thời điểm này, giả thuyết duy nhất tôi có là có một lỗi trong triển khai select() của Windows gây ra việc chọn chủ đề chính() không để ý rằng đầu kia của cặp socket đã bị đóng bởi stdin-thread. Có lời giải thích nào khác không? (Lưu ý rằng vấn đề này đã được báo cáo trong Windows 7 là tốt, mặc dù tôi chưa xem xét nó trên nền tảng đó)

Trả lời

1

Chỉ để ghi lại, vấn đề này hóa ra lại là một vấn đề hoàn toàn khác, không liên quan đến luồng, Windows, hoặc stdin. Vấn đề thực tế là một bế tắc liên quá trình, trong đó quá trình cha mẹ bị chặn, chờ đợi các tiến trình con bỏ, nhưng đôi khi các tiến trình con sẽ bị chặn đồng thời, chờ cha mẹ cung cấp chúng với một số dữ liệu. tiến về phía trước.

Xin lỗi tất cả vì đã lãng phí thời gian của bạn cho cá trích đỏ; nếu có một cách tiêu chuẩn để đóng trường hợp này là không chính đáng, hãy cho tôi biết và tôi sẽ làm điều đó.

-Jeremy

0

Có thể bạn có điều kiện chủng tộc không? Ví dụ. Bạn có đảm bảo rằng hàm CreateConnectedSocketPair() đã được trả về trước khi stdin-thread có cơ hội thử đóng socket của nó không?

+0

Đó là một dự đoán rất tốt, nhưng tôi đã kiểm tra lại và chuỗi stdin không được khởi chạy cho đến sau khi CreateConnectedSocketPair() đã trả về thành công. Vì vậy, tôi không nghĩ rằng đó là vấn đề. –

0

Tôi đang học trong mã của bạn. Trong CreateConnectedSocketPair(), socket1 được sử dụng cho listen() và newfd được sử dụng để gửi/recv data. Vì vậy, tại sao "socket1 = newfd"? Làm thế nào để đóng listenfd sau đó?

+0

Các ổ cắm được tính tham chiếu (lưu ý rằng loại của chúng là ConstSocketRef, không phải int). Vì vậy, listenfd sẽ bị đóng khi số tham chiếu của nó rơi xuống 0 (nghĩa là bởi toán tử gán trong dòng bạn đã trích dẫn) –

0

Không phải là một giải pháp, nhưng là một giải pháp, bạn không thể gửi một số thông điệp "stdin đã đóng" ma thuật trên socket TCP và kết thúc nhận của bạn ngắt kết nối ổ cắm của nó khi nó thấy và chạy bất cứ điều gì 'stdin đã đóng' xử lý?

+0

Điều đó sẽ hoạt động trong hầu hết các trường hợp, nhưng nếu tôi muốn kết nối xử lý dữ liệu tùy ý, thì bất kỳ chuỗi "thông điệp ma thuật" nào byte có thể vô tình được gửi bởi trùng hợp ngẫu nhiên như một phần của dữ liệu thông thường được gửi qua stdin. Tôi muốn tránh khả năng đó nếu có thể. –

0

Thành thật mà nói, mã của bạn quá dài và tôi không có thời gian để chi tiêu.

Rất có thể sự cố xảy ra trong một số trường hợp việc đóng ổ cắm không gây tắt máy (FIN).

Kiểm tra các ngoại lệ trở về từ lựa chọn của bạn có thể chiếm phần còn lại của các trường hợp. Ngoài ra còn có khả năng (mỏng) mà không có thông báo nào thực sự được gửi đến socket mà đầu kia đã đóng. Trong trường hợp đó, không có cách nào khác hơn là thời gian chờ hoặc "tiếp tục"/ping tin nhắn giữa các thiết bị đầu cuối để biết rằng các ổ cắm đã đóng cửa.

Nếu bạn muốn tìm ra chính xác những gì đang xảy ra, hãy loại bỏ Wireshark và tìm kiếm FINs và RST (và sự vắng mặt của bất kỳ thứ gì). Nếu bạn thấy chuỗi FIN thích hợp đi qua khi ổ cắm của bạn bị đóng, thì vấn đề phải nằm trong mã của bạn. nếu bạn thấy RST, nó có thể bị phát hiện bởi các ngoại lệ, và nếu bạn không thấy bất cứ thứ gì bạn cần phải tạo ra một cách trong giao thức 'ping' mỗi bên của kết nối để đảm bảo chúng vẫn còn sống, hoặc thiết lập thời gian chờ đủ ngắn để có thêm dữ liệu.

+1

"Thành thật mà nói, mã của bạn quá dài và tôi không có thời gian để chi tiêu." - Đây là IMHO không công bằng, giới thiệu một câu trả lời có khả năng hữu ích. Không có bất kỳ mã nào trong câu hỏi (có lẽ là một vấn đề trong chính nó) nhưng câu hỏi thực sự cần mức độ chi tiết đó. Tại sao không chỉ nói rằng bạn có một gợi ý, linh cảm, giả thuyết? – JXG

+0

Anh ấy liên kết đến một tệp .cpp có trên 1400 dòng. Tôi xin lỗi, nhưng phân tích rằng đó là ra khỏi câu hỏi. Tôi đã xem xét nó một cách ngắn gọn, và tôi thậm chí không thể tìm thấy khu vực được đề cập (các lựa chọn duy nhất là để thiết lập một kết nối ban đầu) khiến tôi tin rằng đó là tập tin sai. – SoapBox

+0

Liên kết có liên quan đến hàm CreateConnectedSocketPair() mà tôi đã đề cập, không phải vòng lặp sự kiện (như bạn nói, nằm trong tệp khác) –

0

Thay vì theo đuổi các lỗi đã biết trong select(), tôi sẽ giải quyết sự sai lệch ban đầu của bạn khiến bạn tránh xa thiết kế đơn giản, đáng tin cậy, đơn luồng.

Bạn nói "Bạn không thể đọc không bị chặn trên STDIN_FILE_HANDLE. Điều đó có nghĩa là không có cách nào để đọc stdin từ chuỗi chính, vì ReadFile() có thể chặn vô thời hạn" nhưng điều này đơn giản không phải là toàn bộ câu chuyện. Nhìn vào ReadConsoleInput, WSAEventSelect và WaitForMultipleObjects. Các stdin xử lý sẽ được báo hiệu chỉ khi có đầu vào và ReadConsoleInput sẽ trở lại ngay lập tức (khá nhiều ý tưởng tương tự đằng sau select() trong Unix).

Hoặc sử dụng ReadFileEx và WaitForMultipleObjectsEx để bàn điều khiển đọc cháy APC (không phải tất cả không đồng bộ, nó chạy trên luồng chính và chỉ trong WaitForMultipleObjectsEx hoặc hàm chờ rõ ràng khác).

Nếu bạn muốn gắn bó với việc sử dụng chuỗi thứ hai để có I/O không đồng bộ trên stdin, bạn có thể thử đóng chốt xử lý để chọn thay vì tắt một socket (thông qua closesocket ở đầu kia). Trong kinh nghiệm của tôi select() có xu hướng trở lại thực sự nhanh chóng khi một trong những fds nó đang chờ đợi được đóng lại.

Hoặc, có thể vấn đề của bạn là một cách khác. Các tài liệu được chọn nói "Đối với các ổ cắm hướng kết nối, khả năng đọc cũng có thể cho biết rằng yêu cầu đóng socket đã được nhận từ đồng đẳng". Thông thường, bạn sẽ gửi "yêu cầu đóng socket" bằng cách gọi shutdown(), chứ không phải closesocket().

+0

Vâng, tôi đã dành một số giờ cố gắng để có được ReadFileEx() để làm không chặn đọc trên stdin ... không có xúc xắc. ReadFileEx() sẽ vẫn chặn trên stdin, ngay cả khi tôi thiết lập tất cả các công cụ chồng chéo I/O được cho là để kích hoạt hành vi không chặn. Tôi đã không cố gắng ReadConsoleInput(), do đó, có lẽ đó sẽ làm việc. Trong mọi trường hợp, mục tiêu của API là cho phép chủ đề chính sử dụng cùng một vòng lặp sự kiện() dựa trên sự kiện ... để buộc vòng lặp sự kiện sử dụng WaitForMultipleObjectsEx() trong Windows không phải là mong muốn. –

+0

Vâng, bạn chỉ đơn giản là không thể mở CONIN $ hoặc bất kỳ giao diện điều khiển cho chồng chéo I/O, các tài liệu CreateFile nói rằng tất cả các lá cờ được bỏ qua khi truy cập một giao diện điều khiển. Dù sao, chọn thực sự là một API khủng khiếp. Trên unix bạn thường muốn sử dụng bình chọn, trong đó có ngữ nghĩa gần giống với WaitForMultipleObjectsEx. Thêm vào đó, hôm nay tôi đã hoàn thành việc viết mã đa nền tảng cho điều này và nó không quá tệ (thực sự là các phiên bản chọn và bình chọn đã hoạt động được nhiều năm, nhưng chúng chỉ có thể bị gián đoạn trên * nix chứ không phải Windows, vì vậy tôi chỉ cần thêm biến thể WaitForMultipleObjectsEx). –

Các vấn đề liên quan