banner
Rick Sanchez

Rick Sanchez

OS && DB 爱好者,深度学习炼丹师,蒟蒻退役Acmer,二刺螈。

Socket プログラミングにおける sockaddr および sockaddr_in 構造体の分析

FwfiZF0acAAyLU8

1. 引言#

Socket プログラミングでは、私たちがよく使用する構造体 sockaddr_in を使ってソケット情報を構築します。

struct sockaddr_in serv_addr;
memset(&serv_addr, 0, sizeof(serv_addr));
serv_addr.sin_family = AF_INET;
serv_addr.sin_addr.s_addr = inet_addr(ip);
serv_addr.sin_port = htons(port);

sockaddr_in 構造体のソースコードを見てみましょう:

struct sockaddr_in {
    short       sin_family; // アドレスファミリー(Address Family)、AF_INET
    u_short     sin_port;   // 16ビットTCP/UDPポート番号、ネットワークバイトオーダー(Network Byte Order)
    struct      in_addr sin_addr;   // 32ビットIPアドレス、ネットワークバイトオーダー(Network Byte Order)
    char        sin_zero[8];  // 一時的に使用されていない、埋めるために使用可能
};

私たちは、第 4 行で s_addr フィールドを直接使用して IP アドレスを表していないことに気付きますが、その中に構造体 sin_addr がネストされています。

では、これにはどんな利点があるのでしょうか?

2. 分析#

Unix プラットフォームでは、in_addr 構造体は次のように定義されています:

typedef uint32_t in_addr_t;
struct in_addr {
    in_addr_t s_addr; // 32ビットのIPV4アドレス、ネットワークバイトオーダー
};

Windows プラットフォームでは、in_addr 構造体は次のように定義されています:

struct in_addr {
    union {
        struct {
            u_char s_b1, s_b2, s_b3, s_b4;
        } S_un_b;
        struct {
            u_short s_w1, s_w2;
        } S_un_w;
        u_long S_addr;
    } S_un;
};

異なるプラットフォームで s_addr フィールドの処理方法が異なることがわかります。したがって、この設計はプラットフォームの互換性を保証します。
これが、sockaddr_in 構造体で s_addr フィールドが in_addr 構造体でラップされている理由です。

3. in_addr 中の Union の分析#

Windows プラットフォームでは、in_addr 構造体で Union タイプを使用して s_addr フィールドを表現し、4 バイト、2 つの 16 ビット整数、または 1 つの 32 ビット整数として IPV4 アドレスの異なる部分を解釈します。

では、in_addr フィールドを初期化した後:

serv_addr.sin_addr.s_addr = inet_addr(ip);

上記の 3 種類の Union タイプを使用して IPV4 アドレスを解釈できます。

4. sockaddr 構造体#

struct sockaddr{
    sa_family_t  sin_family;   // アドレスファミリー(Address Family)、すなわちアドレスタイプ
    char         sa_data[14];  // IPアドレスとポート番号
};

struct sockaddr_in{
    sa_family_t     sin_family;   // アドレスファミリー(Address Family)、すなわちアドレスタイプ
    uint16_t        sin_port;     // 16ビットのポート番号
    struct in_addr  sin_addr;     // 32ビットIPアドレス
    char            sin_zero[8];  // 使用しない、一般的に0で埋める
};

struct sockaddr_in6 {
sa_family_t sin6_family;  //(2)アドレスタイプ、AF_INET6の値を取る
in_port_t sin6_port;  //(2)16ビットポート番号
uint32_t sin6_flowinfo;  //(4)IPv6フロー情報
struct in6_addr sin6_addr;  //(4)具体的なIPv6アドレス
uint32_t sin6_scope_id;  //(4)インターフェース範囲ID
};

sockaddrsockaddr_in、および sockaddr_in6 の長さは実際には同じであり、sockaddr は IP アドレスとポート番号を組み合わせているのに対し、後者の 2 つは前者の派生型です。

では、なぜ IP:Port の形式で直接渡さないのでしょうか?
API は IPPort を解析するための関連関数を提供しておらず、元の sockaddr は使い勝手が悪いため、後者の 2 つが存在します。

しかし、使用する際には、例えば:

bind(serv_sock, (struct sockaddr*)&serv_addr, sizeof(server_addr)))

私たちは type punning の方法(すなわち強制型変換)を使用して、上記の関数を呼び出します。これにより、sockaddr_insockaddr_in6 の両方が互換性を持って使用できるようになります。

type punning: C/C++ において異なる型を使用して同じストレージスペースにアクセスする技術であり、ストレージスペースの型を間接的に変更することができます。すなわち、変数の型を変更することで特定のビットパターンを取得します。
type punning の方法はいくつかあり、Union や強制型変換を通じて、または memcpy などの公式に許可された方法を使用します。

ただし、type punning を使用する際には strict aliasing の問題が発生する可能性があるため、慎重に使用する必要があります。

strict aliasing: C/C++ の最適化機能の一種であり、異なる型のオブジェクトにアクセスすることを絶対に許可しないことを指します。これにより、最適化エラーを効果的に回避し、操作の正確性を保証します。

ここで合理的に使用できる理由:これらの 2 つの構造体の長さは同じであり、強制的に型を変換してもバイトが失われず、余分なバイトもありません。

読み込み中...
文章は、創作者によって署名され、ブロックチェーンに安全に保存されています。