->back to toppage

課題内容

サンプルプログラム(1)のサーバプログラム(server.c)はinetdか ら起動するものであるが、inetdを使用せずに同じ動作をするデーモン 型のサーバプログラムを作成し、実行結果を示すとともに、inetdを使 用するサーバプログラムとそうでないものとの実装上の違いを説明せ よ。

実験結果

myserver.cのソース

     1	/*** TCP Server for inetd ***/
     2	
     3	#include <stdio.h>
     4	#include <sys/types.h>
     5	#include <sys/errno.h>
     6	#include <string.h>
     7	#include <ctype.h>
     8	#include <sys/socket.h>
     9	#include <netinet/in.h>
    10	#include <netdb.h>
    11	
    12	#define	DATAFILE	"/Users/j04009/slab2/data"
    13	#define	DELIMITS	" \t\n\r"
    14	
    15	char	datavalue[256];
    16	
    17	
    18	int	GetLineFromPeer(int new_sockfd, char *line)
    19	{
    20	int	i;
    21	
    22	for(i = 0 ; ; )
    23		{
    24		if(1 != read(new_sockfd,line + i,1))
    25			continue;
    26		else
    27			{
    28			i++;
    29			*(line + i) = (char)0;
    30			}
    31	
    32		if(((char)('\n')) == (*(line + i - 1)))
    33			return i;
    34		}
    35	}
    36	
    37	char	*GetKeywordData(char *key)
    38	{
    39	FILE	*fp;
    40	char	buff[256];
    41	
    42	if((FILE *)NULL == (fp = fopen(DATAFILE,"r")))
    43		return (char *)NULL;
    44	
    45	for(;;)
    46		{
    47		char	*p;
    48		char	*pp;
    49	
    50		fgets(buff,256,fp);
    51	
    52		if(feof(fp))
    53			break;
    54	
    55		if(ferror(fp))
    56			break;
    57	
    58		if((char *)NULL == (p = strtok(buff,DELIMITS)))
    59			continue;
    60	
    61		if((char *)NULL == (pp = strtok((char *)NULL,DELIMITS)))
    62			continue;
    63	
    64		if(0 == strcmp(p,key))
    65			{
    66			fclose(fp);
    67			strcpy(datavalue,pp);
    68			return datavalue;
    69			}
    70		}
    71	
    72	fclose(fp);
    73	return (char *)NULL;
    74	}
    75	
    76	
    77	int	main()
    78	{
    79	  int sockfd;
    80	  int new_sockfd;
    81	  int writer_len;
    82	  struct sockaddr_in reader_addr;
    83	  struct sockaddr_in writer_addr;
    84	
    85	  /*ソケットの生成*/
    86	  if((sockfd = socket(AF_INET,SOCK_STREAM,0)) < 0){
    87	       perror("reader:socket");
    88	       exit(1);
    89	  }
    90	
    91	  /*通信ポート・アドレスの設定*/
    92	  bzero((char *) &reader_addr, sizeof(reader_addr));
    93	  reader_addr.sin_family = AF_INET;
    94	  reader_addr.sin_addr.s_addr = htonl(INADDR_ANY);
    95	  reader_addr.sin_port = htons(5682);
    96	
    97	  /*ソケットにアドレスを結びつける*/
    98	  if(bind(sockfd, (struct sockaddr *)&reader_addr, sizeof(reader_addr)) < 0){
    99	    perror("reader:bind");
   100	    exit(1);
   101	  }
   102	
   103	  /*コネクト要求をいくつまで待つかを設定*/
   104	  if(listen(sockfd,5) < 0){
   105	    perror("reader:listen");
   106	    close(sockfd);
   107	    exit(1);
   108	  }
   109	
   110	char	RecvBuff[256];
   111	
   112	for(;;) /*デーモンとして動かすのでエラー以外でプログラムを終了させる命令を消した*/
   113	{
   114	  int flag = 1;      /*下にあるwhile文の抜けるために使う*/
   115	
   116	  /*コネクト要求を待つ*/
   117	  if((new_sockfd = accept(sockfd,(struct sockaddr *)&writer_addr, &writer_len)) < 0){
   118	    perror("reader:accept");
   119	    exit(1);
   120	  }else{
   121	    while(flag)
   122	      {
   123		char	*p;
   124		char	SendBuff[256];
   125		char	*pp;
   126		
   127		(void)GetLineFromPeer(new_sockfd, RecvBuff);	/* Get 1 line from peer */
   128		
   129		if(((char)('=')) == *(RecvBuff))	/* クライアントが接続を切ったときに動く */
   130		  {
   131		    close(new_sockfd);  /*ソケットを廃棄*/
   132		    flag = 0;           /*flagを偽(0)とする*/
   133		    break;              /*このときflagの値によりwhileを抜ける*/
   134		  }else{
   135		  if((char *)NULL != (p = strchr(RecvBuff,'=')))
   136		    {
   137		      *p = (char)0;
   138		      
   139		      if((char *)NULL == (pp = GetKeywordData(RecvBuff)))
   140			sprintf(SendBuff,"%s=\n",RecvBuff);
   141		      else
   142			sprintf(SendBuff,"%s=%s\n",RecvBuff,pp);
   143		      
   144		      (void)write(new_sockfd,SendBuff,strlen(SendBuff));
   145		    }
   146		}
   147	      }
   148	  }
   149	 }
   150	}

myclient.cのソース


ここは課題2で使うclient.cと変わらないのでソースは省略する.
 

実行結果

[nw0409:~/slab2] j04009% ps aux | grep inetd
j04009  1718   0.0  0.0     8860      8 std  R+    4:58PM   0:00.00 grep inetd
[nw0409:~/slab2] j04009% ./myserver &
[1] 1719
[nw0409:~/slab2] j04009% ps | grep myserver
 1719 std  S      0:00.01 ./myserver
 1721 std  R+     0:00.00 grep myserver
[nw0409:~/slab2] j04009% ./myclient 
Connected.
Input Keyword = warning: this program uses gets(), which is unsafe.
123
Keyword = [123] / Data = [456]

Input Keyword = shiro
Keyword = [shiro] / Data = [kuro]

Input Keyword = abc
Keyword = [abc] / Data = []

Input Keyword = 
Disocnnected.
[nw0409:~/slab2] j04009% ps | grep myserver
 1719 std  S      0:00.01 ./myserver
 1724 std  R+     0:00.00 grep myserver
[nw0409:~/slab2] j04009% 

考察

実行結果の説明


まず,inetdが動いてないことを確認してからmyserverをバックグラウンドで 実行する.そして,その後myclientを実行した後,myserverがまだ起動 していることを確認した.
このことから,このmyserverプログラムは,デーモン型のサーバプログラムと いえる.


実装上の違い


 inetdは,1つのプロセスで複数のポートを監視しており,クライアントからの 接続要求がきた場合に初めてサーバプログラムを実行する.この役割からスー パーサーバとも呼ばれる.


 inetdは,socket,bind,listen,accept,close などの接続作業をす べてやってくれる.そのため,inetdとサーバプログラムは標準入出力を使ってク ライアントプログラムと通信できるので,サーバプログラムは標準入出力を使 うプログラムだけで良い.
 一方,inetdを使わないサーバプログラムは,socket,bind,listen,accept, close を実装しなければならない.それに,サーバプログラムをデーモン として動かしておく必要がある.


サンプル(server.c)に追加した部分の説明


socket()


これは,85行目にある.
socket()には引数が3つあり、第一引数はプロトコルファミリー,第二引数はソケッ トの通信方式,第三引数はソケットに使用される固有のプロトコルを指定 する.

myserver.cの設定
  • AF_INET
    IPv4インターネットプロトコル

  • SOCK_STREAM
    順序性と信頼性がある双方向のコネクション通信

  • 0
    プロトコルが一つの場合は0としてよい.0とすることで,使用するプロトコルが 上の二つの引数の組み合わせから自動的に設定される.


これでTCP/IPプロトコルで使用できるソケット記述子を返す.それはソケット に関連するシステムコールで使用される.
socket()が失敗すると -1 を返して終了.

bind()


これは,bind()の設定は92行目からである.

はじめにsockaddr_in構造体の中身をbzero()関数を使って0に埋める(初期化する)。
次にsockaddr_in構造体のメンバ変数を設定していく.

myserver.cの設定
  • sin_family
    プロトコルファミリAF_INET(socketと同じ).

  • sin_addr.s_addr
    htol()を通じてINADDR_ANY(どのホストでも接続可)というアドレス

  • sin_port
    htons()を使って,通信に利用するポート番号


htonlは32ビット,htonsは16ビットのホストバイトオーダをネットワークバ イトオーダに変換する.これはネットワークに流すデータ形式がビッグエン ディアンということが決まっているからである.



bind()には引数が3つあり、第一引数はソケット記述子,第二引数 はソケットのアドレスや ポート番号を定義するソケットアドレス 構造体へのポ インタ,第三引数は.ソケットアドレス構造体のサ イズを指定する.

myserver.cの設定
  • sockfd
    ソケット記述子が割り振られた変数

  • (struct sockaddr *)&reader_addr
    sockaddr_in型のポインタをsockaddr型のポインタに変換. こうすることでさまざまなプロトコルの違いを吸収できる.

  • sizeof(reader_addr)
    構造体の大きさ


これでソケット記述子に構造体で指定されたアドレスやポート番号 が割り振られる.

listen()


これは,104行目にある.

第一引数は接続準備する対象となるソケット記述子.第二引 数は待ち行列の最大長を指定する.

myserver.cの設定
  • sockfd
    接続準備する対象のソケット記述子(socketで作ったもの)

  • 5
    接続要求のための待ち行列の最大数を5に指定.


複数の接続要求が来るような場 合,サーバが何らかの処理をしていると接続要求を失う可能性があ る.処理しきれない接続要求を一時的に待ち状態にしておく必要が あり,その最大数が第二引数となる.ただし,その最大数 はオペレーティングシステムに制限され,5以下になることが多い ため,第二引数は5とした.

accept()


これは,117行目にある.

接続要求の待機はaccept()システムコールを使用する.accept()は接続要求 があるまで終了しない.つまり,サーバは accept()をを実行した時点で, 一時停止し,接続要求があると,accept()直後から再開する.

第一引数sockfdはソケット番号,第二引数addrはsockaddr構造体への ポインタである.第三引数には addr のサイズを格納する数値へ のポインタを指定する.

myserver.cの設定
  • sockfd
    ソケット記述子(socketで作ったもの)

  • ,(struct sockaddr *)&writer_addr
    bind()の所の 「(struct sockaddr *)&reader_addr」と同じ働き.

  • &writer_len
    bind()の所のと sizeof(reader_addr)と同じ働き.


accept()はクライアントから接続要求が来ると,接続処理を行うと同時に新 しいソケットを生成する.この新しいソケットはその接続に使われるソケッ トであり,元々のソケットは次の接続要求の受け付けのために使われる. なお,返値はその接続要求のあったソケットの番号(新しく作成されたソケッ ト番号)となる.失敗した場合は-1を返す.

close()


close引数はクローズするソケットの番号を指定する.クローズに 失敗した場合は-1を返す.

参考文献


->previous problem
->go to toppage
-> next problem