Boost」など、さまざまなカンファレンスを企画・運営しています。BOOKS(コードジン・ブックス)は、CodeZineの連載からカットアップした、開発現場の課題解決に役立つ書籍シリーズです。~QUITコマンド~おわりに参考文献サーバの起動この節ではPOPサーバが起動するまでのコードを解説します。最初は順当にプログラムの頭から見ていくことにしましょう。Rubyプログラムには、Cのmain関数やJavaのMainクラスのような決まった開始地点はありませんが、私はいつもmainメソッドを自分で書くことにしています。mainメソッドの冒頭を以下に示します。:endこのコードではコマンドラインオプションを解析しています。POPdでは、optparseライブラリ(OptionParserクラス)を使ってコマンドラインオプションを解析します。OptionParserでコマンドラインオプションを解析するには、次のような手順を踏みます。OptionParser.newメソッドでOptionParserオブジェクトを作るOptionParser#onメソッドでコマンドラインオプションを登録するOptionParser#parse!メソッドで実際にARGVを解析するOptionParser#onメソッドにはいろいろと機能があるのですが、上記のコードに登場している基本的な使い方だけマスターしておけばとりあえずは十分でしょう。OptionParser#onメソッドの主な使用パターンは以下のとおりです。ショートオプション「-i」を登録する。第2引数はオプションの説明で、ヘルプメッセージを生成するときに使われる。ショートオプション「-i」と、それと同等の効果を持つロングオプション「--inetd」を登録する。第3引数はオプションの説明で、ヘルプメッセージを生成するときに使われる。ロングオプション「--inetd」を登録する。第2引数はオプションの説明で、ヘルプメッセージを生成するときに使われる。引数を持つロングオプション「--port」を登録する。引数はブロック引数numに渡ってくる。第2引数はオプションの説明で、ヘルプメッセージを生成するときに使われる。OptionParser#banner=メソッドで登録した文字列はヘルプメッセージの冒頭に出力されます。ヘルプメッセージはOptionParser#helpメソッドで得られます。見たときには、OptionParserを使ったコードはややこしく見えるかもしれません。最初のうちは、動くコードをコピー&ペーストして自分のプログラムに応用していくのがいいでしょう。使っていればそのうち慣れてしまいます。続いてサーバを起動するコードを見ていきます。mainメソッドの続きを以下に示します。logger)この部分はPOPdオブジェクトを設定に合わせて作成しています。POPdクラスはサーバ全体を表現するクラスです。ここで例えば、POPd::Maildirを適切なクラスに変更してやれば、別のメールボックス形式にも対応できるように設計してあります。POPd#initializeは以下のとおり、accountsとloggerをインスタンス変数に代入するだけです。endmainメソッドのコード2行目のSyslog.openでは、OSに用意されているロギングインターフェイスに接続しています。第1引数は自プログラム名、第2引数はログ形式の指定、第3引数は自プログラムの分類です。上記のようにSyslog.openを呼び出すと、書き出されるログは以下のような形式になります。192.168.1.34Syslog.openメソッドの詳細については、本稿では説明しません。リファレンスマニュアルのSyslogモジュールの項を見てください。mainメソッドの続きに戻りましょう。mainメソッドの末尾、サーバを起動する部分を以下に示します。happen'end変数modeが:standaloneならスタンドアロンモード(デーモンモード)、modeが:inetdならinetdモードで起動します。以下、スタンドアロンモードの場合とinetdモードの場合に分けて説明していきます。デーモンになるスタンドアローンモードの場合、特に知識が必要になる個所は2点あります。デーモンになる方法TCP接続を受け付ける方法1点目のデーモンになるコードは、daemonメソッドで実装されています。2点目のTCP接続を受け付けるコードは、POPd#listenメソッドで実装されています。daemonメソッドから見ていきましょう。daemonメソッドは自プロセスをデーモンにするメソッドです。以下にdaemonメソッドのコードを示します。exit!endこのメソッドがやることは4つあります。forkメソッドとProcess.setsidメソッドを呼び出すルートディレクトリ(/)にchdirする標準入出力を「/dev/null」にリダイレクトするINTシグナル(Ctrl-Cによる割り込み)、TERMシグナル、HUPシグナルのハンドラを登録するまず、1.のforkメソッドとsetsidメソッドが一番重要です。このメソッドはそれぞれfork()システムコールとsetsid()システムコールに対応しており、fork()→setsid()→fork()の順番で呼び出すことによってプロセスをデーモン化します。次に、2.3.4.はサーバプログラムとしての作法です。ルートディレクトリにchdirするのは、ファイルシステムをアンマウントできなくなる問題を防ぐためです。UNIXでは、ファイルシステムに含まれるディレクトリがプロセスのカレントディレクトリになっていると、そのファイルシステムをアンマウントできないという仕様があります。そこで例えば、システム管理者がうっかりして、マウントしたCD-ROMファイルシステムからサーバを起動したと考えてみましょう。この場合、サーバのカレントディレクトリはCD-ROMファイルシステムなので、サーバが終了するまでCD-ROMをアンマウントできなくなってしまいます。このような問題を防ぐため、サーバはひとまずルートディレクトリにchdirするのが作法なのです。ルートディレクトリにchdirしなければいけないわけではありません。サーバが動作するために適切なディレクトリが特にあるなら、そのディレクトリにchdirしても構いません。次に、3.の標準入出力を「/dev/null」へリダイレクトする理由は、うっかり標準入出力を使ってしまったときにエラーにならないようにするためです。標準入出力は端末につながっていますが、デーモンから端末に対して入出力を行うとエラーになってしまうのです。プログラムが完璧に書けていればそんなことは起こらないはずですが、完璧なプログラムは滅多にありませんから、念のために実行しておきましょう。最後に4.のシグナルハンドラ設定は一般的な慣習のようなものです。INTシグナル(割り込み)はサーバでは大抵無視します。TERMシグナルは一般にサーバを停止するときに使われます。HUPシグナルは設定ファイルなどを読み直すときに使われます。それから、daemonメソッドではexit!メソッドを使っているところも特徴的です。exit!メソッドは_exit()システムコールに対応するRubyのメソッドです。exit!メソッドには、通常のexitメソッドと違って入出力バッファをフラッシュしない、などの特徴があります。このメソッドで通常通りexitメソッドを使ってしまうと、ここまでにプログラムが出力した内容が二重になってしまうなどの問題が発生します。このように、デーモンになる手順はなかなか面倒です。面倒ではありますが、手順は決まっているので、上記のコードをコピー&ペーストすれば済むことでもあります。私は上記のコードに関しては特に著作権を主張しないので、皆さんがデーモンになるコードを書くときは自由にコピーして使ってください。TCP接続を待つ次に、スタンドアロンモードの第2の難関、TCP接続を受け付けるためのコードを見ていきます。TCP接続を受け付けるコードを実装しているのはPOPd#listenメソッドです。POPd#listenメソッドのコードを以下に示します。endendTCP接続を受け付けるためには、以下の2つの手順が必要です。TCPServer.newメソッドを呼んで、TCPServerオブジェクトを作成するTCPServer#acceptメソッドを呼んで、実際にTCP接続を待つまず、TCPServer.newメソッドでTCPServerオブジェクトを作ります。第1引数は待ち受けるポート番号です。port)なので、一般的には「110」を渡せばよいでしょう。1023番以下のポートはスーパーユーザー(root)権限がないと接続を待てないので、一般ユーザー権限でPOPdを動かすときは「--port」オプションを使ってもっと大きなポート番号を指定してください。ちなみに、TCPServerオブジェクトの実体は、TCP接続待ち受け用のソケットです。socket)と言います。次に、TCPServer#acceptメソッドを呼びます。TCPServer#acceptメソッドは、クライアントから接続があるまで待ち、接続が完了したら、クライアントと接続済みになったTCPSocketオブジェクトを返します。説明したように、TCPSocketオブジェクトに対しては普通のFileオブジェクトと同じようにgetsメソッドやprintメソッドを使って入出力が可能です。複数のクライアントの並行処理TCP接続を待ち受けるときの基本は以上の通りですが、上記のPOPd#listenメソッドでは、もう一工夫を加えています。スタンドアロンモードの場合、クライアントからのTCP接続を次々に受け付けなければいけません。そこで次のようなコードを書いたとしましょう。sockendところが、これでは問題があります。クライアントからの接続はいつ来るか分かりませんから、もしかすると、既にクライアントと接続してserviceメソッドを実行しているあいだに、別のクライアントからも接続要求が来てしまうかもしれません。現在処理中のクライアントの処理が非常に長時間かかってしまうと、そのあいだ次のクライアントはずっと待たされてしまいます。そこで、複数のクライアントを並行処理できるように、Rubyのスレッドを使うことを考えましょう。次のようにコードを改善してみました。......残念ながら、これは間違いです。Thread.forkを呼んだ時点でスレッドが1つ増えて並行処理が始まりますから、生成されたスレッドがブロックを実行してsockを参照する前に、whileループの次の回が実行されて、ローカル変数sockが上書きされてしまう可能性があります。このループは、さきほど見せたPOPd#listenメソッドのように、Thread.forkの引数を使って書かなければいけないのです。正しいコードは以下のとおりです。}endこう書くことで、server.acceptの返り値(接続済のTCPSocketオブジェクト)がブロック引数sockに渡されます。ブロック引数sockは、スレッドローカルなので、別のスレッドに変数を上書きされてしまうことはなくなります。続いてPOPd#serviceメソッドに進みましょう。POPd#serviceメソッドのコードを以下に示します。sessionendさきほどのPOPd#listenメソッドの最後は次のようになっていました。sock従って、スタンドアロンモードならば@input(入力元)も@output(出力先)もソケットということになります。inetdモードなら次のようにserviceメソッドを呼び出します。STDOUTこれでinetdモードのときはめでたくSTDIN(標準入力)が入力、STDOUT(標準出力)が出力となります。ここまでのコードでスタンドアロンモードとinetdモードの違いが共通化できたので、以降はどちらのモードでもコードは完全に同じになります。ミネロウ)ふつうの文系プログラマ。Ruby界隈で活動中。主著は『ふつうのLinuxプログラミング』(ソフトバンククリエイティブ、2005)、『Rubyソースコード完全解説』(インプレス、2002)。NEW1DMM.comの施策から見る、事業をむしばむ「技術負債」への処方箋──リファクタリングの「言語化」でインシデントを予防2企業の7割超が「2025年の崖」を理解していないことが明らかに。NEW1DMM.comの施策から見る、事業をむしばむ「技術負債」への処方箋──リファクタリングの「言語化」でインシデントを予防2企業の7割超が「2025年の崖」を理解していないことが明らかに。NEW1DMM.comの施策から見る、事業をむしばむ「技術負債」への処方箋──リファクタリングの「言語化」でインシデントを予防2企業の7割超が「2025年の崖」を理解していないことが明らかに。12:00法務担当者、6割超が今後業務で「生成AIを使用したい」と回答。NEW新着記事一覧を見るイベント今後の予定を見るCodeZineは、株式会社翔泳社が運営するソフトウェア開発者向けのWebメディアです。「デベロッパーの成長と課題解決に貢献するメディア」をコンセプトに、現場で役立つ最新情報を日々お届けします。イラストの無断転載を禁じます。記載されているロゴ、システム名、製品名は各社及び商標権者の登録商標あるいは商標です。