ソケットアドレス構造体

TCP/IPをやるときに、大多数の人が案外「ソケットアドレス」という物に躓きます。

というよりも、所謂BSDソケットに於ける「ソケットアドレス」って非情に曖昧なんです。

だから、理解しようとしちゃ駄目(なら説明しようとするな(ぁ。

ソケットアドレスって何?

TCP/IPに限らず、PFLOCAL…古く言えばPFUNIX、要するにUNIXドメインソケットに関する操作だったり、とにかく socket という物を使用して通信を行う際に「どこに投げたいのか」を指定する。

その為の最小公倍数が「ソケットアドレス」という概念です。

 

既に難しそうに書いてる気もするんだけど・・・

単純に「どこに投げたいのか」を指定すれば、後はその他操作は同じに、というのがたぶんBSDソケットの思想。

逆に使うだけなら特に難しい事は何にもありません。

 

それでもやっぱり実際はデータ指定が必要

とはいえ、結局実際のデータは中身を持っています。

とりあえず、具体的な構造体データを元に、いくつかサンプルを挙げます。

 

一応、その為使用する用語定義を。

ネットワークエンディアン
CPU に依っては、0x01020304 という値が実際のメモリ上に 04030201 と格納されたり(リトルエンディアン)、そのまま 01020304 と格納されたり(ビックエンディアン)という違いがあります。
つまり、そのままネットワークに流したら、CPU の違いで不具合が出てしまう事になります。
その為、ネットワーク上のデータはビックエンディアンにする・・・という決まりが有るらしいです。
それをネットワークエンディアンとかネットワークバイトオーダとか言います。
32bit int値にしたIPアドレス
IPv4 では、IPアドレスは 1byte ずつを ‘.’ 区切りの数字で 4byte 分使って表現しています。
それを単純に32bit で表現すれば良いということで・・・
例えば、192.168.0.1 は・・・16進数で C0.A8.00.01つまり 0xC0A80001 になるわけです。
最近では、IPv6云々も考慮して getaddrinfo を使用するのを推奨しますが

 

UNIXドメインの場合

#define UNIX_PATH_MAX    108

struct sockaddr_un {
  sa_family_t  sun_family;              /* AF_UNIX */
  char         sun_path[UNIX_PATH_MAX]; /* pathname */
};

sunfamily には AFLOCAL(AF_UNIX) を入れます。これは必須です。

sun_path には、UNIXドメインソケットを識別するためのパス名(NULL終端)を指定します。

/* Sample for UNIX Domain Socket Address */
struct sockaddr_un  localAddress = { 0 };
socklen_t   len = sizeof(localAddress) - sizeof(localAddress.sun_path);
localAddress.sun_family = AF_LOCAL;
len += strlcpy ( localAddress.sun_path,
                 "/tmp/mylocal-socket",
                 sizeof(localAddress.sun_path) );

この例では、”/tmp/mylocal-socket”に依り結びつけられるUNIXドメインソケットのアドレスを設定します。

# 因みに strlcpy は指定したバッファサイズのNULL末端保証/設定後の文字列長を返すOpenBSD由来の非標準関数

 

IPv4 TCP/IPの場合

struct sockaddr_in {
  sa_family_t    sin_family;
  u_int16_t      sin_port;
  struct in_addr sin_addr;
};

struct in_addr {
  u_int32_t      s_addr;
};

sinfamily には AFINET を入れます。これは必須です。

sin_port にはポート番号(これはIPv4だろうがIPv6だろうが変わらない)をネットワークエンディアンで入れます。

sinaddr(.saddr) には32bit int値にしたIP アドレスを入れます。

/* Sample for IPv4 Socket Address */
struct sockaddr_in  peerAddress = { 0 };
peerAddress.sin_family      = AF_INET;
peerAddress.sin_port        = htons( 80 );
peerAddress.sin_addr.s_addr = inet_addr ( "127.0.0.1" );

あまりにも汚い、と言えば汚いんですが。案外よく見る形の・・・^^;

IPv4でのポート番号:80/アドレス情報 127.0.0.1 直値変換でのIPv4アドレス指定です。

# この形のコードは正常なブロードキャストアドレスを認識しないっていう問題があるらしいです(僕は書かないから知らない(ぁ

 

IPv6 TCP/IPの場合

struct sockaddr_in6 {
  sa_family_t     sin6_family;
  in_port_t       sin6_port;
  uint32_t        sin6_flowinfo;
  struct in6_addr sin6_addr;
  uint32_t        sin6_scope_id;
};

struct in6_addr {
  union {
  uint8_t         u6_addr8[16];
  uint16_t        u6_addr16[8];
  uint32_t        u6_addr32[4];
} in6_u;
#define s6_addr   in6_u.u6_addr8
#define s6_addr16 in6_u.u6_addr16
#define s6_addr32 in6_u.u6_addr32
};

sin6family には AFINET6 を入れます。 これは必須です。

sin6_port には、ポート番号(これはIPv4だろうがIPv6だろうが変わらない)をネットワークエンディアンで入れます。

sin6_addr は名前まんま IPv6 のアドレスを。

sin6_flowinfo はフローラベルを格納しますが・・・とりあえず 0 で(ぉ

sin6scopeid はインタフェースを識別する為のスコープID値を設定します。

/* Sample for IPv6 Socket Address */
struct sockaddr_in6 serverAddress = { 0 };
serverAddress.sin6_family   = AF_INET6;
serverAddress.sin6_port     = htons( 80 );
serverAddress.sin6_flowinfo = 0;
serverAddress.sin6_addr     = in6addr_loopback;
serverAddress.sin6_scope_id = 0;

サンプル自体は超手抜きです(貴様

# IPv4と同じく・・・こんなのは書くなと(何

 

sockaddr* の仕組み

ここまで説明した所で、インタフェースとして指定する sockaddr に関して簡単に。

struct sockaddr {
  sa_family_t     sa_family;
  char            sa_data[14];
};

sa_data は飾りです(何

インタフェースとして重要なのは、sa_family です。

 

例えばライブラリを作成する様な場合で、sockaddr へのポインタを引数に取る関数を作るとします。

この様な場合に、sa_family の値を見ることでどの様な型で指定されたかどうか、を判別します。

# 安全性を考慮するなら、合わせて addrlen とかもチェックした方が良いとは思いますが割愛(ぉ

/**
 *  getSocket - 指定アドレス情報に応じたソケット生成を行う
 */
CSocket *getSocket ( struct sockaddr *addr, socklen_t *addrlen )
{
    switch ( addr->sa_family )
    {
    case PF_INET:   return new CInet4Socket ( addr, addrlen );
    case PF_INET6:  return new CInet6Socket ( addr, addrlen );
    case PF_LOCAL:  return new CLocalSocket ( addr, addrlen );

    default;
        Assert ( !"ENotSupportedException" );
        throw ENotSupportedException;
    }
    /* NOTREACHED */
}

特に socket に対する操作、と思わなければ「良くある」コードじゃないかなと。

要するに、こういう事をやりたいが為の sockaddr インタフェースとして理解してもらえれば十分かなと。

 

補足: *BSD等のsa_lenを持つ実装

一部実装では、上記の safamily の他に、salen というメンバを持っている実装があります。

# 僕は使ったことが無いんですが・・・

その場合、UNIXドメインやTCPの場合にもそれぞれ、sunlen や sinlen などのメンバを設定する事になります。

  それで何が出来るかというと・・・

struct sockaddr*    addr = { 0 };

// ... 適当に設定関係のコード

// こう書ける
bind ( sock, addr, addr->sa_len );  // addr に length が含まれているからそのまま

要するにsockaddr単位でのデータを持ち回るのが楽、っていう話なんですが。

# 「プログラマ」に優しい・・・?w

 

** 中身は適当にやってよ

「いや、僕は細かい構造体の設定とかはどうでもいいんです。
 IPv4とかIPv6とかもどうでも良くて、とにかく、TCPで、www.example.com から HTTP でデータを取得したいんです。」

良くあるパターンで、「やりたいこと」はデータの取得なのに、面倒なアドレス情報の設定をしなきゃならない。

# しかもこういう時に限って、さらにどうでも良いIPv4だのIPv6だのを考慮しなきゃいけないんですな(衰

 

こういう場合に getaddrinfo(3)を使用すると幸せになれます。

int getaddrinfo(const char *node, const char *service,
                const struct addrinfo *hints,
                struct addrinfo **res);

void freeaddrinfo(struct addrinfo *res);

struct addrinfo {
  int     ai_flags;
  int     ai_family;
  int     ai_socktype;
  int     ai_protocol;
  size_t  ai_addrlen;
  struct sockaddr *ai_addr;
  char   *ai_canonname;
  struct addrinfo *ai_next;
};

node はアドレスを取得したいホスト名(“www.example.com”の様なFQDNでも”127.0.0.1”の様なアドレス文字列でも可)

serv はポート番号を取得したいサービス名(“http”の様に/etc/servicesで定義されるサービス名でも”80”でも良い)

hints は上記node/servからアドレス情報を取得するためのヒント(というか、優先するタイプ指定?)

res には取得したデータを返す為のポインタを設定します。

/**
 *  myconnect - 指定アドレス/ポートに対して接続したソケットを返す
 *    @param nodename   接続先ノードアドレス(ex.IPアドレス)
 *    @param servname   接続先サービスアドレス(ex.ポート番号 *文字列* )
 */
int myconnect ( const char *nodename, const char *servname )
{
    struct addrinfo *entry, *myentry, hint = { 0, PF_UNSPEC, SOCK_STREAM };
    int sock;


    if ( getaddrinfo ( nodename, servname, &hint, &entry ) != 0 )
        return -1;
    Assert ( entry != NULL );

    for ( myentry = entry; myentry != NULL; myentry = myentry->ai_next )
    {
        // 取得アドレス情報でソケット生成
        if ( ( sock = socket ( myentry->ai_family,
                               myentry->ai_socktype,
                               myentry->ai_protocol ) ) != -1 )
        {
            // 生成できたので接続してみる
            if ( connect ( sock,
                            myentry->ai_addr,
                            myentry->ai_addrlen ) != -1 )
                // OKの為ループ抜ける
                break;  // for

            // ソケットは一端破棄
            close ( sock );

            // ループ後、上位でのエラー判定の為 -1 を設定
            sock = -1;
        }
    }
    // ループを抜けた時点で、接続してある状態であれば sock != -1
    freeaddrinfo ( entry );
    return sock;
}

ネタ元

 

履歴

  • 2005-08-10 初版(もっと前に書いてたんだけど、手元に死蔵してた・・・w)

- スポンサードリンク -