読者です 読者をやめる 読者になる 読者になる

FLYING

〈全日本・紀文豆乳飲料シリーズ「麦芽コーヒー」の500ミリリットルパックを扱う小売店が少ないことに遺憾の意を表明する会〉活動記録

多人数チャット

ruby network

まだまだ続くぜ脳内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

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