初めてのRubyネットワークプログラミング(分散とかも)

解析の試験があると言う日にtwitterでぼそっと呟いた発言(http://twitter.com/ranha/statuses/842565499)を、初めてのRubyを読み終わったのでRubyの練習という事で実装してしまいました。
例によって例のごとく適当に動くだけですが、まぁ適当にでも動いているのでそこんとこは宜しくしてください。

require 'curses'
require 'drb/drb'
require 'pp'

sl_width = -1
SL_HEIGHT = 10


class SL
  attr_accessor :self
  attr_accessor :next
  attr_accessor :prev
  def initialize(uri,targ)
    puts "Distributed SL Start!"
    @mutex = Mutex.new
    @self = 'druby://'+uri+':8888'
    @status = "initializing"
    if targ==nil
      @next = 'druby://'+uri+':8888'
      @prev = 'druby://'+uri+':8888'
    else
      @next = 'druby://'+targ+':8888'
      @prev = nil
    end
    @status = "waiting command"
  end

  def get_status()
    @status
  end

  def change_status(state)
    @mutex.synchronize do
      @status = state
    end
  end

  def draw_SL()
    change_status("start sl rendering")
    Signal.trap(:INT,"SIG_IGN")
    Curses::init_screen
    begin
      sw = Curses::cols
      sh = Curses::lines
      x = sw-1
      loop do
        Curses::clear
        Curses::setpos(0,0)
        blit(x,sh/2-SL_HEIGHT/2,sw,sh)
        x-=1
        break if x < -sw
        Curses::refresh
        sleep 0.01
      end
    ensure
      Curses::close_screen
    end
    change_status("waiting command")
    Signal.trap(:INT,"DEFAULT")
  end
  
  def start()
    connect(@next)
    loop do
      if @next == @self
        change_status("start sl")
        command('Go!')
      else
        if @status == "start sl"
          command('Go!')
          change_status("waiting command")
        end
        sleep 0.1
      end
    end
  end
      
  def command(str)
    if str=="Go!"
      draw_SL()
      change_status("get next")
      nnode = DRbObject.new_with_uri(@next)
      change_status("waiting command")
      nnode.change_status("start sl")
    end
  end
  
  def connect(targ)
    puts "Connect "+targ
    @next = targ
    nnode = DRbObject.new_with_uri(@next)
    pnode = DRbObject.new_with_uri(nnode.prev)
    
    nnode.change_status("start reconnect")
    nnode.prev = @self
    nnode.change_status("waiting command")
    
    pnode.change_status("start reconnect")
    pnode.next = @self
    @prev = pnode.self
    pnode.change_status("waiting command")
  end 

  def getout()
    puts "escape!"
    puts "self:"+@self
    puts "next:"+@next
    puts "prev:"+@prev

    nnode = DRbObject.new_with_uri(@next)
    pnode = DRbObject.new_with_uri(@prev)
    nnode.change_status("start reconnect")
    nnode.prev = @prev
    nnode.change_status("waiting command")
    
    pnode.change_status("start reconnect")
    pnode.next = @next
    pnode.change_status("waiting command")
  end
end

$sl_pattern = Array.new
for i in 0..(SL_HEIGHT-1)
  $sl_pattern[i] = String.new
end

def check line
  line.each_byte{|c|
    if c!=?- && c!=?\n
      return true
    end
  }
  return false
end

  
line = 0
open("sl.pattern"){|file|
  while l = file.gets
    if(check l)
      $sl_pattern[line] << l.chomp
      line += 1
    else
      line = 0
    end
  end
}

$sl_width = $sl_pattern.map{|s| s.length}.max


def blit x,y,sw,sh
  for _y in 0..(SL_HEIGHT-1)
    if x >= 0
      Curses::setpos(y+_y,x)
      if (sw-x)>$sl_width
        Curses::addstr($sl_pattern[_y])
      else
        Curses::addstr($sl_pattern[_y][0..(sw-x-1)])
      end
    else x<0
      Curses::setpos(y+_y,0)
      Curses::addstr($sl_pattern[_y][-x..-1])
    end
  end
end

uri = ARGV.shift
targ = ARGV.shift
mysl = SL.new(uri,targ)
DRb.start_service(mysl.self,mysl)

begin
  mysl.start()
  sleep
rescue Interrupt => error
  mysl.getout()
ensure
  puts "Error owata"
  exit(0)
end

使い方は至って簡単。

一番最初に実行する人
ruby sl.rb 自分のアドレス
二番目以降の人
ruby sl.rb 自分のアドレス 既にこのあほくさいサービスに参加しているアドレス
実例
ruby sl.rb 192.168.10.3 <- 1台目

ruby sl.rb 192.168.10.4 192.168.10.3<- 2台目

ruby sl.rb 192.168.10.5 192.168.10.4<- 3台目(10.4ではなくて、当然10.3でも良い)

別途必要なものとしては、sl.patternというファイルです。
これはslのページから取ってくるか

-------------------------------------------------------------------------------
      ====        ________                ___________
  _D _|  |_______/        \__I_I_____===__|_________|
   |(_)---  |   H\________/ |   |        =|___ ___|
   /     |  |   H  |  |     |   |         ||_| |_||
  |      |  |   H  |__--------------------| [___] |
  | ________|___H__/__|_____/[][]~\_______|       |
  |/ |   |-----------I_____I [][] []  D   |=======|__
__/ =| o |=-~~\  /~~\  /~~\  /~~\ ____Y___________|__
 |/-=|___||    ||    ||    ||    |_____/~\___/
  \_/      \__/  \__/  \__/  \__/      \_/
-------------------------------------------------------------------------------
 
 
    _________________ 
   _|                \_____A
 =|                        |
 -|                        |
__|________________________|_
|__________________________|_
   |_D__D__D_|  |_D__D__D_|
    \_/   \_/    \_/   \_/
-------------------------------------------------------------------------------

これをそのままsl.patternとして保存してあげてください。


終了する時はSLが走っていない時に Ctrl-C です。


自分一人しか参加していない時は延々と走り続けるのでCtrl-\(\ バックスラッシュ)してあげてください。


動作確認は、Ruby1.8.6以上におけるOS X Tiger,LeopardといくつかのLinuxディストリビューションで行いました。
rubyのversionがある程度以上で、sl.patternファイルがちゃんとあれば動くと思います。

開発的な事

はじめてhoge.rb的な物を作ってプログラム書いたかもしれません。
はじめてdistributedなプログラムを書きました。
はじめてネットワークプログラミング的なプログラムを書きました。


drubyは凄い楽だという事が判明。後はもっとrubyらしく書ける気がするんですけど、勢いに任せて数時間で書いたのでまぁこのまんまで。


本当はしゅぽしゅぽ走る感じの方が良いのですが、そこは面倒臭いのでやめました。



さらにうざくする為には、現在見ているターミナル(のタブ)のttyみっけて、/dev/ttyに吐いてしまうという方法があると思うのですが、いかにしてそれをrubyでやるのかとか分からんのでやっていません。


後は自分のIPアドレス入れるとかばかじゃねーのと思うかもしれません。はい確かにそれはそうでifconfigの結果なりを自動で取得しろという話ですが、それも良く分からなかったのでやっていません。



やったのは右から左に文字列出して、ノードの追加と脱落をある程度安全に行っているだけです。



後change_statusがやたら多いのは、本当は中で再帰呼び出し的な事をしていたのですがそれだとthreadが解放されなくて途中で落ちてしまっていたので改善した跡地ということで。

改善点

起動した所でしか走らないのであんまりうざくない。


emcasとかvimとか開いている所に走ってきたら相当うざいだろうなーにひひとか思っているけど、どうやるんだろうね。
後それだと容易にcursesが使えなさそうで面倒くさそうだ。やりたいけど、今日も試験がががが。