Mam mały program serwera, który akceptuje połączenia na TCP lub lokalnym gnieździe UNIX, czyta proste polecenie i, w zależności od polecenia, wysyła odpowiedź. Problem polega na tym, że klient czasami nie jest zainteresowany odpowiedzią i wychodzi 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 sprawdzenia, czy druga strona linii nadal czyta? (select () nie wydaje się tutaj działać, ponieważ zawsze mówi, że gniazdo można zapisać). A może powinienem po prostu złapać SIGPIPE za pomocą programu obsługi i zignorować go?
Inną metodą jest zmiana gniazda, aby nigdy nie generowało SIGPIPE przy write (). Jest to wygodniejsze w bibliotekach, w których może nie być potrzebny globalny moduł obsługi sygnału dla SIGPIPE.
W większości systemów opartych na BSD (MacOS, FreeBSD ...) (zakładając, że używasz C / C ++) możesz to zrobić za pomocą:
W ten sposób zamiast generowanego sygnału SIGPIPE zostanie zwrócony EPIPE.
źródło
Jestem spóźniony na imprezę, ale
SO_NOSIGPIPE
nie jestem przenośny i może nie działać w twoim systemie (wydaje się, że to BSD).Dobrą alternatywą, jeśli używasz, powiedzmy, systemu Linux bez,
SO_NOSIGPIPE
byłoby ustawienieMSG_NOSIGNAL
flagi w wywołaniu send (2).Przykład zastąpienia
write(...)
przezsend(...,MSG_NOSIGNAL)
(patrz komentarz nobara )źródło
QTcpSocket
obiektu Qt do zawijania użycia gniazd, musiałem zastąpićwrite
wywołanie metody przez system operacyjnysend
(za pomocąsocketDescriptor
metody). Czy ktoś zna czystszą opcję ustawienia tej opcji wQTcpSocket
klasie?W tym poście opisałem możliwe rozwiązanie dla przypadku Solaris, gdy nie jest dostępny ani SO_NOSIGPIPE, ani MSG_NOSIGNAL.
Przykładowy kod na https://github.com/kroki/XProbes/blob/1447f3d93b6dbf273919af15e59f35cca58fcc23/src/libxprobes.c#L156
źródło
Obsługiwać SIGPIPE lokalnie
Zwykle najlepiej jest obsługiwać błąd lokalnie, a nie w globalnej procedurze obsługi zdarzeń sygnału, ponieważ lokalnie będziesz miał większy kontekst, co się dzieje i jakie środki należy podjąć.
Mam warstwę komunikacyjną w jednej z moich aplikacji, która pozwala mojej aplikacji komunikować się z zewnętrznym akcesorium. Kiedy pojawia się błąd zapisu, rzucam wyjątek w warstwie komunikacyjnej i pozwalam, aby bąbelkowała do bloku try catch, aby go tam obsłużyć.
Kod:
Kod ignorujący sygnał SIGPIPE, umożliwiający obsługę go lokalnie, to:
Ten kod zapobiegnie podniesieniu sygnału SIGPIPE, ale podczas próby użycia gniazda pojawi się błąd odczytu / zapisu, więc musisz to sprawdzić.
źródło
Nie możesz uniemożliwić wyjścia z procesu na drugim końcu potoku, a jeśli zakończy się ono przed zakończeniem pisania, otrzymasz sygnał SIGPIPE. Jeśli SIG_IGN sygnał, twój zapis wróci z błędem - i musisz zanotować i zareagować na ten błąd. Samo łapanie i ignorowanie sygnału w module obsługi nie jest dobrym pomysłem - należy pamiętać, że potok jest teraz nieczynny i modyfikuje zachowanie programu, aby nie zapisywał go ponownie (ponieważ sygnał zostanie wygenerowany ponownie i zignorowany ponownie, a spróbujesz jeszcze raz, a cały proces może trwać długo i marnować dużo mocy procesora).
źródło
Wierzę, że tak jest. Chcesz wiedzieć, kiedy drugi koniec zamknął swój deskryptor i tak mówi SIGPIPE.
Sam
źródło
Podręcznik Linuksa powiedział:
Ale w przypadku Ubuntu 12.04 nie jest to właściwe. Napisałem test dla tej sprawy i zawsze otrzymuję EPIPE bez SIGPIPE. SIGPIPE jest generowany, jeśli za drugim razem spróbuję napisać do tego samego uszkodzonego gniazda. Więc nie musisz ignorować SIGPIPE, jeśli ten sygnał się zdarzy, oznacza to błąd logiczny w twoim programie.
źródło
Wyłącz sigpipe jak wszyscy lub złap i zignoruj błąd.
Tak, użyj select ().
Musisz wybrać na odczytanych bitach. Prawdopodobnie możesz zignorować bity zapisu .
Kiedy drugi koniec zamyka uchwyt pliku, wybierz powie ci, że są dane gotowe do odczytu. Gdy to zrobisz, otrzymasz 0 bajtów, tak system operacyjny informuje, że uchwyt pliku został zamknięty.
Jedynym momentem, w którym nie można zignorować bitów zapisu, jest wysyłanie dużych woluminów i istnieje ryzyko zalegania drugiego końca, co może spowodować wypełnienie buforów. Jeśli tak się stanie, wówczas próba zapisu do uchwytu pliku może spowodować zablokowanie lub niepowodzenie programu / wątku. Testowanie select przed zapisaniem 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 (), a także podczas pisania.
Zamknij opróżnia buforowane dane. Jeśli drugi koniec został już zamknięty, zamknięcie zakończy się niepowodzeniem, a otrzymasz sigpipe.
Jeśli używasz buforowanego protokołu TCPIP, pomyślny zapis oznacza po prostu, że dane zostały umieszczone w kolejce do wysłania, co nie oznacza, że zostały wysłane. Do czasu pomyślnego połączenia blisko nie wiesz, że Twoje dane zostały wysłane.
Sigpipe mówi ci, że coś poszło nie tak, nie mówi ci, co lub co powinieneś z tym zrobić.
źródło
W nowoczesnym systemie POSIX (tj. Linux) możesz korzystać z tej
sigprocmask()
funkcji.Jeśli chcesz później przywrócić poprzedni stan, pamiętaj, aby zapisać
old_state
bezpieczne miejsce. Jeśli wywołasz tę funkcję wiele razy, musisz albo użyć stosu, albo zapisać tylko pierwszy lub ostatniold_state
... lub może mieć funkcję, która usuwa określony zablokowany sygnał.Aby uzyskać więcej informacji, przeczytaj stronę podręcznika man .
źródło
sigprocmask
dodaje do zestawu zablokowanych sygnałów, dzięki czemu możesz to wszystko zrobić za pomocą jednego połączenia.old_state
razie potrzeby móc go później przywrócić. Jeśli wiesz, że nie będziesz musiał nigdy przywracać stanu, w rzeczywistości nie ma potrzeby czytania i przechowywania go w ten sposób.sigprocmask()
odczytania starego stanu, wezwanie do jegosigaddset
modyfikacji, drugie wezwanie dosigprocmask()
zapisania go. Możesz usunąć pierwsze połączenie, zainicjowaćsigemptyset(&set)
i zmienić drugie połączenie nasigprocmask(SIG_BLOCK, &set, &old_state)
.