123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197 |
- require 'socket'
- require 'fiber'
- include Socket::Constants
- class FServer
- def initialize
- @timers = {}
- @reading = []
- @writing = []
- @clients = {}
- @client_id = 0
- end
-
- class Client
- attr_reader :io
- attr_reader :id
-
- def initialize(server, id, io)
- @server = server
- @id = id
- @io = io
- @fiber = Fiber.new { serve }
- @busy = true
- end
-
- def alive?
- @fiber.alive? && @busy
- end
-
- def command(cmd, args)
- @fiber.resume(cmd, args)
- end
-
- def write(data)
- @io.write(data)
- end
-
-
- def on_start
- p "Starting client fiber"
- return nil
- end
-
- def on_write(data)
- p "On write:"
- self.write("Client #{socket}:")
- self.write(args)
- end
-
- def on_read
- data = @io.readpartial(4096)
- p "After read: #{data}"
- @io.flush
- return data
- end
-
- def wait_for_input
- loop do
- cmd, arg = Fiber.yield
- data = nil
- case cmd
- when :start
- on_start
- when :read
- return on_read
- when :write
- on_write(arg)
- else
- p "Unknown command #{cmd}"
- end
- end
- end
-
-
- def ask_login
- @login = nil
- while @login.nil? || @login.empty?
- write("Login:")
- @login = wait_for_input.chomp
- end
- true
- end
- def ask_password
- @password = nil
- while @password.nil? || @password.empty?
- write("\r\nPassword:")
- @password = wait_for_input.chomp
- end
- true
- end
-
- def handle_command
- order = wait_for_input.chomp
- case order
- when "/quit"
- write("Byebye!\r\n")
- @busy = false
- else
- @server.broadcast("#@login said #{order}\r\n")
- end
- end
-
-
- def serve()
- data = nil
- lok = ask_login
- return false unless lok
- pok = ask_password
- return false unless pok
- write("\r\nWelcome #{@login} #{@password}!\r\n")
- while @busy do
- handle_command
- end
- end
- end
-
- def start
- @server = TCPServer.new('localhost', 7000)
- @reading << @server
- serve
- end
- def add_client
- socket = @server.accept_nonblock
- @reading << socket
- @client_id += 1
- client = Client.new(self, @client_id, socket)
- @clients[socket] = client
- client.command(:start, nil)
- puts "Client #{socket} connected"
- return client
- end
- def broadcast(message)
- @clients.each_pair do | id, client |
- client.write(message + "\r\n")
- end
- end
-
- def add_timer(id, delta = 1)
- now = Time.now
- stop = now + delta
- @timers[id.to_sym] = stop
- end
-
- def serve
- add_timer(:test, 15)
- loop do
- # Kick out clients that disconnected.
- disconnected = @clients.reject { |c, cl| cl.alive? }
- disconnected.each do |c, cl|
- rsock = c
- @clients.delete(rsock)
- @reading.delete(rsock)
- rsock.close
- end
-
- # Nodify timers that expired
- now = Time.now
- expired = @timers.select { |k, t| t < now }
- expired.each do |k, t|
- broadcast("Timer #{k} expired: #{t}, #{now}.\n\r")
- @timers.delete(k)
- end
-
-
- readable, writable = IO.select(@reading, @writing, nil, 0.1)
- if readable
- readable.each do | rsock |
- if rsock == @server
- add_client
- # Kick out clients with broken connections.
- elsif rsock.eof?
- @clients.delete(rsock)
- @reading.delete(rsock)
- rsock.close
- else
- # Tell the client to get their read on.
- client = @clients[rsock]
- text = client.command(:read, nil)
- end
- end
- end
- end
- end
-
- end
- server = FServer.new
- server.start
|