server.cr 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199
  1. require 'socket'
  2. require 'fiber'
  3. require 'timeout'
  4. require 'time'
  5. require_relative '../monolog'
  6. module Woe
  7. class Server
  8. include Socket::Constants
  9. include Monolog
  10. def initialize(port = 7000, host='0.0.0.0')
  11. @port = port
  12. @host = host
  13. @timers = {}
  14. @reading = []
  15. @writing = []
  16. @clients = {}
  17. @client_id = 0
  18. # Used for MSSP
  19. @mssp = {
  20. "NAME" => "Workers Of Eruta",
  21. "UPTIME" => Time.now.to_i.to_s,
  22. "PLAYERS" => "0",
  23. "CRAWL DELAY" => "0",
  24. "CODEBASE" => "WOE",
  25. "CONTACT" => "beoran@gmail.com",
  26. "CREATED" => "2015",
  27. "ICON" => "None",
  28. "LANGUAGE" => "English",
  29. "LOCATION" => "USA",
  30. "MINIMUM AGE" => "18",
  31. "WEBSITE" => "beoran.net",
  32. "FAMILY" => "Custom",
  33. "GENRE" => "Science Fiction",
  34. "GAMEPLAY" => "Adventure",
  35. "STATUS" => "Alpha",
  36. "GAMESYSTEM" => "Custom",
  37. "INTERMUD" => "",
  38. "SUBGENRE" => "None",
  39. "AREAS" => "0",
  40. "HELPFILES" => "0",
  41. "MOBILES" => "0",
  42. "OBJECTS" => "0",
  43. "ROOMS" => "1",
  44. "CLASSES" => "0",
  45. "LEVELS" => "0",
  46. "RACES" => "3",
  47. "SKILLS" => "900",
  48. "ANSI" => "1",
  49. "MCCP" => "1",
  50. "MCP" => "0",
  51. "MSDP" => "0",
  52. "MSP" => "0",
  53. "MXP" => "0",
  54. "PUEBLO" => "0",
  55. "UTF-8" => "1",
  56. "VT100" => "1",
  57. "XTERM 255 COLORS" => "1",
  58. "PAY TO PLAY" => "0",
  59. "PAY FOR PERKS" => "0",
  60. "HIRING BUILDERS" => "0",
  61. "HIRING CODERS" => "0"
  62. }
  63. end
  64. # Returns the MSSP data
  65. def mssp
  66. @mssp["PLAYERS"] = @clients.size.to_s
  67. return @mssp
  68. end
  69. # Look for a free numeric client ID in the client hash.
  70. def get_free_client_id
  71. cli = 0
  72. @clients.each do |client|
  73. return cli if client.nil?
  74. cli += 1
  75. end
  76. return cli
  77. end
  78. def run
  79. log_info("Starting server on #{@host} #{@port}.")
  80. @server = TCPServer.new(@host, @port)
  81. @reading << @server
  82. serve
  83. end
  84. def add_client
  85. socket = @server.accept_nonblock
  86. @reading << socket
  87. client_id = get_free_client_id
  88. client = Client.new(self, client_id, socket)
  89. @clients[client_id] = client
  90. client.command(:start, nil)
  91. puts "Client #{client.id} connected on #{socket}."
  92. return client
  93. end
  94. def broadcast(message)
  95. @clients.each_pair do | id, client |
  96. client.write(message + "\r\n")
  97. end
  98. end
  99. def add_timer(id, delta = 1)
  100. now = Time.now
  101. stop = now + delta
  102. @timers[id.to_sym] = stop
  103. end
  104. # Nodify timers that expired
  105. def handle_disconnected
  106. # Kick out clients that disconnected.
  107. disconnected = @clients.reject { |id, cl| cl.alive? }
  108. disconnected.each do |id, cl|
  109. rsock = cl.io
  110. @clients.delete(id)
  111. @reading.delete(rsock)
  112. cl.close
  113. end
  114. end
  115. # Handle timers
  116. def handle_timers
  117. now = Time.now
  118. expired = @timers.select { |k, t| t < now }
  119. expired.each do |k, t|
  120. broadcast("Timer #{k} expired: #{t}, #{now}.\n\r")
  121. @timers.delete(k)
  122. end
  123. end
  124. def client_for_socket(sock)
  125. @clients.each do |k, c|
  126. return c if c.io == sock
  127. end
  128. return nil
  129. end
  130. # Notify clients that have a read timeout set
  131. def handle_timeouts
  132. now = Time.now
  133. @clients.each do |id, cl|
  134. if cl.timeout?
  135. cl.command(:timeout, nil)
  136. end
  137. end
  138. end
  139. def serve
  140. @busy = true
  141. while @busy
  142. handle_disconnected
  143. handle_timers
  144. handle_timeouts
  145. readable, writable = IO.select(@reading, @writing, nil, 0.1)
  146. if readable
  147. readable.each do | rsock |
  148. if rsock == @server
  149. add_client
  150. # Kick out clients with broken connections.
  151. elsif rsock.eof?
  152. cli = client_for_socket(rsock)
  153. @clients.delete(cli.id)
  154. @reading.delete(rsock)
  155. cli.close
  156. else
  157. # Tell the client to get their read on.
  158. client = client_for_socket(rsock)
  159. if client
  160. text = client.command(:read, nil)
  161. end
  162. end
  163. end
  164. end
  165. end
  166. end
  167. def self.run(port = 7000, host = '0.0.0.0', logname = 'woe.log')
  168. Monolog.setup_all(logname)
  169. server = self.new(port, host)
  170. server.run
  171. Monolog.close
  172. end
  173. end # class Server
  174. end # module Woe