システムプログラム(第8回): Pthreadによる複数のクライアントに対するサービスの同時提供

                                       筑波大学 システム情報系 情報工学域
                                       新城 靖
                                       <yas@cs.tsukuba.ac.jp>

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

Ptheadとは

Pthread とは、C言語から使えるスレッド・ライブラリである。 オペレーティングシステムIIの資料 に簡単な説明がある。

echo-server-pthread.c

fork版 では、クライアントから接続要 求を受け付けるたびに、新しいプロセスを作っていた。以下の echo-server-pthread.cでは、プロセスではなく スレッドを作っている。
   1:	
   2:	/*
   3:	  echo-server-pthread.c -- 受け取った文字列をそのまま返すサーバ(pthread版)
   4:	  ~yas/syspro/ipc/echo-server-pthread.c
   5:	*/
   6:	#include <stdio.h>
   7:	#include <stdlib.h>     /* exit() */
   8:	#include <sys/types.h>  /* socket(), time() */
   9:	#include <sys/socket.h> /* socket() */
  10:	#include <netinet/in.h> /* struct sockaddr_in */
  11:	#include <pthread.h>    /* pthread_create */
  12:	#include <netdb.h>      /* getnameinfo() */
  13:	#include <string.h>     /* strlen() */
  14:	#include <unistd.h>     /* getpid(), gethostname() */
  15:	
  16:	struct echo_server_thread_arg;
  17:	
  18:	extern  void echo_server( int portno, int ip_version );
  19:	extern  void *echo_server_thread( struct echo_server_thread_arg *arg );
  20:	
  21:	extern  void echo_receive_request_and_send_reply( int com );
  22:	extern  int  echo_receive_request( char *line, size_t size, FILE *in );
  23:	extern  void echo_send_reply( char *line, FILE *out );
  24:	extern  void print_my_host_port( int portno );
  25:	extern  void tcp_sockaddr_print( int com );
  26:	extern  void tcp_peeraddr_print( int com );
  27:	extern  void sockaddr_print( struct sockaddr *addrp, socklen_t addr_len );
  28:	extern  int  tcp_acc_port( int portno, int pf );
  29:	extern  int  fdopen_sock( int sock, FILE **inp, FILE **outp );
  30:	
  31:	int
  32:	main( int argc, char *argv[] )
  33:	{
  34:	        int portno, ip_version;
  35:	
  36:	        if( !(argc == 2 || argc==3) ) {
  37:	                fprintf(stderr,"Usage: %s portno {ipversion}\n",argv[0] );
  38:	                exit( 1 );
  39:	        }
  40:	        portno = strtol( argv[1],0,10 );
  41:	        if( argc == 3 )
  42:	                ip_version = strtol( argv[2],0,10 );
  43:	        else
  44:	                ip_version = 46; /* Both IPv4 and IPv6 by default */
  45:	        echo_server( portno, ip_version );
  46:	}
  47:	
main() は、 forkなし版fork版 とほとんど同じである。
  48:	struct echo_server_thread_arg {
  49:	        int com;
  50:	};
  51:	
  52:	void
  53:	echo_server( int portno, int ip_version )
  54:	{
  55:	        int acc,com ;
  56:	        pthread_t worker ;
  57:	        struct echo_server_thread_arg *arg;
  58:	
  59:	        acc = tcp_acc_port( portno, ip_version );
  60:	        if( acc<0 )
  61:	                exit( 1 );
  62:	        print_my_host_port( portno );
  63:	        tcp_sockaddr_print( acc );
  64:	        while( 1 )
  65:	        {
  66:	                printf("[%d] accepting incoming connections (acc==%d) ...\n",
  67:	                       getpid(),acc );
  68:	                if( (com = accept( acc,0,0 )) < 0 )
  69:	                {
  70:	                        perror("accept");
  71:	                        exit( -1 );
  72:	                }
  73:	                tcp_peeraddr_print( com );
  74:	                arg = malloc( sizeof(struct echo_server_thread_arg) );
  75:	                if( arg == NULL )
  76:	                {
  77:	                        perror("malloc()");
  78:	                        exit( -1 );
  79:	                }
  80:	                arg->com = com;
  81:	                if( pthread_create( &worker, NULL, (void *)echo_server_thread, (void *)arg)
  82:	                    != 0 )
  83:	                {
  84:	                        perror("pthread_create()");
  85:	                        exit( 1 );
  86:	                }
  87:	                pthread_detach( worker );
  88:	        }
  89:	}
  90:	

このプログラムの特徴を以下にまとめる。

  91:	void *
  92:	echo_server_thread( struct echo_server_thread_arg *arg )
  93:	{
  94:	        echo_receive_request_and_send_reply( arg->com );
  95:	        free( arg );
  96:	        return( NULL );
  97:	}
  98:	

echo_server_thread() は、スレッドが生成された時に最初に呼ばれる関数であ る。引数の構造体から com を取り出し、 echo_receive_request_and_send_reply() を呼んでいる。それからリターンし た後には、引数の構造体を free している。

他の関数は、fork版、forkなし版と同じである。

 $ diff -c echo-server-fork-fdopen.c echo-server-pthread.c
 *** echo-server-fork-fdopen.c	2020-06-10 15:54:19.003067000 +0900
 --- echo-server-pthread.c	2020-06-10 16:20:54.182336000 +0900
 ***************
 *** 1,21 ****

   /*
 !   echo-server-fork-fdopen.c -- 受け取った文字列をそのまま返すサーバ(fork版)
 !   ~yas/syspro/ipc/echo-server-fork-fdopen.c
   */
   #include <stdio.h>
   #include <stdlib.h>	/* exit() */
 ! #include <sys/types.h>	/* socket(), wait4() */
   #include <sys/socket.h>	/* socket() */
   #include <netinet/in.h>	/* struct sockaddr_in */
 ! #include <sys/resource.h> /* wait4() */
 ! #include <sys/wait.h>	/* wait4() */
   #include <netdb.h>	/* getnameinfo() */
   #include <string.h>	/* strlen() */
   #include <unistd.h>	/* getpid(), gethostname() */

   extern	void echo_server( int portno, int ip_version );
 ! extern	void delete_zombie();
   extern	void echo_receive_request_and_send_reply( int com );
   extern  int  echo_receive_request( char *line, size_t size, FILE *in );
   extern	void echo_send_reply( char *line, FILE *out );
 --- 1,23 ----

   /*
 !   echo-server-pthread.c -- 受け取った文字列をそのまま返すサーバ(pthread版)
 !   ~yas/syspro/ipc/echo-server-pthread.c
   */
   #include <stdio.h>
   #include <stdlib.h>	/* exit() */
 ! #include <sys/types.h>	/* socket(), time() */
   #include <sys/socket.h>	/* socket() */
   #include <netinet/in.h>	/* struct sockaddr_in */
 ! #include <pthread.h>	/* pthread_create */
   #include <netdb.h>	/* getnameinfo() */
   #include <string.h>	/* strlen() */
   #include <unistd.h>	/* getpid(), gethostname() */

 + struct echo_server_thread_arg;
 + 
   extern	void echo_server( int portno, int ip_version );
 ! extern	void *echo_server_thread( struct echo_server_thread_arg *arg );
 ! 
   extern	void echo_receive_request_and_send_reply( int com );
   extern  int  echo_receive_request( char *line, size_t size, FILE *in );
   extern	void echo_send_reply( char *line, FILE *out );
 ***************
 *** 43,53 ****
	 echo_server( portno, ip_version );
   }

   void
   echo_server( int portno, int ip_version )
   {
	 int acc,com ;
 ! 	pid_t child_pid ;

	 acc = tcp_acc_port( portno, ip_version );
	 if( acc<0 )
 --- 45,60 ----
	 echo_server( portno, ip_version );
   }

 + struct echo_server_thread_arg {
 +     	int com;
 + };
 + 
   void
   echo_server( int portno, int ip_version )
   {
	 int acc,com ;
 ! 	pthread_t worker ;
 ! 	struct echo_server_thread_arg *arg;

	 acc = tcp_acc_port( portno, ip_version );
	 if( acc<0 )
 ***************
 *** 56,62 ****
	 tcp_sockaddr_print( acc );
	 while( 1 )
	 {
 - 		delete_zombie();
		 printf("[%d] accepting incoming connections (acc==%d) ...\n",
			getpid(),acc );
		 if( (com = accept( acc,0,0 )) < 0 )
 --- 63,68 ----
 ***************
 *** 65,98 ****
			 exit( -1 );
		 }
		 tcp_peeraddr_print( com );
 ! 		if( (child_pid=fork()) > 0 ) /* parent */
		 {
 ! 			close( com );
		 }
 ! 		else if( child_pid == 0 ) /* child */
		 {
 ! 			close( acc );
 ! 			echo_receive_request_and_send_reply( com );
 ! 			exit( 0 );
		 }
 ! 		else
 ! 		{
 ! 			perror("fork");
 ! 			exit( -1 );
 ! 		}  
	 }
   }

 ! void
 ! delete_zombie()
   {
 ! 	pid_t pid ;
 ! 
 ! 	while( (pid=wait4(-1,0,WNOHANG,0)) >0 )
 ! 	{
 ! 		printf("[%d] zombi %d deleted.\n",getpid(),pid );
 ! 		continue;
 ! 	}
   }

   #define	BUFFERSIZE	1024
 --- 71,99 ----
			 exit( -1 );
		 }
		 tcp_peeraddr_print( com );
 ! 		arg = malloc( sizeof(struct echo_server_thread_arg) );
 ! 		if( arg == NULL )
		 {
 ! 			perror("malloc()");
 ! 			exit( -1 );
		 }
 ! 		arg->com = com;
 ! 		if( pthread_create( &worker, NULL, (void *)echo_server_thread, (void *)arg)
 ! 		    != 0 )
		 {
 ! 			perror("pthread_create()");
 ! 			exit( 1 );
		 }
 ! 		pthread_detach( worker );
	 }
   }

 ! void *
 ! echo_server_thread( struct echo_server_thread_arg *arg )
   {
 ! 	echo_receive_request_and_send_reply( arg->com );
 ! 	free( arg );
 ! 	return( NULL );
   }

   #define	BUFFERSIZE	1024
 $ 
実行例。

サーバ側。サーバは、終了しないので、最後に、^C を押して、割り 込みを掛けて終了させる。

注意:全員がポート番号 1231 を使うとプログラムが動かないことがある。

$ cp ~yas/syspro/ipc/echo-server-pthread.c . [←]
$ make echo-server-pthread [←]
cc     echo-server-pthread.c   -o echo-server-pthread
$ ./echo-server-pthread [←]
Usage: ./echo-server-pthread portno {ipversion}
$ ./echo-server-pthread 1231 [←]
run telnet aloe30.local 1231
[16505] accepting (fd==4) to [::]:1231
[16505] accepting incoming connections (acc==4) ...
[16505] connection (fd==5) from [2001:2f8:3a:1711::230:39]:53138
[16505] accepting incoming connections (acc==4) ...
[16505] received (fd==5) 5 bytes, [012
]
[16505] connection (fd==7) from [2001:2f8:3a:1711::230:40]:63507
[16505] accepting incoming connections (acc==4) ...
[16505] received (fd==7) 5 bytes, [abc
]
[16505] received (fd==7) 5 bytes, [def
]
[16505] received (fd==5) 5 bytes, [345
]
[16505] connection (fd==5) closed.
[16505] connection (fd==7) closed.
^C
$ []
クライアント側(その1)。
$ telnet aloe30 1231 [←]
Trying 2001:2f8:3a:1711::230:30...
Connected to aloe30.
Escape character is '^]'.
012[←]
012
345[←]
345
^]
telnet> ^D
Connection closed.
$ []
クライアント側(その2)。
$ telnet aloe30 1231 [←]
Trying 2001:2f8:3a:1711::230:30...
Connected to aloe30.
Escape character is ^]'.
abc[←]
abc
def[←]
def
^]
telnet> ^D
Connection closed.
$ []
'

Last updated: 2020/06/04 16:56:29
Yasushi Shinjo / <yas@cs.tsukuba.ac.jp>