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

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

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()になる。


```c
// 名前解決.
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構造体を取得することができる関数。

```c
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では次のように定義されている。

```c
// 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|低レベルのパケット|