bind()는 socket 으로 통신하는 대상의 주소를 할당하는 함수이다.

// socket 생성 및 bind 코드
SOCKET s = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);

sockaddr_in s_in = {};
s_in.sin_family = AF_INET;
s_in.sin_addr.S_un.S_addr = INADDR_ANY;
s_in.sin_port = htons(50000);

if (bind(s, reinterpret_cast<sockaddr*>(&s_in), sizeof(s_in)) == SOCKET_ERROR) {
	cout << "bind error: " << WSAGetLastError() << endl;
	return;
}

왜 sockaddr 구조체를 직접 쓰지 않고 sockaddr_in 구조체를 사용할까?

위 코드에서 bind() 함수 실행 시 sockaddr_in 타입의 s_in 변수를 sockaddr* 타입으로 캐스팅하는 것을 볼 수 있다. 그러면 bind 시 필요한 건 sockaddr* 타입의 데이터인데 왜 sockaddr 타입의 변수를 초기화 하지 않고 sockaddr_in 타입으로 변수를 초기화한 걸까?

bind의 2번째 인자로 필요한 것은 통신 대상의 주소이다. sockaddr 구조체는 주소를 담는 구조체로, 모양은 아래와 같다.

struct sockaddr {
	ADDRESS_FAMILY sa_family;
	CHAR sa_data[14];                   // Up to 14 bytes of direct address.
}

sa_family 변수에 할당되는 값은 AF_INET, AF_INET6 와 같은 주소 체계이다.

이어서 오는 sa_data 14 byte에는 IP:PORT와 같은 주소가 담겨야 한다.

IPv4를 예로 보면, sa_family = AF_INET, sa_data 에는 127.0.0.112345 같은 값이 들어가게 된다.

그런데 sockaddr 구조체 자체는 IPv4만이 아닌 IPv6에도 사용 가능하도록, 즉 다른 프로토콜에서도 sockaddr 구조체를 사용해서 socket 통신이 가능하도록 구현해 놓은 것이다. 그렇기 때문에 sockaddr 구조체의 데이터는 실제 사용되는 프로토콜에 따라 값이 다뤄지는 유형이 달라질 수 있다. 이러한 실정에서 프로토콜에 맞는 방식으로 값을 쉽게 작성하도록 돕는 게 sockaddr_in 과 같은 구조체이다. sockaddr_inIPv4를 사용할 때에 사용하도록 되어 있다.

참고

https://www.bangseongbeom.com/af-inet-vs-pf-inet.html

https://techlog.gurucat.net/292