Our First Client/Server
Whew.
We’ve now looked at establishing connections and a whole lot of information about exchanging data. Up until now we’ve mostly been working with very small, self-contained bits of example code. It’s time to put everything we’ve seen together into a network server and client.
The Server
For our server we’re going to write the next NoSQL solution that no one has heard of. It’s just going to be a network layer on top of a Ruby hash. It’s aptly named CloudHash
.
Here’s the full implementation of a simple CloudHash
server:
require 'socket'
module CloudHash
class Server
def initialize(port)
# Create the underlying server socket.
@server = TCPServer.new(port)
puts "Listening on port #{@server.local_address.ip_port}"
@storage = {}
end
def start
# The familiar accept loop.
Socket.accept_loop(@server) do |connection|
handle(connection)
connection.close
end
end
def handle(connection)
# Read from the connection until EOF.
request = connection.read
# Write back the result of the hash operation.
connection.write process(request)
end
# Supported commands:
# SET key value
# GET key
def process(request)
command, key, value = request.split
case command.upcase
when 'GET'
@storage[key]
when 'SET'
@storage[key] = value
end
end
end
end
server = CloudHash::Server.new(4481)
server.start
The Client
And here’s a simple client:
require 'socket'
module CloudHash
class Client
class << self
attr_accessor :host, :port
end
def self.get(key)
request "GET #{key}"
end
def self.set(key, value)
request "SET #{key} #{value}"
end
def self.request(string)
# Create a new connection for each operation.
@client = TCPSocket.new(host, port)
@client.write(string)
# Send EOF after writing the request.
@client.close_write
# Read until EOF to get the response.
@client.read
end
end
end
CloudHash::Client.host = 'localhost'
CloudHash::Client.port = 4481
puts CloudHash::Client.set 'prez', 'obama'
puts CloudHash::Client.get 'prez'
puts CloudHash::Client.get 'vp'
Put It All Together
Let’s stitch it all together!
Boot the server:
$ ruby code/cloud_hash/server.rb
Remember that the data structure is a Hash. Running the client will run the following operations:
$ tail -4 code/cloud_hash/client.rb
puts CloudHash::Client.set 'prez', 'obama'
puts CloudHash::Client.get 'prez'
puts CloudHash::Client.get 'vp'
$ ruby code/cloud_hash/client.rb
Thoughts
So what have we done here? We’ve wrapped a Ruby hash with a network API, but not even the whole Hash
API, just the getter/setter. A good chunk of the code is boilerplate networking stuff, so it should be easy to see how you could extend this example to expose more of the Hash
API.
I commented the code so you can get an idea of why things are done the way they are, I made sure to stick to concepts that we’ve already seen, such as establishing connections, EOF, etc.
But overall, CloudHash
is kind of a kludge. In the last few chapters we’ve gone over the basics of establishing connections and exchanging data. Both of those things were applied here. What’s missing from this example is best practices about architecture patterns, design, and some advanced features we haven’t seen yet.
For example, notice that the client has to initiate a new connection for each request it sends? If you wanted to send a bunch of requests all in a row,, each would require its own connection. The current server design requires this. It processes one command from the client socket and then closes it.
There’s no reason why this needs to be the case. Establishing connections incurs overhead and it’s certainly possible for CloudHash
to handle multiple requests on the same connection.
This can be remedied in a few different ways. The client/server could communicate using a simple message protocol that doesn’t require sending EOF to delimit messages. This change would allow for multiple requests on a single connection but the server will still process each client connection in sequence. If one client is sending a lot of requests or leaving their connection open for a long time, then other clients will not be able to interact with the server.
We can resolve this by building some form of concurrency into the server. The rest of the book builds on the basic knowledge you have thus far and focuses on helping you write efficient, understandable, and sane network programs. CloudHash
, as it stands, does not provide a good example of how socket programming should be done.