システムプログラム(第6週): tcp_connect()

                                       筑波大学 システム情報工学研究科 
                                       コンピュータサイエンス専攻, 電子・情報工学系
                                       新城 靖
                                       <yas@is.tsukuba.ac.jp>

このページは、次の URL にあります。
http://www.coins.tsukuba.ac.jp/~syspro/2009/No6_files/tcp_connect.html
あるいは、次のページから手繰っていくこともできます。
http://www.coins.tsukuba.ac.jp/~syspro/2009/
http://www.coins.tsukuba.ac.jp/~yas/

tcp_connect()

tcp_connect() は、通信路の開 設の仕事のうち、クライアント側の仕事をする関数である。
  65:	/* 新城、筑波大学/情報学類/システムプログラム講義用 */
  66:	int
  67:	tcp_connect( char *server, int portno )
  68:	{
  69:	    struct addrinfo hints, *ai;
  70:	    struct sockaddr_in addr ;   /* IPv4 */
  71:	    int s ;
  72:	    int err ;
  73:	
  74:	        if( (s = socket(PF_INET, SOCK_STREAM, 0)) < 0 )
  75:	        {
  76:	            perror("socket");
  77:	            return( -1 );
  78:	        }
  79:	        if( sockaddr_in_init( &addr, sizeof(addr), server, portno )<0 )
  80:	        {
  81:	            perror("sockaddr_in_init");
  82:	            return( -1 );
  83:	        }
  84:	        if( connect(s, (struct sockaddr *)&addr, sizeof(addr)) < 0 )
  85:	        {
  86:	            perror( server );
  87:	            close( s );
  88:	            return( -1 );
  89:	        }
  90:	        return( s );
  91:	}
  92:	

まず、クライア ント側のソケットを、socket() システムコールで作成している。 PF_INET と SOCK_STREAMの組み合わせなので、 TCP を使うことを意味する。

socket() の引数で、PF_INET の変りに、AF_INET と書いてもよい。ここでは、 Protocol を選んでいるので、PF_ が正しいが、実際には、PF_INET と AF_INET は同じであり、また、多くのテキストで混在されて使われいる。

ソケットが作成できたら、connect() システムコールで接続する。その第2引 数と第3引数で、接続先のアドレスを指定しなければならない。 接続先には、sockaddr を使う。 この構造体には、IP アドレスとポート番号が含まれている。 この構造体を初期化するめたに、sockaddr_in_init() という 関数を呼び出している。

sockaddr_in_init()

sockaddr_in_init() は、connect() や bind() で使用する構造体 struct sockaddr_in を設定する関数である。 この関数は、役割さえわかれば、細かい内容は飛ばしてもよい。
  93:	int
  94:	sockaddr_in_init( struct sockaddr_in *addr, int addrlen,
  95:	                  char *hostname, int portno )
  96:	{
  97:	    struct addrinfo hints, *ai;
  98:	    int err ;
  99:	
 100:	        if( addrlen < sizeof(struct sockaddr_in) )
 101:	        {
 102:	            fprintf(stderr,"sockaddr_in, not enough space (%d) > (%d)\n",
 103:	                     addrlen, sizeof(struct sockaddr_in) );
 104:	            return( -1 );
 105:	        }
 106:	        memset( &hints, 0, sizeof(hints) );
 107:	        hints.ai_family   = AF_INET ; /* IPv4 */
 108:	        if( (err = getaddrinfo( hostname, NULL, &hints, &ai )) )
 109:	        {
 110:	            fprintf(stderr,"unknown host %s (%s)\n",hostname,
 111:	                    gai_strerror(err) );
 112:	            return( -1 );
 113:	        }
 114:	        if( ai->ai_addrlen > addrlen )
 115:	        {
 116:	            fprintf(stderr,"sockaddr too large (%d) > (%d)\n",
 117:	                    ai->ai_addrlen,sizeof(addr) );
 118:	            freeaddrinfo( ai );
 119:	            return( -1 );
 120:	        }
 121:	        memcpy( addr, ai->ai_addr, ai->ai_addrlen );
 122:	        addr->sin_port = htons( portno );
 123:	        freeaddrinfo( ai );
 124:	
 125:	        return( 0 );
 126:	}
 127:	

IP アドレスは、文字列で与えられた接続先のサーバのホスト名から 標準の ライブラリ関数 getaddrinfo() を使って調べている。 getaddrinfo() は、ホスト名を IP アドレスに変換し、さらに、struct sockaddr の先頭に定数 AF_INET を設定している。ポート番号は、第2引数が NULL なので、0 を指定している。 (従来は、getaddrinfo() ではなく gethostbyname() が よく使われていた。getaddrinfo() は、IPv6 にも対応している。)

ポート番号は、引数で与えられたものをそのまま使う。ただし、ネットワーク・ バイトオーダ(ビッグエンディアン)に変換する必要がある。 ポート番号として、整数(short)ではなく、/etc/services ファイルに含まれ た文字列で指定したいこともある。その場合は、getaddrinfo() 関数の第2引 数に文字列を渡す。

getaddrinfo() の中で malloc() されたメモリは、free() を直接呼び出して 解放するのではなく、freeaddrinfo() を呼び出して解放する。 (直接 free() を呼ぶと、ai_next の先が解放されない。)

バイトオーダ

複数バイトの整数をメモリに置く時に、連続した複数番地のメモリを使う。 1つの番地には、1バイト(8ビット)のデータを保持できる。 複数バイトの整数をメモリに置くとき、 どういう順番で置くかで2つの方法がある。
ビッグエンディアン。番地が小さい方に上位バイトをを置く。
リトルエンディアン。番地が小さい方に下位バイトを置く。
上位バイトとは、数の大小比較の時に、最も効いてくるバイト(most significant byte)。表記した時には、左側に来る。

例:0x12345678の置き方。

0x12345678 が CPU とメモリに置かれている。

図? バイト・オーダ

注意:CPUのレジスタの中では、バイト・オーダは関係ない。

バイトオーダを変換するには、次のような関数を使う。

#include <netinet/in.h>
unsigned long  int htonl(unsigned long  int hostlong );
unsigned short int htons(unsigned short int hostshort);
unsigned long  int ntohl(unsigned long  int netlong  );
unsigned short int ntohs(unsigned short int netshort );
ここで、n は、network、h は、ホスト、s は short、l は、long を意味する。 ネットワークの変りに、ファイルでもよい。ファイルやネットワークに出力す る時には、htonl() や htons() で標準系(ビッグエンディアン)に変換する。 入力の時は、逆に ntohl() や ntohs() で元にもどす。

ポート番号は、16 ビットなので、ライブラリ関数 htons() (host to network, short))を使って変換している。

IP アドレスもネットワーク・バイトオーダになっている必要がある。これは getaddrinfo() 関数で、struct addrinfo の ai_addr に保存それた段階で既に そうなっている。


Last updated: 2009/06/02 20:41:07
Yasushi Shinjo / <yas@is.tsukuba.ac.jp>