==================================================================== ソケット通信プログラム覚書 98/03/18 Wednesday Susumu Ishihara Solaris 2.x 向けに書いた私的メモ ==================================================================== ■ ポート番号について 1-1023 well-known port 1024-49151 reserved 49142-65535 free ■ ストリーム型ソケットパラダイム ================================================================ Client Server ================================================================ socket socket ソケットの作成 bind IPアドレスとポート番号をソケットに 与える listen ソケットを受動的オープンに設定 connect ----> accept 接続要求,接続受付 send ----> recv 送信,受信 recv <---- send shutdown shutdown close close 通信終了,ソケット開放 ================================================================ ■ データグラム型ソケットパラダイム ================================================================ Client Server ================================================================ socket socket ソケットの作成 bind IPアドレスとポート番号をソケットに 与える sendto ----> recvfrom 送信,受信 recvfrom <---- send close close 通信終了,ソケット開放 ================================================================ ■ ソケット操作関数 for C on Solaris2.6) この節では上記のようなネットワーク接続手順に必要なソケット操作関数 の使い方について解説する.なお,以下の関数の使用には,コンパイルオ プションに -lsocket -lnsl が必要である. - socket(3N) ソケットの作成 #include #include int socket(int domain, int type, int protocol); domain 通信ドメイン PF_UNIX UNIX システム内部プロトコル PF_INET インターネットプロトコル type 通信タイプ SOCK_STREAM ストリーム型(TCP) SOCK_DGRAM データグラム型(UDP) [その他は略] protocol プロトコル番号 0 その型のソケットで仕様可能なプロト コルが自動選択される [普通は 0 でよい] 戻り値 成功時: ソケットディスクリプタ, 失敗: -1 ● サンプル TCP ソケットの作成 int s; if ((s = socket(PF_INET, SOCK_DGRAM, 0)) < 0) { /* error routine */ } - bind(3N) ソケットにアドレス,ポートを割り当てる #include #include int bind(int s, const struct sockaddr *name, int namelen); s ソケットディスクリプタ name 接続相手のアドレスを格納した領域へのポインタ アドレス領域は本来 sockaddr型だが TCP/IP では同サイ ズの sockaddr_in 型で作る.アドレスの作成は一般に繁 雑であるため後述する. namelen nameの指す実体のサイズ 戻り値 成功: 0, 失敗: -1 ※ 書式は connect と同じ ● エラー番号 EADDRINUSE アドレスが使用中 コネクション開放後,アドレスは一定期間再利用が禁止 されるため,このエラーが出た場合,リトライするよう にする.このエラーは良く出るものなので対指摘にしな くても良いが,他のエラーの場合はなんらかのエラーメッ セージを表示するようにした方が良いだろう. ● サンプル while (--retry_left) { if (bind(s, (struct sockaddr *)&sin, sizeof(sin)) == 0) { break; /* success */ if (errno != EADDRINUSE) { /* error routine */ } sleep(5); /* retry */ } if (retry_left == 0) { /* retry over */ } - listen(3N) ソケットを受動的にオープン #include #include int listen(int s, int backlog); s ソケットディスクリプタ backlog コネクション保留用待ち行列最大長 - この値の最大値は SOMAXCONN - 0 を指定すると待ち行列がなくなるので指定してはい けない. 戻り値 成功: 0, 失敗: -1 ● サンプル if (listen(s, SOMAXCONN) < 0) {/* error routine */} - connect(3N) 能動的接続要求 #include #include int connect(int s, struct sockaddr *name, int namelen); s ソケットディスクリプタ name 接続相手のアドレスを格納した領域へのポインタ アドレス領域は本来 sockaddr型だが TCP/IP では同サイ ズの sockaddr_in 型で作る.アドレスの作成は一般に繁 雑であるため後述する. namelen name の指す実体のサイズ 戻り値 成功: 0, 失敗: -1 ※ 書式は bind と同じ - accept(3N) 接続受付 #include #include int accept(int s, struct sockaddr *addr, int *addrlen); s ソケットディスクリプタ addr 接続相手のアドレスを格納する領域へのポインタ addrlen addr の指す実体のサイズが入った変数へのポインタ 戻り値 成功時: 新しい接続に対応するソケットディスクリプタ 失敗時: -1 ※ accept をすると新しいソケットディスクリプタとアドレスが得られる ので,以後の通信ではこれらを用いる. ※ 通信が 1 度きりなら古いソケットはクローズしてしまっても構わない. inet.d のように接続後に子プロセスを fork していく場合には親プロ セスは新しいソケットをクローズし,古いソケットで次の接続を待つよ うにする.このとき子プロセスは古いソケットをクローズして新しいソ ケットで通信をする. ● サンプル int len = sizeof(sin); if (s2 = accept(s, (struct sockaddr *)&sin, &len) < 0) { /* error routine */ } close(s); /* 以後 s2 を用いて通信 */ - shutdown(3N) 全二重コネクションの切断 int shutdown(int s, int how); s ソケットディスクリプタ how 切断の種類 0: 受信のみ切断 1: 送信のみ切断 2: 送受信ともに切断 戻り値 成功: 0, 失敗: -1 ※ 単純に接続を切るだけなら shutdown(s, 2) でよい. - close(2) ファイル(ソケット)ディスクリプタのクローズ #include int close(int fields); fields ファイル(ソケット)ディスクリプタ 戻り値 成功: 0, 失敗: -1 - send(3N) ソケットからのメッセージの送信 (コネクション型) - sendto(3N) ソケットからのメッセージの送信 (コネクションレス型) #include #include int send(int s, const char *msg, int len, int flags); int sendto(int s, const char *msg, int len, int flags, const struct sockaddr *to, int tolen); int sendmsg(int s, const struct msghdr *msg, int flags); s ソケットディスクリプタ msg メッセージ文字列へのポインタ len msg の長さ flags (普通は 0 でいいでしょう) to 宛先アドレス tolen to のサイズ 戻り値 成功時: 送信したバイト数 失敗: -1 - recv(3N) ソケットからのメッセージの受信 (コネクション型) - recvfrom(3N) ソケットからのメッセージの受信 (コネクションレス型) #include #include #include int recv(int s, char *buf, int len, int flags); int recvfrom(int s, char *buf, int len, int flags, struct sockaddr *from, int *fromlen); s ソケットディスクリプタ buf バッファの先頭ポインタ len 最大受信サイズ flags (普通は 0 でいいでしょう) from 相手のアドレス fromlen from のサイズ 戻り値 成功時: 受信したバイト数 失敗: -1 ■ sockaddr 構造体の処理 bind, connect, accept 等の関数では引数に sockaddr 型を使う.この型 はインターネット通信に限らず一般的なアドレスや名前を表現するために 用いられる.実際のプログラミングでは,ホスト名やポート番号の情報を この型の中に押し込めなければならない.この節では TCP/IP 通信を想定 して sockaddr 構造体の中身の作り方を説明する. - sockaddr 構造体 #include struct sockaddr { sa_family_t sa_family; /* address family */ char sa_data[]; /* socket address (variable-length data) */ } sockaddr 構造体にそのままホスト名やポート番号をいれるのは面倒なので, sockaddr 構造体にコンパチブルな構造体として sockaddr_in 構造体が用 意されている.実際に利用するときには sockadr_in 構造体で定義したア ドレスを sockaddr 構造体にキャストして使用する. - sockaddr_in 構造体 #include typedef unsigned short sa_family_t; typedef unsigned short in_port_t; typedef unsigned int in_addr_t; struct in_addr { /* 略 */ #define s_addr _S_un._S_addr /* should be used for all code */ }; struct sockaddr_in { sa_family_t sin_family; /* 常に AF_INET */ in_port_t sin_port; /* ポート番号 */ struct in_addr sin_addr; /* インターネットアドレス */ char sin_zero[8]; /* sockaddr とのサイズ調整 */ } - sin_family には常に AF_INET をいれる - sin_port はネットワークバイトオーダでポート番号を指定する.ホスト バイトオーダからネットワークバイトオーダへの変更には htons(3N) を 使用する. もし,ポート番号の検索が必要ならば, getservbyname(3N) を用いて /etc/services ファイルからエントリを検索できる. - sin_addr にはインターネットアドレスを指定する. - サーバでは bind 時点では相手のアドレスは分からないので INADDR_ANY としておく. - クライアントではホスト名を /etc/hosts から検索して sin_addr に格納する.この検索には gethostbyname (3N) などを利用する. - sin_zero は sockaddr とのサイズ調整のためだけに存在するので,単純 に 0 で埋めておけば良い.おの操作のために bzero(3C) が良く用いら れる. ■ ネットワークデータベースファイルの検索 上記より,bind, connect をするためには結局ポート番号,インターネッ トアドレスの内部表現を獲得する必要があることが分かったはずである. この節ではポート番号とインターネットアドレスをネットワークデータベー スファイルから検索して内部表現を得る方法を説明する. ■ ネットワーク関連データベースファイル - /etc/hosts ホスト名とインターネットアドレスとの対応 - /etc/networks ネットワーク名とネットワーク番号との対応 - /etc/services ポート番号とサービス名との対応 - /etc/protocols プロトコル名とプロトコル番号との対応 ■ データベース検索関数 名前,番号のどちらからでも検索できるように関数が用意されている. - 以下の関数のコンパイルには -lxnet が必要. - #include ================================================================ 関数名 検索されるファイル 戻り値の型 ================================================================ [名前から検索] gethostbyname(3XN) (/etc/hosts) hostent* getnetbyname(3XN) (/etc/networks) netent* getservbyname(3XN) (/etc/services) servent* getprotobyname(3XN) sport; } - ホスト名から sin_addr に格納する値を得る struct sockaddr_in sin; struct hostent *hp; if ((hp = gethostbyname(hostname)) != NULL) { bcopy(hp->h_addr, &(sin.sin_addr.s_addr), sizeof(struct in_addr)); } ポートやアドレスが数値表現の場合にはネットワークバイトオーダに変換 する関数を使えば良い. - "133.6.80.2" をネットワークバイトオーダに変換して sin_addr に格納 する struct sockaddr_in sin; sin.sin_addr.s_addr = inet_addr("133.6.80.2"); - ポート番号 23 をネットワークバイトオーダに変換して sin_port に格 納する struct sockaddr_in sin; sin.sin_port = htons(23); ■ バイトオーダの変換マクロ ホストバイトオーダとネットワークバイトオーダとの変換には以下の関数 を使う. #include #include ulong htonl(u_long hostlong); /* host->network (long) */ u_short htons(u_short hostshort); /* host->network (short) */ u_long ntohl(u_long netlong); /* network->host (long) */ u_short ntohs(u_short netshort); /* network->host (short) */ ■ バイト列に関するユーティリティルーチン(3C) アドレスの操作のために以下のユーティリティを良く使う #include void bcopy(const void *s1, void *s2, size_t n); int bcmp(const void *s1, const void *s2, size_t n); void bzero(void *s, size_t n); bcopy s1 から s2 に長さ n バイトだけコピー bcmp s1 と s2 を長さ n バイト分の領域で比較 bzero s から n バイトだけゼロで埋める ● 使用例 - bcopy - /* hostent 構造体からアドレス部を取り出す */ if ((hp = gethostbyname(host)) != NULL ) { bcopy(hp->h_addr, &(sin.sin_addr.s_addr), sizeof(struct in_addr)); } ● 使用例 - bzero - /* sockaddr_in 構造体の初期化 */ /* この構造体の後ろの方はパディングであるため */ struct sockaddr_in sin; bzero((char *)&sin, sizeof(sin)); ■ ソケットオプション操作関数 ソケットには様々なオプションが設定できる.以下 TCP/IP で重要にか変 わるオプションとそれらの操作関数についてまとめる. - getsockopt(3N) ソケットオプションの取得 - setsockopt(3N) ソケットオプションの設定 #include #include int getsockopt(int s, int level, int optname, char *optval, int *optlen); int setsockopt(int s, int level, int optname, const char *optval, int optlen); s ソケットディスクリプタ level レベル optname オプション名 optval オプションが入る(ある)変数へのポインタ optlen optval の示す実体のサイズ ソケットオプション (TCP/IP 関連のみ) ==================================================================== level optname get/set comment type ==================================================================== IPPROTO_IP IP_OPTIONS get/set IPヘッダ中のオプション IPPROTO_TCP TCP_MAXSEG get TCP最大セグメントサイズ int TCP_NODELAY get/set 合体パケットの送信を int 遅延しない(flag) SOL_SOCKET SO_BROADCAST get/set ブロードキャストメッ int セージの許可(flag) SO_DEBUG get/set デバッグ情報の記録(flag)int SO_DONTROUTE get/set インターフェースアドレ int スのみを使用(flag) SO_ERROR get エラー情報の取り出しと int クリア SO_KEEPALIVE get/set 接続の保持(flag) int SO_LINGER get/set クローズ時にデータがあ struct るならば linger linger SO_OOBINLINE get/set 帯域外データをインライ int ンで受け取る (flag) SO_RCVBUF get/set 受信バッファサイズ int SO_SNDBUF get/set 送信バッファサイズ int SO_REUSEADDR get/set ローカルアドレスの再利 int 用許可(flag) SO_TYPE get ソケットタイプの取得 int ==================================================================== (* flag True: 0 以外の値, False: 0)