Squeak で説明しても WikiPhone の面白さがいまいちよく分からないと思うので、Ruby 1.8.5 で簡単な WikiPhone クライアントを実装してみます。今回出力する側と入力する側を別々に作ってみましたが、どちらも 40行ちょっとで書けます。
パイプとしての WikiPhone
WikiPhone は、ここでは単なるパイプのように振舞います。URL は、WikiPhone サーバ内であればなんでも使えます。メッセージの送受信はこのようにします。
送信側
$ echo "Hi there" | ./wpput.rb http://languagegame.org:9090/chat
受信側
$ ./wpget.rb http://languagegame.org:9090/chat Hi there
Ruby + WikiPhone でファイル転送
WikiPhone をファイル転送に使ってみます。受信側から起動します。
$ ./wpget.rb http://languagegame.org:9090/chat > dst.html
次に送信側を起動します。
$ ./wpput.rb http://languagegame.org:9090/chat < src.html
ダサいですが、ファイルを受け取ったと思ったら受信側は自分で Ctrl + C します。
Ruby で IP 電話???
さて、いよいよ Ruby だけで VoIP の実験です。/dev/dsp をそのまま使います。送信側から。
$ ./wpput.rb http://languagegame.org:9090/voip < /dev/dsp
受信側です。
$ ./wpget.rb http://languagegame.org:9090/voip > /dev/dsp
Ruby と Squeak WikiPhone の会話ログをとる。
/dev/dsp というのは、音声の入出力を扱うデバイスで、linux や cygwin 等で使えます。これは Squeak 純正の WikiPhone とは互換性が無いので、Squeak の WikiPhone の信号を扱いたければ変換する必要があります。gsm 22050 Hz を使っていますので、例えば会話のログをとるワンライナーは以下のようになります。
./wpget.rb http://languagegame.org:9090/phone | sox -t gsm -r22050 - -r22050 -c1 -sw -fs log.wav
以下に wpget.rb と wpput.rb のソースを載せます。サーバに興味のある方は http://www.squeaksource.com/WikiPhone.html と Squeak3.8 を使ってください。なお、languagegame.org は玄箱という小さなサーバで動かしていますので、遅くてよく止まります。
#!/bin/env ruby # wpget.rb: WikiPhone GET client require "socket" require "uri" require 'net/http' def read_a_chunk sock dest = "" line = sock.readline hexlen = line.slice(/[0-9a-fA-F]+/) or raise Net::HTTPBadResponse, "wrong chunk size line: #{line.dump}" len = hexlen.hex break if len == 0 # $stderr.print "length: #{len}\n" sock.read len, dest sock.read 2 # chop CRLF dest end def main if ARGV.size < 1 then $stderr.print "usage: wpget url\n" exit 1 end uri = URI.parse ARGV.first socket = TCPSocket.new(uri.host, uri.port) socket.write "GET #{uri.path} HTTP/1.1\r\n\r\n" begin response = Net::HTTPResponse.read_new(Net::BufferedIO.new(socket)) end while response.kind_of?(Net::HTTPContinue) # response.each { |name, value| $stderr.print "#{name} : #{value}\n" } while true print read_a_chunk(socket) end end $stdout.sync = true main
#!/bin/env ruby # wpput.rb: WikiPhone PUT client require "socket" require "uri" require 'net/http' def post_a_chunk (socket, data) len = data.length header = "#{len.to_s(16)};\r\n"; socket.write(header) socket.write(data) socket.write("\r\n") end def main if ARGV.size < 1 then $stderr.print "usage: wpput.rb url < input\n" exit 1 end uri = URI.parse ARGV.first socket = TCPSocket.new(uri.host, uri.port) socket.write "PUT #{uri.path} HTTP/1.1\r\n" socket.write "Transfer-Encoding: chunked\r\n\r\n" begin while true buffer = $stdin.readpartial 130 post_a_chunk(socket, buffer) end rescue EOFError end end main