문제

나는 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 시스템을 사용하는 경우 좋은 대안입니다. SO_NOSIGPIPE 설정하는 것입니다 MSG_NOSIGNAL send(2) 호출에 플래그를 지정하세요.

교체 예 write(...) ~에 의해 send(...,MSG_NOSIGNAL) (보다 노바르님의 댓글)

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가 원래 보류 중이었다면 우리는 아무것도 하지 않습니다.그렇지 않으면 지금 보류 중인지 확인합니다.그렇다면(out 작업이 하나 이상의 SIGPIPE를 생성했음을 의미) 이 스레드에서 대기하여 보류 상태를 지웁니다(이를 위해 시간 제한이 없는 sigtimedwait()를 사용합니다.이는 악의적인 사용자가 SIGPIPE를 전체 프로세스에 수동으로 보내는 시나리오에서 차단을 방지하기 위한 것입니다.이 경우 보류 중인 것으로 표시되지만 변경 사항이 발생하기 전에 다른 스레드에서 이를 처리할 수 있습니다.)보류 상태를 지운 후 이 스레드에서 SIGPIPE를 차단 해제합니다. 단, 원래 차단되지 않은 경우에만 가능합니다.

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

SIGPIPE를 로컬에서 처리

일반적으로 전역 신호 이벤트 처리기보다는 로컬에서 오류를 처리하는 것이 가장 좋습니다. 로컬에서는 무슨 일이 일어나고 있고 어떤 조치를 취해야 하는지에 대해 더 많은 컨텍스트를 얻을 수 있기 때문입니다.

내 앱 중 하나에 내 앱이 외부 액세서리와 통신할 수 있게 해주는 통신 레이어가 있습니다.쓰기 오류가 발생하면 통신 계층에서 예외를 발생시키고 그곳에서 이를 처리하기 위해 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가 알려주는 것입니다.

리눅스 매뉴얼은 다음과 같이 말했습니다:

에피페이프 로컬 엔드는 연결 지향 소켓에서 종료되었습니다.이 경우 MSG_NOSIGNAL이 설정되지 않는 한 프로세스는 SIGPIPE도 수신합니다.

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

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

모든 사람에 따라 시그파이프를 비활성화하거나 오류를 포착하고 무시하십시오.

상대방이 여전히 읽고 있는지 확인할 수 있는 방법이 있나요?

예, select()를 사용하세요.

select()는 항상 소켓이 쓰기 가능하다고 말하므로 여기서는 작동하지 않는 것 같습니다.

에서 선택해야 합니다. 읽다 비트.아마도 무시할 수 있습니다. 쓰다 비트.

상대방이 파일 핸들을 닫으면 Select는 읽을 준비가 된 데이터가 있음을 알려줍니다.가서 그것을 읽으면 0바이트가 반환됩니다. 이는 OS가 파일 핸들이 닫혔다는 것을 알려주는 방식입니다.

쓰기 비트를 무시할 수 없는 유일한 경우는 대용량을 전송하는 경우이고 상대방이 백로그를 받아 버퍼가 가득 찰 위험이 있는 경우입니다.그런 경우 파일 핸들에 쓰려고 하면 프로그램/스레드가 차단되거나 실패할 수 있습니다.쓰기 전에 선택을 테스트하면 이러한 문제로부터 보호받을 수 있지만 반대쪽 끝이 정상적이거나 데이터가 도착할 것이라고 보장할 수는 없습니다.

작성할 때뿐만 아니라 close()에서도 sigpipe를 얻을 수 있습니다.

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

버퍼링된 TCPIP를 사용하는 경우 성공적인 쓰기는 데이터가 전송 대기열에 있다는 의미일 뿐 전송되었다는 의미는 아닙니다.닫기를 성공적으로 호출할 때까지는 데이터가 전송되었는지 알 수 없습니다.

Sigpipe는 뭔가 잘못되었다고 알려주지만, 이에 대해 무엇을 해야 하는지, 무엇을 해야 하는지는 알려주지 않습니다.

최신 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...또는 특정 차단 신호를 제거하는 기능이 있을 수도 있습니다.

자세한 내용은 다음을 읽어보세요. 매뉴얼 페이지.

라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top