SIGPIPE를 방지하는 방법 (또는 올바르게 처리)


259

TCP 또는 로컬 UNIX 소켓에서 연결을 수락하고 간단한 명령을 읽고 명령에 따라 응답을 보내는 작은 서버 프로그램이 있습니다. 문제는 클라이언트가 때로는 대답에 관심이없고 일찍 빠져 나올 수 있으므로 해당 소켓에 쓰면 SIGPIPE가 발생하고 서버가 중단됩니다. 여기서 충돌을 방지하는 가장 좋은 방법은 무엇입니까? 줄의 다른 쪽이 여전히 읽고 있는지 확인하는 방법이 있습니까? (select ()는 소켓이 쓰기 가능하다는 것을 항상 나타 내기 때문에 여기서 작동하지 않는 것 같습니다). 아니면 처리기로 SIGPIPE를 잡아서 무시해야합니까?

답변:


253

일반적으로 SIGPIPE코드 를 무시하고 코드에서 직접 오류를 처리 하려고합니다 . C의 신호 핸들러에는 수행 할 수있는 작업에 많은 제한이 있기 때문입니다.

가장 편리한 방법은 SIGPIPE핸들러를 로 설정하는 것 SIG_IGN입니다. 이렇게하면 소켓이나 파이프 쓰기로 인해 SIGPIPE신호 가 발생하지 않습니다 .

SIGPIPE신호 를 무시하려면 다음 코드를 사용하십시오.

signal(SIGPIPE, SIG_IGN);

send()통화를 사용하는 경우 다른 옵션은 옵션을 사용 MSG_NOSIGNAL하는 것입니다. 이 옵션 을 사용하면 SIGPIPE통화별로 동작이 해제됩니다. 모든 운영 체제가 MSG_NOSIGNAL플래그를 지원하지는 않습니다 .

마지막으로 일부 운영 체제에서 SO_SIGNOPIPE설정할 수있는 소켓 플래그 를 고려할 수도 있습니다 setsockopt(). 이렇게하면 SIGPIPE설정된 소켓에 대한 쓰기만으로 발생 하지 않습니다.


75
이를 명시 적으로 나타내려면 : signal (SIGPIPE, SIG_IGN);
jhclark

5
종료 리턴 코드 141 (128 + 13 : SIGPIPE)을받는 경우에 필요할 수 있습니다. SIG_IGN은 무시 신호 핸들러입니다.
velcrow

6
소켓의 경우 @talash가 말한 것처럼 MSG_NOSIGNAL과 함께 send ()를 사용하는 것이 실제로 더 쉽습니다.
Pawel Veselov

3
프로그램이 깨진 파이프 (필자의 경우 소켓)에 쓰면 어떻게됩니까? SIG_IGN은 프로그램이 SIG_PIPE를 무시하도록하지만, 그 결과 send ()가 바람직하지 않은 일을합니까?
sudo

2
내가 그것을 한 번 발견 한 후 나중에 잊고 혼란스러워서 두 번째 발견했다는 점을 언급 할 가치가 있습니다. 디버거 (gdb가 알고 있음)는 신호 처리를 무시하므로 무시 된 신호는 무시되지 않습니다! SIGPIPE가 더 이상 발생하지 않도록 디버거 외부에서 코드를 테스트하십시오. stackoverflow.com/questions/6821469/…
Jetski S-type

155

또 다른 방법은 소켓을 변경하여 write ()에서 SIGPIPE를 생성하지 않는 것입니다. 이는 SIGPIPE에 대한 글로벌 신호 핸들러를 원하지 않는 라이브러리에서 더 편리합니다.

대부분의 BSD 기반 (MacOS, FreeBSD ...) 시스템 (C / C ++를 사용한다고 가정)에서 다음을 사용하여이를 수행 할 수 있습니다.

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

이로 인해 SIGPIPE 신호가 생성되는 대신 EPIPE가 반환됩니다.


45
이것은 잘 들리지만 SO_NOSIGPIPE는 Linux에 존재하지 않는 것 같습니다. 따라서 광범위하게 이식 가능한 솔루션이 아닙니다.
nobar

1
안녕하십니까,이 코드를 어디에 두어야하는지 말씀해 주시겠습니까? 나는 감사의 이해 해달라고
R. 데위에게

9
리눅스에서, 나는 플래그에 MSG_NOSIGNAL 옵션 참조 보내기
Aadishri

Mac OS X에서 완벽하게 작동
kirugan

116

나는 파티에 늦었지만 SO_NOSIGPIPE휴대용이 아니며 시스템에서 작동하지 않을 수 있습니다 (BSD 일 것 같습니다).

예를 들어 Linux 시스템이없는 경우 send (2) 호출 SO_NOSIGPIPE에서 MSG_NOSIGNAL플래그 를 설정하는 것이 좋은 대안 입니다.

예 교체 write(...)에 의해 send(...,MSG_NOSIGNAL)(참조 nobar 의 코멘트)

char buf[888];
//write( sockfd, buf, sizeof(buf) );
send(    sockfd, buf, sizeof(buf), MSG_NOSIGNAL );

5
즉, send (..., MSG_NOSIGNAL)을 write () 대신 사용하면 SIGPIPE를 얻지 못합니다. 소켓 (지원되는 플랫폼)에서는 잘 작동하지만 send ()는 파이프 (파이프가 아닌) 소켓과 함께 사용하는 것으로 제한되어 있으므로 SIGPIPE 문제에 대한 일반적인 해결책은 아닙니다.
nobar

@ Ray2k 누가 Linux <2.2 용 애플리케이션을 개발하고 있습니까? 그것은 실제로 반의 심한 질문입니다. 대부분의 배포판은 2.6 이상으로 제공됩니다.
Parthian Shot

1
@Parthian Shot : 답을 다시 생각해야합니다. 이전 임베디드 시스템을 유지 보수하는 것은 이전 Linux 버전을 관리하는 데 유효한 이유입니다.
kirsche40

1
@ kirsche40 저는 OP가 아닙니다. 그냥 궁금했습니다.
Parthian Shot 23

@ sklnd 이것은 나를 위해 완벽하게 작동했습니다. QTcpSocket소켓 사용을 줄이려면 Qt 객체를 사용하고 있으며 write메소드 호출을 OS send( socketDescriptor메서드 사용 ) 로 바꿔야했습니다 . 누구나 QTcpSocket수업 에서이 옵션을 설정하는 더 깨끗한 옵션을 알고 있습니까?
Emilio González Montaña

29

에서는 SO_NOSIGPIPE와 MSG_NOSIGNAL을 모두 사용할 수없는 Solaris 사례에 대해 가능한 해결책을 설명했습니다.

대신 라이브러리 코드를 실행하는 현재 스레드에서 SIGPIPE를 일시적으로 억제해야합니다. 이를 수행하는 방법은 다음과 같습니다. SIGPIPE를 억제하려면 먼저 보류 중인지 확인합니다. 그렇다면이 스레드에서 차단 된 것이므로 아무 것도하지 않아도됩니다. 라이브러리가 추가 SIGPIPE를 생성하면 보류중인 라이브러리와 병합되며 이는 작동하지 않습니다. SIGPIPE가 보류 중이 아닌 경우이 스레드에서 차단하고 이미 차단되었는지 확인합니다. 그런 다음 쓰기를 자유롭게 수행 할 수 있습니다. SIGPIPE를 원래 상태로 복원 할 때 다음을 수행합니다. SIGPIPE가 원래 보류 중이면 아무 작업도 수행하지 않습니다. 그렇지 않으면 현재 보류 중인지 확인합니다. 그렇다면 (아웃 액션이 하나 이상의 SIGPIPE를 생성했음을 의미)이 스레드에서 기다립니다. 따라서 보류 상태를 지우려면 (종료 시간이 0 인 sigtimedwait ()을 사용합니다. 이는 악의적 인 사용자가 SIGPIPE를 전체 프로세스에 수동으로 보낸 시나리오에서 차단을 피하기위한 것입니다.이 경우 보류 상태로 표시되지만 다른 스레드는 기다릴 변경이 있기 전에 처리하십시오. 보류 상태를 지운 후이 스레드에서 SIGPIPE의 차단을 해제하지만 원래 차단되지 않은 경우에만 차단합니다.

https://github.com/kroki/XProbes/blob/1447f3d93b6dbf273919af15e59f35cca58fcc23/src/libxprobes.c#L156의 예제 코드


22

SIGPIPE를 로컬에서 처리

일반적으로 전역 신호 이벤트 처리기보다는 오류를 로컬로 처리하는 것이 가장 좋습니다. 로컬에서는 진행 상황과 수행 할 내용에 대한 컨텍스트가 더 많기 때문입니다.

내 앱 중 하나에 앱이 외부 액세서리와 통신 할 수 있도록하는 통신 계층이 있습니다. 쓰기 오류가 발생하면 통신 계층에서 예외를 throw하고 try catch 블록까지 버블 링하여 처리합니다.

암호:

로컬에서 처리 할 수 ​​있도록 SIGPIPE 신호를 무시하는 코드는 다음과 같습니다.

// 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);

이 코드는 SIGPIPE 신호가 발생하지 않도록하지만 소켓을 사용하려고 할 때 읽기 / 쓰기 오류가 발생하므로이를 확인해야합니다.


소켓을 연결 한 후 또는 그 전에 호출해야합니까? 어디에 두는 것이 가장 좋습니다. 감사
Ahmed

@Ahmed 개인적으로 applicationDidFinishLaunching에 넣었습니다 . 중요한 것은 소켓 연결과 상호 작용하기 전에 있어야한다는 것입니다.
Sam

그러나 소켓을 사용하려고 할 때 읽기 / 쓰기 오류가 발생하므로 확인해야합니다. -이것이 어떻게 가능한지 물어봐도 될까요? signal (SIGPIPE, SIG_IGN)은 저에게 효과적이지만 디버그 모드에서는 오류를 반환합니다. 해당 오류를 무시할 수 있습니까?
jongbanaag '10

@ Dreyfus15 로컬에서 처리하기 위해 try / catch 블록의 소켓을 사용하여 호출을 래핑했습니다. 이걸 본지 오래되었습니다.
Sam

15

파이프의 맨 끝에있는 프로세스가 종료되는 것을 막을 수 없으며 쓰기가 끝나기 전에 종료되면 SIGPIPE 신호를받습니다. 신호를 SIG_IGN하면 쓰기 오류가 발생하며 해당 오류를 기록하고 대응해야합니다. 핸들러에서 신호를 포착하고 무시하는 것은 좋은 생각이 아닙니다. 파이프가 이제 기능을 잃고 프로그램의 동작을 수정하여 파이프에 다시 쓰지 않도록주의해야합니다 (신호가 다시 생성되고 무시되기 때문에) 다시 시도하면 전체 프로세스가 오랫동안 진행되어 많은 CPU 전력을 낭비 할 수 있습니다).


6

아니면 처리기로 SIGPIPE를 잡아서 무시해야합니까?

나는 그것이 옳다고 믿는다. 다른 쪽 끝이 설명자를 닫았을 때 SIGPIPE가 알려주는 것을 알고 싶습니다.


3

리눅스 매뉴얼은 말했다 :

EPIPE 연결 지향 소켓에서 로컬 끝이 종료되었습니다. 이 경우 MSG_NOSIGNAL을 설정하지 않으면 프로세스에 SIGPIPE도 수신됩니다.

그러나 우분투 12.04의 경우 올바르지 않습니다. 해당 사례에 대한 테스트를 작성했으며 항상 SIGPIPE가없는 EPIPE를받습니다. SIGPIPE는 동일한 깨진 소켓에 두 번째로 쓰려고하면 생성됩니다. 따라서이 신호가 발생하면 SIGPIPE를 무시할 필요가 없습니다. 프로그램에서 논리 오류를 의미합니다.


1
가장 확실하게 리눅스의 소켓에서 EPIPE를 보지 않고 SIGPIPE를받습니다. 이것은 커널 버그처럼 들립니다.
davmac

3

여기서 충돌을 방지하는 가장 좋은 방법은 무엇입니까?

모두에 따라 sigpipe를 비활성화하거나 오류를 포착하고 무시하십시오.

줄의 다른 쪽이 여전히 읽고 있는지 확인하는 방법이 있습니까?

예, select ()를 사용하십시오.

select ()는 소켓이 쓰기 가능하다는 것을 항상 나타 내기 때문에 여기서 작동하지 않는 것 같습니다.

읽기 비트 를 선택해야합니다 . 쓰기 비트 는 무시해도 됩니다.

파 엔드가 파일 핸들을 닫으면 선택하면 읽을 준비가 된 데이터가 있음을 알려줍니다. 당신이 그것을 읽을 때, 당신은 0 바이트를 다시 얻을 것입니다. 이것은 OS가 파일 핸들이 닫 혔음을 알려주는 방법입니다.

쓰기 비트를 무시할 수없는 유일한 시간은 대용량을 전송하는 경우이고 다른 쪽 끝이 백 로그 될 위험이있어 버퍼가 가득 찰 수 있습니다. 이 경우 파일 핸들에 쓰려고하면 프로그램 / 스레드가 차단되거나 실패 할 수 있습니다. 쓰기 전에 선택 테스트를하면 그로부터 보호 할 수 있지만 다른 쪽 끝이 건강하거나 데이터가 도착한다고 보장 할 수는 없습니다.

close ()와 쓸 때 sigpipe를 얻을 수 있습니다.

닫기는 버퍼링 된 데이터를 플러시합니다. 다른 쪽 끝이 이미 닫힌 경우 닫기가 실패하고 sigpipe를 받게됩니다.

버퍼링 된 TCPIP를 사용하는 경우 성공적인 쓰기는 데이터가 전송 대기 중임을 의미한다고해서 전송 된 것은 아닙니다. 닫기를 성공적으로 마치기 전까지는 데이터가 전송되었음을 알 수 없습니다.

Sigpipe는 무언가 잘못되었다고 말하고, 무엇을 어떻게해야하는지 알려주지 않습니다.


Sigpipe는 무언가 잘못되었다고 말하고, 무엇에 대해 무엇을해야하는지 알려주지 않습니다. 바로 그거죠. 사실상 SIGPIPE의 유일한 목적은 다음 단계에서 더 이상 입력이 필요하지 않을 때 파이프 된 명령 행 유틸리티를 분리하는 것입니다. 프로그램이 네트워킹을 수행하는 경우 일반적으로 프로그램 전체에서 SIGPIPE를 차단하거나 무시해야합니다.
DepressedDaniel

정확하게. SIGPIPE는 "find XXX | head"와 같은 상황을위한 것입니다. 헤드가 10 개의 라인과 일치하면 찾기를 계속할 수 없습니다. 그래서 머리가 나오고 다음에 발견하려고하면 시그 파이프 (sigpipe)를 얻습니다.
Ben Aveling

실제로 SIGPIPE의 유일한 목적은 프로그램을 느슨하게하고 쓰기 오류를 검사하지 않도록하는 것입니다!
윌리엄 퍼셀

2

최신 POSIX 시스템 (예 : Linux)에서는이 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 ...
}

나중에 이전 상태를 복원하려면 old_state안전한 곳에 저장하십시오 . 해당 함수를 여러 번 호출하는 경우 스택을 사용하거나 첫 번째 또는 마지막 만 저장하거나 old_state특정 차단 신호를 제거하는 기능이 있어야합니다.

자세한 내용은 매뉴얼 페이지를 참조하십시오 .


이와 같이 차단 된 신호 세트를 읽고 수정하고 쓸 필요는 없습니다. sigprocmask차단 된 신호 세트에 추가하므로 한 번의 호출로이 모든 작업을 수행 할 수 있습니다.
Luchs

나는 read-modify-write를 쓰지 않고 old_state, 내가 선택한 경우 나중에 복원 할 수 있도록 현재 상태를 저장하기 위해 읽습니다 . 상태를 복원 할 필요가 없다는 것을 알면 실제로 이런 식으로 상태를 읽고 저장할 필요가 없습니다.
Alexis Wilke

1
그러나 첫 번째 호출 sigprocmask()은 이전 상태 를 읽고 호출하면 상태를 sigaddset수정하고 두 번째 호출은 상태를 sigprocmask()씁니다. 첫 번째 통화를 제거하고로 초기화 sigemptyset(&set)하고 두 번째 통화를로 변경할 수 sigprocmask(SIG_BLOCK, &set, &old_state)있습니다.
Luchs

아! ㅋ! 네가 옳아. 나는한다. 글쎄 ... 내 소프트웨어에서 이미 차단하거나 차단하지 않은 신호를 알 수 없습니다. 그래서 나는 이것을 이렇게합니다. 그러나, 당신이 "이 방법으로 신호를 설정"한 개만 가지고 있다면 간단한 명확한 + 설정이면 충분합니다.
Alexis Wilke
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.