poll()を使用したechoサーバの例

#include        "unp.h"

#define OPEN_MAX 16

int
main(int argc, char **argv)
{
     int       i;
     int       max_fd_index = 0; /* 利用可能なディスクリプタの最大の添字 */
     int       listen_fd;
     int       connected_fd;
     int       socket_fd;
     int       num_of_ready_fd;
     ssize_t   n;
     char      line[MAXLINE];
     socklen_t clilen;
     struct pollfd      client[OPEN_MAX];
     struct sockaddr_in client_addr;
     struct sockaddr_in server_addr;


     /* サーバ起動の常套手順 */
     listen_fd = Socket(AF_INET, SOCK_STREAM, 0);
     bzero(&server_addr, sizeof(server_addr));
     server_addr.sin_family      = AF_INET;
     server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
     server_addr.sin_port        = htons(SERV_PORT);
     Bind(listen_fd, (SA *) &server_addr, sizeof(server_addr));
     Listen(listen_fd, LISTENQ);


     client[0].fd     = listen_fd;  /* 配列の最初のディスクリプタはリスニングソ
                                     * ケットである 
                                     */
                                     
     client[0].events = POLLIN;     /* リクエストイベントの設定。ここでは、読み
                                     * 出し可能となる、つまり新規コネクションが
                                     * 受け付け可能な状態になったら、pollから通
                                     * 知されるようにする。
                                     */

     /* 最初の要素以外は利用可能な状態で初期化する */
     for (i = 1; i < OPEN_MAX; i++) {
          client[i].fd = -1;
     }

     for ( ; ; ) {
          /* タイムアウト無しでディスクリプタ集合におけるイベントを待つ */
          num_of_ready_fd = Poll(client, max_fd_index+1, INFTIM);

          if (client[0].revents & POLLIN) {
               /* リスニングソケットのリターンイベントが"読み出し可能"であった場
                * 合、つまり、新規クライアントコネクションが発生した場合
                */

               clilen = sizeof(client_addr);
               connected_fd = Accept(listen_fd, (SA *) &client_addr, &clilen);

               /* エントリに余裕があれば、新規コネクションのディスクリプタをディ
                * スクリプタ集合に保存する
                */
               for (i = 1; i < OPEN_MAX; i++) {
                    if (client[i].fd < 0) {
                         client[i].fd = connected_fd;
                         break;
                    }
               }
               if (i == OPEN_MAX) {
                    err_quit("too many clients");
               }

               /* 新規コネクションを読み出し待ちでポーリングするように設定する */
               client[i].events = POLLIN;

               /* 必要なら、添字を更新する */
               if (i > max_fd_index) {
                    max_fd_index = i;
               }

               num_of_ready_fd--;
               if (num_of_ready_fd <= 0) {
                    /* 読み出し可能なディスクリプタが無いので以下の処理はスキップ */
                    continue;
               }
          }

          /* 読み出し可能なディスクリプタが存在すればここに到達する */
          for (i = 1; i <= max_fd_index; i++) {

               /* ディスクリプタが設定されている要素を検索する */
               if ( (socket_fd = client[i].fd) < 0) {
                    continue;
               }

               if (client[i].revents & (POLLIN | POLLERR)) { /* POLLERRは状況に応じて自動
                                                              * 的にセットされる 
                                                              */

                    if ( (n = readline(socket_fd, line, MAXLINE)) < 0) {
                         /* エラー処理 */

                         if (errno == ECONNRESET) {
                              /* connection reset by client */
                              Close(socket_fd);
                              client[i].fd = -1;
                         } else
                              err_sys("readline error");
                    } else if (n == 0) {
                         /* コネクションがクローズされた。Linux 2.6.17 以降ではPOLLRDHUPが
                          * 使える。
                          */

                         Close(socket_fd);
                         client[i].fd = -1;
                    } else {
                         /* 通常処理 */

                         Writen(socket_fd, line, n);
                    }

                    num_of_ready_fd--;
                    if (num_of_ready_fd <= 0) {
                         /* 読み出し可能なディスクリプタがこれ以上無い */
                         break;
                    }
               }
          }
     }
}

P.157より。select()を使用することにより、複数のディスクリプタに対してそれらの準備が出来るまで待つことが出来る。また、ソケットプログラミングにおいて相手側のコネクションがクロースするや否や、それを通知することが出来る。また、shutdown()を使用することにより、TCPをハーフクローズさせている。これにより、送信は不可でありながら受信は可能となる。

#include        "unp.h"

/*
 * echoクライアントのメイン関数。
 * 1. ファイルポインタ(普通は標準入力)から入力を受け取り、それをソケットに書き込む。
 * 2. 書き込みに応じた応答をソケットから受け取り、それを標準出力に書き出す。
 */
void
str_cli(FILE *fp, int socket_fd)
{
     int    stdin_eof_flg = 0;  /* このフラグがゼロである限りループ内で標準入力
                                 * の読み出し可能性をselect()で検査し続ける
                                 */
     int    fd;
     int    max_fd_plus1;
     fd_set rset;
     char   sendline[MAXLINE];
     char   recvline[MAXLINE];

     FD_ZERO(&1rset);              /* ディスクリプタ集合の初期化 */
     for ( ; ; ) {

          /* フラグがゼロならば標準入力を検査する。そうでなければ入力は終了して
           * いるので検査しない。
           */
          if (stdin_eof_flg == 0) {
               fd = fileno(fp);   /* fileno()は、ファイルポインタのディスクリプタを返す */
               FD_SET(fd, &rset); /* fdに対応するビットをディスクリプタ集合にセットする */
          }

          /* socket_fdに対応するビットをディスクリプタ集合にセットする */
          FD_SET(socket_fd, &rset);

          /* 2つのディスクリプタの最大値の算出。これは重要 */
          max_fd_plus1 = max(fileno(fp), socket_fd) + 1;

          /* いよいよselectの実行。どちらかのディスクリプタが読み出し可能となる
           * までブロックする。尚、ディスクリプタ集合rsetは"値-結果引数"であるの
           * で、関数から戻った時には用意できていないディスクリプタのビットはク
           * リアされている。よってselect()をcallする度に監視対象ディスクリプタ
           * をセットし直す必要が有ることに注意すること。
           *
           * ・書き込みの監視:しない
           * ・例外の監視    :しない
           * ・タイムアウト  :なし
           */
          Select(max_fd_plus1, &rset, NULL, NULL, NULL);

          if (FD_ISSET(socket_fd, &rset)) {     /* ソケットが読み出し可能となった */
               if (Readline(socket_fd, recvline, MAXLINE) == 0) {
                    if (stdin_eof_flg == 1) {

                         /* 標準入力からの入力が完了状態であり、かつ、ソケット上
                          * でEOF状態を検出した(つまりサーバがFINを発行した)ので、
                          * 正常終了とする
                          */
                         goto end;
                    }
                    else {
                         err_quit("str_cli: server terminated prematurely");
                    }
               }

               Fputs(recvline, stdout);
          }

          if (FD_ISSET(fileno(fp), &rset)) {    /* 標準入力が読み出し可能となった */
               if (Fgets(sendline, MAXLINE, fp) == NULL) {
                    stdin_eof_flg = 1;

                    /* もはやソケットへの書き込みは行わない為、shutdown()を送信
                     * 禁止でcallする。これにより対面のTCPエンドポイントへFINが
                     * 送られる。これ以後、ソケットへの送信は行われないが、受信
                     * は継続して行われる。これは、クライアントの入力が終了して
                     * もサーバへの、或いはサーバからのパイプ中にデータがある可
                     * 能性があることに対する対処である。
                     */
                    Shutdown(socket_fd, SHUT_WR);

                    /* 標準入力をディスクリプタ集合から削除する */
                    FD_CLR(fileno(fp), &rset);
                    continue;
               }

               Writen(socket_fd, sendline, strlen(sendline));
          }
     }

end:
     return;
}

リチャードスティーブンス氏の著作より、簡単なTCP daytimeクライアントとサーバのコード。

  • daytimetcpcli.c
#include    "unp.h"

int
main(int argc, char **argv)
{     
     int  socket_fd;
     int  read_bytes;
     char recvline[MAXLINE + 1];
     struct sockaddr_in servaddr; /* インターネットソケットアドレス構造体 */

     if (argc != 2)
          err_quit("usage: a.out ");

     /* TCPソケットの作成。インターネット(AF_INET)のストリーム(SOCK_STREAM)
      * ソケット、つまりTCPソケットを作成する。
      */
     if ( (socket_fd = socket(AF_INET, SOCK_STREAM, 0)) < 0)
          err_sys("socket error");

     /* サーバのIPアドレスとポートの指定 */
     memset(&servaddr, 0, sizeof(servaddr));
     servaddr.sin_family = AF_INET;     /* アドレスファミリの指定 */
     servaddr.sin_port   = htons(13);   /* ポート番号をホストバイトオーダーから
                                         * ネットワークバイトオーダーに変換(host
                                         * to network short)する。13はdaytime se
                                         * rverのウェルノウンポート。
                                         */
     /* 引数で指定したIPアドレスを変換(presentation to numeric)して構造体に格納する */
     if (inet_pton(AF_INET, argv[1], &servaddr.sin_addr) <= 0)
          err_quit("inet_pton error for %s", argv[1]);


     /* ソケットアドレス構造体で指定したサーバとのTCPコネクションを確立する。
      * &serveraddrをキャストしている理由についてはP.60を参照のこと。
      */
     if (connect(socket_fd, (struct sockaddr *) &servaddr, sizeof(servaddr)) < 0)
          err_sys("connect error");

     /* サーバからの応答をread()で読み取り、標準出力に書き出す。TCPはレコード境
      * 界を持たないバイトストリームプロトコルであるため、使用には注意が必要。
      */
     while ( (read_bytes = read(socket_fd, recvline, MAXLINE)) > 0) {
          /* ゼロは他方のエンドのコネクションクローズ。負の数はエラー。 */

          recvline[read_bytes] = '\0';  /* null terminate */
          if (fputs(recvline, stdout) == EOF)
               err_sys("fputs error");
     }
     if (read_bytes < 0)
          err_sys("read error");

     /* TCPソケットもファイルディスクリプタなのでexit()によりクローズされる。 */
     exit(0);
}

こちらはサーバ。先頭が大文字の関数は、エラー処理を内包したラッパ関数であることを意味する。

  • daytimetcpsrv.c
#include    "unp.h"
#include    

int
main(int argc, char **argv)
{
     int    listen_fd;
     int    connected_fd;
     char   buff[MAXLINE];
     time_t ticks;
     struct sockaddr_in servaddr;


     listen_fd = Socket(AF_INET, SOCK_STREAM, 0);

     memset(&servaddr, 0, sizeof(servaddr));
     servaddr.sin_family      = AF_INET;
     servaddr.sin_port        = htons(13);  /* daytime server */

     /* 接続を待つIPアドレスの指定。 INADDR_ANYは、どのインターフェースに宛てた
      * クライアントからのコネクションでも受け付ける事を意味する。
      * INADDR_ANY = (in_addr_t) 0x00000000
      */
     servaddr.sin_addr.s_addr = htonl(INADDR_ANY);

     /* ソケットにローカルアドレスservaddrを割り当てる。伝統的にこの処理は「ソケ
      * ットに名前をつける」と呼ばれる。
      */
     Bind(listen_fd, (struct sockaddr *) &servaddr, sizeof(servaddr));

     /* listen()を呼び出し、ソケットをリスニングソケットに変換する。これにより、
      * カーネルがクライアントからのコネクションを受け付けるようになる。
      * socket, bind, listenという3段階の処理は、全てのTCPサーバがリスニングデ
      * ィスクリプタを用意する為の通常の手段である。
      */
     Listen(listen_fd, LISTENQ);

     /* クライアントからのコネクションを受け付け、応答を返す。尚、このサーバは同
      * 時に1クライアントしか扱えない事に留意すること。
      */
     for ( ; ; ) {
          /* accept()は、接続要求キューから要求を取り出し、接続済みソケットを作
           * 成し、そのソケットのディスクリプタを返す。元のソケットは影響を受け
           * ない。尚、ここではクライアントプロセスのプロトコルアドレスの取得は
           * 行わない(NULL, NULL)
           */
          connected_fd = Accept(listen_fd, (struct sockaddr *) NULL, NULL);

          ticks = time(NULL);
          snprintf(buff, sizeof(buff), "%.24s\r\n", ctime(&ticks));

          /* 接続済みディスクリプタに値を書き込む。これによりクライアントへデー
           * タが送信される。
           */
          Write(connected_fd, buff, strlen(buff));

          /* クライアントとのコネクションをクローズする。これによりTCPコネクショ
           * ンの通常終了シーケンス(双方向のFIN等)が起動される。
           */
          Close(connected_fd);
     }
}

サーバ側でクライアントのIPアドレスとポートを出力するには、daytimetcpsrv.cを以下のように変更する。

 --- intro/daytimetcpsrv.c.org     1997-05-09 06:00:59.000000000 +0900
 +++ intro/daytimetcpsrv.c         1997-06-06 20:40:57.000000000 +0900
 @@ -5,7 +5,8 @@ int
  main(int argc, char **argv)
  {
         int                     listenfd, connfd;
 -       struct sockaddr_in      servaddr;
 +       socklen_t               len;
 +       struct sockaddr_in      servaddr, cliaddr;
         char                    buff[MAXLINE];
         time_t                  ticks;
 
 @@ -21,7 +22,11 @@ main(int argc, char **argv)
         Listen(listenfd, LISTENQ);
 
         for ( ; ; ) {
 -               connfd = Accept(listenfd, (struct sockaddr *) NULL, NULL);
 +               len = sizeof(cliaddr);
 +               connfd = Accept(listenfd, (struct sockaddr *) &cliaddr, &len);
 +               printf("connection from %s, port %d\n",
 +                          Inet_ntop(AF_INET, &cliaddr.sin_addr, buff, sizeof(buff)),
 +                          ntohs(cliaddr.sin_port));
 
          ticks = time(NULL);
          snprintf(buff, sizeof(buff), "%.24s\r\n", ctime(&ticks));

[Solaris] Apache2サービスの起動


SMFサービス詳細情報の確認

/ # svcs -a | grep apache
legacy_run      9月_16  lrc:/etc/rc3_d/S50apache    ←Apacheはレガシーサービス
disabled        9月_16  svc:/network/http:apache2   ←Apache2はSMFサービス

/ # svcs -l apache2  ←サービスの詳細
fmri         svc:/network/http:apache2
name         Apache 2 HTTP server
有効         false
状態         disabled
next_state   none
state_time   2008年09月16日 16時47分41秒
リスタータ   svc:/system/svc/restarter:default
dependency   require_all/error svc:/network/loopback:default (online)
dependency   optional_all/error svc:/network/physical:default (online)

/ # svcprop apache2 | grep exec  ←起動スクリプトの確認
start/exec astring /lib/svc/method/http-apache2\ start
stop/exec astring /lib/svc/method/http-apache2\ stop
refresh/exec astring /lib/svc/method/http-apache2\ refresh


Apache2マニフェストファイル
/var/svc/manifest/network/http-apache2.xml


Apache2サービスの起動

/ # svcadm -v enable apache2
svc:/network/http:apache2 が有効になりました。

/ # svcs -a | grep apache2
online         18:07:09 svc:/network/http:apache2

/ # cat /var/run/apache2/httpd.pid
13130

/ # ptree | grep http
          13160 /usr/bin/grep http
13130 /usr/apache2/bin/httpd -k start
  13131 /usr/apache2/bin/httpd -k start
  13132 /usr/apache2/bin/httpd -k start
  13133 /usr/apache2/bin/httpd -k start
  13134 /usr/apache2/bin/httpd -k start
  13135 /usr/apache2/bin/httpd -k start
  13154 /usr/apache2/bin/httpd -k start

/ # netstat -an -f inet -P tcp | grep \.80
      *.80                 *.*                0      0 49152      0 LISTEN

SMF(svc)
Service Management Facility。共通マネージメントシステム。従来のSolarisでは、各サービスはinit.d以下のrcスクリプトから起動されていた。それに変わるもの。


Solarisコンテナ
Solaris10の新機能。一台の物理ハードウェア上に複数の独立した実行環境(コンテナ)を動作させる技術。Solarisコンテナは、サーバの仮想化を行うSolarisゾーンと、ゾーンに対してリソース配分を行うSolarisリソース管理の2つの機能で構成される。


DTrace
Solaris10から。主にシステム障害やパフォーマンス低下を特定する為のツール。カーネル内部に埋め込まれた観測ポイント(プローブ)の動的ON/OFFが可能。