チャットプログラムのテスト

とあることがキッカケで、Rubyでシンプルなチャットプログラムを書いてみました。
チャットプログラムのザックリと要件を考えると以下の2点になる。

  • 受信したメッセージを適宜表示
  • 入力したメッセージを送信

ThreadとSTDIN/STDOUTの相性

要は「受信しつつ、送信」なので、Threadを使ったほうがよさそうな感じ。そこで慣れていないThread関係のテストプログラムを書いてたら、いきなり躓いた。そのコードは次のようなもの。

def recvLoop
  count = 0
  while true
    p "RECV:#{count}"   # とりあえず.
    count += 1
  end
end

def sendLoop
  while true
    p "SEND:"+gets  # 入力待ち.
  end
end

Thread.start{
    recvLoop
}
sendLoop

上記プログラムのダメなところは、sendLoop内のgetsでコンソール(標準入力)が待ち状態になり、recvLoopからの出力が行えなくなってしまうのです。
ウィンドウアプリなら入出力は別にするだろうし、コンソールアプリでも受信部と送信部を別々のプログラムにして別ウィンドウで処理したら問題ないのですが、このまま引き下がるのも悔しいのでなんとかならないか考えてみた。
「キー入力があったら、getsに遷移する」という方法でなんとかなるかもしれないと思って書いたコードが次のコード。(※参考サイト:小ネタいろいろ - Ruby Tips

require 'Win32API'
$kbhit = Win32API.new( 'msvcrt', '_kbhit', [], 'l')

def recvLoop
  count = 0
  while true
    p "RECV:#{count}"   # とりあえず.
    count += 1
  end
end

def sendLoop
  while true
    if $kbhit.call != 0 # キー入力があったら..
      p "SEND:"+gets    # getsを行う.
    end
  end
end

Thread.start{
    recvLoop
}
sendLoop

これでなんとか、コンソールアプリの問題点もクリアできた。
自分の入力中は受信出来てもすぐに表示できないけど、今回はしょうがないということで目をつむり、以前のサーバクライアントプログラムを参考にしつつ、とりあえず完成させてみた。

ちなみに、TCPServer#acceptのタイミングには注意する必要がある。メインスレッドでやるとそこで処理が止まってしまうので、別スレッドで行うようにした。

チャットテストスクリプトの完成

続きを読む

RUBY-LANG.ORG/JPのコード

何気なくRubyオフィシャルサイトを見ていて、ふとあるコードが書いてあることに気づいた。短いコードなのだが、なかなか面白い、というか、一部分知らないStringメソッドが使われてたので、ちょっと調べてみた。

上記サイトに書かれているコードは次のようなもの。

# Output "I love Ruby"
say = "I love Ruby"
puts say
 
# Output "I *LOVE* RUBY"
say['love'] = "*love*"
puts say.upcase
 
# Output "I *love* Ruby"
# five times
5.times { puts say }

文字列(String)の部分文字列を[]で指定し、そこに文字列を代入すると文字列の置換が出来るようなのです。なるほどねー。
普通使うであろうメソッドでいうところの String#sub! に相当しているようで、String#gsub! 的には使えないみたい。

say = "I love Ruby, I have a glove."
say['love'] = '*love*'
puts say   #=> "I *love* Ruby, I have a glove"

say = "I love Ruby, I have a glove."
say.sub!('love', '*love*')
puts say   #=> "I *love* Ruby, I have a glove"

say = "I love Ruby, I have a glove."
say.gsub!('love', '*love*')
puts say   #=> "I *love* Ruby, I have a g*love*"

DynDNSのIPを更新するRubyスクリプト

DynDNS使っています

最近のエントリから察せられるように、VineLinuxを入れたPCで自宅サーバを試しています。そして、ダイナミックDNS対策にDynDNSを使っているのですが、IP割り当てが一定時間ごとに変わる一般プロバイダを通していると、定期的にDynDNSへのIPの登録を変更する必要があります。
それを自動で行ってくれるプログラムがいくつか公開されています。

まず、ndyndnsを使ってみようと、インストールして試してみたのだが、グローバルIPをうまく取得できなかった。

DynDNS Update Clientを作ってみる

ndyndnsはC言語で書かれていて、またソースが公開されているので、ざっくりと中身の流れは分かっていた。それを頭に入れRubyで書いてみることにしました。
処理の流れは、以下の3ステップ。

  1. 現在のグローバルIPアドレスを取得
  2. 以前設定したIPアドレスと比較
  3. 変化があるか?
    1. なければ終了
    2. あれば、それを登録して、そのIPアドレスをメモしておく

グローバルIPを取得する

ルータにアクセスしてグローバルIPを取得する、という方法もあったのですが、解析に手間取りそうで却下。
DynDNSがIPを返してくれるページを設置してくれているのでそれを使うことにしました。

また、VALUE-DOMAINも同様なページを設置しています。

このページを取得したら正規表現で抜き出せば、現在のグローバルIPが取得できます。

Basic認証を行う

Rubyでページを取得するには、"open-uri"という便利なライブラリがあるのですが、Basic認証を超えて取得する方法が分かりにくかったので以下のページを参考に実装しました。

具体的には以下のような内容になります。

require "open-uri"
auth = "Basic "+["#{Username}:#{Password}"].pack("m").chomp
hashAuth = {
  "Authorization" => auth,
  "User-Agent"    => "update_dyndns.rb"
}
open( "http://need-basic.example.com/", hashAuth )

ハマったポイントは以下の2点。

  • packした文字列をchompする
  • "User-Agent"を設定しておく(DynDNSにつながるのに必要)

update_dyndns.rb

以上の情報をまとめて書いたコードが以下のものです。
使用するには、コードの冒頭に羅列している"DynDNS_"の定数を定義してから実行してみてください。
(これをcronで5分置き程度に実行するように設定すれば完了です。)

続きを読む

EclipseでRuby

眠くなってきたところで、ふと思い立ち、rubyの開発環境をeclipseにしてみたいなー、と衝動セットアップ。今使っているPCにRubyはインストールしていたのだが、Eclipseをインストールしてなかったので、まずeclipseをインストールするところからメモしておく。

プレゼントが来た!

Rubyist Magazine 20号のプレゼント企画に応募していたら「Ruby クックブック」が当選しました!

おとといメールが来て、住所とか返信したら、今日届いた。早いなー!
で、届いた荷物がやけに大きいので開けてみたら、こんな感じで、本以外にもいろいろオライリーグッズが入っていた。

トートバッグはJavaScript、マグカップPerl。という具合に、本以外は、特にRubyと関係ないのがどうにもこうにも。
で、パラパラとめくってみたら、昨日のエントリで書いたPing.pingechoの記事も載っていた!
なんだ、ここにあったのかー。(p.226
他にも、面白そうな記事がいくつかある。仕事の合間に読み進めてみよう。

Rubyクックブック ―エキスパートのための応用レシピ集

Rubyクックブック ―エキスパートのための応用レシピ集

こんな感じですばらしいプレゼントだったのですが、オライリーさんが書かれた住所が間違っていたのが玉に瑕。住所の番地が24となるべきところが42となっていたのです。
しかーし、佐川のおじさんはそれを読み解き、キチンと届けてくれたのがすばらしい!こういう解決ってやっぱり近所をウロウロして、それっぽい住所をいくつか当たってみたりしてくれたのだろうか。
最近いくつかの宅配業者を使ったときも佐川の対応が一番よかったと思ったのだが、今回の件でさらに佐川のカブが上がりましたよ。ま、他の業者だったら届けられなかった、のかと言えば、そうではないのかもしれないけどね。
(自分が間違えたのかも、と思い確認したけど、やっぱりメールで返信してた住所は正しかった。)

ピンピンPing!

pingをつかって、目的のネットワークサービスが起動しているかどうかを確認したい。
rubyなら何か簡単なライブラリが提供されているんじゃないだろうか、と調べてみたら、ありました!

下記に使い方のサンプルをいくつか載せておきます。

require "Ping"

# ローカルホストでテスト.
if Ping.pingecho( "localhost", 3, "80" )
  p "ローカルホスト(ポート80)がpingを返しました."
end

# IPアドレス指定.
if Ping.pingecho( "127.0.0.1", 3, "80" )
  p "ローカルホストのIPアドレスでもOKでした."
end

# サービス名で指定.
if Ping.pingecho( "127.0.0.1", 3, "ftp" )
  p "192.168.0.10がftpサービスを起動しています."
end

日本語PDFを作れなかった話

昨日の「PDFJをインストールしてみる」より前に試していたのだが、アップするタイミングを逃していたPDF::Writeモジュールについての調査結果をアップしておく。
経験上、「何が出来て、何が出来なかったか。また、その理由。」というのは「うまくいく理由」よりも、あとで大事になる情報だからね。

PDF::Writerと出会う

まず、Rubyで使えるPDFライブラリを探してみた。こういうのはRubyGemを使うと簡単。

>gem search -r pdf
*** REMOTE GEMS ***
Bulk updating Gem source index for: http://gems.rubyforge.org

jpeg2pdf (0.12)
    jpeg2pdf is a free program that converts a directory of JPEG files
    to a PDF file.

pdf-labels (1.0.0)
    Easy label creation in Ruby through PDF::Writer and using templates
    from gLabels. Contains the library pdf_labels, the Rails engine
    LabelPageEngine and an example application FileClerk.

pdf-toolkit (0.49, 0.5.0)
    A wrapper around pdftk to allow PDF metadata manipulation

pdf-writer (1.1.3, 1.1.2, 1.1.1, 1.1.0, 1.0.1, 1.0.0)
    A pure Ruby PDF document creation library.

説明文を読むと、pdf-writerが良さそうな気がします。

上記のようにいろいろリンクがありますが、Ruby Gemでインストールすれば、依存ライブラリも適当に返事するだけでインストールしてくれます。
以下のようにタイプします。

>gem install pdf-writer
Bulk updating Gem source index for: http://gems.rubyforge.org
Install required dependency color-tools? [Yn]  Y
Install required dependency transaction-simple? [Yn]  Y
Successfully installed pdf-writer-1.1.3
Successfully installed color-tools-1.3.0
Successfully installed transaction-simple-1.4.0
Installing ri documentation for pdf-writer-1.1.3...
Installing ri documentation for color-tools-1.3.0...
Installing ri documentation for transaction-simple-1.4.0...
Installing RDoc documentation for pdf-writer-1.1.3...
Installing RDoc documentation for color-tools-1.3.0...
Installing RDoc documentation for transaction-simple-1.4.0...

>

以上でインストールは完了。

PDF::Writerを試す

早速使ってみる。
sampleにあるhello.rbを参考にして以下のようなコードを実行してみた。

def initLibs
  begin
    require 'pdf/writer'
    print "Load OK\n"
  rescue LoadError => le
    print "Load Error: #{le}\n"
    exit
  end
end

def createPDF
  pdf = PDF::Writer.new
  pdf.select_font( "Times-Roman" )
  pdf.text( "Hello, Ruby with PDF::Writer",\
  :font_size => 72, :justification => :center )
  pdf.save_as( "hello.pdf" )
end

initLibs
createPDF

これで、特に問題なく、hello.pdfが作成されました!おぉ〜、ちょっと感動。

PDF::Writerは日本語に非対応

その後、いろいろソースを試してたら、現在配布されているPDF::Writerでは日本語フォントに対応していないことが判明した。
検索してみたら、日本語フォントパッチと名づけられたものを発見。

しかし、ダウンロードしてdiffファイルを見てみると、ソース内でフォント名をハードコーディングして対応している。それはRyuminとGothicBBBの二つのみ。これでは使えない・・。
ちなみに、こちらの記事が上記パッチ実装までの過程のようです。

PDF::Writerはフォント埋め込みも非対応

もう一つの難点が、フォント埋め込みに非対応、という点。
ちなみにパッチはここにある。

結論

とりあえず今やっている仕事では、日本語で縦書きされたPDFを作りたかったので、使えなさそう。
テキストが英語程度で、グラフィック中心ならかなり使い勝手が良さそうな気がします。
2005年末から2年、全然更新されていないのが残念でならない。せめてフォント埋め込みパッチを正規リポジトリに対応させたりして欲しいところ。
オープンソースなんだからおまえがやれ、と言われそうだけど。

ここでも読んでしっかり勉強しなきゃ、かも。

他言語のPDFライブラリ

PDFJ .. Perl

自分でイロイロ試した顛末込みでエントリをアップしています。

FPDF .. PHP

日本語対応もできているようだし、サーバサイドのアプリならこれも良さそう。縦書きの対応状況は未確認。