accept() 함수 질문입니다
조회수 670회
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_family
와sa_family
는 이름만 다르고 동일한 구조체이며,sin_port
와sa_data
는 타입이 다르지만 바이트 크기는 동일합니다.sockaddr
관점에서 보면sockaddr_in
의 앞 부분은sockaddr
과 ABI관점에서 유사하다고 볼 수 있습니다. 다시말해sockaddr_in
은sockaddr
의 확장된 형태라고 볼 수 있는 거죠.BSD 소켓의
accept()
는 여러 프로토콜을 지원할 수 있는 형태의 API입니다. 각 프로토콜에 따라서 주소의 표현 방식은 다양합니다. 새로운 프로토콜이 개발되고 이 API를 통해 사용하게 될 수도 있습니다.accept()
란 이름의 함수는 가능한 그대로 유지하면서 다양한 타입의 주소를 받아 들이고 싶습니다. 하지만 C언어에서는 한 함수의 이름으로 다양한 타입을 받아 들이는 방법이 없습니다.그래서
sa_family
를 통해 어떤 프로토콜을 사용하는지 확인하고, 해당 주소가 어떤 타입 인지를 파악합니다.(앞부분의 ABI가 동일하니 어떤 프로토콜인지 판별하는 거 까지는 문제가 발생하지 않습니다.)sa_family
가AF_INET
이면addr
의 타입을sockaddr_in
으로 판단합니다.sa_family
가AF_UNIX
이면addr
의 타입을sockaddr_un
으로 판단합니다.
동작 상으로 위의 방법대로 돌아가나 컴파일러는 타입이 다르다고 판단하여 오류를 방색하니, 강제로 해당 타입의 속여서
accept()
에 입력합니다. 그래서(struct sockaddr*)&clnt_add
와 같이 동일한 주소이나 타입이 다르게 입력하게 됩니다.타입 안전성은 떨어지나 C언어 표현의 한계를 극복할 수 있는 방법인 거죠.
- 타입이 다르다는 것이 프로토콜이 다르다는 말 인가요?
제가 말한 타입은 데이터 타입(정수, 구조체 등)을 의미합니다. 프로토콜 마다 주소 표현 방식이 다르고 그로 인해 사용해야할 구조체가 다르다 라는 것이죠.
- 왜 오류를 막나요?
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
을 여러 타입을 수용하는 구현을 갖습니다. 하지만addr
은struct sockaddr*
입니다. 따라서 다음과 같은 경우 컴파일 오류가 발생합니다.struct sockaddr_in address; accept(sock, &address, sizeof(address));
왜 냐면
&address
는struct sockaddr*
가 아닌struct sockaddr_in*
이기 때문입니다. 이 문제를 회피하기 위해서 다음과 같이 강제 캐스팅으로 컴파일러를 속여줍니다.struct sockaddr_in address; accept(sock, (struct sockaddr*)&address, sizeof(address));
이 방법은 충분히 위험한 방법입니다. 하지만 앞서 말한 ABI 측면이나 accept() 구현이 이런 코드가 정상 동작할 것을 보장합니다.
댓글 입력