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; }
ネタ元
- IPv6ネットワークプログラミング (network technology series)
- getaddrinfo(3) LDP man-pages
- address family independent socket programming (little tutorial)
履歴
- 2005-08-10 初版(もっと前に書いてたんだけど、手元に死蔵してた・・・w)