fserver.rb 3.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197
  1. require 'socket'
  2. require 'fiber'
  3. include Socket::Constants
  4. class FServer
  5. def initialize
  6. @timers = {}
  7. @reading = []
  8. @writing = []
  9. @clients = {}
  10. @client_id = 0
  11. end
  12. class Client
  13. attr_reader :io
  14. attr_reader :id
  15. def initialize(server, id, io)
  16. @server = server
  17. @id = id
  18. @io = io
  19. @fiber = Fiber.new { serve }
  20. @busy = true
  21. end
  22. def alive?
  23. @fiber.alive? && @busy
  24. end
  25. def command(cmd, args)
  26. @fiber.resume(cmd, args)
  27. end
  28. def write(data)
  29. @io.write(data)
  30. end
  31. def on_start
  32. p "Starting client fiber"
  33. return nil
  34. end
  35. def on_write(data)
  36. p "On write:"
  37. self.write("Client #{socket}:")
  38. self.write(args)
  39. end
  40. def on_read
  41. data = @io.readpartial(4096)
  42. p "After read: #{data}"
  43. @io.flush
  44. return data
  45. end
  46. def wait_for_input
  47. loop do
  48. cmd, arg = Fiber.yield
  49. data = nil
  50. case cmd
  51. when :start
  52. on_start
  53. when :read
  54. return on_read
  55. when :write
  56. on_write(arg)
  57. else
  58. p "Unknown command #{cmd}"
  59. end
  60. end
  61. end
  62. def ask_login
  63. @login = nil
  64. while @login.nil? || @login.empty?
  65. write("Login:")
  66. @login = wait_for_input.chomp
  67. end
  68. true
  69. end
  70. def ask_password
  71. @password = nil
  72. while @password.nil? || @password.empty?
  73. write("\r\nPassword:")
  74. @password = wait_for_input.chomp
  75. end
  76. true
  77. end
  78. def handle_command
  79. order = wait_for_input.chomp
  80. case order
  81. when "/quit"
  82. write("Byebye!\r\n")
  83. @busy = false
  84. else
  85. @server.broadcast("#@login said #{order}\r\n")
  86. end
  87. end
  88. def serve()
  89. data = nil
  90. lok = ask_login
  91. return false unless lok
  92. pok = ask_password
  93. return false unless pok
  94. write("\r\nWelcome #{@login} #{@password}!\r\n")
  95. while @busy do
  96. handle_command
  97. end
  98. end
  99. end
  100. def start
  101. @server = TCPServer.new('localhost', 7000)
  102. @reading << @server
  103. serve
  104. end
  105. def add_client
  106. socket = @server.accept_nonblock
  107. @reading << socket
  108. @client_id += 1
  109. client = Client.new(self, @client_id, socket)
  110. @clients[socket] = client
  111. client.command(:start, nil)
  112. puts "Client #{socket} connected"
  113. return client
  114. end
  115. def broadcast(message)
  116. @clients.each_pair do | id, client |
  117. client.write(message + "\r\n")
  118. end
  119. end
  120. def add_timer(id, delta = 1)
  121. now = Time.now
  122. stop = now + delta
  123. @timers[id.to_sym] = stop
  124. end
  125. def serve
  126. add_timer(:test, 15)
  127. loop do
  128. # Kick out clients that disconnected.
  129. disconnected = @clients.reject { |c, cl| cl.alive? }
  130. disconnected.each do |c, cl|
  131. rsock = c
  132. @clients.delete(rsock)
  133. @reading.delete(rsock)
  134. rsock.close
  135. end
  136. # Nodify timers that expired
  137. now = Time.now
  138. expired = @timers.select { |k, t| t < now }
  139. expired.each do |k, t|
  140. broadcast("Timer #{k} expired: #{t}, #{now}.\n\r")
  141. @timers.delete(k)
  142. end
  143. readable, writable = IO.select(@reading, @writing, nil, 0.1)
  144. if readable
  145. readable.each do | rsock |
  146. if rsock == @server
  147. add_client
  148. # Kick out clients with broken connections.
  149. elsif rsock.eof?
  150. @clients.delete(rsock)
  151. @reading.delete(rsock)
  152. rsock.close
  153. else
  154. # Tell the client to get their read on.
  155. client = @clients[rsock]
  156. text = client.command(:read, nil)
  157. end
  158. end
  159. end
  160. end
  161. end
  162. end
  163. server = FServer.new
  164. server.start