솔라리스 환경에서 select 함수 에러?

lopad의 이미지

솔라리스 8 (x86)
사용자 세션을 50개정도 유지하고 있는 서버이고 멀티쓰레드환경임.
두개의 쓰레드를 두어서 세션을 반씩 나누어 조회를 처리합니다.

각세션은 모두 blocking 모드임, 모든 쓰레드는 signal IGN, 별도의 한 쓰레드만 signal(SIGSEGV,SIGBUS) 받음.

조회처리는 방법은 사용자 fd 리스트에 대한 select 호출후
header와 body를 나누어서 2번에 읽음
이때 RecvTime은 내부적으로 fd에 대한 타임아웃설정후 select
read 함. (코드는 아래 참조)

문제현상 : 특정시점에 특정사용자에게 truss결과처럼 RecvTime block 발생
select 호출하고 나서 데이타를 읽을려고 보니 데이타가 없다?

원인이 ?
OS 버그인지 아니면 select 연속해서 계속 부른게 관계가 있는지?
RecvTime 오류인지?

조언을 부탁드립니다.

이런현상은 네트웍상태가 아주 안좋을 때 일어남. 한달에 한번 있을 까 말까 함.

쓰레드 메인조회

while (1) {
     select(maxfd, &rset, 0, 0, 0);
     ...
     if (FD_ISSET(fdtable[i].fd, &rset) {
           RecvTime(fdtable[i].fd, &Header, sizeof(Header), 4);
           ....
           RecvTime(fdtable[i].fd, &body, Header.len, 4);
           // do something
    }
}

RecvTime은
while(1) {
     loop++
     ret_cnt = select(maxfd, &rset,0,0,time_out);
     ...
     if (FD_ISSET(fd, &rset) {
         n = recv(fd, ptr+process, remain, 0);
         if (n == 0) {
             return process;
         } else if (n < 0) {
             if (errno == EWOULDBLOCK || errno == EAGAIN) {
                  continue;
             }
             return process;
         }
         process += n;
         remain -= n;
         if (remain <= 0) break;
     }
}

truss 추적결과
/4:     recvmsg(1, 0xCE407CFC, 0)       (sleeping...)
/5:     recv(51,0x0xCE9FF4F8, 65, 0)       (sleeping...)
------------------------------------------------------------------
....

gdb RecvTime내의 변수값들
maxfd = 52
remain = 65
process = 0
ret_cnt = 1
loop = 1
timeout = {tv_sec = 4, tv_usec = 0}
rset = {fds_bits = {0x0, 0x80000, 0x0 <repeats 30 times>}}
allset = {fds_bits = {0x0, 0x80000, 0x0 <repeats 30 times>}}

미리 감사

lopad의 이미지

추가하여
blocking 이 발생하는 곳은
header 읽고난 후 body를 읽을려고 할 때(두번째 RecvTime) 발생합니다.

mach의 이미지

일단 원격지가 연결종료시 return process하는데, 이에 대한 처리를 메인에서 관과한듯보입니다. 헤더읽다가 연결종료, 바디읽다가 연결종료를 고려하시고요.
또한가지는 Status를 도입하는 것입니다. 즉, header_read_state, body_read_state, wait_state, Exe_state등으로 처리하는것을 추천하는 바입니다.

------------------ P.S. --------------
지식은 오픈해서 검증받아야 산지식이된다고 동네 아저씨가 그러더라.

stoneshim의 이미지

lopad wrote:
각세션은 모두 blocking 모드임

blocking socket이란 말씀인가요?

그렇다면 select에서 readable 하다고 하더라도 block이 될 수 있는것 아닐까요?

Quote:
if (errno == EWOULDBLOCK || errno == EAGAIN) {
continue;
}

이 부분을 보아서는 nonblocking socket일것 같은데요...

저도 mach님 처럼 nonblocking socket 에 state machine 구현을 추천합니다.

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

lopad의 이미지

제가 올린 소스 부분이 생략이 좀 되어 있어서 그런 것 같은데..
아래와 같습니다.

쓰레드 메인조회

while (1) { 
     rset = allset;
     nsel = select(maxfd, &rset, 0, 0, 0); 
     if (nsel < 0) { 
          //에러처리
     }
     if (nsel > 0) {
          if (FD_ISSET(fdtable[i].fd, &rset) { 
               n = RecvTime(fdtable[i].fd, &Header, sizeof(Header), 4); 
               if (n <= 0) { 
                   // 소켓 close 처리
               }
               .... 
                n= RecvTime(fdtable[i].fd, &body, Header.len, 4);  // 여기서 block 상태로 빠지는 경우가 있음
                if (n <=0) {
                    // 소켓 close 처리
                }
                // do something 
           }
    } 
}

RecvTime
while(1) { 
     loop++ 
     ret_cnt = select(maxfd, &rset,0,0,time_out); 
     ... 
     if (ret_cnt == 0) { // 타임아웃
         return -2;
     }

     if (FD_ISSET(fd, &rset) { 
         n = recv(fd, ptr+process, remain, 0); 
         if (n == 0) { 
             return process; 
         } else if (n < 0) { 
             if (errno == EWOULDBLOCK || errno == EAGAIN) { 
                  continue; 
             } 
             return process; 
         } 
         process += n; 
         remain -= n; 
         if (remain <= 0) break; 
     } 
}

gdb로 보았을 때 select 호출후 받은 ret_cnt =1 이거든요.
막상 FD_ISSET 체크하고나서 read해 보니 데이타 없어라고 나오는것 같은데..

현재 서버 구조상 nonblock으로 처리하기에는 복잡한 문제가 있어서 block 으로 처리해야 합니다.

RecvTime에 있는 nonblocking 모드처리는 옛전 쓰던 코드를 가져와서 그냥 그대로 남아 있어서 그럽니다.

stoneshim의 이미지

일단 blocking socket이라면

         n = recv(fd, ptr+process, remain, 0); 
         if (n == 0) { 
             return process; 
         } else if (n < 0) { 
             if (errno == EWOULDBLOCK || errno == EAGAIN) { 
                  continue; 
             } 
             return process; 
         } 

이부분에서 errno == EWOULDBLOCK || errno == EAGAIN 체크 부분은 의미가 없겠네요.

오히려 EINTR 에 대한 처리가 필요할 듯 싶습니다.( 물론 현재 말씀하신 문제와는 무관합니다 )

아시겠지만 select 가 readable 하다고 return 하는 의미는 해당 socket의 버퍼에 read 할 data가 있다는 의미일 뿐이기 때문에 원하는 크기( 예를 드신 코드에서는 Header.len )만큼의 data가 socket 버퍼에 존재하는것을 보장하는것은 아닙니다.

원하는 크기만큼의 data가 socket 버퍼에 존재하지 않는 경우에 block이 되겠군요.

이 경우는 client측에서 정확한 크기만큼의 데이터를 모두 전송하지 못했을 것으로 의심이되네요.

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

bejoy4him의 이미지

:shock:
얼마전 15일 밤. 저희도 비슷한 문제가 발생하더군요... select에서 블럭이 걸려 다음날 아침까지 9시간이 넘더록 hang up되어 있었습니다.
select 이함수에 timeout값을 0으로 설정해 주었음에도 일어난 일이었습니다.
최악의 경우에 keepalive 타임이라도 걸려서 10분전후로 블록이 플려야 정상이지 않을까요?
저희도 역시 메시지를 헤더와 본문으로 두번 읽는데... 헤더를 제대로 읽지 못하면 메시지 fail로그를 찍도록 되어있습니다. 하지만 문제가 일어났을땐 어떤 로그도 찍지 못하고 그대로 중지... 이거였습니다. ㅡㅡ;;
어쨌던 5개의 쓰레드 중 select를 사용하는 두개의 쓰레드가 동시에 block이 걸리고, 소켓에 write를 하는 1개의 쓰레드는 err를 찍어내더군요... 게다가 2개의 소켓중 하나는 로컬로 있는 다른 프로세스와 소켓통신을 하는 것입니다. 그것도 동시에 죽었으니 말 다했죠...

한데 문제는 링크를 재접속하기위한 루틴은 select를 사용하는 쓰레드에 코딩되어있기에 select의 블록으로 인해 재접속을 시도도 못한채 멍청히 있었습니다.
솔라리스와 윈도2000서버와 소켓통신 하고 있었는데.. 윈도2000과 통신하는 다른 서버들을 링크단절의 현상이 없었죠.... 그런데 더욱 웃긴것은 솔라리스에서 소켓을 닫았는데 그건 잘 닫히더라 이겁니다.(상대편 서버에 로그가 남아있습니다.)

select문에서 블럭이 걸려있고, 링크를 테스트하는 다른 쓰레드에서 메시지를 서버로 보냈는데(성공여부는 체크하지 않습니다.) 응답이 없어(select블럭이 플리지 않아), 재접속 플래그를 셋팅하고 소켓을 닫았는데, 닫는것은 잘되어 상대편에 로그가 남았지만 select는 fin_ack를 감지하지 못했는지 여전히 block상태이다.

정말 이상한 문제지 않나요? 따라서 저희는 이것을 솔라리스의 TCP레벨의 송수신버퍼에 무언가 일시적 문제가 생기지 않았나 하고 잠정결론을 내렸지만 정말황당하더군요.....

음... 솔라리스에 어떤문제가...... ㅡㅡ;;

lopad의 이미지

stoneshim wrote:
원하는 크기만큼의 data가 socket 버퍼에 존재하지 않는 경우에 block이 되겠군요.

select에서 readable 하다면
n =recv(fd, ptr+process, remain, 0)

recv는 block 되지 않고 n <= remain 형태 돌아옵니다. 소켓버퍼에 있는 데이타가 remain 크기가 아니더라고 현재 call한 시점에 있는 버퍼를 비우게 됩니다. (테스트 해 보시면 압니다..)

stoneshim wrote:

최악의 경우에 keepalive 타임이라도 걸려서 10분전후로 블록이 플려야 정상이지 않을까요?

tcp_keepalive 시간(default 2시간) 지나면 풀립니다.
이런 현상은 상대방 network cable 빠졌다던가 power off 또는 방화벽이 세션을 짜르는 경우 등에서 발생하겠죠.

로그를 추적해 보니 client가 재접속을 요청해 왔음. 미루어 짐작컨데 방화벽이 세션을 짜르는것 같은데.. 물론 다른경우도 있을 수 있지만 현재까지 추정가능한 상황일 것 같네요.

lopad의 이미지

물론 app 상에서 keepalive 을 하고 있습니다. 저희쪽 방화벽이 건들지 않을 시간에 안에 더미통신을 합니다.

궁금한것은 이런상항이더라도 RecvTime(select, recv) 하면 block 없이 로직이 흘러갈야 할 것 같은데 아닌가요?

mach의 이미지

bejoy4him wrote:
:shock:
얼마전 15일 밤. 저도 똑같은 문제가 발생하더군요... select에서 블럭이 걸려 다음날 아침까지 9시간이 넘더록 hang up되어 있었습니다.
select 이함수에 timeout값을 0으로 설정해 주었음에도 일어난 일이었습니다.

select ( maxfds+1, &rset, &wset, &eset, 0);
이렇게 준다면, 마지막 파라메터 0은 NULL로 인식되고 select는 timeout을 무시합니다.
timeout을 0으로 주려면
struct timeval timeout;

timeout.tv_sec = 0;
timeout.tv_usec = 0;

select ( maxfds+1, &rset, &wset, &eset, &timeout);
이렇게 주어야 하는데 혹시 null(0)로 주시지 않았나 우려됩니다.

bejoy4him wrote:
:shock:
최악의 경우에 keepalive 타임이라도 걸려서 10분전후로 블록이 플려야 정상이지 않을까요?
어쨌던 5개의 쓰레드 중 select를 사용하는 두개의 쓰레드가 동시에 block이 걸리고, 소켓에 write를 하는 1개의 쓰레드는 err를 찍어내더군요... 게다가 2개의 소켓중 하나는 로컬로 있는 다른 프로세스와 소켓통신을 하는 것입니다. 그것도 동시에 죽었으니 말 다했죠...

한데 문제는 링크를 재접속하기위한 루틴은 select를 사용하는 쓰레드에 코딩되어있기에 select의 블록으로 인해 재접속을 시도도 못한채 멍청히 있었습니다.
솔라리스와 윈도2000서버와 소켓통신 하고 있었는데.. 윈도2000과 통신하는 다른 서버들을 링크단절의 현상이 없었죠....

음... 솔라리스에 어떤문제가...... ㅡㅡ;;

5개의 쓰레드가 각기 select()를 호출할 경우 5개중 어느 쓰레드가 이벤트를 감지할지는 알 수 없습니다.
이것은 며느리도 모르는 사안입니다. race condition이 되겠지요.
모든 쓰레드가 모든 socket descriptor에 대한 처리를 수행하는 로직을 가지고 있어야 합니다.
당연히 모든 socket descriptor는 모든 쓰레드에 의해 공유됩니다.
예를 들어보겠습니다. 2개의 쓰레드만 생각해보겠습니다.

1) listen하고 accept하는 쓰레드가 select를 호출하고있다.
2) 연결된 소켓에 대해 read를 하기 위해 select를 호출하는 쓰레드가 있다.
* 프로그래머의 오류
프로그래머의 의도는 새로운 연결에 대한 select는 1)로 되고, 그외의 read,write등의 이벤트는 2)에서 검출되기를 기대할 수 있습니다.
* 그러나, select에 검출될 수 있는 이벤트가 1)에 걸릴지, 2)에 걸릴지는 아무도 모른다는 얘기입니다.
* 문제 분석
- 쓰레드들이 select()를 호출할경우 race condition을 검토해야한다.
- select()가 자신이 원하는 fd의 이벤트만 검출하는것은 아니다.(한 프로세스의 전체 fd +1 보다 작은 모든
파일디스크립터(소켓포함)에 대해 이벤트를 검출하기 때문이다.

* 결론
따라서, select를 한프로세스(다수개의 쓰레드로 구성됨) 내에서 1개로 줄이는 구조로 가야한다.
그리고, 각각에 스테이트(state)를 두어 일종의 state machine프로그램 구조로 코딩한다.

------------------ P.S. --------------
지식은 오픈해서 검증받아야 산지식이된다고 동네 아저씨가 그러더라.

bejoy4him의 이미지

Quote:

select ( maxfds+1, &rset, &wset, &eset, 0);
이렇게 준다면, 마지막 파라메터 0은 NULL로 인식되고 select는 timeout을 무시합니다.
timeout을 0으로 주려면
struct timeval timeout;

timeout.tv_sec = 0;
timeout.tv_usec = 0;

select ( maxfds+1, &rset, &wset, &eset, &timeout);
이렇게 주어야 하는데 혹시 null(0)로 주시지 않았나 우려됩니다.


네.. 저도 이 부분은 발견하여 말씀하신대로 수정하였습니다.
하지만 솔라리스의 경우는 그 부분을 그냥 0으로 주어도, 블록이 걸리지 않더군요.. 테스트해보았거든요.. ^^
그래도 일단은 모르니 그렇게 조치하였습니다.

Quote:

따라서, select를 한프로세스(다수개의 쓰레드로 구성됨) 내에서 1개로 줄이는 구조로 가야한다.

그리고 제가 말한 select를 사용하는 쓰레드가 여러개라는 것은 한 소켓에 대해서 여러개의 쓰레드가 select를 사용했다는 이야기가 아니구요....

일종의 미들웨어다 보니 소켓을 2개 열어서 한개의 소켓은 다른 머신에 있는 프로세스에 연결되어 있고, 또 다른 하나의 소켓은 로컬 머신에 있는 다른 프로세스(쓰레드가 아닙니다.)에 연결되어 있는 것입니다. 그렇게 해서 select를 2개 사용하게 된거죠. 2개의 쓰레드에서 자신이 담당하는 소켓 하나에 대해 각각 select함수를 이용하여 메시지 수신... 이상태에서 race condition이라...
이런 경우에는 그것이 일어날 이유가 없지요....

저도 로그를 기록하는 함수를 이용함에 있어서 교착상태에 빠지는 것은 아닐까 살펴보았지만... 뮤텍스가 적용되어 있고.. 다른 쓰레드들이 블록되어있는 상황에서 링크 테스트를 하는 쓰레드는 링크테스트시도하는 로그를 찍고 있었으므로 그것도 아니겠지요....

이 프로세스가 hang up되었던 일은 작년초 이래 첨이네요.. 작년에는 로그파일을 기록하는 함수에 문제가있었지요...

bejoy4him의 이미지

Quote:
tcp_keepalive 시간(default 2시간) 지나면 풀립니다.
이런 현상은 상대방 network cable 빠졌다던가 power off 또는 방화벽이 세션을 짜르는 경우 등에서 발생하겠죠.

로그를 추적해 보니 client가 재접속을 요청해 왔음. 미루어 짐작컨데 방화벽이 세션을 짜르는것 같은데.. 물론 다른경우도 있을 수 있지만 현재까지 추정가능한 상황일 것 같네요.


제가 알기론 일반적인 디폴트 값은 7200~7500정도로 셋팅되어 있어서 약 10분 내외(계산 방법이 좀 복잡하더군요... FreeBSD의 경우지만 7500ms * (count(8로 하드코딩) + 1) = 10분 75초.. 이런식으로 keepalive time이 적용되더군요...)로 알고있습니다.
그리고 2시간은 웹서버의 경우로 특별히 그렇게 설정을 하여서 세션이 그렇게 오래 유지될수 있는 것으로 알고 있습니다.

머신은 다르지만 같은 프로세스를 가지고 똑같은 버전의 프로그램을 상대로 시험하여 랜선을 뽑아보았지만 역시 10분 내외면 블럭이 플립니다. ^^
설사 2시간이 기본값이라고 하더라도 다음날 아침 9시까지 사람이 직접 프로세스를 죽이고 다시 살릴때까지 블록이 걸려있었다는 것은 좀 많이 이상하죠.... ^^

그리고 그 둘 사이에 방화벽은 없습니다. 허브를 중간에 두고 바로 연결된 상태로 알고있습니다. 바로 옆에 붙어 있는 머신하고 통신하는데 방화벽까지 이용할 필요는 없을테니까요...

stoneshim의 이미지

Quote:
recv는 block 되지 않고 n <= remain 형태 돌아옵니다. 소켓버퍼에 있는 데이타가 remain 크기가 아니더라고 현재 call한 시점에 있는 버퍼를 비우게 됩니다. (테스트 해 보시면 압니다..)

네... 제가 잠시 착각을 했습니다.

Quote:
RecvTime에 있는 nonblocking 모드처리는 옛전 쓰던 코드를 가져와서 그냥 그대로 남아 있어서 그럽니다.

여기서 벌써 언급을 하셨었는데... 제가 제대로 못보고 말씀을 또 드린 결과가 되었군요. 죄송합니다.

Quote:
두개의 쓰레드를 두어서 세션을 반씩 나누어 조회를 처리합니다.

다시 읽어보고 생각이 든건데... 혹시 두개의 thread에서 select 파라미터의 &rset 에 동일 fd가 등록될 가능성은 전혀 없나요?
그렇다면 말씀하신 상황이 발생할 수 있을듯 한데요.

아니면 정말 Solaris 8 버그일 수도 있겠네요... Sun 에서 제공하는 버그패치 내용들은 모두 살펴 보셨나요?

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

lopad의 이미지

Quote:
두개의 쓰레드를 두어서 세션을 반씩 나누어 조회를 처리합니다.
다시 읽어보고 생각이 든건데... 혹시 두개의 thread에서 select 파라미터의 &rset 에 동일 fd가 등록될 가능성은 전혀 없나요?

가장 가능성 있는 시나리오인데...클라이언트가 들어오는 둘중 한 쓰레드를 선택하기때문에 중목해서 select를 호출할 경우는 없습니다. 흠..

그현상이 나타났을 때 오직 그 한 클라언트에서만 계속해서 그런문제를 양산했습니다. 안되니까 끊고 다시 붙으면 다시 먹통 또 끊고 하는 또 붙고..
붙었던 서버프로세스들은 전부 먹통상태로 결국 그날은 9시부터 16까지 그러했습니다. 그이후부터는 또 정상....
먹통되었던 프로세스들은 keepalive 시간후 RecvTime에 리턴되었고. 이후 새로운 클라언트들은 정상적으로 처리되고 있습니다. 물론 그동안 다른 클라언트세션들은 처리되지 않았지만...

이런 현상으로 미루어보면 클라언트-서버네트웍을 타는 거처럼 보입니다.

solaris patch를 찾아보아도 없군요... 총 3대로 서비스하는데 그중 한대는 이 그전에 전부 patch를 했습니다. -_-;;

mach의 이미지

for (; ;) {
   ...
   FD_SET(sockfd, &rset); // 이 부분이 의심이 가는군요. 쓰레드별로...
   ...
   select();
}

이런 식으로 코딩이 되어있겠지요.
이때, FD_SET()을 취한 부분의 코드를 정확히 보여주실수 있습니까?
물론, accept()를 호출하는 부분과, RecvTime()내부(쓰레드)에서,즉, 둘다,
이 부분에 대해 코드를 보여주십시요.

* 사실 저는 최근의 솔라리스는 이런 버그는 없다고 생각하는데요. 과거 초창기
솔라리스는 버그가 많다는게 정설이었지만요.

------------------ P.S. --------------
지식은 오픈해서 검증받아야 산지식이된다고 동네 아저씨가 그러더라.

lopad의 이미지

Quote:
이때, FD_SET()을 취한 부분의 코드를 정확히 보여주실수 있습니까?
물론, accept()를 호출하는 부분과, RecvTime()내부(쓰레드)에서,즉, 둘다,
이 부분에 대해 코드를 보여주십시요.

쓰레드 메인조회부분
FD_ZERO(&allset);
FD_ZERO(&rset);
FD_SET(pipe_fd[thr_index][0], &allset);
maxfd = _pipe_fd[thr_index][0];
while (1) { 
     rset = allset; 
     nsel = select(maxfd, &rset, NULL,NULL,NULL); 
     if (nsel < 0) { 
          //에러처리 
     } 
     if (nsel > 0) { 
          for (i=0; i < THREAD_MAX_FD; i++) {
               if (FD_ISSET(fdtable[i].fd, &rset) { 
                    n = RecvTime(fdtable[i].fd, &Header, sizeof(Header), 4); 
                    if (n <= 0) { 
                         // 소켓 close 처리 
                    } 
                    .... 
                    n= RecvTime(fdtable[i].fd, &body, Header.len, 4);  // 여기서 block 상태로 빠지는 경우가 있음 
                    if (n <=0) { 
                        // 소켓 close 처리 
                    } 
                    // do something 
               } 
          } 
          if (FD_ISSET(pipe_fd[thr_index][0], &rset)) { // accept 받은 fd를 table에 저장하고  pipe로 정보를 넘겨준다.
               retp = read(pipe_fd[thr_index][0], &n, 1);
               if (retp > 0) {
                    fd = find_accept_table(n);
                    FD_SET(fd, &allset);
                    maxfd = max(maxfd, fd);
               }
          }
    } 
}

accept부분은 다른 프로세스가 수행하고 fd를 이 프로세스로 passing 해주면
fd를 수신을 기다리는 쓰레드에서 받아서 위의 쓰레드로 pipe로 보내준다.

fd수신 쓰레드

void *rcvfd_thread()
{
      while (1) {
          n = read_fd(STDOUT_FILENO, &rc, 1, &client_fd);     
          if (n <= 0) {
              // 에러처리
              continue;
          }
          jth_index = find_which_thread();   // 쓰레드 선택
          write(pipe_fd[jth_index][1], " ", 1);
          if (n <=0 )  {
               //에러처리
          }
     }
}

RecvTime에서

int RecvTime(int fd, void *ptr, size_t nbytes, int time)
{
if (fd < 0)
    return -1;

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

maxfd = fd+1;

remain = nbytes;
process = 0;

while(1) { 
     loop++;
     rset = allset;

     timeout.tv_sec = time;
     timeout.tv_usec = 0;
 
     ret_cnt = select(maxfd, &rset,NULL,NULL,&time_out); 
     if (ret_cnt < 0) {
        if (process == 0) return -1;
        else {
            return process;
       }
    }
     if (ret_cnt == 0) { // 타임아웃 
         return -2; 
     } 

     if (FD_ISSET(fd, &rset) { 
         n = recv(fd, ptr+process, remain, 0); 
         if (n == 0) { 
             return process; 
         } else if (n < 0) { 
             if (errno == EWOULDBLOCK || errno == EAGAIN) { 
                  continue; 
             } 
             return process; 
         } 
         process += n; 
         remain -= n; 
         if (remain <= 0) break; 
     } 
}
mach의 이미지

음......
님의 코드를 보고있습니다.
근데, 우려되는 사항이 하나 있습니다.

서버가 1.2.3.4이고 9999포트로 동작중이라면, 서버가 동작하고 있는 동안에,
즉, 다른 클라이언트가 각각 0, 1, 2개 동작중인 상황에서,
터미널을 다음과 같이 2개 띄우시고 다음과 같이 해보세요.

Quote:
$ telnet 1.2.3.4 9999[엔터]
<아무것도 치지않고 대기한다.>
$ telnet 1.2.3.4 9999[엔터]
<아무것도 치지않고 대기한다.>

이때 증세를 한번 검토해보시지요. 연결의 분배가 각각의 쓰레드에 되었다면, 서버 전체(2개 쓰레드)가 block될 듯 한데요.

------------------ P.S. --------------
지식은 오픈해서 검증받아야 산지식이된다고 동네 아저씨가 그러더라.

lopad의 이미지

Quote:
Quote:
$ telnet 1.2.3.4 9999[엔터]
<아무것도 치지않고 대기한다.>
$ telnet 1.2.3.4 9999[엔터]
<아무것도 치지않고 대기한다.>

이때 증세를 한번 검토해보시지요. 연결의 분배가 각각의 쓰레드에 되었다면, 서버 전체(2개 쓰레드)가 block될 듯 한데요.

네 검토해 보았습니다. 정상적입니다.
(서비스전에 이런경우에 대한 테스트가 있습니다...)
accept된 fd는 pipe를 통해서 두개쓰레드(select 중)중하나로 분배되는데 이때는 fd는 바로 두번의 RecvTime를 타고 allset에 등록이 됩니다.
(소스에서 code를 긁어올 때 빠졌습니다...혼란을 드려 죄송합니다.)

메인쓰레드 조회중

FD_ZERO(&allset); 
FD_ZERO(&rset); 
FD_SET(pipe_fd[thr_index][0], &allset); 
maxfd = _pipe_fd[thr_index][0]; 
while (1) { 
     rset = allset; 
     nsel = select(maxfd, &rset, NULL,NULL,NULL); 
     if (nsel < 0) { 
          //에러처리 
     } 
     if (nsel > 0) { 
          for (i=0; i < THREAD_MAX_FD; i++) { 
               if (FD_ISSET(fdtable[i].fd, &rset) { 
                    n = RecvTime(fdtable[i].fd, &Header, sizeof(Header), 4); 
                    if (n <= 0) { 
                         // 소켓 close 처리 
                    } 
                    .... 
                    n= RecvTime(fdtable[i].fd, &body, Header.len, 4);  // 여기서 block 상태로 빠지는 경우가 있음 
                    if (n <=0) { 
                        // 소켓 close 처리 
                    } 
                    // do something 
               } 
          } 
          if (FD_ISSET(pipe_fd[thr_index][0], &rset)) { // accept 받은 fd를 table에 저장하고  pipe로 정보를 넘겨준다. 
               retp = read(pipe_fd[thr_index][0], &n, 1); 
               if (retp > 0) { 

                 
                 while ( fd = find_accept_table(n)) { // read_fd에서 수신 table에 fd를 기록하고 표시를하는데 그 표시된 fd를 찾아서 돌려줌
 
                    // table loop를 도는 것 같은 코드임
                    n = RecvTime(fd, &Header, sizeof(Header), 4); 
                    if (n <= 0) { 
                         // 소켓 close 처리 
                    } 
                    .... 
                    n= RecvTime(fd, &body, Header.len, 4);       // 이부분은 확률상 적게 타는 루틴이라거 그런지 이쪽은 block이 아직 발견되지 않음.               
                    if (n <=0) { 
                        // 소켓 close 처리 
                    } 
                    // do something 
                    
                    FD_SET(fd, &allset); 
                    maxfd = max(maxfd, fd); 
                 }
               } 
          } 
    } 
}
mach의 이미지

님의 현재버전 RecvTime은 block될 부분이 없습니다. 엄청나게 큰 크기의
데이터가 입력될 경우도 클라이언트가 데이터만 지속적으로 보내준다면
(4초 인터벌내에) block될 이유가 없습니다. 현재 버전에서는 RecvTime은
블록될 수 없습니다.

단지, 한가지 고려사항을 말씀드리면,
아래 코드에서
"...."부분있지요? 이부분에서 헤더정보의 validity 즉, 부적절한 body사이즈
라던가, 불법 프로토콜이라던가를 검출하여 처리하는 부분만 있다면
무리없는 코드로 보입니다.

lopad wrote:

if (nsel > 0) {
for (i=0; i < THREAD_MAX_FD; i++) {
if (FD_ISSET(fdtable[i].fd, &rset) {
n = RecvTime(fdtable[i].fd, &Header, sizeof(Header), 4);
if (n <= 0) {
// 소켓 close 처리
}
....
n= RecvTime(fdtable[i].fd, &body, Header.len, 4); // 여기서 block 상태로 빠지는 경우가 있음
if (n <=0) {
// 소켓 close 처리
}

또하나는 만일 RecvTime()내에서 타임아웃을 NULL == 0으로 주고 했다면,
위에 제가 말씀드린 telnet 테스트에서 블록될 수 있을 것으로
보이나(음 1자(1바이트)정도 입력하고 대기),
이경우도 4초가 지나면 블록이 깨지게 되니, 문제가 없네요.
결론적으로, RecvTime()에서 블록될 수 있는 부분은 select()호출이지만, 이부분의 블록은 현재 코드에서 불가능하고,
루프에서 블록될 수 있으나, 이는 데이터(body)가 아주 큰 경우인 상황이므로
블록이 언젠가는 풀리겠지요. 아니면 select()에서 풀리거나.
따라서, RecvTime()은 블록될 수 없습니다.

* 적어도 현재 코드로 봐서는 RecvTime()에서 블록되는 사태는 없는 코드로 보입니다.

------------------ P.S. --------------
지식은 오픈해서 검증받아야 산지식이된다고 동네 아저씨가 그러더라.

lopad의 이미지

Quote:
"...."부분있지요? 이부분에서 헤더정보의 validity 즉, 부적절한 body사이즈
라던가, 불법 프로토콜이라던가를 검출하여 처리하는 부분만 있다면

맞습니다. 그러부분이 ...에 있습니다.
Quote:
결론적으로, RecvTime()에서 블록될 수 있는 부분은 select()호출이지만,

RecvTime에서 발생하는 block은 select()가 아니라 recv(..)에서 이루어집니다. 즉 select호출후 FD_ISSET()으로 보니 데이타가 있다 하니 recv()를 호출했는데 데이타 아무것도 없어서 계속 기다리는 상황입니다. 처음올린 질문란에 gdb로 RecvTime내의 로컬변수 값들이 표시한 부분이 있습니다.

Quote:
루프에서 블록될 수 있으나, 이는 데이터(body)가 아주 큰 경우인 상황이므로
블록이 언젠가는 풀리겠지요. 아니면 select()에서 풀리거나.

데이타(body)가 클때 block 올만한 상황이 있나요?
mach의 이미지

lopad wrote:

데이타(body)가 클때 block 올만한 상황이 있나요?

response time이 길어진다는 의미입니다. 데이터크기가 커져도 무방하겠지요.
recv()에서 블록된다면, 이제 다른 원인을 찾아봐야 할것같습니다.
1. 시그널 처리(SIG_PIPE)에 대한 재고
음 ......
2. RecvTime()내의 select()에서 Exception을 검출하게 하고, OOB(Out of Band data)에 대한 검출을 시도해본다.
스무고개가 되어가는듯하네요.
고민되지만 재미있군요.(죄송 불순한 의도는 아닙니다.)

------------------ P.S. --------------
지식은 오픈해서 검증받아야 산지식이된다고 동네 아저씨가 그러더라.

최종호의 이미지

lopad wrote:
Quote:
두개의 쓰레드를 두어서 세션을 반씩 나누어 조회를 처리합니다.
다시 읽어보고 생각이 든건데... 혹시 두개의 thread에서 select 파라미터의 &rset 에 동일 fd가 등록될 가능성은 전혀 없나요?

가장 가능성 있는 시나리오인데...클라이언트가 들어오는 둘중 한 쓰레드를 선택하기때문에 중목해서 select를 호출할 경우는 없습니다. 흠..

그현상이 나타났을 때 오직 그 한 클라언트에서만 계속해서 그런문제를 양산했습니다. 안되니까 끊고 다시 붙으면 다시 먹통 또 끊고 하는 또 붙고..
붙었던 서버프로세스들은 전부 먹통상태로 결국 그날은 9시부터 16까지 그러했습니다. 그이후부터는 또 정상....

붙었던 서버프로세스가 먹통이 된다는게 서버프로세스의 2개의 thread가 결국에는 모두 block상태에 빠진다는 건가요?
아니면 그 중 문제있는(?) client가 접속한 thread 하나만 먹통에 빠진다는건가요?
문제의 클라이언트 장비는 다른 잘되는 클라이언트와 구별되는 점이 있나요?

넷웍 상태가 안 좋을 때 문제가 발생한다고 하셨는데, 넷웍 상태가 안 좋은 경우는 traffic이 엄청나게 많은 경우인가요?
아님 외부망을 사용해서 서비스가 이루어질 때 외부망의 속도가 떨어지거나 링크가 잘 끊어지는 경우인가요?

그리고 혹시 client 쪽 프로세스에도 timeout이 있어서 일정시간안에 응답을 못 받으면 연결을 종료시키는 코드가 있나요?

recv()에서 블록된 거니까 읽을 데이터가 없어서 발생하는 문제일텐데,
정말로 다른 스레드가 읽어갔거나
(흠.. header를 읽을 때가 아니라 데이터를 읽을 때라는게 좀 걸리기는 하지만서도요.),
클라이언트가 안 보내거나,
클라이언트가 보냈는데 서버는 안 받았거나 할텐데요.
또는 OS버그로 인해 이 어떤 상황이 발생했거나요.

sniffer류를 넷웍에 걸어서 client장비에서
보내는 데이터와 서버장비에서 받는 데이터간의 일치,
연결종료 등에 대해서 조사를 해 보면 좋을 듯 싶습니다.
TCP연결이 한쪽장비에서는 ESTABLISHED인데
연결의 또 다른 한쪽의 다른 장비는 아예 없는 경우도 발생을 하던데요.

그리고 장기전으로 생각하시고,
run-time시에 (시그널등으로) 실시간으로 디버깅 레벨을 조정할 수 있도록 하시고,
문제가 발생하면 디버깅 레벨을 높여서 서버 프로세스내의 두 thread의 select()/recv() 그리고 close() 를
thread id, fd와 함께 찍어보도록 하는게 좋을 듯 싶습니다.
(accept()받는 프로세스가 두 thread간에 분배를 해 주기 때문에 문제가 발생안하는 것으로 됐지만,
어디까지나 정상상황일 경우이고 개발자의 의도잖아요. ^^
accept()한 프로세스는 처리프로세스의 thread에 noti만 날려주고
처리프로세스에서 함수를 불러서 소켓번호를 읽는데,
동기화의 문제도 있을 수 있고, 희박하지만 여러가지 가능성들이 있으니까요.)
음.. Keep-alive 이후에는 block을 빠져나오니 이 문제는 아닌 것 같기도 하네요.

(논외의 문제이지만, accept()하는 녀석이 별도의 프로세스가 아니라
별도의 thread아닌가요? 프로세스에서 fd를 전달해주지 않고 noti만 주고
noti받은 녀석이 테이블에 가서 찾아오는 방식으로 동작하는데,
fd 번호가 공유메모리에 있어도 fd번호를 가져와서 받은 프로세스가 fd로 사용하기가 힘들 것 같아보이는데요.
noti 받은 녀석이 find_accept_table ()안에서 accept()하는
프로세스에게 다시 파이프를 통해 fd를 전달해 달라고 하지는 않을 것 같고요.)

드물게 발생하지만 문제가 발생하면 여러번 반복된다니 그래도 희망이 있네요. ㅡ.ㅡ;;

Quote:

먹통되었던 프로세스들은 keepalive 시간후 RecvTime에 리턴되었고. 이후 새로운 클라언트들은 정상적으로 처리되고 있습니다. 물론 그동안 다른 클라언트세션들은 처리되지 않았지만...

한 서버에 여러개의 서버 프로세스들을 띄우는 건가요?
아님 3대의 장비에 있는 프로세스들이 동시에 먹통되는건가요?

Quote:

이런 현상으로 미루어보면 클라언트-서버네트웍을 타는 거처럼 보입니다.

클라이언트-서버네트웍을 탄다는 게 어떤 의미인지?
특정 클라이언트쪽에서 문제가 발생한다는 의미인가요?
아니면 넷웍의 환경에 의해 넷웍 오동작(?)이 있다는 뜻인지?
'*클라이언트-서버*네트웍' 이라는 표현에서 클라이언트-서버가 붙어서 뭔가 특별한 문맥으로 쓰인게 아닌가 생각해 보는데 잘 모르겠네요.

--

select()후에 accept()에서 블록된다는지 select()후에 recv()가 블록되는 문제가 드물게 발생하는 것 같은데,
이런 문제 원인 찾기가 쉽지 않네요. ㅡ.ㅡ;;

lopad의 이미지

mach wrote:
1. 시그널 처리(SIG_PIPE)에 대한 재고

시그널은 ingnore된 상태입니다.

mach wrote:
2. RecvTime()내의 select()에서 Exception을 검출하게 하고, OOB(Out of Band data)에 대한 검출을 시도해본다.

select(int nfds, fd_set *readfds, fd_set *writefds,fd_set *errorfds, struct timeval *timeout)
OOB는 errorfds에서 검출하지 않나요?
Exception을 무얼 말씀하시는지 잘 모르겠습니다.

최종호 wrote:
붙었던 서버프로세스가 먹통이 된다는게 서버프로세스의 2개의 thread가 결국에는 모두 block상태에 빠진다는 건가요?
아니면 그 중 문제있는(?) client가 접속한 thread 하나만 먹통에 빠진다는건가요?
문제의 클라이언트 장비는 다른 잘되는 클라이언트와 구별되는 점이 있나요?

1. 문제의 클라언트가 프로세스에 접속했을 때 2개중 하나의 쓰레드에 배정받고 난뒤 block. 사용자가 먹통된 client(서버 block이 일어나니 당연히 클라언트도 먹통) 종료후 다시 접속 이때 같은 프로세스에 붙어서 나머지 하나의 쓰레드에 배정 역시 block (이러면 프로세스 전체가 block에 빠지죠.). 이런식으로 프로세스들을 마비시켜 나갑니다. 문제 클라언트의 사용자 환경은 하나로통신 adsl이였고 이 클라언트와 서버사이는 패킷손실이 10%가 넘고 속도도 안 좋았습니다. block시 물론 서버상에서 보면 connection established 상태로 보입니다.

최종호 wrote:
sniffer류를 넷웍에 걸어서 client장비에서
보내는 데이터와 서버장비에서 받는 데이터간의 일치,
연결종료 등에 대해서 조사를 해 보면 좋을 듯 싶습니다.

2. 네트웍 데이타가 많은 경우라서 추적하기 힘들군요.. 다른 프로세스에 별도의 세션이 있는데 이녁석은 완전히 server에서 끊임없이 데이타를 퍼 줍니다.(물론 이쪽은 모든 정상적입니다.)

최종호 wrote:
run-time시에 (시그널등으로) 실시간으로 디버깅 레벨을 조정할 수 있도록 하시고,
문제가 발생하면 디버깅 레벨을 높여서 서버 프로세스내의 두 thread의 select()/recv() 그리고 close() 를
thread id, fd와 함께 찍어보도록 하는게 좋을 듯 싶습니다

3.그렇게 하고 있는데 block이 일어나고 난뒤에 로그가 남지 않아서...별로 도움이 못되고 있습니다.

최종호 wrote:
(논외의 문제이지만, accept()하는 녀석이 별도의 프로세스가 아니라
별도의 thread아닌가요? 프로세스에서 fd를 전달해주지 않고 noti만 주고
noti받은 녀석이 테이블에 가서 찾아오는 방식으로 동작하는데,
fd 번호가 공유메모리에 있어도 fd번호를 가져와서 받은 프로세스가 fd로 사용하기가 힘들 것 같아보이는데요.

4.접속이 이루어지는 과정은 대략 다음과 같습니다
                       (by 유닉스도메인소켓)
process A   -----------------------------------> Process A_child
accept 후 fd 전달                   thread i (fd수신 및 기록, 2개 쓰레드중 선택)
                                              pipe --> thread 1 or 2

스티븐씨 책에 보면 나옵니다.

최종호 wrote:
한 서버에 여러개의 서버 프로세스들을 띄우는 건가요?
아님 3대의 장비에 있는 프로세스들이 동시에 먹통되는건가요?

5. 한서버내에는 여러 프로세들이 존재 합니다. 대략 12개정도.
동시에 먹통이 되지는 않고 문제의 클라언트가 접속한 프로세스의 쓰레드들이 먹통됩니다. 프로세스내의 2개 쓰레드가 다 먹통다면 전체 프로세스가 먹통이죠.

최종호 wrote:
클라이언트-서버네트웍을 탄다는 게 어떤 의미인지?
특정 클라이언트쪽에서 문제가 발생한다는 의미인가요?

6. 특정 클라언트에서 발생합니다.
codebank의 이미지

노파심(?)에서 알지도 못하면서 끼어들었습니다.
예전에 어떤 프로그램을 작성할때 회사차원에서 디버깅 수준의(화면에 printf를 써서
값을 출력) 프로그램일때는 잘돌던 프로그램이 양산에 들어가면 문제가 발생한다는
것이었습니다.
그래서 원인을 규명하려고 몇일을 고민하다가 저에게 문의가 들어오더군요.
찾아보았지만 해결방법이 없어서 혹시나하는 마음에 버퍼를 플레쉬 시켜보라고
했더니 잠잠하더군요. --.--;
문제가 해결되었는데 결과를 안알려줘서 해결된지도 몰랐었는데...

각설하고...
제가 집어보고 싶은것은 서버단이 아닌 Client단입니다.
즉, write()를 한 이후에 모든 데이터가 반드시 날아가서 상대편에게 전달이 되었다고
생각하지만 잘보면 write()를 한 이후 데이터는 곧바로 네트워크 라인을 타는것이 아닌
일종의 공용버퍼에 쌓이게 되어있습니다.
일단 이곳에 쌓여있다가 버퍼가 차거나 flush가 되면 그때가서 데이터를 보내게
되는거죠.
한번 client쪽 프로그램을 점검해보시라는게 제 의견입니다.
다른것도 필요없고 wirte()이후에 fflush (stdout); 이것만 넣어주면 되니까요...

알고 있는 것을 쓸데없이 주절거린건 아닌지 모르겠네요... :oops:

------------------------------
좋은 하루 되세요.

최종호의 이미지

클라이언트에서 데이터를 보낼 때 헤더와 데이터를 두번에 나눠서 보내시나요?
아니면 한번에 보내시나요?
현재 받으려는 데이터가 65바이트이면 한번에 보낸다면
서버쪽에서 헤더는 정상적으로 읽고 데이터 읽는 곳에서 블록되지는 않을 것 같고요.

그리고 매번 장애발생시 헤더를 읽은 후 데이터를 읽을 때만 블록되는건가요?
만일 넷웍의 문제라면 서버쪽에서 데이터 읽는 곳에서 뿐만 아니라
헤더 읽는 경우에도 블록될 가능성이 있어보이는데요.

서버가 블록된 상태에서 서버의 소켓상태는 ESTABLISHED 일 때
클라이언트쪽의 소켓상태는 어떤가요?
아마 소켓이 없거나 연결이 (TCP단에서) CLOSE 를 기다리는 상태에 있을 것 같은데요.

넷웍쪽 문제라면 아무래도 블록킹 모드로 소켓을 쓰는 한에는
문제를 해결하기가 쉽지 않을 것으로 보입니다. ㅡ.ㅡ;;

sniffer는 해당 장비의 다른 프로세스들 성능저하만 어느정도 감수할 수 있다면
src, dest의 ip와 port 를 sniffer 옵션에 주어서 필터링해서
원하는 것만 뽑아서 쓰면 될 것 같은데요.
장애를 발생시키는 클라이언트가 고정되어 있으니까
서버측에서는 해당 클라이언트 ip에서 오는 요청 중 dest 포트가 서버 포트와 동일한 것만 뽑으면 될테고,
클라이언트 장비에서는 서버 ip의 서버 포트로 통신을 하는 녀석들을 뽑도록
필터링을 주시면 되지 않을까요?

암튼 꼭 해결되었으면 합니다.

lopad의 이미지

codebank wrote:
한번 client쪽 프로그램을 점검해보시라는게 제 의견입니다.다른것도 필요없고 wirte()이후에 fflush (stdout); 이것만 넣어주면 되니까요...

좋은 의견입니다. 그런데 서버입장에서 있는 사람으로서 클라언트가 무슨짓(?) 하던 서버가 안정되게 서비스를 하는 것을 원합니다. 근본적으로 서버 프로그램이 뭐가 잘못되었는지 알고 싶거든요...^^;;

최종호 wrote:
현재 받으려는 데이터가 65바이트이면 한번에 보낸다면
서버쪽에서 헤더는 정상적으로 읽고 데이터 읽는 곳에서 블록되지는 않을 것 같고요.

헤더 읽고 난후 body부분 데이타가 65bytes입니다. block이 일어날때 헤더가 아닌 body에서 발생합니다. 헤더는 쓰레드관리 소켓 list에 대한 select후 읽고 body는 그 소켓(헤더를 읽은 소켓)에 대한 select후에 읽습니다.

클라언트는 헤더와 body를 따로 따로 보내는데 즉 최소 2번 호출은 있습니다. 그리고 클라언트 상황은 파악 못했습니다. 사용자에게 연락하는 방법이 현재로서는 힘들어서요.

최종호 wrote:
장애를 발생시키는 클라이언트가 고정되어 있으니까

장애를 발생시키는 클라언트는 그 장애시에만 해당되고 그 시간대가 지나면 정상이고 또 장애가 발생했을 때 다른 클라언트(물론 같을 수도 있지만 현재까지는 다른 클라언트에서 발생)에서 발생합니다. 즉 유동적입니다.
그런 상황일 때 sniff한 번 걸어보겠습니다.
mach의 이미지

lopad wrote:

OOB는 errorfds에서 검출하지 않나요?
Exception을 무얼 말씀하시는지 잘 모르겠습니다.

OOB데이터를 송신하는 클라이언트를 간단히 만들어서 전송해 보시기 바랍니다.
이때, rset에서 검출될텐데, 보통은 errorfds에서 검출되기를 기대하겠지만요.
이경우의 처리를 해보시고요.
또한, errorfds로 검출도 해보시기 바랍니다.
플래그 처리잘해보시고요.

문제의 원점으로 돌아가서, recv()에서 블록되는게 문제라면, block안되게 하면 되겠지요?
recv()를 호출하기 전에 non-blocking으로 세팅하셨다가. recv()호출이후,
blocking으로 세팅하는것도 방법입니다.
보다 확장하면, RecvTime()내부에서만 non-blocking으로 동작하게
작성하는 것도 방법입니다.

* 또한가지는 recv()나 send()호출전에 해당 descriptor의 연결상태등을 검사해보는 로직으로
ioctl()을 적용하는 분도 있더군요(FLUSH옵션으로 주고, invalidness를 리턴값으로 검사함).
참고하시기 바랍니다.

------------------ P.S. --------------
지식은 오픈해서 검증받아야 산지식이된다고 동네 아저씨가 그러더라.

mach의 이미지

* 참고
바로 위에 언급한 ioctl()처리는 원격지 연결종료상태를 소심한(세심한?)나머지 한번
더 검사하는 사례라고 보시면 되겠습니다.
소위 다음과 같은 코드겠지요.

ret = ioctl(FLUSH옵션); // 몇몇 기종에서는 fcntl()써야 함(당근!) 
if (ret ==연결정상이니?)
{
   recv()또는 send();
}

어쩌면 이러한 코드를 추가하여 님의 코드를 좀더 견고하게 할 수 있지
않을까합니다. ioctl()연산이 잡아먹는 시간(낭비?)은? 통신프로그램에서는
아주 미미할 것으로 판단됩니다.그러나, 견고성으로는 보다 나아지리라 봅니다.
한마디로 피보다 살이되는 것이라고 주장하고 픕니다.

------------------ P.S. --------------
지식은 오픈해서 검증받아야 산지식이된다고 동네 아저씨가 그러더라.

김경태의 이미지

ret = ioctl(FLUSH옵션); // 몇몇 기종에서는 fcntl()써야 함(당근!)
if (ret ==연결정상이니?)
{
recv()또는 send();
}

ioctl, fcntl 공히 FLUSH라는 옵션은 없는 것 같습니다.

실제 코드를 예를 들어서 설명해 주시면 도움이 될 것 같습니다.

그리고, 보통은....

select
if ( select OK)
{
recv() 또는 send()
}

와 같은 코드인데 이와 같은 경우에도 위코드를 첨가해야 할 필요가
있을까요?

고수님들이 많은 조언 기다리겠습니다. ^^;

댓글 달기

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
이것은 자동으로 스팸을 올리는 것을 막기 위해서 제공됩니다.