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

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

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

getaddinfoの使い方

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

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