[질문] 클라이언트 소켓에서 Connection Time out 구현을 어떻게

alonecrow의 이미지

클라이언트에서 서버로 connect 시 응답시간이 너무 오래 걸리는 경우가 자주 발생해 Time out 시간을 주려고 합니다.

예)
state = connect(sockfd, (struct sockaddr *)&address, len);

자료를 아무리 뒤져도 그런부분에 대한 글이 없는데요... :shock:

클라이언트에서 타임아웃주는 방법을 알려주시면 감사하겠습니다.

펑키의 이미지

타임아웃.. 예외없이 SELECT로 해결하시면 될듯합니다. 다만 CONNECT시 응답이 늦으면 완전히 블락이 걸리는 특성이 있으니 이전에 넌블락 모드로 설정하신후에 마지막에 이것을 해제해주시면 될듯합니다.
int status;

socket(); // 접속용 소켓 오픈
fcntl(); // 넌블락으로 셋
status = connect();

이부분에서 기종에 따라 리턴값이 달라집니다. 보통은 0보다 작으면서 PROGRESS메시지를 리턴하는데 이때 루프에 들어가 select 해주시면 됩니다.
while()
select(); 이부분은 쓰기검사를 하시고요.
connect();

대충 생각나는데로 해보았는데 저의 경험으로는 단지 리눅스용으로 하실거라면 이정도만 해도 잘 돌아 갑니다. 그런데 다른 기종에 포팅을 생각하시면 에러 리턴을 조금더 세밀하게 맞추셔야 할겁니다. 제가 LINUX/AIX/HPUX/SOLARIS 이렇게 맞추었는데 조금씩 다르더라구요.

시간이 되시면 모자익 소스를 한번 보시는게 어떠실런지요. 이거를 조금 수정하셔서 하시는게 좋을듯 싶습니다. 잠깐만요. 주소를 한번 찾아 보구요. 지금 찾았습니다.

아랫부분을 조금 수정하셔서 사용하시는게 어떠실런지요.

http://archive.ncsa.uiuc.edu/SDG/Software/XMosaic/

PUBLIC int HTDoConnect (char *url, char *protocol, int default_port, int *s)
{
  struct sockaddr_in soc_address;
  struct sockaddr_in *sin = &soc_address;
  int status;

  /* Set up defaults: */
  sin->sin_family = AF_INET;
  sin->sin_port = htons(default_port);
  
  /* Get node name and optional port number: */
  {
    char line[256];
    char *p1 = HTParse(url, "", PARSE_HOST);
    int status;

    sprintf (line, "Looking up %s.", p1);
    HTProgress (line);

    status = HTParseInet(sin, p1);
    if (status) 
      {
        sprintf (line, "Unable to locate remote host %s.", p1);
        HTProgress(line);
        free (p1);
        return HT_NO_DATA;
      }

    sprintf (line, "Making %s connection to %s.", protocol, p1);
    HTProgress (line);
    free (p1);
  }

  /* Now, let's get a socket set up from the server for the data: */      
  *s = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);

#ifdef SOCKS
  /* SOCKS can't yet deal with non-blocking connect request */
  HTClearActiveIcon();
  status = Rconnect(*s, (struct sockaddr*)&soc_address, sizeof(soc_address));
  if ((status == 0) && (strcmp(protocol, "FTP") == 0))
     SOCKS_ftpsrv.s_addr = soc_address.sin_addr.s_addr;
  {
    int intr;
    intr = HTCheckActiveIcon(1);
    if (intr)
      {
        if (TRACE)
          fprintf (stderr, "*** INTERRUPTED in middle of connect.\n");
        status = HT_INTERRUPTED;
        errno = EINTR;
      }
  }
  return status;
#else /* SOCKS not defined */


  /*
   * Make the socket non-blocking, so the connect can be canceled.
   * This means that when we issue the connect we should NOT
   * have to wait for the accept on the other end.
   */
  {
    int ret;
    int val = 1;
    char line[256];
    
    ret = ioctl(*s, FIONBIO, &val);
    if (ret == -1)
      {
        sprintf (line, "Could not make connection non-blocking.");
        HTProgress(line);
      }
  }
  HTClearActiveIcon();

  /*
   * Issue the connect.  Since the server can't do an instantaneous accept
   * and we are non-blocking, this will almost certainly return a negative
   * status.
   */
  status = connect(*s, (struct sockaddr*)&soc_address, sizeof(soc_address));

  /*
   * According to the Sun man page for connect:
   *     EINPROGRESS         The socket is non-blocking and the  con-
   *                         nection cannot be completed immediately.
   *                         It is possible to select(2) for  comple-
   *                         tion  by  selecting the socket for writ-
   *                         ing.
   * According to the Motorola SVR4 man page for connect:
   *     EAGAIN              The socket is non-blocking and the  con-
   *                         nection cannot be completed immediately.
   *                         It is possible to select for  completion
   *                         by  selecting  the  socket  for writing.
   *                         However, this is only  possible  if  the
   *                         socket  STREAMS  module  is  the topmost
   *                         module on  the  protocol  stack  with  a
   *                         write  service  procedure.  This will be
   *                         the normal case.
   */
#ifdef SVR4
  if ((status < 0) && ((errno == EINPROGRESS)||(errno == EAGAIN)))
#else
  if ((status < 0) && (errno == EINPROGRESS))
#endif /* SVR4 */
    {
      struct timeval timeout;
      int ret;

      ret = 0;
      while (ret <= 0)
	{
          fd_set writefds;
          int intr;
          
          FD_ZERO(&writefds);
          FD_SET(*s, &writefds);

	  /* linux (and some other os's, I think) clear timeout... 
	     let's reset it every time. bjs */
	  timeout.tv_sec = 0;
	  timeout.tv_usec = 100000;

#ifdef __hpux
          ret = select(FD_SETSIZE, NULL, (int *)&writefds, NULL, &timeout);
#else
          ret = select(FD_SETSIZE, NULL, &writefds, NULL, &timeout);
#endif
	  /*
	   * Again according to the Sun and Motorola man pagse for connect:
           *     EALREADY            The socket is non-blocking and a  previ-
           *                         ous  connection attempt has not yet been
           *                         completed.
           * Thus if the errno is NOT EALREADY we have a real error, and
	   * should break out here and return that error.
           * Otherwise if it is EALREADY keep on trying to complete the
	   * connection.
	   */
          if ((ret < 0)&&(errno != EALREADY))
            {
              status = ret;
              break;
            }
          else if (ret > 0)
            {
	      /*
	       * Extra check here for connection success, if we try to connect
	       * again, and get EISCONN, it means we have a successful
	       * connection.
	       */
              status = connect(*s, (struct sockaddr*)&soc_address,
                               sizeof(soc_address));
              if ((status < 0)&&(errno == EISCONN))
                {
                  status = 0;
                }
              break;
            }
	  /*
	   * The select says we aren't ready yet.
	   * Try to connect again to make sure.  If we don't get EALREADY
	   * or EISCONN, something has gone wrong.  Break out and report it.
	   * For some reason SVR4 returns EAGAIN here instead of EALREADY,
	   * even though the man page says it should be EALREADY.
	   */
          else
            {
              status = connect(*s, (struct sockaddr*)&soc_address,
                               sizeof(soc_address));
#ifdef SVR4
              if ((status < 0)&&(errno != EALREADY)&&(errno != EAGAIN)&&
			(errno != EISCONN))
#else
              if ((status < 0)&&(errno != EALREADY)&&(errno != EISCONN))
#endif /* SVR4 */
                {
                  break;
                }
            }
          intr = HTCheckActiveIcon(1);
          if (intr)
            {
              if (TRACE)
                fprintf (stderr, "*** INTERRUPTED in middle of connect.\n");
              status = HT_INTERRUPTED;
              errno = EINTR;
              break;
            }
	}
    }

  /*
   * Make the socket blocking again on good connect
   */
  if (status >= 0)
    {
      int ret;
      int val = 0;
      char line[256];
      
      ret = ioctl(*s, FIONBIO, &val);
      if (ret == -1)
	{
          sprintf (line, "Could not restore socket to blocking.");
          HTProgress(line);
	}
    }
  /*
   * Else the connect attempt failed or was interrupted.
   * so close up the socket.
   */
  else
    {
	close(*s);
    }

  return status;
#endif /* #ifdef SOCKS */
}
alonecrow의 이미지

찾아보니 자료가 더 있어 소스한개를 더 올립니다.
UNIX Networking programming 에 있는 소스입니다.
두가지 모두 테스트 해보았는데 모두잘되더군요...

int connect_nonb(int sockfd, char *strIP)
{
	int len=0, status, flags;
	struct sockaddr_in address;
	fd_set rset, wset;
	struct timeval tval;

	address.sin_family = AF_INET;
	address.sin_addr.s_addr = inet_addr(strIP);
	address.sin_port = htons(SMTP_PORT);

	len = sizeof(address);

#ifdef TEST
	printf("Connect IP:%s\n", strIP);
#endif

	// Non Block 모드로 만든다.
	flags = fcntl(sockfd, F_GETFL, 0);
	if (fcntl(sockfd, F_SETFL, flags | O_NONBLOCK) < 0) {
#ifdef TEST
		switch (errno) {
			case EACCES: fprintf(stderr,"EACCES\n"); break;
			case EAGAIN: fprintf(stderr,"EAGAIN\n"); break;
			case EBADF:  fprintf(stderr,"EBADF\n"); break;
			case EDEADLK:fprintf(stderr,"EDEADLK\n"); break;
			case EFAULT: fprintf(stderr,"EFAULT\n"); break;
			case EINTR:  fprintf(stderr,"EINTR\n"); break;
			case EINVAL: fprintf(stderr,"EINVAL\n"); break;
			case EMFILE: fprintf(stderr,"EMFILE\n"); break;
			case ENOLCK: fprintf(stderr,"ENOLCK\n"); break;
			case EPERM:  fprintf(stderr,"EPERM\n"); break;
		}
#endif
		return -1;
	}

	status = connect(sockfd, (struct sockaddr *)&address, len);

	if ((status < 0) && (errno == EINPROGRESS))
	{
#ifdef TEST
		fprintf(stderr, "errno: %d\n", errno);

		switch (errno) {
			case EBADF:		fprintf(stderr,"EBADF\n"); break;
			case EFAULT:            fprintf(stderr,"EFAULT\n"); break;
			case ENOTSOCK:          fprintf(stderr,"ENOTSOCK\n"); break;
			case EISCONN:           fprintf(stderr,"EISCONN\n"); break;
			case ECONNREFUSED:      fprintf(stderr,"ECONNREFUSED\n"); break;
			case ETIMEDOUT:         fprintf(stderr,"ETIMEDOUT\n"); break;
			case ENETUNREACH:       fprintf(stderr,"ENETUNREACH\n"); break;
			case EADDRINUSE:        fprintf(stderr,"EADDRINUSE\n"); break;
			case EINPROGRESS:       fprintf(stderr,"EINPROGRESS\n"); break;
			case EALREADY:          fprintf(stderr,"EALREADY\n"); break;
			case EAGAIN:            fprintf(stderr,"EAGAIN\n"); break;
			case EAFNOSUPPORT:      fprintf(stderr,"EAFNOSUPPORT\n"); break;
			case EACCES:            fprintf(stderr,"EACCES\n"); break;
			case EPERM:             fprintf(stderr,"EPERM\n"); break;
			default:		fprintf(stderr,"예외\n"); break;
		}
#endif
		return -1;
	}

	if (status == 0) goto done;

	FD_ZERO (&rset);
	FD_SET (sockfd, &rset);
	wset = rset;

	tval.tv_sec = TIMEOUT;
	tval.tv_usec = 0;

	if ((status = select(sockfd + 1, &rset, &wset, NULL, TIMEOUT ? & tval : NULL)) == 0) {
		close(sockfd);
		errno = ETIMEDOUT;
		return (-1);
	}

	if (FD_ISSET(sockfd, &rset) || FD_ISSET(sockfd, &wset)) {
		len = sizeof(errno);
		if (getsockopt(sockfd, SOL_SOCKET, SO_ERROR, &errno, &len) < 0)
			return (-1);
	} else {
		fprintf(stderr, "SELECT error: sockfd not set");
		return (-1);
	}


done:
	fcntl(sockfd, F_SETFL, flags);

	if (errno) {
		close(sockfd);
		return (-1);
	}

	return (0);
} 
은빛연어의 이미지

alarm signal을 이용하시면 간단한데...

signal(SIGALRM, socket_timeout);
alarm(1);
state = connect(sockfd, (struct sockaddr *)&address, len); 
if (state < 0) {
	alarm(0);
	return(-1);
}
alarm(0);

....
나중에 socket_timeout 함수만 구현해주면 끝나염..

이렇게 하면 간단하게 구현할 수 있습니다~~~ 참조하세요~

fanuk의 이미지

은빛연어 wrote:
alarm signal을 이용하시면 간단한데...

signal(SIGALRM, socket_timeout);
alarm(1);
state = connect(sockfd, (struct sockaddr *)&address, len); 
if (state < 0) {
	alarm(0);
	return(-1);
}
alarm(0);

....
나중에 socket_timeout 함수만 구현해주면 끝나염..

이렇게 하면 간단하게 구현할 수 있습니다~~~ 참조하세요~

아.. 이거 좋군요..;; recv()에 대해서도 똑같이 적용할 수 있나요? 있을거 같긴 한데.. 흠..

은빛연어의 이미지

당근이졈~~ ^^*

댓글 달기

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 주소와/이메일 주소를 클릭할 수 있는 링크로 자동으로 바꿉니다.
댓글 첨부 파일
이 댓글에 이미지나 파일을 업로드 합니다.
파일 크기는 8 MB보다 작아야 합니다.
허용할 파일 형식: txt pdf doc xls gif jpg jpeg mp3 png rar zip.
CAPTCHA
이것은 자동으로 스팸을 올리는 것을 막기 위해서 제공됩니다.