2010年9月23日木曜日

Rubyでマルチスレッドメール送信クライアント


Rubyでマルチスレッドを使い、メール送信できるクライアントを作成する。

【条件】
・送信元アドレスは1つ
・受信アドレスは複数
  ※この受信アドレス宛に一斉にメールを送りたいわけである。

下記がコードである。
短いので説明するより見てもらえば分かるだろう。
クラス名として使っているAmailのAはAsynchronous(非同期)の頭文字である。


#!/usr/bin/ruby
require "net/smtp"
require 'time'
require 'timeout'
require 'thread'

Thread.abort_on_exception = true

$from = 'fromアドレス'

class Amail
  def initialize(mta)
    @mta = mta

    @rcpts = Array.new
    @rcpts.push('rcpt_user1@example.com')
    @rcpts.push('rcpt_user2@example.com')
    @rcpts.push('rcpt_user3@example.com')
    @rcpts.push('rcpt_user4@example.com')
    @rcpts.push('rcpt_user5@example.com')
 
    @max_threads = 10
    @num_thread = 0
    @mutex = Mutex.new
    @cond = ConditionVariable.new
    @threads = Array.new
  end

  def get()
    while @rcpts.length != 0 do
      multi()
    end

    for i in 1..@threads.length
      @threads[i-1].join
    end
  end

  def multi()
    @threads << Thread.new(@rcpts.shift) do |rcpt|
      sendmail = Sendmail.new(rcpt, @mta)

      @mutex.synchronize do
        while @num_thread == @max_threads
          @cond.wait(@mutex)
        end

        @num_thread += 1
      end

      begin
        sendmail.run()
      ensure
        @mutex.synchronize do
          @num_thread -= 1
          @cond.signal
        end
      end
    end
  end
end


class Sendmail
  def initialize(rcpt, mta)
    @rcpt = rcpt
    @mta = mta
    @timeout = 10
    content()
  end

  def content()
@content = <<EOF
From: #{$from}
To:   #{@rcpt}
Subject: TEST FULLMESH
Date: #{Time.now.rfc2822}

#{$from}
#{@rcpt}
test fullmesh
EOF
  end

  def run()
    begin
      @t0 = Time.now

      timeout(@timeout) do
        Net::SMTP.start(@mta, 25) do | smtp |
          smtp.send_mail @content, $from, @rcpt
        end
      end

      @t1 = Time.new

      printf("OK time:%f " ,@t1-@t0)
      print("mta:#{@mta} from:#{$from} to:#{@rcpt}\n")

    rescue StandardError, Timeout::Error => ex
      print("NG: mta:#{@mta} from:#{$from} to:#{@rcpt} #{ex}\n")
      exit 2
    end
  end
end


amail = Amail.new('example.comのMX')
amail.get()


スレッドパターン以前の並行処理プログラミングの基礎はここを参考してほしい。