UNIXネットワークプログラミングボリューム1回射クライアントプログラムTCPクライアントプログラム設計パターン

10637 ワード

本文はsenlieオリジナルで、転載はこの住所を残してください:http://blog.csdn.net/zhengsenlie
次に、同じTCPプロトコルを使用するクライアントプログラムのいくつかの異なるバージョンを紹介します.それぞれ、停止バージョン、selectブロッキングI/Oバージョン、非ブロッキングI/Oバージョン、forkバージョン、スレッド化バージョンです.これらは、同じmain関数によって呼び出され、同じ機能、すなわち、リターンプログラムクライアントを実現します.
標準入力から1行のテキストを読み込み、サーバに書き込み、サーバのローへの戻りを読み込み、戻り行を標準出力に書き込みます.
ここで、非ブロックI/Oバージョンは、すべてのバージョンで最も高速に実行されますが、コードは複雑です.
新しいスレッドを作成するのはforkを使用して新しいプロセスを派生するよりもずっと速いです.
次のコードのスレッド化バージョンの実行速度はforkバージョンよりやや速く、非ブロックI/Oよりやや遅い.一般的なプログラミングでは、スレッドバージョンが推奨されます.
以下は、「Unixネットワークプログラミング:ボリューム1」の各バージョンのパフォーマンステスト結果です.
354.0秒、停止バージョン
12.3秒、selectプラスブロックI/Oバージョン
6.9秒、 非ブロックI/Oバージョン
8.7秒、
forkバージョン
8.5秒、 スレッド化バージョン
次はmain関数のコードです
#include	"unp.h"


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


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


	//1.   TCP    
	sockfd = Socket(AF_INET, SOCK_STREAM, 0);


	//2.       IP      
	bzero(&servaddr, sizeof(servaddr));
	servaddr.sin_family = AF_INET;
	servaddr.sin_port = htons(SERV_PORT);
	Inet_pton(AF_INET, argv[1], &servaddr.sin_addr);


	//3.         
	Connect(sockfd, (SA *) &servaddr, sizeof(servaddr));


	//4.str_cli                
	str_cli(stdin, sockfd);		


	exit(0);
}

最初のコード(停止バージョン): 
#include	"unp.h"


void
str_cli(FILE *fp, int sockfd)
{
	char	sendline[MAXLINE], recvline[MAXLINE];


	//1.  fp     ,    sendline
	while (Fgets(sendline, MAXLINE, fp) != NULL) {
		//2.  sendline       sockfd       
		Writen(sockfd, sendline, strlen(sendline));


		//3.         ,    recvline
		if (Readline(sockfd, recvline, MAXLINE) == 0)
			err_quit("str_cli: server terminated prematurely");


		//4.  recvline          
		Fputs(recvline, stdout);
	}
}

質問1:ソケットにイベントが発生した場合、お客様がfgets呼び出しをブロックする可能性があります.
改善1:selectを使用してstr_を書き換えるcli関数、select呼び出しにブロックされるか、標準入力が読めるのを待つか、
あるいはソケットが読めるのを待つ.これにより、サーバが終了すると、お客様はすぐに通知を受けることができます.
/**
* TCP    select
**/
#include	"unp.h"


void
str_cli(FILE *fp, int sockfd)
{
	int			maxfdp1; //  maxfdpl              
	fd_set		rset;   //       
	char		sendline[MAXLINE], recvline[MAXLINE];


	//1.   select
	FD_ZERO(&rset); //    rset
	for ( ; ; ) {
		FD_SET(fileno(fp), &rset); //     I/O     fp     
		FD_SET(sockfd, &rset); 	  //       sockfd     
		maxfdp1 = max(fileno(fp), sockfd) + 1;
		//   select             
		Select(maxfdp1, &rset, NULL, NULL, NULL);


		//2.       
		if (FD_ISSET(sockfd, &rset)) {	
			//   readline       ,   fputs     stdout
			if (Readline(sockfd, recvline, MAXLINE) == 0)
				err_quit("str_cli: server terminated prematurely");
			Fputs(recvline, stdout);
		}


		//3.      
		if (FD_ISSET(fileno(fp), &rset)) { 
			//   fgets       ,   writen         
			if (Fgets(sendline, MAXLINE, fp) == NULL)
				return;		/* all done */
			Writen(sockfd, sendline, strlen(sendline));
		}
	}
}

改善1の問題:selectで作成したリターンクライアントプログラムを一括して実行し、ユーザー入力の末尾に遭遇しても、
データがサーバに運ばれたパイプまたはサーバからのパイプにある可能性があります.
改善2(selectブロッキングI/Oバージョン):shutdown関数を使用してTCPのハーフクローズ特性を利用
次のコードはselectとshutdownを使用しています.前者はサーバが一端の接続を閉じるだけでお客様に通知します.
後者は、お客様がバッチ入力を正しく処理できるようにします.このバージョンでは、テキストの動作を中心としたコードも廃棄され、バッファに対する操作が公開されます.
/**
* TCP    select       
**/
#include	"unp.h"


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


	//1.   select
	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);


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


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


		//3.      
		if (FD_ISSET(fileno(fp), &rset)) {  
			//           EOF  ,  stdineof    1
			//    shutdown          FIN,             
			if ( (n = Read(fileno(fp), buf, MAXLINE)) == 0) { 
				stdineof = 1;
				Shutdown(sockfd, SHUT_WR);	
				FD_CLR(fileno(fp), &rset);
				continue;
			}


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

改善2問題:ブロックI/O
たとえば、標準入力に1行のテキストが読める場合は、readを呼び出して読み込み、呼び出します. writenはそれをサーバに送信します.
しかし、ソケット送信バッファが満たされている場合、writen呼び出しはブロックされます.プロセスがwriten呼び出しにブロックされている間、ソケット受信バッファからのデータが読み出される可能性があります.
同様に、ソケットから1行の入力テキストが読み取り可能である場合、標準出力がネットワークよりも遅い場合、プロセスは後続のwrite呼び出しにブロックされる可能性があります.
改善3(非ブロックI/Oバージョン):非ブロックI/Oの使用
/* include nonb1 */
#include	"unp.h"


void
str_cli(FILE *fp, int sockfd)
{
	int			maxfdp1, val, stdineof;
	ssize_t		n, nwritten;
	fd_set		rset, wset;
	char		to[MAXLINE], fr[MAXLINE];
	//to                
	//from                
	char		*toiptr, *tooptr, *friptr, *froptr;


	//1.          
	val = Fcntl(sockfd, F_GETFL, 0);
	Fcntl(sockfd, F_SETFL, val | O_NONBLOCK); //      


	val = Fcntl(STDIN_FILENO, F_GETFL, 0);
	Fcntl(STDIN_FILENO, F_SETFL, val | O_NONBLOCK); //    


	val = Fcntl(STDOUT_FILENO, F_GETFL, 0);
	Fcntl(STDOUT_FILENO, F_SETFL, val | O_NONBLOCK); //    


	//2.        
	toiptr = tooptr = to;	
	friptr = froptr = fr;
	stdineof = 0;
	maxfdp1 = max(max(STDIN_FILENO, STDOUT_FILENO), sockfd) + 1;
	
	//3.   :   select 
	//   select                  
	for ( ; ; ) {
		//         
		FD_ZERO(&rset);
		FD_ZERO(&wset);
		if (stdineof == 0 && toiptr < &to[MAXLINE])
			FD_SET(STDIN_FILENO, &rset);	/*                  */
		if (friptr < &fr[MAXLINE])
			FD_SET(sockfd, &rset);			/*                 */
		if (tooptr != toiptr)
			FD_SET(sockfd, &wset);			/*                 */
		if (froptr != friptr)
			FD_SET(STDOUT_FILENO, &wset);	/*                  */
		//   select
		Select(maxfdp1, &rset, &wset, NULL, NULL);
/* end nonb1 */
/* include nonb2 */
		//4.              
		//      read
		if (FD_ISSET(STDIN_FILENO, &rset)) {
			//read     。 EWOULDBLOCK       ,             ,       
			if ( (n = read(STDIN_FILENO, toiptr, &to[MAXLINE] - toiptr)) < 0) {
				if (errno != EWOULDBLOCK) 
					err_sys("read error on stdin");


			} 
			//read    EOF,        
			else if (n == 0) {
#ifdef	VOL2
				fprintf(stderr, "%s: EOF on stdin
", gf_time()); #endif stdineof = 1; /* */ if (tooptr == toiptr) // to Shutdown(sockfd, SHUT_WR);/* */ } //read else { #ifdef VOL2 fprintf(stderr, "%s: read %d bytes from stdin
", gf_time(), n); #endif toiptr += n; // toiptr FD_SET(sockfd, &wset); // } } // read if (FD_ISSET(sockfd, &rset)) { //read 。 EWOULDBLOCK , , if ( (n = read(sockfd, friptr, &fr[MAXLINE] - friptr)) < 0) { if (errno != EWOULDBLOCK) err_sys("read error on socket"); } //read EOF, else if (n == 0) { #ifdef VOL2 fprintf(stderr, "%s: EOF on socket
", gf_time()); #endif if (stdineof) // EOF , EOF , ; return; /* normal termination */ else err_quit("str_cli: server terminated prematurely"); } //read else { #ifdef VOL2 fprintf(stderr, "%s: read %d bytes from socket
", gf_time(), n); #endif friptr += n; // friptr FD_SET(STDOUT_FILENO, &wset); // } } /* end nonb2 */ /* include nonb3 */ //write if (FD_ISSET(STDOUT_FILENO, &wset) && ( (n = friptr - froptr) > 0)) { //write 。 if ( (nwritten = write(STDOUT_FILENO, froptr, n)) < 0) { if (errno != EWOULDBLOCK) err_sys("write error to stdout"); } //write else { #ifdef VOL2 fprintf(stderr, "%s: wrote %d bytes to stdout
", gf_time(), nwritten); #endif froptr += nwritten; // if (froptr == friptr) froptr = friptr = fr; // , } } //write if (FD_ISSET(sockfd, &wset) && ( (n = toiptr - tooptr) > 0)) { //write if ( (nwritten = write(sockfd, tooptr, n)) < 0) { if (errno != EWOULDBLOCK) err_sys("write error to socket"); } //write else { #ifdef VOL2 fprintf(stderr, "%s: wrote %d bytes to socket
", gf_time(), nwritten); #endif tooptr += nwritten; if (tooptr == toiptr) { // toiptr = tooptr = to; if (stdineof) // , , Shutdown(sockfd, SHUT_WR); /* send FIN */ } } } } } /* end nonb3 */

改善3:非ブロックバージョンが複雑
改善4:非ブロックI/Oが必要な場合、より簡単な方法はアプリケーションタスクを複数のプロセス(forkを使用)または複数のスレッドに分割することです
forkバージョン:
/**
* TCP        (fork)
**/
#include	"unp.h"


void
str_cli(FILE *fp, int sockfd)
{
	pid_t	pid;
	char	sendline[MAXLINE], recvline[MAXLINE];


	//   :             stdout
	if ( (pid = Fork()) == 0) {		
		while (Readline(sockfd, recvline, MAXLINE) > 0)
			Fputs(recvline, stdout);
		
		kill(getppid(), SIGTERM);	/*             SIGTERM   ,      */
		exit(0);
	}


	//   :  stdin            
	while (Fgets(sendline, MAXLINE, fp) != NULL)
		Writen(sockfd, sendline, strlen(sendline));


	//  stdin    EOF,    ,      
	Shutdown(sockfd, SHUT_WR);	/* EOF on stdin, send FIN */
	
	//           pause          
	pause();
	return;
}

スレッド化バージョン:
/**
* TCP       
**/
#include	"unpthread.h"


void	*copyto(void *);


static int	sockfd;		/* global for both threads to access */
static FILE	*fp;


void
str_cli(FILE *fp_arg, int sockfd_arg)
{
	char		recvline[MAXLINE];
	pthread_t	tid;


	//1.           
	sockfd = sockfd_arg;	/* copy arguments to externals */
	fp = fp_arg;


	//2.     
	Pthread_create(&tid, NULL, copyto, NULL);


	//3.     :           
	while (Readline(sockfd, recvline, MAXLINE) > 0)
		Fputs(recvline, stdout);
}


//4.copyto   :           
void *
copyto(void *arg)
{
	char	sendline[MAXLINE];


	while (Fgets(sendline, MAXLINE, fp) != NULL)
		Writen(sockfd, sendline, strlen(sendline));


	Shutdown(sockfd, SHUT_WR);	/* EOF on stdin, send FIN */


	return(NULL);
		/* 4return (i.e., thread terminates) when EOF on stdin */
}