Table of Contents

TCP/IP

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.

Client-side TCP

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.

Read Timeouts

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.

Connection lost

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

Server-side TCP

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?

Identifying the Client

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_ext2 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

Waiting for Data

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.

Competitive Comparison

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.

Other things to look at

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)