socketのgetaddrinfoの使い方や仕組みについてまとめてみる
今回は、socketのgetaddrinfo
の仕組みや使い方についてまとめていく。
getaddrinfo
は仕様がごちゃごちゃしていて難しいけど、重要な部分さえ分かれば、あとは細々とした点をその都度調べていけば良いので、ここでは重要な点のみ絞って解説していく。
getaddinfoの使い方
getaddrinfo
は以下のような引数を用意するが、正直引数の使い方がいまいち掴みにくい。ここでは、引数を1つ1つ丁寧にみていく。
#include
第1引数const char *node
にはサーバー側のhostnameを入れる。例えば、localhost
とかexample.com
などを入れれば良い。
第2引数const char *service
にはポート番号を入れる。
そして第3引数と第4引数だが、初めに第4引数struct addrinfo **res
は、getaddrinfo
で見つかったIPアドレスやポート番号を格納される構造体struct addrinfo
を格納する働きがある。つまり、第4引数はgetaddrinfo
の結果を受け取るための引数と言える。
getaddrinfo
でアドレスを取得する時に、「IPv4形式のアドレスのみ欲しい」という風に取得したいアドレスや通信方法を絞りたい場合がある。その時には、第3引数const struct addrinfo *hints
を使って指定する事で、自分が欲しいアドレスの種類を取得できる。
addrinfo構造体について
上記の説明でaddrinfo
構造体が出てきたが、addrinfo
は以下のように定義されている。
struct addrinfo { int ai_flags; int ai_family; int ai_socktype; int ai_protocol; socklen_t ai_addrlen; struct sockaddr *ai_addr; char *ai_canonname; struct addrinfo *ai_next; }; たくさんのメンバ変数があるが、上記の構造体は大きく分けて以下の2つに分けれる。
struct addrinfo { // 第3引数のhintsで使うところ int ai_flags; int ai_family; int ai_socktype; int ai_protocol; // 第4引数のgetaddrinfo()の結果を受けるところ socklen_t ai_addrlen; struct sockaddr *ai_addr; // ここにアドレスとかポート番号が格納される char *ai_canonname; struct addrinfo *ai_next; }; 例えば、第3引数のhints
を以下の様に定義すると、ソケット通信はSOCK_STREAM
形式でかつ、IPv4形式のIPアドレスのみを取得できる。
struct addrinfo hints; memset(&hints, 0, sizeof(hints)); hints.ai_family = AF_INET; hints.ai_socktype = SOCK_STREAM; getaddrinfo("localhost", port, &hints, &res); そして、特徴的なのがstruct addrinfo *ai_next;
のところ。
実はstruct addrinfo
はリスト構造になっている。「サーバーのアドレスは1つに決まっているだろ」と思うかもしれないが、同じアドレスでもポート番号が異なることもあるし、アドレス形式がIPv4かIPv6かでも違うし、通信形式がTCPかUDPとか色々あるので、リスト構造として提供されている。
getaddrinfoの具体例
以下は、getaddrinfoの具体例を書いていく。
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <sys/types.h>
#include <stdlib.h>
#include <stdio.h>
int main() {
char * hostname = "localhost";
struct addrinfo * res, * res0;
struct addrinfo hints;
// 第3引数のhints。memsetで0で埋めておかないと、上手く動かない時がある
memset( & hints, 0, sizeof(hints));
hints.ai_family = AF_UNSPEC; // AF_UNSPECとすることで、AF_INET AF6_INETの2つを定義できる。
hints.ai_socktype = SOCK_STREAM;
if (getaddrinfo(hostname, NULL, & hints, & res) < 0) {
printf("ERROR! LINE:%c", __LINE__);
exit(1);
}
char addr_buf[64];
res0 = res;
int i = 0;
void * ptr;
for (; res; res = res -> ai_next) {
printf("STRUCT: %d\n", i);
printf("FAMILY: %d\n", res -> ai_family);
if (res -> ai_family == AF_INET) {
ptr = & ((struct sockaddr_in * ) res -> ai_addr) -> sin_addr;
} else if (res -> ai_family == AF_INET6) {
ptr = & ((struct sockaddr_in6 * ) res -> ai_addr) -> sin6_addr;
}
inet_ntop(res -> ai_family, ptr, addr_buf, sizeof(addr_buf));
printf("ADDRESS: %s\n", addr_buf);
i++;
}
printf("END\n");
freeaddrinfo(res0);
}