I’ve had this small project in mind to write a server which would execute arbitrary ruby. I didn’t want to have a concept of state, so assignment and memory management weren’t a big deal. It should:
1. Accept a class method, like puts “Hello, world!”
2. Output on the server “Hello, world!”
3. Output on the client “puts “Hello, world!””
I knew GServer existed in the stdlib, but didn’t know how to work it. I saw a neat trick on the peepcode screencast with Aaron Patterson: download the source tree and ask _that_ your questions, don’t bother with the docs.
I downloaded ruby’s source tree, kind of nervous, and found the GServer implementation using sublime text’s fuzzy search function. The comments said to just inherit from the class and implement the serve method, and all would rock.
I was surprised by how much cleared reading the source and comments were than reading the rdoc that’s generated from those same source and comments. I was reading through the code really happily, learning about GServer, and managed to get the functionality I was looking for in about 20 minutes. If you want to download the code, it’s available on github: https://github.com/jamandbees/arbitrary-server
One of the neat things reading the code was that I actually read the implementation of each method, and I noticed something in the start method that got me thinking. There’s a class to create a new TCPServer:
@tcpServer = TCPServer.new(@host,@port) — from GServer.rb
The requires for the class are socket and thread, so I assumed this came from socket. I read socket.rb, though, and I honestly didn’t see a TCPServer definition, so I checked its requires: socket.so.
Socket.so is a compiled library. I did a quick search for socket.c and found documentation in there that references TCPServer.new, but which doesn’t seem to have any implementation that would recognise the name “TCPServer.new”.
Socket.c includes rubyserver.h. Reading that, I saw these two lines:
extern VALUE rb_cTCPServer;
void rsock_init_tcpserver(void);
So we’re mentioning a tcpserver in an included library, finally!
From here, I didn’t know where to go.I did a search of the source tree for tcpserver and found tcpserver.c. It also includes rubyserver.h, so presumably somewhere in that include is where the relationship between socket and tcpserver is created, but I can’t see where.
I do see where the class is made, though:
void
rsock_init_tcpserver(void)
{
/*
* Document-class: TCPServer < TCPSocket
*
* TCPServer represents a TCP/IP server socket.
*
* A simple TCP server may look like:
*
* require 'socket'
*
* server = TCPServer.new 2000 # Server bind to port 2000
* loop do
* client = server.accept # Wait for a client to connect
* client.puts "Hello !"
* client.puts "Time is #{Time.now}"
* client.close
* end
*
* A more usable server (serving multiple clients):
*
* require 'socket'
*
* server = TCPServer.new 2000
* loop do
* Thread.start(server.accept) do |client|
* client.puts "Hello !"
* client.puts "Time is #{Time.now}"
* client.close
* end
* end
*
*/
rb_cTCPServer = rb_define_class("TCPServer", rb_cTCPSocket);
rb_define_method(rb_cTCPServer, "accept", tcp_accept, 0);
rb_define_method(rb_cTCPServer, "accept_nonblock", tcp_accept_nonblock, 0);
rb_define_method(rb_cTCPServer, "sysaccept", tcp_sysaccept, 0);
rb_define_method(rb_cTCPServer, "initialize", tcp_svr_init, -1);
rb_define_method(rb_cTCPServer, "listen", rsock_sock_listen, 1); /* in socket.c */
}
So, as long as I can get from tcpserver.c through to socket.c, I think I’ll be okay.
Funny how you sometimes wander down paths.