java의connectionreset 이상 처리 분석

6912 단어 connectionreset
Java에서 자주 볼 수 있는 몇 가지 connectionrest exception, Broken pipe, Connection reset, Connection reset by peer
Socked reset case
Linux에서 흔히 볼 수 있는 sock reset 상황의 오류 코드 2개가 있습니다.
ECONNRESET
이 오류는 "connectionreset by peer", 즉 "상대 리셋 연결"으로 설명되며, 이러한 상황은 일반적으로 서비스 프로세스가 고객 프로세스보다 앞당겨 종료되는 데 발생한다.서비스 프로세스가 종료되면 고객 TCP에 FIN 섹션이 전송되고 고객 TCP는 ACK에 응답하며 서비스 TCP는 FIN_로 전환됩니다.WAIT2 상태.이 때 클라이언트 프로세스가 이 FIN을 처리하지 않으면 (예를 들어 다른 호출에 막혀 Socket을 닫지 않았을 때) 클라이언트 TCP는 CLOSE_WAIT 상태.고객 프로세스가 다시 FIN_WAIT2 상태의 서비스 TCP에서 데이터를 전송하면 서비스 TCP가 RST에 즉시 응답합니다.일반적으로 이런 상황은 또 다른 응용 프로그램의 이상을 일으킬 수 있다. 고객 프로세스는 데이터를 보낸 후에 네트워크 IO에서 데이터를 수신하기를 기다린다. 전형적인read나readline 호출과 같다. 이때 실행 시퀀스 때문에 이 호출이 RST 섹션을 받기 전에 실행되면 결과는 고객 프로세스가 예상치 못한 EOF 오류를 얻게 된다.이 때 일반적으로 "서버 terminated prematurely"- "서버 너무 일찍 종료"오류가 출력됩니다.
EPIPE
오류는 "broken pipe", 즉 파이프 파열로 설명됩니다. 이런 상황은 일반적으로 클라이언트 프로세스가 Socket 오류를 무시하거나 제때에 처리하지 않고 서비스 TCP에 더 많은 데이터를 쓸 때 커널은 클라이언트 프로세스에 SIGPIPE 신호를 보냅니다. 이 신호는 기본적으로 프로세스를 종료합니다. (이 때 프론트 프로세스가 코어 dump를 하지 않습니다.)위의 ECONNRESET 오류를 통해 알 수 있는 FIN_WAIT2 상태의 서비스 TCP(ACK 응답 FIN 세그먼트)는 데이터를 쓰는 데 문제가 되지 않지만 RST를 받은 Socket을 쓰는 것은 오류입니다.
Java의 socket input stream/output stream 처리
코드 세션부터 볼게요.
SocketInputStream.c

switch (errno) { 
case ECONNRESET: 
case EPIPE: 
  JNU_ThrowByName(env, "sun/net/ConnectionResetException",   
  "Connection reset"); 
  break; 
         .... 
SocketOutputStream.c

if (errno == ECONNRESET) { 
          JNU_ThrowByName(env, "sun/net/ConnectionResetException", 
            "Connection reset"); 
    } else { 
      NET_ThrowByNameWithLastError(env, "java/net/SocketException",  
      "Write failed"); 
    } 
자바가 읽고 쓰는 걸 볼 수 있어요. EPIPE에 대한 처리가 달라요.
read의 경우, Reset은 모두 ConnectionResetException을 던지고, 제시된 오류 정보는 Connection Reset입니다.
write의 경우 Reset은 ECONNRESET에 대해 ConnectionResetException을, EPIPE에 대해서는 SocketException을, 오류 정보는 Broken pipe를 던집니다.
Broken pipe 인쇄 방법
SIGPIPE 신호 처리 함수
reset 패키지를 받은 후 socket을 읽고 쓰면 EPIPE가 오류가 발생하고 SIGPIPE 신호를 자주 받습니다.
프로그램에서 자바가 write를 처리하지 않은 상황에서 오류 EPIPE를 처리하지 않은 것을 볼 수 있습니다. 시작할 때 오류가 발생한 이상은 신호 처리 함수로 던진 것입니다
먼저 신호 SIGPIPE에 대한 처리 함수를 살펴보겠습니다. Linux::install_signal_handlers에서 함수 호출

set_signal_handler(SIGSEGV, true); 
set_signal_handler(SIGPIPE, true); 
set_signal_handler(SIGBUS, true); 
set_signal_handler(SIGILL, true); 
set_signal_handler(SIGFPE, true); 
set_signal_handler(SIGXFSZ, true); 
반면 함수 set_signal_handler, 에서 대응하는 신호 처리 함수는signalHandler

sigAct.sa_handler = SIG_DFL; 
 if (!set_installed) { 
  sigAct.sa_flags = SA_SIGINFO|SA_RESTART; 
 } else { 
  sigAct.sa_sigaction = signalHandler; 
  sigAct.sa_flags = SA_SIGINFO|SA_RESTART; 
 } 
결국 함수 JVM이 호출되었습니다_handle_linux_signal
X86 아키텍처에서 함수 JVM_handle_linux_signal

extern "C" int 
JVM_handle_linux_signal(int sig, 
            siginfo_t* info, 
            void* ucVoid, 
            int abort_if_unrecognized) { 
 ucontext_t* uc = (ucontext_t*) ucVoid; 
 
 Thread* t = ThreadLocalStorage::get_thread_slow(); 
 
 SignalHandlerMark shm(t); 
 
 // Note: it's not uncommon that JNI code uses signal/sigset to install 
 // then restore certain signal handler (e.g. to temporarily block SIGPIPE, 
 // or have a SIGILL handler when detecting CPU type). When that happens, 
 // JVM_handle_linux_signal() might be invoked with junk info/ucVoid. To 
 // avoid unnecessary crash when libjsig is not preloaded, try handle signals 
 // that do not require siginfo/ucontext first. 
 
 if (sig == SIGPIPE || sig == SIGXFSZ) { 
  // allow chained handler to go first 
  if (os::Linux::chained_handler(sig, info, ucVoid)) { 
   return true; 
  } else { 
   if (PrintMiscellaneous && (WizardMode || Verbose)) { 
    char buf[64]; 
    warning("Ignoring %s - see bugs 4229104 or 646499219", 
        os::exception_name(sig, buf, sizeof(buf))); 
   } 
   return true; 
  } 
 } 
... 
} 
신호 SIGPIPE에 대해 chained handler 처리를 사용했습니다. 즉, 시스템의 원래 신호 처리 함수를 사용했습니다. 또한 이상이 신호 처리 함수가 던진 것이 아니라는 것을 증명했습니다.
NET_ThrowByNameWithLastError 함수
신호 처리 함수가 던진 이상이 아닌 이상 원래의 outputstream 프로그램을 계속 보십시오

if (errno == ECONNRESET) { 
          JNU_ThrowByName(env, "sun/net/ConnectionResetException", 
            "Connection reset"); 
    } else { 
      NET_ThrowByNameWithLastError(env, "java/net/SocketException",  
      "Write failed"); 
    } 
즉else의 경우 EPIPE의 오류에 대해java가 던진 socketexception, 오류 정보는 Write failed입니다. 사실 우리가 볼 수 있는 것은 SockedException입니다. 이상하게 맞았지만 정보는 Broken pipe이지 Write failed가 아닙니다.
관건은 함수 NET_ThrowByNameWithLastError

void 
NET_ThrowByNameWithLastError(JNIEnv *env, const char *name, 
          const char *defaultDetail) { 
  char errmsg[255]; 
  sprintf(errmsg, "errno: %d, error: %s
", errno, defaultDetail); JNU_ThrowByNameWithLastError(env, name, errmsg); }
함수 JNU_ThrowByNameWithLastError

JNIEXPORT void JNICALL 
JNU_ThrowByNameWithLastError(JNIEnv *env, const char *name, 
         const char *defaultDetail) 
{ 
  char buf[256]; 
  int n = JVM_GetLastErrorString(buf, sizeof(buf)); 
 
  if (n > 0) { 
  jstring s = JNU_NewStringPlatform(env, buf); 
  if (s != NULL) { 
    jobject x = JNU_NewObjectByName(env, name, 
            "(Ljava/lang/String;)V", s); 
    if (x != NULL) { 
    (*env)->Throw(env, x); 
    } 
  } 
  } 
  if (!(*env)->ExceptionOccurred(env)) { 
  JNU_ThrowByName(env, name, defaultDetail); 
  } 
} 
프로그램은 JVM_을 먼저 표시합니다.GetLastErrorString의 정보입니다. 정보가 비어 있는 경우에만 defaultDetail의 이상 정보가 표시됩니다. 즉, 대응하기 시작하는 Write failed!
JVM_GetLastErrorString은 hpi::lasterror, 즉 함수sysGetLastErrorString은 linux와 solaris에서 동일합니다

int 
sysGetLastErrorString(char *buf, int len) 
{ 
  if (errno == 0) { 
  return 0; 
  } else { 
  const char *s = strerror(errno); 
  int n = strlen(s); 
  if (n >= len) n = len - 1; 
  strncpy(buf, s, n); 
  buf[n] = '\0'; 
  return n; 
  } 
} 
원래strerror (errno), 즉 linuxkernel이 이 errornumber에 대응하는 오류를 직접 표시합니다.
결론: Broken pipe는 내부 핵에 대응하는 오류 정보로 자바가 제공한 정보가 아니다
이상은 본문의 전체 내용입니다. 본고의 내용이 여러분의 학습이나 업무에 일정한 도움을 줄 수 있는 동시에 저희를 많이 지지해 주시기 바랍니다!

좋은 웹페이지 즐겨찾기