Client Computer가 Power off되었을 때 server의 대책은?

김경태의 이미지


int writen(int fd, const void *vptr, size_t n) { 
    size_t nleft; 
    int nwrite; 
    const char* ptr; 
    
    signal(SIGALRM, sig_alrm); 
    ptr = (char *)vptr; 
    nleft = n;  
    alarm(10);  
    while (nleft > 0) { 
        if ( ((int)nwrite = write(fd, ptr, nleft)) <= 0) { 
            if (errno == EINTR) 
                return -1; 
            else if(errno == EPIPE) 
                return -2; 
            else 
                return (-3); 
        } 
        nleft -= nwrite; 
        ptr += nwrite; 
    } 
    alarm(0); 
    return (n); 
} 

static void sig_alrm(int signo) 
{ 
    return; 
} 

Server에서 Select를 이용한 다중접속을 구현하고(Single Thread), Client
와의 통신을 시도하였는데 Client Program을 Kill시킬 경우에는 <errno==
EPIPE>에 걸리게 됩니다.

그런데 만약 Client Program은 Connection상태로 두고 컴퓨터의 Power
를 Off시킨 경우 Server는,

1. 해당 컴퓨터를 켜면 -> Server 프로그램 정상적으로 작동
2. 해당 컴퓨터가 계속 꺼놓으면 -> 상당시간 후에 Server 프로그램 정상작동

와 같은 결과를 만들어 냅니다.
(물론 이때는 위 코드의 SIGALRM은 정상작동되지 않습니다.)

결국 하나의 Client Program이 임의로 Power Off되는 경우에 전 시스템이
마비상태가 됩니다.

<질문>

Client Computer가 Power Off되는 것을 Server에서 Write 전에 미리 아
는 방법이 있을까요?

아니면 Write시에 Server Process가 멈추지 않고 client Process의 power
off 사실을 알수 있는 방법이 있을까요?

Client Program이 kill되는 것은 read에서 size == 0, write에서 errno ==
EPIPE로 알수 있지만 Client Computer가 Power off되는 것은 write에서
멈춰버리니 그 사실을 알수 없는것 같습니다.

고수님들의 현명한 조언을 학수 고대 하겠습니다.

^__________________________^;

P.S. 노파심에서 말씀드리지만, 위에 대한 대답으로 NON BLOCKING 모드로
서버 프로그램을 바꾼다든가 혹은 Multi Thread 버전으로 바꾼다고 하는
식의 대답은 현실적으로 구현하기 힘든 부분입니다.

가급적 Single Thread, Blocking Mode에서 적절한 답변을 해주시면
매우 감사하겠습니다.

moonzoo의 이미지

select를 사용한 서버를 구축하신다고 했으니..

말그대로 select를 사용하시면 될 듯합니다.

write하기 전에 select로 write가능한지 걸어놓고

가능할때만 write하시면 어떨지요?

client 컴터가 죽었다면..select에서 timeout이 걸리겠네요.

김경태의 이미지

먼저 저의 질문에 성의 있게 답변해 주셔서 감사드립니다.

^^;

그런데 님께서 말씀하신 부분은 지금 현재도 다음과 같이 반영되어 있습니다.


int writable_timeo(int fd, int sec) {
    fd_set  wset;
    struct timeval tv;

    FD_ZERO(&wset);
    FD_SET(fd, &wset);

    tv.tv_sec = sec;
    tv.tv_usec = 0;

    return (select(fd + 1, NULL, &wset, NULL, &tv));
}

위 코드를 사용하여 writable이 가능한지 먼저 체크한후에 writen을 호출합
니다.

if (0 >= writable_timeo(fd, 3)) {
    //Error Handling Code;
}

if ((ret = writen(fd, buf, ilen)) != ilen) {
    //Error Handling Code;
}

^^;

kn31232의 이미지

뭐 자세하게 제가 네트웍플밍을 잘 아는건 아니지만....

죽어있는 클라이언트에 write를 시도할때 어떤 시그날이 발생하지 않을까 하는 생각입니다.

전에 SIGPIPE라는 수신자가 없는 파이프에 write를 시도할때 발생하는 시그날이 발생하지 않을까 하는 생각이....-_-;

뭐 이거 말고도 다른 어떤 시그날이나 리턴값에 대한 처리를 해주면 되지 않을까요?

리턴값이 없이 그냥 죽어버린다면 어디서 어떻게 죽는지 디버깅해서 그부분에 대한 처리를 해주시면 될꺼같다는...뭐 말이 쉽지만...-_-;;

다 아는거 말씀 드린거 같다는 생각이....ㅎㅎ

이상 허접 답변이였습니당~! -_-;;;

飛上

stoneshim의 이미지

UNIX Socket FAQ의 내용입니다.

Quote:
If the peer reboots, or sets l_onoff = 1, l_linger = 0 and then closes, then we should get ECONNRESET (eventually) from read(), or EPIPE from write().

다음 URL을 참고하세요
http://www.ibrado.com/sock-faq/#faq8

우리 모두 리얼리스트가 되자. 그러나 가슴에 이룰 수 없는 꿈을 가지자

purewell의 이미지

recv/send 할 때, 연결이 끊기면 ㅡ_-)

-1 리턴해주지 않나요?

_____________________________
언제나 맑고픈 샘이가...
http://purewell.biz

김경태의 이미지

먼저 대답해주신 분들의 관심과 높은 가르침에 감사드립니다.

하나 약간 오해하신 부분이 계신 것 같아서 몇가지 답변에 대한 보충 설명을
드리고자 합니다.

If the peer reboots, or sets l_onoff = 1, l_linger = 0 and then closes, then we should get ECONNRESET (eventually) from read(), or EPIPE from write().

이 문장을 해석해 보면 "만약 peer가 reboot하게 되면, ... 우리는 write를
통하여 EPIPE를 받게 된다"라고 되어 있습니다.

맞습니다. 그래서 제가 처음 질문을 올릴때

1. 해당 컴퓨터를 켜면 -> Server 프로그램 정상적으로 작동

라고 했습니다.

이것은 다른말로 <컴퓨터가 켜지면>정상적인 error handling code를 타게 되
어 server가 멈추는 최악의 상황을 방지한다는 것입니다.

이러면 아주 좋겠지만, 만약 유저가 컴퓨터를 꺼놓은채 그대로 방치하는 경우에,

2. 해당 컴퓨터가 계속 꺼놓으면 -> 상당시간 후에 Server 프로그램 정상작동

에 해당하게 되며 매우 장시간동안 Server는 모든 동작을 멈추게 될 것입니다.

이러한 경우에 해당된다면 결국 문제는 위 문장에서 해결될 성질의 것이 아니
라고 사료되며 여전히 client의 power off로 인한 server의 blocking에 대
한 문제는 해결이 불가능한 채로 남아 있게 됩니다.

이점을 적절히 고려하셔서 만약 Client가 connection을 맺은채로 power
off하고 장시간동안 컴퓨터를 reboot하지 않은 경우에도 server가 멈추지
않고 정상 동작될 수 있는 방법이 있다면 이에 대한 좋은 가르침을 주셨으면
좋겠습니다.

결국은,

1. server측에서 write하기전에 먼저 client가 power off인지 아닌지 확인
2. Server측에서 write할때 Client가 power off이면 즉시 return

이 둘중의 한가지 방법을 single thread && blocking mode에서 찾을 수
있어야 할텐데...어렵군요. ^^;

다시한번 높으신 가르침을 부탁드립니다. ^^;

stoneshim의 이미지

그렇군요... rebooting이 아니라 단지 power off인 경우라면 정말 난감한 경우겠습니다.

제가 알고 있기로는 이런 경우라면( 특히나 single thread에 blocking socket 이라면) 별다른 방법이 없습니다.

SOCKET FAQ의 2-8 항목을 봐도 어렵다는 말만 있군요.
http://www.ibrado.com/sock-faq/#faq14

시스템 전체적으로 KEEPALIVE time을 줄이거나, read 혹은 write timeout을 시도해 볼 수도 있겠지만.. single thread에 blocking socket 이라면 그 시간동안은 서버가 block되는 것을 피할 수는 없어 보입니다.

우리 모두 리얼리스트가 되자. 그러나 가슴에 이룰 수 없는 꿈을 가지자

ontow의 이미지

Heart Beat(alive 등등 같은말)를 이용하는 수 밖에 없습니다.
컴퓨터의 전원이 내려지면 connection close 패킷이 오지 못하고 접속이
끊어지는 경우라 서버측에서는 접속되어 있는것으로 착각하는거죠

대안은 클라이언트가 일정시간을 주기로 서버측에 소량의 데이타를 보냅니다.
서버는 타이머를 리셋합니다. 응답은 필요없죠

결론은 근본적인 해결책은 없다 라는겁니다.

shkwon81의 이미지

말 그대로 커넥션이 조용히 끊어져 버린 경우죠..

이런 경우에도 서버는 클라이언트와의 연결이 끊여졌다는 사실을 모르기 때문에, send() 호출은 성공합니다.

실제로 커넥션이 끊여져 있다는 것을 파악하기까지는 1분 이상의 시간이 소요됩니다. (이것도 커널 옵션이 있는지는 모르겠네요.) 실제로 이런 경우는 꽤 흔히 나타나므로 커넥션이 장시간 지속되는 구조의 경우라면 반드시 고려하셔야 합니다.

이를 방지하기 위해 먼저 윗 분이 설명해 주신 keepalive를 사용하는 방법이 있습니다.

이를 위한 방법으로 또 두 가지를 들 수 있습니다.
하나는 TCP 단에서 지원하는 keepalive 옵션을 사용하는 것입니다. 이것은 setsockopt() 호출을 통해 설정할 수 있을 것입니다. 이 옵션을 설정해 두면 TCP 단에서 주기적으로 keepalive(살았나 죽었나) 체크를 하고, 실패한 경우 TCP 연결을 끊습니다.

그러나 윗 방법은 그리 추천되지 않습니다. (정확한 이유는 잘 생각이 안 나네요.. 윗 방법으로도 끊어졌다는 사실을 알 수 없는 케이스가 있었는데..)
하여튼 주로 추천되는 방법은 위와 같은 keepalive를 애플리케이션 프로토콜 단에서 구현하는 것입니다. 즉, 프로토콜 상에서 1분에 한번씩 살았나 죽었나를 체크해 보자.. 하는 것입니다.
만약에 견고한 서버를 새로 제작해야 하는 상황이라면 이 방법을 사용할 것을 권합니다.

그런데, 질문하신 분 의도로는 일단 위와 같은 방법은 사용이 힘들 듯 합니다.

그런 경우에는 김경태 님의 코드에서 alarm 시그널 핸들링을 이용하는 방법을 생각해 볼 수 있습니다.(별로 추천하고 싶지는 않지만, 현재 구조를 바꿀 수 없다면 어쩔 수 없겠지요)

Quote:
static void sig_alrm(int signo)
{
return;
}

위 시그널 핸들러에서 그냥 return을 하셨는데, 여기서 return을 하지 마시고,

siglongjmp() 함수를 이용해서 미리 설정되어 있는 실행을 재시작할 수 있는 지점으로 jump 하도록 작성하세요. 물론 jump 지점은 프로그램이 정상적으로 구동될 수 있는 시점으로 이어져야 합니다. jump 지점을 설정하기 위해서는 sigsetjmp() 함수를 이용하시면 됩니다.

이외에도 sigjmp()와 setjmp() 를 이용할 수도 있지만, jump를 다룰 때는 가급적 signal block 도 생각하시는 것이 좋습니다.

siglongjmp(), sigsetjmp() 류의 함수들은 위와 같은 상황을 처리하기 위한 low-level error handling 용도로 사용하시면 유용합니다. (물론 명백히 쓸만한 이유가 있을 때만 쓰셔야 겠지요.)

ssoo76의 이미지

제가 알기로는 TCP의 특성상 한쪽에서 power off 된 경우 복구할 수 없는 것으로 알고 있습니다.

아마도 Server Computer에서 netstat를 치면 power off 된 장비와 연결된 정보가 timeout이 아닌 establish로 되어 있을 겁니다.

그러니 socket api에서는 해당 문제를 해결할 수가 없습니다.

app 레벨에서 alive check를 하던지 아니면 client가 초기 접속시 이전 접속에 대해서 초기화를 하는 방향으로 app을 수정하셔야 할겁니다.

세상은 하나..........

shkwon81의 이미지

Quote:
아마도 Server Computer에서 netstat를 치면 power off 된 장비와 연결된 정보가 timeout이 아닌 establish로 되어 있을 겁니다.

그러니 socket api에서는 해당 문제를 해결할 수가 없습니다.

맞습니다. ESTABLISHED 로 나오게 됩니다.

이런 경우에 close() 함수로 소켓을 닫아도, 상대방이 응답을 못하므로,
소켓은 FIN_WAIT_1 상태에서 머무르게 됩니다.(이런 상태가 몇 분간이나 지속됩니다.)

그렇지만 방법이 없다고는 말할 수 없습니다. FIN_WAIT_1 상태가 적어도 잘못된 ESTABLISHED 상태보다는 낫습니다. 따라서 위에서 제시한 siglongjmp()를 이용하고, 동시에 적절한 곳에서 close() 를 호출하여 socket을 닫은 후, 다음으로 진행하면 됩니다.

이 때, 한 가지 가능한 예로 timeout을 나타내는 플래그 변수를 이용하여 적절한 close() 등의 적절한 clear 동작을 수행하게 할 수 있을 것입니다.

댓글 달기

Filtered HTML

  • 텍스트에 BBCode 태그를 사용할 수 있습니다. URL은 자동으로 링크 됩니다.
  • 사용할 수 있는 HTML 태그: <p><div><span><br><a><em><strong><del><ins><b><i><u><s><pre><code><cite><blockquote><ul><ol><li><dl><dt><dd><table><tr><td><th><thead><tbody><h1><h2><h3><h4><h5><h6><img><embed><object><param><hr>
  • 다음 태그를 이용하여 소스 코드 구문 강조를 할 수 있습니다: <code>, <blockcode>, <apache>, <applescript>, <autoconf>, <awk>, <bash>, <c>, <cpp>, <css>, <diff>, <drupal5>, <drupal6>, <gdb>, <html>, <html5>, <java>, <javascript>, <ldif>, <lua>, <make>, <mysql>, <perl>, <perl6>, <php>, <pgsql>, <proftpd>, <python>, <reg>, <spec>, <ruby>. 지원하는 태그 형식: <foo>, [foo].
  • web 주소와/이메일 주소를 클릭할 수 있는 링크로 자동으로 바꿉니다.

BBCode

  • 텍스트에 BBCode 태그를 사용할 수 있습니다. URL은 자동으로 링크 됩니다.
  • 다음 태그를 이용하여 소스 코드 구문 강조를 할 수 있습니다: <code>, <blockcode>, <apache>, <applescript>, <autoconf>, <awk>, <bash>, <c>, <cpp>, <css>, <diff>, <drupal5>, <drupal6>, <gdb>, <html>, <html5>, <java>, <javascript>, <ldif>, <lua>, <make>, <mysql>, <perl>, <perl6>, <php>, <pgsql>, <proftpd>, <python>, <reg>, <spec>, <ruby>. 지원하는 태그 형식: <foo>, [foo].
  • 사용할 수 있는 HTML 태그: <p><div><span><br><a><em><strong><del><ins><b><i><u><s><pre><code><cite><blockquote><ul><ol><li><dl><dt><dd><table><tr><td><th><thead><tbody><h1><h2><h3><h4><h5><h6><img><embed><object><param>
  • web 주소와/이메일 주소를 클릭할 수 있는 링크로 자동으로 바꿉니다.

Textile

  • 다음 태그를 이용하여 소스 코드 구문 강조를 할 수 있습니다: <code>, <blockcode>, <apache>, <applescript>, <autoconf>, <awk>, <bash>, <c>, <cpp>, <css>, <diff>, <drupal5>, <drupal6>, <gdb>, <html>, <html5>, <java>, <javascript>, <ldif>, <lua>, <make>, <mysql>, <perl>, <perl6>, <php>, <pgsql>, <proftpd>, <python>, <reg>, <spec>, <ruby>. 지원하는 태그 형식: <foo>, [foo].
  • You can use Textile markup to format text.
  • 사용할 수 있는 HTML 태그: <p><div><span><br><a><em><strong><del><ins><b><i><u><s><pre><code><cite><blockquote><ul><ol><li><dl><dt><dd><table><tr><td><th><thead><tbody><h1><h2><h3><h4><h5><h6><img><embed><object><param><hr>

Markdown

  • 다음 태그를 이용하여 소스 코드 구문 강조를 할 수 있습니다: <code>, <blockcode>, <apache>, <applescript>, <autoconf>, <awk>, <bash>, <c>, <cpp>, <css>, <diff>, <drupal5>, <drupal6>, <gdb>, <html>, <html5>, <java>, <javascript>, <ldif>, <lua>, <make>, <mysql>, <perl>, <perl6>, <php>, <pgsql>, <proftpd>, <python>, <reg>, <spec>, <ruby>. 지원하는 태그 형식: <foo>, [foo].
  • Quick Tips:
    • Two or more spaces at a line's end = Line break
    • Double returns = Paragraph
    • *Single asterisks* or _single underscores_ = Emphasis
    • **Double** or __double__ = Strong
    • This is [a link](http://the.link.example.com "The optional title text")
    For complete details on the Markdown syntax, see the Markdown documentation and Markdown Extra documentation for tables, footnotes, and more.
  • web 주소와/이메일 주소를 클릭할 수 있는 링크로 자동으로 바꿉니다.
  • 사용할 수 있는 HTML 태그: <p><div><span><br><a><em><strong><del><ins><b><i><u><s><pre><code><cite><blockquote><ul><ol><li><dl><dt><dd><table><tr><td><th><thead><tbody><h1><h2><h3><h4><h5><h6><img><embed><object><param><hr>

Plain text

  • HTML 태그를 사용할 수 없습니다.
  • web 주소와/이메일 주소를 클릭할 수 있는 링크로 자동으로 바꿉니다.
  • 줄과 단락은 자동으로 분리됩니다.
댓글 첨부 파일
이 댓글에 이미지나 파일을 업로드 합니다.
파일 크기는 8 MB보다 작아야 합니다.
허용할 파일 형식: txt pdf doc xls gif jpg jpeg mp3 png rar zip.
CAPTCHA
이것은 자동으로 스팸을 올리는 것을 막기 위해서 제공됩니다.