Writing an Internet Server
Problem
You want to run a server for a TCP/IP application-level protocol, but no one has written a Ruby server for the protocol yet. This may be because its a protocol youve made up.
Solution
Use the gserver library in Rubys standard library. It implements a generic TCP/IP server suitable for small to medium-sized tasks.
Heres a very simple chat server written with gserver. It has no end-user features to speak of. People connect to the server with a telnet client, and are identified to each other only by hostname. But its a fully functional, multithreaded, logging server written in about 30 lines of Ruby.
#!/usr/bin/ruby -w # chat.rb require gserver class ChatServer < GServer def initialize(port=20606, host=GServer::DEFAULT_HOST) @clients = [] super(port, host, Float::MAX, $stderr, true) end def serve(sock) begin @clients << sock hostname = sock.peeraddr[2] || sock.peeraddr[3] @clients.each do |c| c.puts "#{hostname} has joined the chat." unless c == sock end until sock.eof? do message = sock.gets.chomp break if message == "/quit" @clients.each { |c| c.puts "#{hostname}: #{message}" unless c == sock } end ensure @clients.delete(sock) @clients.each { |c| c.puts "#{hostname} has left the chat." } end end end server = ChatServer.new(*ARGV[0..2] || 20606) server.start(-1) server.join
Start the server in a Ruby session, and then use several instances of the telnet program to connect to port 20606 (from several different hosts, if you can). Your telnet sessions will be able to communicate with each other through the server. Your Ruby session will see a log of the connections and disconnections.
Discussion
The GServer class wraps Rubys underlying TCPServer class in a loop that continually receives TCP connections and spawns new threads to process them. Each new thread passes its TCP connection (a TCPSocket object) into the GServer#serve method, which your subclass is responsible for providing.
The TCPSocket works like a bidirectional file. Writing to it pushes data to the client, and reading from it reads data from the client. A server like the sample chat server reads one line at a time from the client; a web server would read the entire request before sending back any data.
In the chat server example, the server echoes one clients input to all the others. In most applications, the client sockets won even know about each other (think a web or FTP server).
The GServer constructor deserves a closer look. Heres its signature, from gserver.rb:
def initialize(port, host = DEFAULT_HOST, maxConnections = 4, stdlog = $stderr, audit = false, debug = false)
The port and host should be familiar to you from other types of server. maxConnections controls the maximum number of clients that can connect to the server at once. Because a chat server is very high-latency, I set the number effectively to infinity in ChatServer.
stdlog is an IO object to be used as a log. You can write a timestamped entry to the log by calling GServer#log. Setting audit to true turns on some default log messages: these are displayed, for instance, whenever a client connects to or disconnects from the server. Finally, setting debug to true means that, if your code throws an exception, the exception object will be passed into GServer#error. You can override this method to do your own error handling.
Gserver is easy to use, but not as efficient as a Ruby Internet server could be. For high-performance servers, youll want to use IO.select and TCPServer objects, programming to the C sockets API.
See Also
- ri GServer
Категории