Jak zapobiec SIGPIPEs (lub obsługiwać je prawidłowo)

Mam mały program serwerowy, który akceptuje połączenia na TCP lub lokalnym gnieździe UNIX, odczytuje proste polecenie i, w zależności od polecenia, wysyła odpowiedź. Problem polega na tym, że klient może czasami nie interesować się odpowiedzią i wyjść wcześniej, więc pisanie do tego gniazda spowoduje SIGPIPE i spowoduje awarię serwera. Jaka jest najlepsza praktyka, aby zapobiec awarii tutaj? Czy istnieje sposób, aby sprawdzić, czy druga strona linii nadal czyta? (select () chyba tu nie działa jak to zawsze mówi, że gniazdo jest zapisywalne). Czy mam złapać SIGPIPE ' a i zignorować go?

Author: Aristotle Pagaltzis, 2008-09-20

10 answers

Zazwyczaj chcesz zignorować SIGPIPE i obsłużyć błąd bezpośrednio w kodzie. Dzieje się tak, ponieważ programy obsługi sygnałów w języku C mają wiele ograniczeń co do tego, co mogą zrobić.

Najbardziej przenośnym sposobem na to jest ustawienie obsługi SIGPIPE na SIG_IGN. Zapobiega to wywołaniu sygnału SIGPIPE przez gniazdo lub pipe write.

Aby zignorować sygnał SIGPIPE, Użyj następującego kodu:

signal(SIGPIPE, SIG_IGN);

Jeśli używasz wywołania send(), inną opcją jest użycie opcji MSG_NOSIGNAL, która wyłącza zachowanie SIGPIPE na podstawie każdego wywołania. Zauważ, że nie wszystkie systemy operacyjne obsługują flagę MSG_NOSIGNAL.

Na koniec warto rozważyć flagę gniazda SO_SIGNOPIPE, którą można ustawić za pomocą setsockopt() w niektórych systemach operacyjnych. Zapobiega to wywołaniu SIGPIPE przez zapis tylko do gniazd, na których jest ustawiony.

 205
Author: dvorak,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-12-18 21:58:10

Inną metodą jest zmiana gniazda tak, aby nigdy nie generowało SIGPIPE podczas write (). Jest to wygodniejsze w bibliotekach, w których możesz nie potrzebować globalnej obsługi sygnału dla SIGPIPE.

Na większości bazujących na BSD (MacOS, FreeBSD...) systemów, (zakładając, że używasz C / C++), możesz to zrobić za pomocą:

int set = 1;
setsockopt(sd, SOL_SOCKET, SO_NOSIGPIPE, (void *)&set, sizeof(int));

Z tym skutkiem, zamiast generowanego sygnału SIGPIPE, zostanie zwrócony EPIPE.

 142
Author: user55807,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-05-29 11:06:07

Jestem bardzo spóźniony na imprezę, ale SO_NOSIGPIPE nie jest przenośny i może nie działać na Twoim systemie(wydaje się, że to coś z BSD).

Miłą alternatywą dla systemu Linux Bez SO_NOSIGPIPE byłoby ustawienie flagi MSG_NOSIGNAL na wywołaniu send(2).

Przykład zastąpienie write(...) przez send(...,MSG_NOSIGNAL) (Zobacz nobar ' s comment)

char buf[888];
//write( sockfd, buf, sizeof(buf) );
send(    sockfd, buf, sizeof(buf), MSG_NOSIGNAL );
 107
Author: sklnd,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 12:26:35

W Tym poście opisałem możliwe rozwiązanie przypadku Solarisa, gdy nie jest dostępne ani SO_NOSIGPIPE, ani MSG_NOSIGNAL.

Zamiast tego musimy tymczasowo wyłączyć SIGPIPE w bieżącym wątku, który wykonuje kod biblioteki. Oto jak to zrobić: aby wyłączyć SIGPIPE najpierw sprawdzamy, czy jest on oczekujący. Jeśli tak, oznacza to, że jest zablokowany w tym wątku i nie musimy nic robić. Jeśli Biblioteka wygeneruje dodatkowy SIGPIPE, zostanie ona scalona z oczekującym jeden, a to jest no-op. jeśli SIGPIPE nie jest oczekujący to blokujemy go w tym wątku, a także sprawdzamy czy już był zablokowany. Wtedy możemy swobodnie wykonywać nasze zapisy. Kiedy mamy przywrócić SIGPIPE do pierwotnego stanu, wykonujemy następujące czynności: jeśli SIGPIPE był pierwotnie oczekujący, nic nie robimy. W przeciwnym razie sprawdzamy, czy to jest w toku. Jeśli tak (co oznacza, że niektóre akcje wygenerowały jeden lub więcej Sigpipów), to czekamy na niego w tym wątku, wyczyszczając w ten sposób jego status oczekujący (do zrobienia to używamy sigtimedwait () z zerowym timeoutem; ma to na celu uniknięcie blokowania w scenariuszu, w którym złośliwy użytkownik wysłał SIGPIPE ręcznie do całego procesu: w tym przypadku zobaczymy go oczekującego, ale inny wątek może obsłużyć go przed zmianą, aby czekać na niego). Po wyczyszczeniu stanu oczekującego odblokowujemy SIGPIPE w tym wątku, ale tylko wtedy, gdy nie był on pierwotnie zablokowany.

Przykładowy kod na https://github.com/kroki/XProbes/blob/1447f3d93b6dbf273919af15e59f35cca58fcc23/src/libxprobes.c#L156

 28
Author: kroki,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-08-15 08:48:42

Obsłuż SIGPIPE lokalnie

Zazwyczaj najlepiej jest obsługiwać błąd lokalnie, a nie w globalnej obsłudze zdarzeń sygnałowych, ponieważ lokalnie będziesz miał więcej kontekstu co się dzieje i co należy wziąć.

Mam warstwę komunikacyjną w jednej z moich aplikacji, która pozwala mojej aplikacji komunikować się z zewnętrznym akcesorium. Gdy wystąpi błąd zapisu, rzucam i wyjątek w warstwie komunikacyjnej i pozwalam mu bąbelkować do bloku try catch, aby go obsłużyć tam.

Kod:

Kod do ignorowania sygnału SIGPIPE, aby móc obsługiwać go lokalnie to:

// We expect write failures to occur but we want to handle them where 
// the error occurs rather than in a SIGPIPE handler.
signal(SIGPIPE, SIG_IGN);

Ten kod uniemożliwi podniesienie sygnału SIGPIPE, ale podczas próby użycia gniazda pojawi się błąd odczytu / zapisu, więc musisz to sprawdzić.

 19
Author: Sam,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-01-27 16:04:09

Nie można uniemożliwić zakończenia procesu na drugim końcu rury, a jeśli zakończy się przed zakończeniem pisania, otrzymasz sygnał SIGPIPE. Jeśli SIG_IGN sygnał, wtedy twój zapis powróci z błędem - i musisz zanotować i zareagować na ten błąd. Samo przechwytywanie i ignorowanie sygnału w module obsługi nie jest dobrym pomysłem - należy zauważyć, że pipe jest teraz nieczynny i zmodyfikować zachowanie programu, aby nie zapisywał ponownie do pipe (ponieważ sygnał będzie wygenerowany ponownie, i zignorowany ponownie, i spróbujesz ponownie, a cały proces może trwać przez Długi Czas i marnować dużo mocy procesora).

 13
Author: Jonathan Leffler,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-09-20 14:45:34

Czy mam złapać SIGPIPE ' a za pomocą Handlera i go zignorować?

/ Align = "left" / Chcesz wiedzieć, kiedy drugi koniec zamknął swój deskryptor i to mówi ci SIGPIPE.

Sam

 4
Author: Sam Reynolds,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-09-20 13:45:23

Linux manual said:

EPIPE the local end has been shut down on a connection oriented Gniazdo. W tym przypadku proces otrzyma również SIGPIPE chyba że ustawiono MSG_NOSIGNAL.

Ale Dla Ubuntu 12.04 to nie jest w porządku. Napisałem test dla tego przypadku i zawsze otrzymuję EPIPE z SIGPIPE. SIGPIPE jest generowany, jeśli próbuję napisać do tego samego uszkodzonego gniazda po raz drugi. Więc nie musisz ignorować SIGPIPE, jeśli ten sygnał się zdarzy oznacza błąd logiczny w programie.

 2
Author: talash,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-03-26 14:41:00

Jaka jest najlepsza praktyka, aby zapobiec katastrofie tutaj?

Albo wyłącz sigpipes jak dla wszystkich, albo Złap i zignoruj błąd.

Czy jest sposób, aby sprawdzić, czy druga strona linii nadal czyta?

Tak, użyj select ().

Select () nie działa tutaj, ponieważ zawsze mówi, że gniazdo jest zapisywalne.

Musisz wybrać na odczytać bity. Prawdopodobnie możesz zignorować napisać bity.

Gdy drugi koniec zamknie swój uchwyt plików, select powie Ci, że są dane gotowe do odczytu. Kiedy to przeczytasz, otrzymasz z powrotem 0 bajtów, czyli w ten sposób system operacyjny informuje Cię, że uchwyt pliku został zamknięty.

Nie można zignorować bitów zapisu tylko wtedy, gdy wysyłasz Duże woluminy i istnieje ryzyko, że drugi koniec zostanie zalegnięty, co może spowodować zapełnienie buforów. Jeśli tak się stanie, próba zapisu do pliku może spowodować program / wątek do zablokowania lub niepowodzenia. Testowanie select przed napisaniem ochroni Cię przed tym, ale nie gwarantuje, że drugi koniec jest zdrowy lub że Twoje dane dotrą.

Zauważ, że możesz uzyskać sigpipe z close (), jak również podczas pisania.

Zamknij spłukuje wszystkie buforowane dane. Jeśli drugi koniec został już zamknięty, to zamknięcie zakończy się niepowodzeniem, a otrzymasz sigpipe.

Jeśli używasz buforowanego TCPIP, wtedy pomyślny zapis oznacza tylko Twoje dane został ustawiony w kolejce do wysłania, nie oznacza to, że został wysłany. Do momentu pomyślnego wywołania programu close nie wiesz, czy Twoje dane zostały wysłane.

Sigpipe mówi ci, że coś poszło nie tak, nie mówi ci, co i co powinieneś z tym zrobić.
 1
Author: Ben Aveling,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-09-15 23:57:28

W nowoczesnym systemie POSIX (np. Linuksie), można używać funkcji sigprocmask().

#include <signal.h>

void block_signal(int signal_to_block /* i.e. SIGPIPE */ )
{
    sigset_t set;
    sigset_t old_state;

    // get the current state
    //
    sigprocmask(SIG_BLOCK, NULL, &old_state);

    // add signal_to_block to that existing state
    //
    set = old_state;
    sigaddset(&set, signal_to_block);

    // block that signal also
    //
    sigprocmask(SIG_BLOCK, &set, NULL);

    // ... deal with old_state if required ...
}

Jeśli chcesz przywrócić poprzedni stan później, pamiętaj, aby zapisać old_state w bezpiecznym miejscu. Jeśli wywołujesz tę funkcję wiele razy, musisz użyć stosu lub zapisać tylko pierwszą lub ostatnią old_state... a może mieć funkcję, która usuwa określony zablokowany sygnał.

Więcej informacji można znaleźć na stronie man .

 -1
Author: Alexis Wilke,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-05-21 23:43:57