Cソケットのconnect()
やbind()
などの関数を見て、sockaddr
構造体へのポインターを取得していることに気付きました。私は読んでいて、アプリケーションをAFに依存しないようにするには、_sockaddr_storage
_構造体ポインターを使用してsockaddr
ポインターにキャストすると便利です。 。
私が疑問に思っているのは、sockaddr
ポインターを要求するconnect()
やbind()
のような関数が、期待している1つ。もちろん、提供する構造体のサイズを渡しますが、_struct *sockaddr
_にキャストしたより大きな構造体へのポインターからIPアドレスを取得するために関数が使用する実際の構文は何ですか?
おそらく、私はOOP言語から来たからでしょうが、それは一種のハックで少し厄介なようです。
struct sockaddr
へのポインターを期待する関数は、struct sockaddr_storage
へのポインターを送信するときに、おそらくsockaddr
に送信するポインターを型キャストします。そのようにして、彼らはstruct sockaddr
であるかのようにそれにアクセスします。
struct sockaddr_storage
は、struct sockaddr_in
とstruct sockaddr_in6
の両方に適合するように設計されています
独自のstruct sockaddr
を作成するのではなく、使用しているIPバージョンに応じて、通常struct sockaddr_in
またはstruct sockaddr_in6
を作成します。使用するIPバージョンを把握しようとするのを避けるために、どちらかを保持できるstruct sockaddr_storage
を使用できます。これは、connect()、bind()などの関数によってstruct sockaddr
に型キャストされ、その方法でアクセスされます。
以下にこれらの構造体をすべて見ることができます(パディングは、アライメントのために実装固有です):
struct sockaddr {
unsigned short sa_family; // address family, AF_xxx
char sa_data[14]; // 14 bytes of protocol address
};
struct sockaddr_in {
short sin_family; // e.g. AF_INET, AF_INET6
unsigned short sin_port; // e.g. htons(3490)
struct in_addr sin_addr; // see struct in_addr, below
char sin_zero[8]; // zero this if you want to
};
struct sockaddr_in6 {
u_int16_t sin6_family; // address family, AF_INET6
u_int16_t sin6_port; // port number, Network Byte Order
u_int32_t sin6_flowinfo; // IPv6 flow information
struct in6_addr sin6_addr; // IPv6 address
u_int32_t sin6_scope_id; // Scope ID
};
struct sockaddr_storage {
sa_family_t ss_family; // address family
// all this is padding, implementation specific, ignore it:
char __ss_pad1[_SS_PAD1SIZE];
int64_t __ss_align;
char __ss_pad2[_SS_PAD2SIZE];
};
ご覧のとおり、関数がIPv4アドレスを予期している場合、最初の4バイトを読み取るだけです(構造体のタイプがstruct sockaddr
であると想定しているためです。
C++では、少なくとも1つの仮想関数を持つクラスにTAGが与えられます。このタグを使用すると、クラスが派生する任意のクラスにdynamic_cast<>()
できます。 TAGは、dynamic_cast<>()
を機能させるものです。多かれ少なかれ、これは数字または文字列です...
Cでは、構造に限定されています。ただし、構造にTAGを割り当てることもできます。実際、theproleが彼の回答で投稿されたすべての構造を見ると、それらはすべて2バイト(符号なしのショート)で始まり、アドレスのファミリーと呼ばれるものを表していることがわかります。これは、構造が何であるか、したがってサイズ、フィールドなどを正確に定義します。
したがって、次のようなことができます。
_int bind(int fd, struct sockaddr *in, socklen_t len)
{
switch(in->sa_family)
{
case AF_INET:
if(len < sizeof(struct sockaddr_in))
{
errno = EINVAL; // wrong size
return -1;
}
{
struct sockaddr_in *p = (struct sockaddr_in *) in;
...
}
break;
case AF_INET6:
if(len < sizeof(struct sockaddr_in6))
{
errno = EINVAL; // wrong size
return -1;
}
{
struct sockaddr_in6 *p = (struct sockaddr_in6 *) in;
...
}
break;
[...other cases...]
default:
errno = EINVAL; // family not supported
return -1;
}
}
_
ご覧のとおり、関数はlen
パラメーターをチェックして、長さが期待される構造に適合するのに十分であることを確認できます。したがって、reinterpret_cast<>()
(C++で呼び出される場合)あなたのポインター。構造内のデータが正しいかどうかは、呼び出し元次第です。そのための選択肢はあまりありません。これらの関数は、データを使用する前にあらゆる種類のものを検証し、問題が検出されるたびに-1とerrno
を返すことが期待されています。
したがって、実際には、_struct sockaddr_in
_または_struct sockaddr_in6
_があり、それを_struct sockaddr
_にキャスト(再解釈)し、bind()
関数(およびその他)がそのポインターをキャストします_struct sockaddr_in
_メンバーをチェックし、サイズを確認した後の_struct sockaddr_in6
_または_sa_family
_.