59

connect()C ソケットのやなどの関数を調べていると、それらが構造体bind()へのポインターを使用していることに気付きましたsockaddr。私は読んでいて、アプリケーションをAFに依存しないようにするために、sockaddr_storage構造体ポインターを使用してポインターにキャストすると便利ですsockaddr。これは、より大きなアドレス用に余分なスペースがあるためです。

私が疑問に思っているのは、ポインタを要求する関数がどのようにconnect()機能し、期待している構造よりも大きな構造を指すポインタからデータにアクセスするかということです。確かに、提供する構造体のサイズを渡しますが、関数がキャスト先のより大きな構造体へのポインターから IP アドレスを取得するために使用する実際の構文は何ですか?bind()sockaddrstruct *sockaddr

私が OOP 言語から来たせいかもしれませんが、ハックのようなもので、少し面倒です。

4

2 に答える 2

60

ポインターを期待する関数は、ポインターを に送信するときに、struct sockaddr送信先のポインターをおそらく型キャストします。そのようにして、彼らはあたかもそれが.sockaddrstruct sockaddr_storagestruct sockaddr

struct sockaddr_storagestruct sockaddr_inとの両方に適合するように設計されています。struct sockaddr_in6

独自の を作成するのではなくstruct sockaddr、通常、使用している IP バージョンに応じてstruct sockaddr_inまたはを作成します。struct sockaddr_in6使用する IP バージョンを知ろうとするのを避けるために、struct sockaddr_storageどちらかを保持できる を使用できます。struct sockaddrこれは、connect()、bind() などの関数によって型キャストされ、その方法でアクセスされます。

これらの構造体はすべて以下に表示されます (パディングは、位置合わせのために実装固有です)。

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です。それ以外の場合は、IPv6 の 16 バイト全体を読み取ります)。

于 2013-04-15T08:23:21.907 に答える
10

少なくとも 1 つの仮想関数を持つ C++ クラスには TAG が与えられます。このタグを使用するとdynamic_cast<>()、クラスの派生元である任意のクラスにアクセスでき、その逆も可能です。TAG は動作を許可dynamic_cast<>()するものです。多かれ少なかれ、これは数値または文字列にすることができます...

C では、構造体に限定されます。ただし、構造にも TAG を割り当てることができます。実際、プロレが彼の回答に投稿したすべての構造を見ると、アドレスのファミリーと呼ばれるものを表す 2 バイト (unsigned short) で始まることがわかります。これは、構造が何であるかを正確に定義し、そのサイズ、フィールドなどを定義します。

したがって、次のようなことができます。

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++ で呼び出されるように) ポインターを使用できます。構造体のデータが正しいかどうかは、呼び出し元次第です。そのため、選択肢はあまりありません。これらの関数は、データを使用する前にあらゆる種類のことを検証しerrno、問題が見つかった場合は常に -1 を返すことが期待されています。

したがって、実際には、struct sockaddr_inorstruct sockaddr_in6に (再解釈して) キャストしstruct sockaddrbind()関数 (およびその他)は、メンバーをチェックしてサイズを確認した後、そのポインターをstruct sockaddr_inorにキャストし直します。struct sockaddr_in6sa_family

于 2015-12-12T04:25:25.407 に答える