LoginSignup
69
83

More than 5 years have passed since last update.

TCP接続する時に使ういろいろな構造体の整理

Last updated at Posted at 2015-04-13

何年かおきに忘れるのでメモしておく。

TCPコネクションを行うためには接続先のホスト名からアドレスを知る必要がある。
そのためには二つの方法がある。

  • gethostbyname()を使う。
  • getaddrinfo()を使う。

どちらの方法も最終的にはconnect()に接続先のアドレスが格納されたsockaddr構造体を渡す。

sockaddr構造体とは、接続するために必要なネットワーク層レベルの通信プロトコルとアドレスとポートの情報を格納した構造体のこと。

ただし、これはそのままでは使われない。
IPv4の場合はsockaddr_in構造体にキャストされて使用される。
IPv6の場合はsockaddr_in6構造体にキャストされる。
サーバーソケットなどで受け取る場合は何が来てもいいように十分な大きさを持つsockaddr_storage構造体にキャストされる。

また、ホスト名が複数のアドレスを持つ場合に複数のsockaddr構造体をリンクリストで保持するaddrinfo構造体やhostent構造体が用意されている。

has-aの関係や同一レベルの階層構造で見るとこうなる。

addrinfo構造体 (48バイト)
メンバ
|-- sockaddr構造体 (16バイト)
|-- sockaddr_in構造体 (16バイト)
   メンバ
   |-- in_addr構造体 [iPv4のアドレス値]
|-- sockaddr_in6構造体 (28バイト)
   メンバ
   |-- in6_addr構造体 [iPv6のアドレス値]
|-- sockaddr_storage構造体 (128バイト)

gethostbyname()を使う

gethostbyname()に文字列でホスト名を渡すとhostent構造体が取得できる。ちなみにgethostbyname()はスレッドセーフではないのでスレッドセーフな関数はgethostbyname_r()になる。

// 名前解決.
struct hostent *hp = gethostbyname("HOST NAME");

// 構造体初期化.
struct sockaddr_in addr;
memset(&addr, 0, sizeof(addr));

// 複数のアドレスの中から最初のものを選択.
addr.sin_addr = *(struct in_addr*)hp->h_addr_list[0];

// インターネット・プロトコルを指定する.
addr.sin_family = AF_INET;

// ポートを指定する.
addr.sin_port = htons(PORT);

// ソケット作成.
if ((_socket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP))<0){
    return -1;
}

// sockaddr構造体(sockaddr_in構造体)を渡して接続.
if (connect(_socket, (struct sockaddr *)&addr, sizeof(addr))<0){
    return -1;
}

getaddrinfo()を使う

getaddrinfo()はホスト名とヒントとなる情報を格納したaddrinfo構造体を渡すことで完全なaddrinfo構造体を取得することができる関数。

struct addrinfo hints, *address;
int errcode = 0;
int socket = 0;

// ヒントとなるaddrinfo構造体を初期化.
memset(&hints, 0, sizeof(hints));

// ヒントとしてIPネットワークプロトコルとTCP通信のソケットタイプを指定
hints.ai_family = AF_INET;
hints.ai_socktype = SOCK_STREAM;

// addrinfo構造体を取得する.
if((errcode = getaddrinfo("ホスト名", ポート番号, &hints, & address)) != 0){
    return -1;
}

// 完全なaddrinfo構造体としてaddressが手に入ったので以後はこれを使う.

// ソケットタイプやプロトコルを指定してソケットを作成する.
if((socket = socket(address->ai_family, address->ai_socktype, address->ai_protocol)) == -1){
    freeaddrinfo(address);
    return -1;
}

// addrinfo構造体のsockaddr構造体を渡して接続.
if(::connect(socket, address->ai_addr, address->ai_addrlen) == -1){
    close(socket);
    freeaddrinfo(address);
    return -1;
}

freeaddrinfo(address);

各構造体はLinuxでは次のように定義されている。

// 32バイト
struct hostent {
    char    *h_name;    /* official name of host */
    char    **h_aliases;    /* alias list */
    int h_addrtype; /* host address type */
    int h_length;   /* length of address */
    char    **h_addr_list;  /* list of addresses from name server */
#if !defined(_POSIX_C_SOURCE) || defined(_DARWIN_C_SOURCE)
#define h_addr  h_addr_list[0]  /* address, for backward compatibility */
#endif /* (!_POSIX_C_SOURCE || _DARWIN_C_SOURCE) */
};

// 48バイト
struct addrinfo {
    int ai_flags;   /* AI_PASSIVE, AI_CANONNAME, AI_NUMERICHOST */
    int ai_family;  /* PF_xxx */
    int ai_socktype;    /* SOCK_xxx */
    int ai_protocol;    /* 0 or IPPROTO_xxx for IPv4 and IPv6 */
    socklen_t ai_addrlen;   /* length of ai_addr */
    char    *ai_canonname;  /* canonical name for hostname */
    struct  sockaddr *ai_addr;  /* binary address */
    struct  addrinfo *ai_next;  /* next structure in linked list */
};

// 16バイト
struct sockaddr {
    __uint8_t   sa_len;     /* total length */
    sa_family_t sa_family;  /* [XSI] address family */
    char        sa_data[14];    /* [XSI] addr value (actually larger) */
};

// 16バイト
struct sockaddr_in {
    __uint8_t   sin_len;
    sa_family_t sin_family;
    in_port_t   sin_port;
    struct  in_addr sin_addr;
    char        sin_zero[8];
};

// 4バイト
struct in_addr {
    in_addr_t s_addr;
};


// 28バイト
struct sockaddr_in6 {
    __uint8_t   sin6_len;   /* length of this struct(sa_family_t) */
    sa_family_t sin6_family;    /* AF_INET6 (sa_family_t) */
    in_port_t   sin6_port;  /* Transport layer port # (in_port_t) */
    __uint32_t  sin6_flowinfo;  /* IP6 flow information */
    struct in6_addr sin6_addr;  /* IP6 address */
    __uint32_t  sin6_scope_id;  /* scope zone index */
};

// 128バイト
struct sockaddr_storage {
    __uint8_t   ss_len;     /* address length */
    sa_family_t ss_family;  /* [XSI] address family */
    char            __ss_pad1[_SS_PAD1SIZE];
    __int64_t   __ss_align; /* force structure storage alignment */
    char            __ss_pad2[_SS_PAD2SIZE];
};

ついでにアドレスファミリーのプロトコルに指定できるものの種類を書いておく。

アドレスファミリー 意味
AF_INET インターネットプロトコル
AF_UNIX UNIXファイルシステムドメイン
AF_ISO ISO標準プロトコル
AF_NS XeroxNetworkSystemsプロトコル
AF_IPX NovellIPXプロトコル
AF_APPLETALK Appletalk
PF_INET AF_INETと同じ
PF_INET6 IPv6
PF_IPX IPX Novell プロトコル
PF_NETLINK カーネルユーザデバイス
PF_X25 ITU-T X.25(ISO-8208プロトコル)
PF_AX25 無線 AX.25プロトコル
PF_ATMPVC ATM PVC
PF_APPLETALK Appletalk
PF_PACKET 低レベルのパケット
69
83
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
69
83