accept() 함수 질문입니다

조회수 672회
serv_sock=socket(PF_INET, SOCK_STREAM, 0);

//...

clnt_sock=accept(serv_sock, (struct sockaddr*)&clnt_addr,&clnt_addr_size); 

accept함수의 인수 역할들은 무슨 말 인지 알겠습니다. 그러나

(struct sockaddr*)&clnt_add 에서 *&가 붙어 있는게 어떤 의미를 가지는 건지 모르겠습니다.

1 답변

  • accept()의 서명은 다음과 같습니다.

    int accept(int sockfd, struct sockaddr *restrict addr, socklen_t *restrict addrlen);
    

    두번째 파라미터 addr의 타입은 struct sockaddr*입니다. 그런데 말씀하신 clnt_addr은 해당 타입이 아니라 아마 struct sockaddr_in일 것 입니다.

    둘이 전혀 다른 타입이지요. 그러면 해당 함수에 clnt_addr의 주소를 넣는 것은 하면안되는 행위일 것입니다.

    두 구조체는 다음과 같습니다.

    struct sockaddr_in {
      sa_family_t    sin_family;
      in_port_t      sin_port;
      struct in_addr sin_addr;
    };
    
    struct sockaddr {
      sa_family_t sa_family;
      char        sa_data[14];
    }
    

    여기서 s_familysa_family는 이름만 다르고 동일한 구조체이며, sin_portsa_data는 타입이 다르지만 바이트 크기는 동일합니다. sockaddr 관점에서 보면 sockaddr_in의 앞 부분은 sockaddr과 ABI관점에서 유사하다고 볼 수 있습니다. 다시말해 sockaddr_insockaddr의 확장된 형태라고 볼 수 있는 거죠.

    BSD 소켓의 accept()는 여러 프로토콜을 지원할 수 있는 형태의 API입니다. 각 프로토콜에 따라서 주소의 표현 방식은 다양합니다. 새로운 프로토콜이 개발되고 이 API를 통해 사용하게 될 수도 있습니다.

    accept()란 이름의 함수는 가능한 그대로 유지하면서 다양한 타입의 주소를 받아 들이고 싶습니다. 하지만 C언어에서는 한 함수의 이름으로 다양한 타입을 받아 들이는 방법이 없습니다.

    그래서 sa_family를 통해 어떤 프로토콜을 사용하는지 확인하고, 해당 주소가 어떤 타입 인지를 파악합니다.(앞부분의 ABI가 동일하니 어떤 프로토콜인지 판별하는 거 까지는 문제가 발생하지 않습니다.)

    • sa_familyAF_INET이면 addr의 타입을 sockaddr_in으로 판단합니다.
    • sa_familyAF_UNIX이면 addr의 타입을 sockaddr_un으로 판단합니다.

    동작 상으로 위의 방법대로 돌아가나 컴파일러는 타입이 다르다고 판단하여 오류를 방색하니, 강제로 해당 타입의 속여서 accept()에 입력합니다. 그래서 (struct sockaddr*)&clnt_add와 같이 동일한 주소이나 타입이 다르게 입력하게 됩니다.

    타입 안전성은 떨어지나 C언어 표현의 한계를 극복할 수 있는 방법인 거죠.


    1. 타입이 다르다는 것이 프로토콜이 다르다는 말 인가요?

    제가 말한 타입은 데이터 타입(정수, 구조체 등)을 의미합니다. 프로토콜 마다 주소 표현 방식이 다르고 그로 인해 사용해야할 구조체가 다르다 라는 것이죠.

    1. 왜 오류를 막나요?

    accept()의 구현 예시를 보시면 좀더 쉽게 이해하실 수 있을 거에요.

    int accept(int sockfd, struct sockaddr* addr, scoklen_t addrlen) {
      if (addr->sa_family == AF_INET && addrlen == sizeof(struct sockaddr_in)) {
        struct sockaddr_in* address = (struct sockaddr_in*) addr;
        address->sin_port;
        // ...
      } else if (addr->sa_family == AF_UNIX && addrlen == sizeof(struct sockaddr_un)) {
        struct sockaddr_un* address = (struct sockaddr_in*) addr;
        address->sun_path;
        // ...
      } 
      // ...
    }
    

    위 와 같이 accept()는 addr을 여러 타입을 수용하는 구현을 갖습니다. 하지만 addrstruct sockaddr* 입니다. 따라서 다음과 같은 경우 컴파일 오류가 발생합니다.

    struct sockaddr_in address;
    accept(sock, &address, sizeof(address));
    

    왜 냐면 &addressstruct sockaddr*가 아닌 struct sockaddr_in*이기 때문입니다. 이 문제를 회피하기 위해서 다음과 같이 강제 캐스팅으로 컴파일러를 속여줍니다.

    struct sockaddr_in address;
    accept(sock, (struct sockaddr*)&address, sizeof(address));
    

    이 방법은 충분히 위험한 방법입니다. 하지만 앞서 말한 ABI 측면이나 accept() 구현이 이런 코드가 정상 동작할 것을 보장합니다.

    • 마지막 문단에서 여쭤볼게 2가지가 있습니다. 1. 타입이 다르다는 것이 프로토콜이 다르다는 말 인가요? 2. 왜 오류를 막나요? 번거롭게 해서 죄송합니다. 로만로만 2021.12.30 19:55
    • 내용 추가하였습니다. 유동욱 2021.12.31 11:33

답변을 하려면 로그인이 필요합니다.

프로그래머스 커뮤니티는 개발자들을 위한 Q&A 서비스입니다. 로그인해야 답변을 작성하실 수 있습니다.

(ಠ_ಠ)
(ಠ‿ಠ)