123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141 |
- require 'socket'
- require 'fiber'
- require 'timeout'
- require_relative '../monolog'
- module Woe
- class Server
- include Socket::Constants
- include Monolog
-
- def initialize(port = 7000, host='0.0.0.0')
- @port = port
- @host = host
- @timers = {}
- @reading = []
- @writing = []
- @clients = {}
- @client_id = 0
- end
-
- # Look for a free numeric client ID in the client hash.
- def get_free_client_id
- cli = 0
- @clients.each do |client|
- return cli if client.nil?
- cli += 1
- end
- return cli
- end
-
- def run
- log_info("Starting server on #{@host} #{@port}.")
- @server = TCPServer.new(@host, @port)
- @reading << @server
- serve
- end
- def add_client
- socket = @server.accept_nonblock
- @reading << socket
- client_id = get_free_client_id
- client = Client.new(self, client_id, socket)
- @clients[client_id] = client
- client.command(:start, nil)
- puts "Client #{client.id} connected on #{socket}."
- 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
-
-
- # Nodify timers that expired
- def handle_disconnected
- # Kick out clients that disconnected.
- disconnected = @clients.reject { |id, cl| cl.alive? }
- disconnected.each do |id, cl|
- rsock = cl.io
- @clients.delete(id)
- @reading.delete(rsock)
- rsock.close
- end
- end
-
- # Handle timers
- def handle_timers
- 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
- end
-
- def client_for_socket(sock)
- @clients.each do |k, c|
- return c if c.io == sock
- end
- return nil
- end
-
- # Nodify clients that havea read timeout set
- def handle_timeouts
- now = Time.now
- @clients.each do |id, cl|
- if cl.timeout_at && cl.timeout_at > now
- cl.command(:timeout, nil)
- end
- end
- end
-
- def serve
- @busy = true
- while @busy
- handle_disconnected
- handle_timers
- handle_timeouts
-
- 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(client_for_socket(rsock))
- @reading.delete(rsock)
- rsock.close
- else
- # Tell the client to get their read on.
- client = client_for_socket(rsock)
- text = client.command(:read, nil)
- end
- end
- end
- end
- end
-
- def self.run(port = 7000, host = '0.0.0.0', logname = 'woe.log')
- Monolog.setup_all(logname)
- server = self.new(port, host)
- server.run
- Monolog.close
- end
-
- end # class Server
- end # module Woe
|