selectとshutdownの使用

5381 ワード

1.selectは、サーバがその一端の接続を閉じると私たちに知らせます.
2.shutdown、バッチ入力を正しく処理できます.
クライアントプログラム:
/* Use standard echo server; baseline measurements for nonblocking version */
#include	"unp.h"

int
main(int argc, char **argv)
{
	int					sockfd;
	struct sockaddr_in	servaddr;

	if (argc != 2)
		err_quit("usage: tcpcli <IPaddress>");

	sockfd = socket(AF_INET, SOCK_STREAM, 0);

	bzero(&servaddr, sizeof(servaddr));
	servaddr.sin_family = AF_INET;
	servaddr.sin_port = htons(7);
	inet_pton(AF_INET, argv[1], &servaddr.sin_addr);

	connect(sockfd, (SA *) &servaddr, sizeof(servaddr));

	str_cli(stdin, sockfd);		/* do it all */

	exit(0);
}

 
#include	"unp.h"

void
str_cli(FILE *fp, int sockfd)
{
	int			maxfdp1, stdineof;
	fd_set		rset;
	char		buf[MAXLINE];
	int		n;

	stdineof = 0;
	FD_ZERO(&rset);
	for ( ; ; ) {
		if (stdineof == 0)
			FD_SET(fileno(fp), &rset);
		FD_SET(sockfd, &rset);
		maxfdp1 = max(fileno(fp), sockfd) + 1;
		select(maxfdp1, &rset, NULL, NULL, NULL);

		if (FD_ISSET(sockfd, &rset)) {	/* socket is readable */
			if ( (n = read(sockfd, buf, MAXLINE)) == 0) {
				if (stdineof == 1)
					return;		/* normal termination */
				else
					err_quit("str_cli: server terminated prematurely");
			}

			write(fileno(stdout), buf, n);
		}

		if (FD_ISSET(fileno(fp), &rset)) {  /* input is readable */
			if ( (n = read(fileno(fp), buf, MAXLINE)) == 0) {
				stdineof = 1;
				shutdown(sockfd, SHUT_WR);	/* send FIN */
				FD_CLR(fileno(fp), &rset);
				continue;
			}

			writen(sockfd, buf, n);
		}
	}
}

5-8 stdineofは0に初期化された新しいフラグです.このフラグが0である限り、メインサイクルでは常にselect標準入力の読み取り可能性があります.
17-25ソケットでEOFを読んだとき、標準入力でEOFに遭遇した場合、それは正常な終了であり、関数は返されます.しかし、標準入力でEOFに遭遇しなかった場合、サーバプロセスは早期に終了しました.readとwriteを使用して、selectが予定通りに動作できるように、テキスト行を使わずにバッファを操作します.
26-34標準入力でEOFに遭遇した場合、新しいフラグstdineofを1に設定し、2番目のパラメータをSHUT_に指定します.WRは、shutdownを呼び出してFINを送信する.
 
 
サーバプログラム:
#include	"unp.h"

int
main(int argc, char **argv)
{
	int				i, maxi, maxfd, listenfd, connfd, sockfd;
	int				nready, client[FD_SETSIZE];
	ssize_t				n;
	fd_set				rset, allset;
	char				buf[MAXLINE];
	socklen_t			clilen;
	struct sockaddr_in	cliaddr, servaddr;

	listenfd = socket(AF_INET, SOCK_STREAM, 0);

	bzero(&servaddr, sizeof(servaddr));
	servaddr.sin_family      = AF_INET;
	servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
	servaddr.sin_port        = htons(SERV_PORT);

	bind(listenfd, (SA *) &servaddr, sizeof(servaddr));

	listen(listenfd, LISTENQ);

	maxfd = listenfd;			/* initialize */
	maxi = -1;					/* index into client[] array */
	for (i = 0; i < FD_SETSIZE; i++)
		client[i] = -1;			/* -1 indicates available entry */
	FD_ZERO(&allset);
	FD_SET(listenfd, &allset);
	for ( ; ; ) {
		rset = allset;		/* structure assignment */
		nready = select(maxfd+1, &rset, NULL, NULL, NULL);

		if (FD_ISSET(listenfd, &rset)) {	/* new client connection */
			clilen = sizeof(cliaddr);
			connfd = accept(listenfd, (SA *) &cliaddr, &clilen);
#ifdef	NOTDEF
			printf("new client: %s, port %d
", inet_ntop(AF_INET, &cliaddr.sin_addr, 4, NULL), ntohs(cliaddr.sin_port)); #endif for (i = 0; i < FD_SETSIZE; i++) if (client[i] < 0) { client[i] = connfd; /* save descriptor */ break; } if (i == FD_SETSIZE) err_quit("too many clients"); FD_SET(connfd, &allset); /* add new descriptor to set */ if (connfd > maxfd) maxfd = connfd; /* for select */ if (i > maxi) maxi = i; /* max index in client[] array */ if (--nready <= 0) continue; /* no more readable descriptors */ } for (i = 0; i <= maxi; i++) { /* check all clients for data */ if ( (sockfd = client[i]) < 0) continue; if (FD_ISSET(sockfd, &rset)) { if ( (n = read(sockfd, buf, MAXLINE)) == 0) { /*4connection closed by client */ close(sockfd); FD_CLR(sockfd, &allset); client[i] = -1; } else writen(sockfd, buf, n); if (--nready <= 0) break; /* no more readable descriptors */ } } } }

26-27 selectは、イベントの発生を待つ:または新しい顧客接続の確立、またはデータ、FINまたはRSTの到着を待つ.28-45リスニングソケットが読み取り可能になった場合、新しい接続が確立されます.acceptを呼び出し、クライアント配列の最初の未使用アイテムを使用して接続記述子を記録するデータ構造を更新します.準備記述子の数を1減らし、値が0になると次のforサイクルに入ることを避けることができます.これにより、selectの戻り値を使用して、未準備の記述子のチェックを回避できます.
46-60既存の顧客接続ごとに、従来の返された記述子セットが色あせているかどうかをテストします.もしそうであれば、お客様からテキストを1行読み込んで返射します.お客様が接続を閉じた場合、愛党は0に戻り、データ構造を更新します.
 
サービス攻撃の拒否
後のブログ<>に分析があります
残念なことに、サーバのプログラムに問題があります.悪意のあるお客様がサーバに接続されている場合、1バイトのデータ(改行ではない)を送信してから睡眠に入ると、何が起こるか.サーバはreadを呼び出し、クライアントからこの1バイトのデータを読み込み、次のread呼び出しにブロックして、クライアントからの残りのデータを変更するのを待つ.サーバは、このようなクライアントのためにブロックされ、他のクライアントにサービスを提供できなくなる(新しい顧客接続を受け入れるか、既存の顧客のデータを読み取るかにかかわらず)、その悪意のある顧客が改行または終了するまで.
ここでの基本的な概念は、1つのサーバが複数のクライアントを処理している場合、単一のクライアントにのみ関連する関数の呼び出しを絶対にブロックすることはできません.サーバが停止し、他のすべてのお客様にサービスを提供することを拒否する可能性があります.これがいわゆるサービス拒否型攻撃である.これは、サーバに対していくつかの動作を行い、サーバが他の合法的な顧客にサービスを提供できなくなったことです.可能な解決策は次のとおりです.
(1)非ブロックI/O使用;
(2)各顧客に対して独立した制御スレッドによってサービスを提供する(例えば、サブプロセスまたはスレッドを作成して各顧客にサービスを提供する).
(3)I/O操作にタイムアウトを設定する.