2009-01-19 16 views
8

Pracuję na systemie Linux (serwer Ubuntu 7.04 z kernelem 2.6.20).wybór na gnieździe UDP nie kończy się, gdy gniazdo jest zamknięte - co robię źle?

Mam program, który ma wątek (wątek1) czeka na wybór dla gniazda UDP, aby stać się czytelne. Używam select (z moim gniazdem jako single readfd i single exceptfd) zamiast tylko wywoływać recvfrom, ponieważ chcę timeout.

Z innego wątku wyłączam i zamykam gniazdo. Jeśli zrobię to, podczas gdy wątek1 zostanie zablokowany w recvfrom, to recvfrom natychmiast się zakończy. Jeśli zrobię to, gdy wątek1 zostanie zablokowany w wybranym z limitem czasu, to wybór NIE zakończy się natychmiastowo, ale ostatecznie upłynie poprawnie.

Czy ktoś może mi powiedzieć, dlaczego zaznaczenie nie kończy się, gdy gniazdo jest zamknięte? Czy to nie wyjątek? Widzę, gdzie jest to nieczytelne (oczywiście), ale jest zamknięte, co wydaje się być wyjątkowe.

Oto otwarcie gniazda (wszystkie obsługi błędów usunięte zachować rzeczy proste):

m_sockfd = socket(PF_INET, SOCK_DGRAM, 0); 
struct sockaddr_in si_me; 
memset((char *) &si_me, 0, sizeof(si_me)); 
si_me.sin_family = AF_INET; 
si_me.sin_port = htons(port); 
si_me.sin_addr.s_addr = htonl(INADDR_ANY); 
if (bind(m_sockfd, (struct sockaddr *)(&si_me), sizeof(si_me)) < 0) 
{ 
// deal with error 
} 

Oto SELECT że thread1 wykonuje:

struct timeval to; 
to.tv_sec = timeout_ms/1000;// just the seconds portion 
to.tv_usec = (timeout_ms%1000)*1000;// just the milliseconds 
            // converted to microseconds 

// watch our one fd for readability or 
// exceptions. 
fd_set readfds, exceptfds; 
FD_ZERO(&readfds); 
FD_SET(m_sockfd, &readfds); 
FD_ZERO(&exceptfds); 
FD_SET(m_sockfd, &exceptfds); 

int nsel = select(m_sockfd+1, &readfds, NULL, &exceptfds, &to); 

UPDATE: Oczywiście (jak nizej), zamknięcie gniazda nie jest wyjątkowym warunkiem (z punktu widzenia wyboru). Myślę, że muszę wiedzieć: dlaczego? I czy to jest zamierzone?

NAPRAWDĘ chcę zrozumieć sposób, w jaki kryje się to wybrane zachowanie, ponieważ wydaje się sprzeczny z moimi oczekiwaniami. Tak więc, oczywiście muszę dostosować moje myślenie o tym, jak działa stos TCP. Proszę mi to wyjaśnić.

Odpowiedz

4

Może powinieneś użyć czegoś innego, aby obudzić selekcję. Może fajka czy coś w tym stylu.

+0

To byłoby dobre rozwiązanie. Niech wybrane czekaj zarówno na gniazdo, jak i potok, a drugi wątek zapisze w potoku, aby spowodować powrót do wyboru. –

+0

To by działało i prawdopodobnie będzie tym, z czym skończę. Chciałem tylko uniknąć dodatkowych ruchomych części. Dzięki! –

4

UDP jest protokołem bezpołączeniowym. Ponieważ nie ma połączenia, żadne nie może zostać zerwane, więc konsument nie wie, że producent nigdy nie wyśle ​​ponownie.

Możesz spowodować, że producent wyśle ​​komunikat "koniec strumienia" i po jego otrzymaniu konsument zakończy działanie.

+1

Prawidłowe, prawdziwe i możliwe obejście problemu, ale: Odsunięcie kończy się po zamknięciu gniazda. Dlaczego nie wybrać? Jestem naprawdę zdezorientowany, dlaczego zamykanie gniazda nie generuje wyjątku, który kończy wybór. –

+0

Wydaje mi się, że źle zrozumiałeś pytanie: dwa wątki nie manipulują dwoma gniazdami UDP. Jest ** tylko jedno gniazdo UDP **. – curiousguy

2

Myślę, że najbardziej oczywistym rozwiązaniem jest to, że zamknięcie nie jest uważane za wyjątkowy warunek. Myślę, że źródłem problemu jest to, że nie akceptujesz filozofii select. Dlaczego, do licha, bawisz się z gniazdem w innym wątku, to brzmi jak przepis na katastrofę.

+0

Jedyne, co robię z gniazdem z innego wątku, zamykam, aby zatrzymać oczekiwanie w toku. W ten sposób mogę spowodować, że mój program wyjdzie ZANIM wygaśnie wybrany limit czasu. –

2

Powiedziałbym, że różnica polega na tym, że recvfrom aktywnie próbuje odczytać wiadomość z pojedynczego gniazda, gdzie select czeka na wiadomość, która może się pojawić, być może na wielu uchwytach i niekoniecznie uchwytach gniazda.

3

Nie możesz wysłać sygnału (np. USR2) do wątku, który spowodowałby powrót funkcji select() z EINTR? Następnie w obsłudze sygnału ustaw flagę mówiącą, aby nie restartował select()?

To usunie konieczność oczekiwania na wiele deskryptorów plików i wydaje się o wiele czystsze niż użycie potoku, aby go zabić.

+0

Tak, to by działało, chociaż w tym konkretnym przypadku nie sądzę, żebym mógł używać sygnałów wewnętrznie (niektóre źle napisane klasy bazowe w naszej bibliotece używają i/lub blokują sygnały w dziwny sposób). Jeśli mogę wyczyścić podstawowe biblioteki, może spróbuję tego w pewnym momencie. –

0

Twój kod jest zasadniczo uszkodzony.Wariacje na temat tego błędu są powszechne i spowodowały poważne błędy z ogromnymi konsekwencjami dla bezpieczeństwa w przeszłości. Oto, czego ci brakuje:

Kiedy zamykasz gniazdo, po prostu nie ma możliwości sprawdzenia, czy inny wątek jest zablokowany w select, czy też blokowanie w select. Rozważmy na przykład:

  1. Wątek zostanie oznaczony jako select, ale nie zostanie zaplanowany.
  2. Zamykasz gniazdo.
  3. W wątku Twój kod jest nieświadomy (może to część wewnętrznego zarządzania pamięcią wewnętrzną platformy lub wewnętrznymi rejestrami) biblioteka otwiera gniazdo i otrzymuje taki sam identyfikator, jak zamknięte gniazdo.
  4. Wątek przechodzi teraz pod select, ale jest on podłączony do gniazda otwartego przez bibliotekę.
  5. Katastrofy.

Nie wolno próbować zwolnić zasobu, gdy inny wątek jest lub może być używany.

Powiązane problemy