FLYING

/* TODO: 気の利いた説明を書く */

多人数チャット

まだまだ続くぜ脳内Ruby祭り。今日のアイテムは一対一チャットの発展形。ただし、以下の理由で実用的ではない。

  • 発言者の名前とかが表示されない
  • スレッド使いすぎ(IO.selectを使うとおいしいらしい)
  • もちろんWindowsネイティブなRubyでは動かない
#!/usr/bin/ruby
# chats.rb

require 'socket'
require 'thread'
Thread.abort_on_exception = true

host = ARGV.shift
unless port = ARGV.shift
  host, port = nil, host
  post ||= 0
end

class ChatClient
  def initialize(host, port)
    @sock = TCPSocket.open(host, port)
    puts "connect: #{host}"
  end
  
  def start
    local = Thread.start { local_session }
    remote = Thread.start { remote_session }
    while local.alive? && remote.alive? do end
    local.kill
    remote.kill
  end
  
  def local_session
    while line = gets do
        line.chomp!
        break if line =~ /^quit/
        @sock.puts(line)
    end
  end
  
  def remote_session
    while line = @sock.gets do
      line.chomp!
      puts(line)
    end
  end
  
  def shutdown
    @sock.close
  end
end

class ChatServer
  def initialize(port)
    @clients = [$stdout]
    @threads = []
    @sock = TCPServer.open(port)
    puts "listen: #{port}"
  end
  
  def start
    local = Thread.start { local_session }
    remote = Thread.start { remote_session }
    while local.alive? && remote.alive? do end
    local.kill
    remote.kill
  end
  
  def local_session
    while line = gets do
      line.chomp!
      break if line =~ /^quit/
      send_message(line, $stdout)
    end
  end
  
  def remote_session
    while true do
      tmp = Thread.new(@sock.accept) {|s|
        add_client(s)
        while line = s.gets do
          line.chomp!
          send_message(line, s)
        end
        delete_client(s)
      }
      @threads.push(tmp)
    end
  end
  
  def add_client(client)
    @clients.push(client)
    send_message("#{client.peeraddr[2]} is connected.")
  end
  
  def delete_client(client)
    @clients.delete(client)
    send_message("#{client.peeraddr[2]} is closed.")
  end
  
  def send_message(mes, from = nil)
    @clients.each {|ci|
      ci.puts(">#{mes}") if ci != from
    }
  end
  
  def shutdown
    @threads.each {|ti|
      Thread.kill(ti)
    }
    @clients.each {|ci|
      ci.puts(">server has been shutdown.")
      ci.close
    }
    @sock = nil
    @clients = []
    @threads = []
  end
end

if host then
  begin
    chat = ChatClient.new(host, port)
    chat.start
  ensure
    chat.shutdown
  end
else
  begin
    chat = ChatServer.new(port)
    chat.start
  ensure
    chat.shutdown
  end
end

そのまま貼るには長すぎるか?