TCP/IP channels come in two varieties: client-side channels and server-side channels. Let me show you how to create both:
client = Cod.tcp('localhost:12345')
server = Cod.tcp_server('localhost:12345')
client.put 'tcp channels!'
server.get # => "tcp channels!"
client.close
server.close
To do simple communication across machines, this will be all you need. But that’s only where things start, not where they end. In the following sections, I’ll explain more about tcp clients and servers.
The channel returned by Cod.tcp
really acts in two phases:
This two-phase connect is the reason why cod programs can send stuff to a server that might not even be listening yet. This is very useful in practice.
If you want to force a connection, try to read from the client channel. It will block forever until data comes in. The next example blocks until a timeout occurs:
channel = Cod.tcp('localhost:12345')
require 'timeout'
begin
value = timeout(0.1){ channel.get }
rescue Timeout::Error
end
Since there is normally no server on localhost:12345, the code will just block and wait, trying to make a connection. After 0.1 seconds, the timeout occurs and we abort processing.
We have chosen not to handle timeouts in cod at all. A connection that did
not deliver the data in time will often need to be reset; the data might still
come later, and the client might not be in a condition to handle it. We recommend using timeout.rb
for all your outtiming needs.
When the server terminates the connection to our client, we’ll eventually get
a Cod::ConnectionLost
error raised. This is pretty much the only
error you have to expect and handle.
# Read until the connection breaks.
begin
loop { client.get }
rescue Cod::ConnectionLost
end
Servers are a little more complex to write. Here’s how the inner loop might look like:
# Get request
channel.get
# Put answer
channel.put # XXX doesn't exist
But there is no Channel#put
for tcp server channels! A tcp server
can of course have more than one client. There would be no way to tell which
client should receive the answer.
The easy answer to solving this problem is to tell the server where to send its answers. Here’s a working example of this:1
client do
channel = Cod.tcp('127.0.0.1:12345')
version = channel.interact [:ehlo, channel]
version # => [:version, 1]
other = channel.interact [:bark, channel]
other # => :unknown_command
# Tell the server to shut down.
channel.put [:shutdown, nil]
end
pid = server do
channel = Cod.tcp_server('127.0.0.1:12345')
loop do
msg, client = channel.get
case msg
when :ehlo
client.put [:version, 1]
when :shutdown
break
else
client.put :unknown_command
end
end
end
# Wait for the server to terminate
Process.wait pid
In the client code (client do ... end
) you might notice the
#interact
method, which hasn’t been introduced yet. It is defined
as:
def interact(*args)
channel.put *args
return channel.get
end
The server code server do ... end
retrieves both the message and
the back-channel from the its channel.get
. It then uses list
comprehension to split that tuple into its components.
Of course, in your code, server and client would be on different machines. Otherwise what is the point. Right?
As shown above, the clients can simply send their connection handle through itself. Like so:
channel.put channel
But sending the back-channel along as part of the message has a few downsides:
There is a better method to handle client connections in cod:
# Just the innermost server-side loop again
msg, back_channel = channel.get_ext
case msg
when ...
back_channel.put :answer
end
Using #get_ext
2 allows you to retrieve both the message that
was sent and the back channel in one command. If you close that channel, you
will terminate the clients connection.
Here’s the revised example from above:
client do
channel = Cod.tcp('127.0.0.1:12345')
version = channel.interact :ehlo
version # => [:version, 1]
other = channel.interact :bark
other # => :unknown_command
# Tell the server to shut down.
channel.put :shutdown
end
pid = server do
channel = Cod.tcp_server('127.0.0.1:12345')
loop do
msg, client = channel.get_ext
case msg
when :ehlo
client.put [:version, 1]
when :shutdown
break
else
client.put :unknown_command
end
end
end
# Wait for the server to terminate
Process.wait pid
Most of the time, your server will have to lurk around, waiting for clients to connect and waiting for data. As we have shown above, waiting for new connections is implicit with cod. Let’s look at how to wait for data to arrive.
In the simplest case, you just call #get
and have cod block:
channel.get # Blocks until data arrives
But in some cases, you’ll want to wait on several sockets. cod features an
extended select: This example assumes that you have two
channels you want to wait on, hodge
and podge
, and
that podge becomes available first:
Cod.select(1, [hodge, podge]) # => [podge] (timeout 1 second)
In the chapter on select you’ll find more on this
special select syntax. Reading up on this is worth your while – select
helps test which selectors have become available, something the plain old
IO.select
doesn’t do.
Although we’re pretty sure that cod doesn’t perform badly, it probably will not quite match your evented/fiberthreaded/control-inversed server. Writing such a server is still hard. Comparing something you wrote in 5 minutes using cod to a program tuned for IO will not be a fair comparison.
Evented IO is mostly overkill in day to day programming. Threading can be and often is actually harmful. What cod proposes is a saner and less convoluted way of expressing your needs. Make your choice.
In the chapter on serialisation you’ll find all about cods wire format. Turns out, beanstalkd channels are really just tcp with the proper serialisation on top! Almost, at least.
If you need selects
, be sure to point your mouse at the chapter
on select. It
1 #client
and #server
are helper methods that
fork a client or a server process respectively. Treat them as synonymous to
Kernel.fork
.
2 @code_link(Cod::TcpServer#get_ext)