Recommanded Free YOUTUBE Lecture: <% selectedImage[1] %>

Contents

어떻게 다른 한쪽의 소켓이 닫혔는지를 알 수 있을 까요 ?

SO_LINGER가 활성화 되지 않은 상태에서 close()를 호출하거나 프로그램이 종료된다면 반대쪽에서는 read()시 0이 리턴됨으로 확실히 확인 가능합니다. 그렇지만 write()를 호출했을 경우에는 확실히 정의되지 않습니다. 곧바로 EPIPE가 떨어질 수도 있지만 그렇지 않고 나중에 떨어 질 수도 있습니다.

만약에 다른 한쪽이 리붓을 하거나 l_onoff = 1, l_linger = 0 인 상태에서 close()를 호출한다면 반대쪽에서는 read() 호출시 ECONNRESET를 리턴받으며, write()시에는 EPIPE를 리턴 받을 수 있다.

주의 해야할점은 EPIPE 리턴이 SIGPIPE 시그널에 의해서 발생한다는 점으로 만약 SIGPIPE를 무시하도록 했다면 EPIPE는 결코 발생하지 않게 됩니다.

만약 여러분이 파일을 받는 프로그램을 만들었다고 가정한다면 다음과 같이 read()의 리턴값을 검사함으로써, 소켓연결이 끝났는지를 확인할 수 있게 된다.
rc = read(sock,buf,sizeof(buf));
if (rc > 0)
{
    write(file,buf,rc);
    /* error checking on file omitted */
}
else if (rc == 0)
{
    close(file);
    close(sock);
    /* 파일을 성공적으로 받았음 */
}
else /* rc < 0 */
{
    /* 파일 받는 도중 에러가 발생했음으로, 현재 만들어진 파일을 
       삭제하고 서버측에 재전송과 같은 메시지를 보내거나 
       에러 로그를 남긴다 */  
}

SO_LINGER 에 대한 자세한 내용은 Socket API 문서를 읽어보기 바란다.

bind()의 두번째 인자에 대해서 설명해 주세요

소켓 구조체로 man 페이지를 보면 strcut sockaddr *my_addr로 되어 있다. 소켓 구조체에는 인터넷 연결방법, 인터넷 주소, 인터넷 포트와 같은 정보들을 가지고 있다. bind()함수는 만들어진 소켓에 인터넷 정보를 묶어주는 역할을 하는데, 이 소켓 구조체를 통해서 소켓에 어떤 포트와 어떤 인터넷 주소를 사용할 것인지등을 명시할 수 있다. 이 소켓 구조체에서 주목할 만한 멤버는 아래와 같은 것들이 있다.

  • sin_family : AF_INET와 같은 인터넷 영역을 명시한다.
  • sin_port : 네트워크 바이트 순서를 따르는 16bit 포트 번호
  • sin_addr : 호스트의 인터넷 주소

서비스 이름으로 부터 포트 번호를 얻어올 수 있나요?

getservbyname(3)함수를 이용하면 된다. 이 함수는 인자로 주어지는 서비스 이름과 프로토콜에 일치하는 값이 있는지 /etc/services에서 찾고, 찾게 될경우 servent구조체의 포인터를 리턴하는데, servent 구조체의 s_port에 포트번호가 저장된다. servent 구조체는 다음과 같이 선언되어 있다.
struct servent {
    char    *s_name;        /* 공식적인 서비스 이름 */
    char    **s_aliases;    /* 별칭 리스트 */
    int     s_port;         /* 포트 번호 */
    char    *s_proto;       /* 사용하는 프로토콜 */
}
아래는 서비스 이름으로 부터 포트번호를 얻어오는 간단한 루틴입니다.
    /* 서비스 이름과 프로토콜을 입력하면 포트번호를 리턴한다.
       만약 서비스 이름이 존재하지 않는다면 -1을 리턴한다. 포트넘버는
       네트워크 오더바이 시켜서 리턴한다.
       */
    int atoport(char *service, char *proto) {
      int port;
      long int lport;
      struct servent *serv;
      char *errpos;

      /* /etc/services에서 서비스이름에 대한 servent정보를 얻어온다. */
      serv = getservbyname(service, proto);
      if (serv != NULL)
        port = serv->s_port;
      else { /* 서비스가 존재하는지 검사한다 */
        lport = strtol(service,&errpos,0);
        if ( (errpos[0] != 0) || (lport < 1) || (lport > 5000) )
          return -1; /* 포트이름이 존재하지 않을 때*/
        port = htons(lport);
      }

bind()가 실패할 경우 소켓 지정자를 어떻게 해야 하나요?

만약 exit를 호출한다면, 유닉스는 정책에 따라서 모든 열린 파일 지정자를 닫게 되며 보통 아무런 문제도 말생하지 않는다. 만약 프로그램을 종료 시키지 않을 생각이라면 close()함수를 호출하기 바란다.

어떻게 해야 소켓을 닫을 수 있는가 ?

가장 손쉬운 방법은 close()를 호출하는 것이다. 혹은 shutdown()을 호출할 수도 있다. 두 함수는 약간의 차이점을 가진다.
  • close(2) : 이것은 내부적으로 열린소켓에 대한 카운터를 유지한다. fork()와 같은 것을 이용해서 하나의 소켓에 대해서 여러개의 파일지정자가 있을 경우, 하나의 fork된 프로세스에서 close()를 호출한다고 해서 소켓이 닫히지는 않는다. 다른 프로세서는 여전히 파일지정자를 이용해서 통신을 할 수 있다. 파일을 닫는것과 동일하다고 보면 된다.
  • shutdown(2) : 소켓을 완전히 닫아버린다. 단 인자를 이용해서 읽기만 닫을건지 쓰기만 닫을건지를 선택할 수 있다.

bind error : Address already in use 의 원인

네트워크 연결이 종료되기 위해서 서버와 클라이언트 간에 FIN(final) 패킷을 교환하게 된다. 이 패킷들은 TCP(:12)플래그만 FIN으로 세팅될 뿐 어떠한 유저데이터도 갖지 않은채 전달된다. 그리고 서버와 클라이언트는 FIN에 대한 ACK(acknowledge)를 전달하며, 이게 완료되어야지만 정상적인 종료가 이루어지게 된다. FIN패킷은 애플리케이션에서 close(), shutdown(), exit()등을 호출하면 전달되는데, ACK 패킷은 애플리케이션이 아닌 운영체제에서 담당하게 된다. 결국 exit() 를 통해서 FIN을 보내지 않았을 경우 애플리케이션이 종료되었다 하더라도 커널은 ACK를 받지 못한 상태 - 연결이 끝어진 상태지만 여전히 네트워크 자원을 가지고 있는 -로 남아있게 된다. 그렇다고 해서 영원히 이 상태로 남아 있는것은 아니다. 일정시간이 지나면 커널이 알아서 자원을 해제한다.

이런 상태에서 동일한 네트워크 자원을 사용하는 서버 애플리케이션을 띄울경우 위의 에러메시지가 출력된다. 아래와 같이 setsockopt(3)를 이용하면 남아있는 네트워크 자원을 재 사용할 수 있다.
setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (char *)&bf, (int)sizeof(bf));
소켓옵션과 관련된 더 자세한 내용은 소켓 API 문서를 참고하기 바란다.

언제 shutdown을 써야 하는가

소켓은 데이터의 전송에 사용되며, 서버/클라이언트 모델을 가진다는 점에서, 파이프(:12)와 매우 비슷하다. 그러나 파이프와는 달리 양방향통신이 가능하다는 특징을 가진다. 소켓을 사용하는 많은 프로그램들이 fork()를 사용해서 소켓지정번호를 자식 프로세스에 상속한다.

close()를 사용하게 되면, close()를 호출한 프로세스에 대해서 소켓이 닫힐 뿐이다. 만약 다른 프로세스가 소켓지정번호를 공유하고 있다면, 이 연결은 여전히 열려진 상태로 있게 된다. 명시적인 연결종료라고 볼 수 있을 것이다. shutdown()을 사용하게 되면, 연결이 정말로 끊기게 된다. 호출한 프로세스 뿐만 아니라, 공유하고 있는 다른 모든 프로세스들역시 소켓연결이 끊기게 된다. 소켓연결종료를 명확히할 수 있으므로, 소켓동기화 측면에서 중요한 특징이다.

shutdown()에 의해서 소켓이 끊기게 되면, 프로세스가 데이터를 읽고자 할때 EOF가 발생한다. 쓰기를 시도할 경우 SIGPIPE(:12)를 받게 된다.

TIME_WAIT 에 대해서

당신은 TCP(:12)가 모든 전송되는 데이터에 대해서 신뢰성을 보증한다는 것을 알고 있을 것이다. 소켓을 닫게 되면 서버는 TIME_WAIT 상태가 되고, 더이상의 데이터 전송이 일어나지 않게 된다. 이때 사소하지만 중대한 두개의 문제가 발생한다.

  1. 마지막 ack 가 성공적으로 전달되었는지 알 수 없다.
  2. 전송되면 처리되었을 데이터 패킷이 네트워크 상에 헤메게 된다.