socketのgetaddrinfoの使い方や仕組みについてまとめてみる

今回は、socketのgetaddrinfoの仕組みや使い方についてまとめていく。

getaddrinfoは仕様がごちゃごちゃしていて難しいけど、重要な部分さえ分かれば、あとは細々とした点をその都度調べていけば良いので、ここでは重要な点のみ絞って解説していく。

getaddinfoの使い方

getaddrinfoは以下のような引数を用意するが、正直引数の使い方がいまいち掴みにくい。ここでは、引数を1つ1つ丁寧にみていく。

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

参考:Man page of GETADDRINFO

第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);
}

ReratedPosts

WindowsのMinGWでdxlib(DXライブラリ)を使う手順を紹介する
C言語の配列で範囲外にアクセスできてしまう理由と対策法
C言語のfopen関数で「警告: 2 番目の ‘fopen’ の引数へ渡すときに整数からキャスト無しにポインタを作成しています [デフォルトで有効]」と言うエラーが出たときの対処法
C言語の型キャストについてのまとめ
C言語のコンパイルにおけるアセンブラ→実行ファイルまでの流れをまとめてみた
C言語のsizeofにおける配列、ポインタ、構造体などの動きのまとめ