123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199 |
- require 'socket'
- require 'fiber'
- require 'timeout'
- require 'time'
- 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
- # Used for MSSP
- @mssp = {
- "NAME" => "Workers Of Eruta",
- "UPTIME" => Time.now.to_i.to_s,
- "PLAYERS" => "0",
- "CRAWL DELAY" => "0",
- "CODEBASE" => "WOE",
- "CONTACT" => "beoran@gmail.com",
- "CREATED" => "2015",
- "ICON" => "None",
- "LANGUAGE" => "English",
- "LOCATION" => "USA",
- "MINIMUM AGE" => "18",
- "WEBSITE" => "beoran.net",
- "FAMILY" => "Custom",
- "GENRE" => "Science Fiction",
- "GAMEPLAY" => "Adventure",
- "STATUS" => "Alpha",
- "GAMESYSTEM" => "Custom",
- "INTERMUD" => "",
- "SUBGENRE" => "None",
- "AREAS" => "0",
- "HELPFILES" => "0",
- "MOBILES" => "0",
- "OBJECTS" => "0",
- "ROOMS" => "1",
- "CLASSES" => "0",
- "LEVELS" => "0",
- "RACES" => "3",
- "SKILLS" => "900",
- "ANSI" => "1",
- "MCCP" => "1",
- "MCP" => "0",
- "MSDP" => "0",
- "MSP" => "0",
- "MXP" => "0",
- "PUEBLO" => "0",
- "UTF-8" => "1",
- "VT100" => "1",
- "XTERM 255 COLORS" => "1",
- "PAY TO PLAY" => "0",
- "PAY FOR PERKS" => "0",
- "HIRING BUILDERS" => "0",
- "HIRING CODERS" => "0"
- }
- end
-
-
- # Returns the MSSP data
- def mssp
- @mssp["PLAYERS"] = @clients.size.to_s
- return @mssp
- 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)
- cl.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
-
- # Notify clients that have a read timeout set
- def handle_timeouts
- now = Time.now
- @clients.each do |id, cl|
- if cl.timeout?
- 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?
- cli = client_for_socket(rsock)
- @clients.delete(cli.id)
- @reading.delete(rsock)
- cli.close
- else
- # Tell the client to get their read on.
- client = client_for_socket(rsock)
- if client
- text = client.command(:read, nil)
- end
- 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
|