チャットプログラムのテスト
とあることがキッカケで、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のタイミングには注意する必要がある。メインスレッドでやるとそこで処理が止まってしまうので、別スレッドで行うようにした。
チャットテストスクリプトの完成
# testChat.rb # チャットテスト #---------- 変数定義 ----------# require 'Win32API' $kbhit = Win32API.new( 'msvcrt', '_kbhit', [], 'l') $recvPort = "7700" # 受信ポート $recvSocket; $friendIP = "192.168.0.27" # 接続先のIP $friendSocket; #---------- 関数定義 ----------# def setup # 受信ポートを準備. $recvSocket = TCPServer.open($recvPort) # 送信ポートの準備. while true p "type friend IP >" inputIP = gets.chomp if inputIP == "" next end p "[#{inputIP}]" $friendIP = inputIP $friendSocket = TCPSocket.open($friendIP, $recvPort) break end print "=== chat with [#{$friendIP}] ===\n" return true end def recvLoop recv = $recvSocket.accept count = 0 # 受信したら出力. while recv.gets print "RECV[#{count+=1}]:"+$_.chomp+"\n" end end def sendLoop while true if $kbhit.call != 0 $friendSocket.write(gets) end end end #---------- 処理本編 ----------# if setup Thread.start{ recvLoop } sendLoop end
「1対1でしか対応できていない」という大きな問題が残っていますが、とりあえず、最初のステップはここまで。
後で考えた
1対1以上の対応はどうしようか、と悩んで、後から気づいたのですが、UDPのブロードキャストを使えばよさそうですね。IP Messengerもメッセージのやり取りはUDPを使っているようですし。(ファイル添付時はTCP)
と思ってテストしたら家のルータのパケットフィルタリングにはじかれました。設定しなおせばいいのですが、今はアレなので、UDPテストはまた後日。