SHOEISHA iD

※旧SEメンバーシップ会員の方は、同じ登録情報(メールアドレス&パスワード)でログインいただけます

DeveloperZine(デベロッパージン)- エンジニアの意思決定を支える技術情報メディア ProductZine

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

特集記事

Rubyで簡易POP3サーバを作る

RubyによるネットワークプログラミングとUNIXシステムプログラミングの基礎

ネットワーク入出力

 この節ではRubyでのネットワーク入出力について解説します。

POPd#_sessionメソッドの定義

 前節ではスタンドアローンモード、inetdモードいずれの場合もsessionメソッドの呼び出しに帰着したところで終わりました。sessionメソッドではエラーに備えていくつか準備をしたあと、_sessionメソッドを呼び出します。この_sessionメソッドがPOP3サーバの処理の中核となっています。

 以下にPOPd#_sessionメソッドのコードを示します。

POPd#_sessionメソッド
def _session
  stamp = apop_stamp()
  print_line "+OK popd #{stamp}"
  mailbox = auth(stamp)
  while cmd = read_cmd()
    mid = "cmd_#{cmd.name}"
    unless respond_to?(mid, true)
      print_line '-ERR unknown command'
      next
    end
    __send__(mid, mailbox, cmd.args)
  end
end

 _sessionメソッドの最初の3行、つまりapop_stampからauthまではAPOP認証のコードです。そして、そのあとのwhileループで認証後のPOPコマンドを1つずつ処理しています。

 しかし、今はまずそのような処理の流れを無視して、入出力にだけ注目しましょう。POPdの入出力は、print_lineメソッドとread_cmdメソッドでほぼすべてです。

 以下、順番に説明しましょう。

POPd#print_lineメソッドの定義

 POPd#print_line@outputに1行書き込むメソッドです。POPdでは、ネットワークへの出力にはPOPd#print_lineメソッドを使います。

 POPd#print_lineメソッドのコードを以下に示します。

POPd#print_lineメソッド
def print_line(str)
  @output.print "#{str}\r\n"
end

 このとおり、@output(ソケットまたはSTDOUT)に対してprintメソッドを呼ぶだけです。普段ファイルに出力する場合とほとんど変わりませんね。

 あえて違うところを探すと、改行コードを明示的に\r\nにしている点が挙げられます。POP3では改行コードとして\r\n(CR+LF)を使うと決まっているので、このように明示的に改行コードを指定しているのです。ここでputsメソッドを使うとLinuxなどでは改行コードが\nになってしまいますから、不適切です。

POPd#read_cmdメソッドの定義

 POPd#read_cmdは、POPコマンド1つ(1行)を入力から読み込んで、POPd::Commandオブジェクトを返すメソッドです。POPdでは、ネットワークからの入力にはPOPd#read_cmdメソッドを使います。

 POPd#read_cmdメソッドのコードを以下に示します。

POPd#read_cmdメソッド
def read_cmd
  line = read_line()  or return nil
  cmd, *args = line.split
  Command.new(cmd, args)
end

 本体1行目のPOPd#read_line@inputから1行読み込んで返すメソッドです。定義はこの後すぐに紹介します。

 read_lineメソッドの返り値がnilだった場合はTCP接続が切れたということなので、read_cmdメソッドもnilを返します。きちんとlineが読み込めた場合は、次の行に移ってPOP3コマンドを解析します。

 POP3のコマンドはどれも空白で単語が区切られた単純な構造なので、String#splitコマンド一発で解析できます。例えば、RETRコマンドであれば、lineは「"RETR 13"\r\n」という文字列です。この文字列に対してsplitメソッドを呼ぶと返り値は「["RETR", "13"]」という配列です。その配列を次のように多重代入すると、

cmd, *args = ["RETR", "13"]

 cmd"RETR"args["13"]となります。あとはPOPd::Commandオブジェクトを作るだけです。POPd::Commandオブジェクトは、ほぼ単なる構造体なので、実装は省略します。

POPd#read_lineメソッドの定義

 POPd#read_lineメソッドのコードを以下に示します。

POPd#read_lineメソッド
READ_TIMEOUT = 90

def read_line
  begin
    timeout($DEBUG ? nil : READ_TIMEOUT) {
      return @input.gets
    }
  rescue TimeoutError
    terminate
  end
end

 このメソッドで実際に入力を行っている部分は@input.getsだけです。あとはタイムアウトのためのコードと、その後処理にすぎません。

 タイムアウトの実装も、timeoutライブラリが提供するtimeoutメソッドを使うだけです。上記のコードではREAD_TIMEOUT秒(90秒)のあいだにgetsメソッドが完了しなかったらタイムアウトするようにしています。

 ただし、デバッグ時($DEBUG == true)にはtimeoutメソッドにnilを渡して、タイムアウト機能を無効にします。これは、デバッグ時には手でPOPコマンドを打ち込む場合がありますから、一定時間内に急いで入力しなければならない仕様だと不便だからです。

 timeoutメソッドの詳細についてはリファレンスマニュアルを見てください。

次のページ
APOP認証

この記事は参考になりましたか?

特集記事連載記事一覧

もっと読む

この記事の著者

青木 峰郎(アオキ ミネロウ)

ふつうの文系プログラマ。Ruby界隈で活動中。主著は『ふつうのLinuxプログラミング』(ソフトバンククリエイティブ、2005)、『Rubyソースコード完全解説』(インプレス、2002)。

※プロフィールは、執筆時点、または直近の記事の寄稿時点での内容です

この記事は参考になりましたか?

この記事をシェア

CodeZine(コードジン)
https://codezine.jp/article/detail/687 2006/11/10 00:00

イベント

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

新規会員登録無料のご案内

  • ・全ての過去記事が閲覧できます
  • ・会員限定メルマガを受信できます

メールバックナンバー