naobe @ ウィキ

UNIX通信

最終更新:

Bot(ページ名リンク)

- view
管理者のみ編集可
Unix/Linuxへ戻る

ストリーム通信

サーバ

  • socketシステムコールを使ってソケット用のファイルデスクリプタ(sockfd)を作成 : fd = socket(AF_INET, SOCK_STREAM, 0)
  • socketオプションを設定して、TIMEWAIT状態でのbindを可能にする。: setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on))
  • sockfdにサーバのIPアドレス、ポート、プロトコルタイプ(internet/unixドメイン)を関連付けする。:bind(fd, (struct sockaddr *) &servaddr, sizeof(servaddr)
  • sockfdの受信キューを設定しクライアントから接続可能を宣言:listen(fd, listenq)
  • クライアントからの接続を待つ:newfd = accept(fd, (struct sockaddr *) &cliaddr, &i) newfd:通信に使うファイルデスクリプタ、cliaddr:接続したクライアントのアドレス/ポート、i:cliaddrのサイズ
  • 子プロセスをforkして処理は子プロセスに任せる。親は次の接続を待機する

 if ( (pid = fork()) < 0)
   err_sys("fork error");
 if (pid > 0) {
   close(newfd);	/* parent closes connected socket */
   WAIT_CHILD();	/* wait for child to output to terminal */
   continue;		/* and back to for(;;) for another accept() */
 } else {
   close(fd);		/* child closes listening socket */
 }

  • 子プロセスは、newfdを使ってread(受信),write(送信)してクライアントと通信する。

クライアント




socket

socketの作成




オプション

以下のAPIでoptnameに設定する

 int setsockopt(int s, int level, int optname, const void *optval, socklen_t optlen);

【引数】
引数 説明
s ソケットを識別する記述子。
level optionの層。SOL_SOCKET:ソケット層オプション
optname オプション名
optval オプションの値
optlen オプションの長さ

level optname 説明
SOL_SOCKET SO_REUSEADDR 設定するときは値に&1を設定する。設定解除には&0を設定する。TCP通信で、サーバ側が先に通信を終了したとき接続していたポートはTIMEWAIT状態になる。TIMEWAIT状態になるとbind不可になり接続を再開できない。これは他のプロセスがポートを使うことを制限するためで、使用していたプロセスが再開するには邪魔になる動き。SO_REUSEADDRを指定することで当該のプロセスがbindすることを許可する。(注1)
SOL_SOCKET SO_KEEPALIVE 設定するときは値に&1を設定する。設定解除には&0を設定する。デフォルトでは、無通信状態からキープアライブパケットを送信するまで2時間。送信間隔は75秒。リトライ回数は9回。リトライ回数を超えると切断する。(注2)
SOL_SOCKET SO_SNDBUF 設定するときは値に&1を設定する。設定解除には&0を設定する。
SO_RCVBUF 設定するときは値に&1を設定する。設定解除には&0を設定する。
SO_LINGER 値は、linger構造体をセットする。
IPPROTO_TCP TCP_NODELAY 値に&1を設定するとNO_DELAYが有効になる。Nagle アルゴリズムを無効にする(複数のパケットをまとめない。)。遅れのないリクエストが必要なHTTPに有効。(注3)



select

複数のファイルデスクリプタの入出力を管理する。複数のポート、UNIXドメイン、端末を1つのプログラムで管理できる。大規模なネットワークの高性能サーバに使う。

システムコール

 int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);

【引数】
引数 説明
nfds 登録したファイルディスクリプタの最大値+1
readfds readを開始するディスクリプタセット
writefds writeを監視するディスクリプタセット
exceptfds 帯域外データ受信を監視するディスクリプタセット
timeout タイムアウト時間。NULLを指定すると監視対象の状態が変化するまで待つ

【戻り値】
状態が変化したディスクリプタの数。-1はエラー。


プログラム


CentOSのmanページのサンプルプログラム。あるポートの接続を待って、別のポートに転送するサーバ。

 #include <stdlib.h>
 #include <stdio.h>
 #include <unistd.h>
 #include <sys/time.h>
 #include <sys/types.h>
 #include <string.h>
 #include <signal.h>
 #include <sys/socket.h>
 #include <netinet/in.h>
 #include <arpa/inet.h>
 #include <errno.h>
 
 static int forward_port;
 
 #undef max
 #define max(x,y) ((x) > (y) ? (x) : (y))
 
 /**
  * ポートを指定してソケット作成、listen
  *
  * @param listen_port ポート番号
  *
  */
 static int listen_socket(int listen_port) {
 	struct sockaddr_in a;
 	int s;
 	int yes;
 	if ((s = socket(AF_INET, SOCK_STREAM, 0)) < 0) {//ソケット作成
 		perror("socket");
 		return -1;
 	}
 	yes = 1;
 	//ソケットオプションを指定
 	if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR, (char *) &yes, sizeof(yes)) < 0) {
 		perror("setsockopt");
 		close(s);
 		return -1;
 	}
 	memset(&a, 0, sizeof(a));
 	a.sin_port = htons(listen_port);//ポート番号をネットワークバイトオーダに変換
 	a.sin_family = AF_INET;//インターネットを指定
 	if (bind(s, (struct sockaddr *) &a, sizeof(a)) < 0) {
 		perror("bind");
 		close(s);
 		return -1;
 	}
 	printf("accepting connections on port %d\n", (int) listen_port);
 	listen(s, 10);// 受信キューの個数を10にしてlisten
 	return s;
 }
 
 /*
  * 転送先へ接続する
  *
  * @param connect_port 接続ポート
  * @param address 転送先アドレス
  * @return ソケットファイルディスクリプタ
  */
 static int connect_socket(int connect_port, char *address) {
 	struct sockaddr_in a;
 	int s;
 	if ((s = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
 		perror("socket");
 		close(s);
 		return -1;
 	}
 
 	memset(&a, 0, sizeof(a));
 	a.sin_port = htons(connect_port);
 	a.sin_family = AF_INET;
 
 	if (!inet_aton(address, (struct in_addr *) &a.sin_addr.s_addr)) {
 		perror("bad IP address format");
 		close(s);
 		return -1;
 	}
 
 	if (connect(s, (struct sockaddr *) &a, sizeof(a)) < 0) {
 		perror("connect()");
 		shutdown(s, SHUT_RDWR);
 		close(s);
 		return -1;
 	}
 	return s;
 }
 
 #define SHUT_FD1 {                      \
                if (fd1 >= 0) {                 \
                    shutdown (fd1, SHUT_RDWR);  \
                    close (fd1);                \
                    fd1 = -1;                   \
                }                               \
            }
 
 #define SHUT_FD2 {                      \
                if (fd2 >= 0) {                 \
                    shutdown (fd2, SHUT_RDWR);  \
                    close (fd2);                \
                    fd2 = -1;                   \
                }                               \
            }
 
 #define BUF_SIZE 1024
 
 /***
  *  selectを使った転送処理
  *
  *   引数1 リッスンポート
  *   引数2 転送ポート
  *   引数3 転送先アドレス
  */
 int main(int argc, char **argv) {
 	int h;
 	int fd1 = -1, fd2 = -1; // fd1:受信ディスクリプタ、fd2:送信ディスクリプタ
 	char buf1[BUF_SIZE], buf2[BUF_SIZE];
 	int buf1_avail, buf1_written;
 	int buf2_avail, buf2_written;
 
 	if (argc != 4) {
 		fprintf(
 				stderr,
 				"Usage\n\tfwd <listen-port> \
        <forward-to-port> <forward-to-ip-address>\n");
 		exit(1);
 	}
 
 	signal(SIGPIPE, SIG_IGN);
 
 	forward_port = atoi(argv[2]);
 
 	h = listen_socket(atoi(argv[1]));
 	if (h < 0)
 		exit(1);
 
 	for (;;) {
 		int r, nfds = 0;
 		fd_set rd, wr, er;
 		FD_ZERO(&rd);
 		FD_ZERO(&wr);
 		FD_ZERO(&er);
 		FD_SET(h, &rd);//リッスンポートを読み込みfdとして登録
 		nfds = max (nfds, h);
 		if (fd1 > 0 && buf1_avail < BUF_SIZE) {
 			FD_SET(fd1, &rd);
 			nfds = max (nfds, fd1);
 		}
 		if (fd2 > 0 && buf2_avail < BUF_SIZE) {
 			FD_SET(fd2, &rd);
 			nfds = max (nfds, fd2);
 		}
 		if (fd1 > 0 && buf2_avail - buf2_written > 0) {
 			FD_SET(fd1, &wr);
 			nfds = max (nfds, fd1);
 		}
 		if (fd2 > 0 && buf1_avail - buf1_written > 0) {
 			FD_SET(fd2, &wr);
 			nfds = max (nfds, fd2);
 		}
 
 		if (fd1 > 0) {
 			FD_SET(fd1, &er);
 			nfds = max (nfds, fd1);
 		}
 		if (fd2 > 0) {
 			FD_SET(fd2, &er);
 			nfds = max (nfds, fd2);
 		}
 		//リッスンポートの接続を待つ
 		r = select(nfds + 1, &rd, &wr, &er, NULL);
 		//
 		if (r == -1 && errno == EINTR)
 			continue;
 		if (r < 0) {
 			perror("select()");
 			exit(1);
 		}
 		/*
 		 *  受信ポートが接続されたらacceptして新しいファイルディスクリプタ作成。
 		 *  転送先へ接続する
 		 *  
 		*/
 		if (FD_ISSET(h, &rd)) {
 			unsigned int l;
 			struct sockaddr_in client_address;
 			memset(&client_address, 0, l = sizeof(client_address));
 			r = accept(h, (struct sockaddr *) &client_address, &l);
 			if (r < 0) {
 				perror("accept()");
 			} else {
 				SHUT_FD1;
 				SHUT_FD2;
 				buf1_avail = buf1_written = 0;
 				buf2_avail = buf2_written = 0;
 				fd1 = r;
 				fd2 = connect_socket(forward_port, argv[3]);
 				if (fd2 < 0) {
 					SHUT_FD1;
 				} else
 					printf("connect from %s\n",
 							inet_ntoa(client_address.sin_addr));
 			}
 		}
 
 		/* NB: read oob data before normal reads */
 		if (fd1 > 0)
 			// 帯域外データ(バッファリングを許されないイベント)が到着したら
 			// 帯域外データとして転送先に転送する
 			if (FD_ISSET(fd1, &er)) {
 				char c;
 				errno = 0;
 				r = recv(fd1, &c, 1, MSG_OOB);
 				//エラーが発生したら受信ポートを閉設
 				if (r < 1) {
 					SHUT_FD1;
 				} else
 					send(fd2, &c, 1, MSG_OOB);
 			}
 		if (fd2 > 0)
 			if (FD_ISSET(fd2, &er)) {
 				char c;
 				errno = 0;
 				r = recv(fd2, &c, 1, MSG_OOB);
 				if (r < 1) {
 					SHUT_FD1;
 				} else
 					send(fd1, &c, 1, MSG_OOB);
 			}
 		/*
 		 *  端点1から要求があれば、最大、バッファの残りまで読み込む
 		 */
 		if (fd1 > 0)
 			if (FD_ISSET(fd1, &rd)) {
 				r = read(fd1, buf1 + buf1_avail, BUF_SIZE - buf1_avail);
 				if (r < 1) {
 					SHUT_FD1;
 				} else
 					buf1_avail += r;
 			}
 		/*
 		 *  端点2から要求があれば、最大、バッファの残りまで読み込む
 		 */
 		if (fd2 > 0)
 			if (FD_ISSET(fd2, &rd)) {
 				r = read(fd2, buf2 + buf2_avail, BUF_SIZE - buf2_avail);
 				if (r < 1) {
 					SHUT_FD2;
 				} else
 					buf2_avail += r;
 			}
 
 		/*
 		 *  端点1が受信準備できていれば、書きこみの残りを送信する
 		 */
 		if (fd1 > 0)
 			if (FD_ISSET(fd1, &wr)) {
 				r = write(fd1, buf2 + buf2_written, buf2_avail - buf2_written);
 				if (r < 1) {
 					SHUT_FD1;
 				} else
 					buf2_written += r;
 			}
 		/*
 		 *  端点2が受信準備できていれば、書きこみの残りを送信する
 		 */
 		if (fd2 > 0)
 			if (FD_ISSET(fd2, &wr)) {
 				r = write(fd2, buf1 + buf1_written, buf1_avail - buf1_written);
 				if (r < 1) {
 					SHUT_FD2;
 				} else
 					buf1_written += r;
 			}
 		//受信量と送信量が一致したら、バッファ位置を初期化する
 		if (buf1_written == buf1_avail)
 			buf1_written = buf1_avail = 0;
 		if (buf2_written == buf2_avail)
 			buf2_written = buf2_avail = 0;
 		/* one side has closed the connection, keep
 		 writing to the other side until empty */
 		if (fd1 < 0 && buf1_avail - buf1_written == 0) {
 			SHUT_FD2;
 		}
 		if (fd2 < 0 && buf2_avail - buf2_written == 0) {
 			SHUT_FD1;
 		}
 	}
 	return 0;
 }

FD_SET

ファイルディスクリプタセットに監視するファイルディスクリプタをセットする。

インタフェース

void FD_SET(int fd, fd_set *set);

項目 IN/OUT 説明
fd IN 監視するファイルディスクリプタ
set IN 監視するファイルディスクリプタの集合

注意

追加するファイルディスクリプタの数は、FD_SETSIZE(CentOS 5.5の場合は、1024)未満でなければならない。


FD_ISSET

ファイルディスクリプタセットにセットしたファイルデスクリプタが使用可能か確認する。

インタフェース

int FD_ISSET(int fd, fd_set *set);

項目 IN/OUT 説明
fd IN ファイルディスクリプタ
set IN ファイルディスクリプタセット
戻り値 OUT 1:使用可能、0:準備中


poll

ファイルディスクリプタセットに登録したファイルディスクリプタが使用可能になるまで待つ。selectの改良版。

インタフェース

int poll(struct pollfd *fds, nfds_t nfds, int timeout);

          struct pollfd {
              int   fd;         /* file descriptor */
              short events;     /* requested events */
              short revents;    /* returned events */
          };

項目 IN/OUT 説明
fds IN ファイルディスクリプタセットの配列。
nfds IN 配列の個数
events IN 監視するイベントのビットマスク
revents OUT 実際に起こったイベントのビットマスク
timeout IN pollがブロックする時間の上限(msec)。負の数を設定すると無限に待つ。
戻り値 OUT 正の数:成功。reventsの数。、0:タイムアウト、-1:エラー

イベントは、poll.hで定義される。

イベント 説明
POLLIN 読み出し可能なデータがある。
POLLPRI 読み出し可能な緊急データ (urgent data) がある
POLLOUT 書き込みが停止 (block) しない状態である。書き込み可能である。
POLLRDHUP Linux 2.6.17 以降。ス トリームソケットの他端が、コネクションを close したか 、コネクションの書き込み側を shutdown した。
POLLERR エラー状態 (出力の場合のみ)。
POLLHUP ハングアップした (出力の場合のみ)。
POLLNVAL 不正な要求: fd がオープンされていない (出力の場合のみ)。

epoll

http://alpha.mixi.co.jp/2007/10631/ 参照
pollの一種。非同期なポールが可能?

int epoll_create(int size)

epollディスクリプタを作成する。

項目 IN/OUT 説明
size IN ディスクリプタの大きさ
戻り値 OUT epollディスクリプタ

int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event)

epollディスクリプタを操作(追加、削除)する

項目 IN/OUT 説明
epfd IN epollディスクリプタ
op IN 操作。
fd IN 対象となるファイルディスクリプタ
event

int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);

epollディスクリプタのI/Oイベントを待つ

項目 IN/OUT 説明
epfd IN epollディスクリプタ
events OUT 呼出側が利用可能なイベント
maxevents IN イベントの最大数
timeout IN タイムアウト(msec)
戻り値 OUT 準備ができているファイルディスクリプタの数。0:タイムアウト。-1:エラー


getaddrinfo

ノードの名前、IPアドレス、サービスからアドレス情報を取得する

int getaddrinfo(const char *node, const char *service,
                      const struct addrinfo *hints,
                      struct addrinfo **res);

項目 IN/OUT 説明
node IN ノードの名前、IPアドレス
service IN
hints IN
res OUT


名前つきパイプ(Named pipe)

プロセス間の通信を行うためのファイル。mkfifoコマンドを用いて作成する。作成した名前つきパイプに対し て、write, readすることによってプロセス間通信を行う。4096バイトを超えるwriteを行うとread開始される まで待たされる。



fcntl

インタフェース

int fcntl(int fd, int cmd);
int fcntl(int fd, int cmd, long arg);
int fcntl(int fd, int cmd, struct flock *lock);

項目 IN/OUT 説明
fd IN ファイルディスクリプタ
cmd IN 操作
arg IN 引数
lock ロック

cmd 説明
F_SETFD ファイルディスクリプタフラグにargで指定した値を設定する
F_GETFD ファイルディスクリプタフラグを読み出す。戻り値が読み出したファイルディスクリプタフラグ。
F_SETFL ファイル状態フラグにargで指定した値を設定する
F_GETFL ファイル状態フラグを読み出す。戻り値が読み出したファイル状態フラグ。

F_SETFD/F_GETFDで使えるフラグは、FD_CLOEXEC のみ。FD_CLOEXECは、execveしたときに親プロセスが開いたファイルディスクリプタを閉じる。
ウィキ募集バナー