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

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



답변

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

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

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

signal(SIGPIPE, SIG_IGN);

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

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


답변

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

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

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

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


답변

나는 파티에 늦었지만 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 );


답변

에서는 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의 예제 코드


답변

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 신호가 발생하지 않도록하지만 소켓을 사용하려고 할 때 읽기 / 쓰기 오류가 발생하므로이를 확인해야합니다.


답변

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


답변

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

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