2013-05-15 10 views
5

Jest to raczej obserwacja, a także sugestia, jaki jest najlepszy sposób na obsłużenie tego scenariusza.sendto() dgrams nie blokują ENOBUFS na OSX

Mam dwa wątki, jeden po prostu pompuje dane, a drugi odbiera dane i wykonuje dużo pracy przed wysłaniem innego gniazda. Oba wątki są połączone za pośrednictwem gniazda domeny. Używany tutaj protokół to UDP. Nie chciałem używać TCP, ponieważ jest on oparty na strumieniu, co oznacza, że ​​jeśli w kolejce jest mało miejsca, moje dane są dzielone i wysyłane. Jest to złe, ponieważ przesyłam dane, które nie powinny być dzielone. Dlatego użyłem DGRAM. Co ciekawe, gdy wątek wysyłania przytłacza wątek recv przez pompowanie tak dużej ilości danych, w pewnym momencie bufor gniazd domeny zostanie wypełniony, a sendto() zwróci ENOBUFS. Byłem zdania, że ​​jeśli tak się stanie, sendto() będzie blokować, dopóki bufor nie będzie dostępny. To byłoby moje pożądane zachowanie. Jednak wydaje się, że tak nie jest. Rozwiązuję ten problem w dość dziwny sposób.

  • metoda Wydajność CPU Jeśli dostanę ENOBUFS, robię sched_yield(); jak nie ma pthread_yield() w OSX. Potem próbuję ponownie wysłać wiadomość. Jeśli to zawiedzie, robię to samo, dopóki nie zostanie zrobione. Jest to złe, ponieważ Iam marnuje cykle procesora, robiąc coś bezużytecznego. Bardzo bym chciał, gdyby sendto() było zablokowane.

  • Metoda uśpienia Próbowałem rozwiązać ten sam problem za pomocą trybu uśpienia (1) zamiast sched_yield(), ale to bezużyteczne jako funkcja uśpienia() uśpiłoby mój proces zamiast tylko tego wątku.

Obydwa z nich nie działają dla mnie i kończą się opcje. Czy ktoś może zasugerować jaki jest najlepszy sposób rozwiązania tego problemu? Czy istnieją jakieś sprytne sztuczki, których nie jestem w stanie uświadomić, które mogą zredukować niepotrzebne cykle procesora? btw, co strona człowiek mówi o sentto() jest błędne, na podstawie tej dyskusji http://lists.freebsd.org/pipermail/freebsd-hackers/2004-January/005385.html

Kodeksu Upd w jądrze:

The udp_output function in /sys/netinet/udp_usrreq.c, seems clear: 

     /* 
      * Calculate data length and get a mbuf 
      * for UDP and IP headers. 
      */ 
     M_PREPEND(m, sizeof(struct udpiphdr), M_DONTWAIT); 
     if (m == 0) { 
       error = ENOBUFS; 
       if (addr) 
         splx(s); 
       goto release; 
     } 
+0

Jak odradzasz wątki? W rzeczywistości [sleep (3)] (https://developer.apple.com/library/mac/documentation/Darwin/Reference/ManPages/man3/sleep.3.html) powinno działać dla wątków POSIX. – artistoex

Odpowiedz

0

Nie jestem pewien, dlaczego sendto() nie blokuje za ty ... ale możesz spróbować wywołanie tej funkcji przed wami każde wywołanie sendto():

#include <stdio.h> 
#include <sys/select.h> 

// Won't return until there is space available on the socket for writing 
void WaitUntilSocketIsReadyForWrite(int socketFD) 
{ 
    fd_set writeSet; 
    FD_ZERO(&writeSet); 
    FD_SET(socketFD, &writeSet); 
    if (select(socketFD+1, NULL, &writeSet, NULL, NULL) < 0) perror("select"); 
} 

Btw, jak duże są pakiety, które próbują wysłać?

+0

W rzeczywistości, Jeremy, jak rozumiem systemy oparte na BSD, nie blokują sendto(), gdzie działają Linux i Solaris. Wybierz nie działa tutaj, ponieważ w przypadku UDP nie ma kolejki bufora wysyłania, która jest dostępna w TCP, więc wybierz nie będzie wiedział o braku buffa. Zezwala zawsze, ale zawiedzie z ENOBUFS. Kod jądra dla UDP jest podany poniżej – user2085689

+0

Wygląda jak dla UDP na BSD, a OSX sendto() nie będzie blokował. Jest błędnie wspomniany na stronach man. Nawet select() nie może pomóc.To jest bardzo interesująca dyskusja, aby przeczytać o tym samym numerze http://lists.freebsd.org/pipermail/freebsd-hackers/2004- styczeń/005369.html – user2085689

+0

Hmm, używam select(), aby ograniczyć stawkę send() na niezablokowanych gniazdach UDP pod OSX i to wydaje się działać dla mnie ... oczywiście nie zapobiega późniejszemu upuszczeniu pakietów (np. przy przełączniku sieciowym), ale robi to tak, że mój program wysyła Pakiety UDP o wartości akceptowanej przez bufor wychodzący-UDP. Dla ciebie możesz po prostu pominąć punktor i użyć TCP z pewną logiką framingową (np. Wysłać nagłówek "rozmiaru pakietu", a następnie dane w "pakiecie", aby odbiorca mógł najpierw odczytać nagłówek i wiedzieć, jak wiele bajtów do wypełnienia bufora przed przetworzeniem) –

0

sendto() na OS X jest naprawdę niezablokowane (tzn. Flaga M_DONTWAIT dla).
Proponuję użyć połączenia opartego na strumieniu i po prostu odbieraj całe dane po drugiej stronie za pomocą flagi MSG_WAITALL funkcji recv. Jeśli twoje dane mają ścisłą strukturę, niż byłoby to proste, podaj właściwy rozmiar do recv. Jeśli nie, po prostu wyślij najpierw jakiś pakiet kontrolny o stałej wielkości z rozmiarem następnej porcji danych, a następnie same dane. Po stronie odbiornika czekałoby się na pakiet kontrolny o stałym rozmiarze, a także dane o rozmiarze z pakietu kontrolnego.