- require "socket"
- # Namespace everything under DND
- module DND
- Users = {
- 'simone' => 'secretpassword',
- 'carl' => 'whatever',
- }
- Delimiter = "\r\n"
- class Server
- attr_reader :socket, :server_thread, :clients
- def initialize(host='0.0.0.0', port=20_000)
- puts "[I] Initialising server"
- @keep_running = true
- @host = host
- @port = port
- @socket = TCPServer.new(@host, @port)
- @clients = []
- @connection_id = 0
- end
- def stop
- @keep_running = false
- end
- # blocks
- def run
- @keep_running = true
- puts "Listening on #{@host}:#{@port}"
- accept_connection while @keep_running
- end
- def accept_connection
- return unless IO.select([@socket], nil, nil, 0.1)
- socket = @socket.accept
- connection_id = new_connection_id # you could just use the socket's object-id or something...
- puts("[Connection]\tAccepted connection with ID: #{connection_id}")
- client = Client.new(self, socket, connection_id)
- if client.login then
- puts("[Connection]\tConnection ID #{connection_id} logged in as #{client.username}")
- client.welcome
- @clients << client
- client.run_thread
- else
- client.disconnect
- end
- end
- def new_connection_id
- @connection_id += 1 # the last expression's value is the return value already
- end
- def disconnect(client)
- @clients.delete(client)
- end
- end
- class Client
- attr_reader :socket, :connection_id, :username
- def initialize(server, socket, connection_id)
- @server = server
- @socket = socket
- @username = nil
- @password = nil
- end
- def login
- say("Welcome to ZombiServer v0.01")
- @username = ask("Your username: ")
- @password = ask("Password: ")
- if Users[@username] == @password then
- true
- else
- @username = nil
- @password = nil
- false
- end
- end
- def welcome
- say("You have successfully logged in as #{@username}")
- end
- def disconnect
- say("You're being disconnected")
- @socket.close
- @server.disconnect(self)
- end
- def ask(question)
- @socket.print(question)
- @socket.flush # IO's are line buffered, you have to flush if you want to emit an incomplete line
- @socket.gets.chomp # chomp removes the trailing newline
- end
- def say(line)
- @socket.print(line, Delimiter)
- end
- def run_thread
- Thread.new do
- while line = @socket.gets # gets returns nil at eof
- process(line.chomp)
- end
- end
- end
- def process(line)
- puts "[Command] User wrote: #{line}"
- case line
- when /\Aquit\z/i
- disconnect
- when /\Ausers\z/i
- say("There are currently #{@server.clients.size} connected users")
- else
- say("Unknown command: #{line.inspect}")
- say("Known commands are: users, quit")
- end
- end
- end
- end
- server = DND::Server.new
- trap("SIGINT") { # handle ctrl-C
- trap("SIGINT", "SYSTEM_DEFAULT") # restore original signal handler
- server.stop
- }
- server.run
- puts "","Server terminated"