server.rb 3.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141
  1. require 'socket'
  2. require 'fiber'
  3. require 'timeout'
  4. require_relative '../monolog'
  5. module Woe
  6. class Server
  7. include Socket::Constants
  8. include Monolog
  9. def initialize(port = 7000, host='0.0.0.0')
  10. @port = port
  11. @host = host
  12. @timers = {}
  13. @reading = []
  14. @writing = []
  15. @clients = {}
  16. @client_id = 0
  17. end
  18. # Look for a free numeric client ID in the client hash.
  19. def get_free_client_id
  20. cli = 0
  21. @clients.each do |client|
  22. return cli if client.nil?
  23. cli += 1
  24. end
  25. return cli
  26. end
  27. def run
  28. log_info("Starting server on #{@host} #{@port}.")
  29. @server = TCPServer.new(@host, @port)
  30. @reading << @server
  31. serve
  32. end
  33. def add_client
  34. socket = @server.accept_nonblock
  35. @reading << socket
  36. client_id = get_free_client_id
  37. client = Client.new(self, client_id, socket)
  38. @clients[client_id] = client
  39. client.command(:start, nil)
  40. puts "Client #{client.id} connected on #{socket}."
  41. return client
  42. end
  43. def broadcast(message)
  44. @clients.each_pair do | id, client |
  45. client.write(message + "\r\n")
  46. end
  47. end
  48. def add_timer(id, delta = 1)
  49. now = Time.now
  50. stop = now + delta
  51. @timers[id.to_sym] = stop
  52. end
  53. # Nodify timers that expired
  54. def handle_disconnected
  55. # Kick out clients that disconnected.
  56. disconnected = @clients.reject { |id, cl| cl.alive? }
  57. disconnected.each do |id, cl|
  58. rsock = cl.io
  59. @clients.delete(id)
  60. @reading.delete(rsock)
  61. rsock.close
  62. end
  63. end
  64. # Handle timers
  65. def handle_timers
  66. now = Time.now
  67. expired = @timers.select { |k, t| t < now }
  68. expired.each do |k, t|
  69. broadcast("Timer #{k} expired: #{t}, #{now}.\n\r")
  70. @timers.delete(k)
  71. end
  72. end
  73. def client_for_socket(sock)
  74. @clients.each do |k, c|
  75. return c if c.io == sock
  76. end
  77. return nil
  78. end
  79. # Nodify clients that havea read timeout set
  80. def handle_timeouts
  81. now = Time.now
  82. @clients.each do |id, cl|
  83. if cl.timeout_at && cl.timeout_at > now
  84. cl.command(:timeout, nil)
  85. end
  86. end
  87. end
  88. def serve
  89. @busy = true
  90. while @busy
  91. handle_disconnected
  92. handle_timers
  93. handle_timeouts
  94. readable, writable = IO.select(@reading, @writing, nil, 0.1)
  95. if readable
  96. readable.each do | rsock |
  97. if rsock == @server
  98. add_client
  99. # Kick out clients with broken connections.
  100. elsif rsock.eof?
  101. @clients.delete(client_for_socket(rsock))
  102. @reading.delete(rsock)
  103. rsock.close
  104. else
  105. # Tell the client to get their read on.
  106. client = client_for_socket(rsock)
  107. text = client.command(:read, nil)
  108. end
  109. end
  110. end
  111. end
  112. end
  113. def self.run(port = 7000, host = '0.0.0.0', logname = 'woe.log')
  114. Monolog.setup_all(logname)
  115. server = self.new(port, host)
  116. server.run
  117. Monolog.close
  118. end
  119. end # class Server
  120. end # module Woe