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

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

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

getaddinfoの使い方

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

#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>

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