The dRuby Book

1.1 Hello, World

Let’s create a server that prints out strings. Then we’ll code a simple client and use it to make the server print “Hello, World.” The client and server will each run in a separate process (and to make that easy, we’ll run each process from a separate terminal window).

Creating the Printing Server

puts00.rb is the puts server.

puts00.rb
Line 1  ​require 'drb/drb'
class Puts ​
def initialize(stream=$stdout)​
​ @stream = stream​
end
​​
def puts(str)​
​ @stream.puts(str)​
end
10  end
​uri = ARGV.shift​
​DRb.start_service(uri, Puts.new) ​
​puts DRb.uri​
​DRb.thread.join() ​

Let’s go through the script:

  1. On line 1, we require the drb library.

  2. We create a class called Puts on line 2. This class contains the puts method that we’ll make available to the client.

  3. On line 12, we start the dRuby service. We provide the URI (which the user passes in on the command line). The URL is the address the client uses to connect to the server. We also provide the object that will be tied to the URI. You’ll find out more about the URI in The dRuby URI, Services, and Clients.

  4. A dRuby service runs in a separate thread. One of the most common mistakes new dRuby programmers make is to forget that their program will simply exit unless they make sure to wait until the thread stops executing. On line 14, we use DRb.thread.join to keep the script up and running.

We’re going to use one terminal window to run the server. Let’s call it terminal 1. In that window, run puts00.rb, passing it the URI of the service.

  ​# [Terminal 1]​
  ​% ruby puts00.rb druby://localhost:12345​
  ​druby://localhost:12345​

The server process waits for the request to arrive. Make sure that the server doesn’t terminate, even after it prints out the URI of the service.

Using the Service from irb

The next step is to write the client. Rather than writing a program file, we’ll just use irb. Open another terminal (terminal 2) and type the following:

  ​# [Terminal 2]​
  ​% irb​
  ​irb(main):001:0> require 'drb/drb'​
  ​=> true​
  ​irb(main):002:0> there = DRbObject.new_with_uri('druby://localhost:12345')​
  ​=> #<DRb::DRbObject: ... >​

We start by requiring the drb library—the client and the server both need it. We then create a dRuby object (of class DRbObject) by calling DRbObject.new_with_uri (refer to OS X and readline if you encounter a problem getting the prompt back), passing it the same URI we used when creating the server. We store this object in the variable there.

Now we can use this dRuby object to access methods on the server. It’s as if the client has access to the Puts object we created on the server.

  ​irb(main):003:0> there.puts('Hello, World.')​
  ​=> nil​

We called the puts method of the Puts server (see Figure 1, Puts server and irb client). You should see “Hello, World.” printed on terminal 1 where the server is running.

images/d2puts00.png

Figure 1. Puts server and irb client
  ​% ruby puts00.rb druby://localhost:12345​
  ​druby://localhost:12345​
  ​Hello, World.​

That’s pretty cool. We needed only a few lines of code to create a simple distributed server.

If you didn’t notice any difference, try other characters. Make sure you observe the server terminal while you are typing in irb.

Back in irb on terminal 2, let’s call the server again.

  ​# [Terminal 2]​
  ​irb(main):004:0> there.puts('R is for Ruby.')​
  ​=> nil​

You should see the second message appear on terminal 1.

The there variable in the client refers to the Puts service object. By sending the puts method to the there variable, you invoke the puts method in the server, and it prints the object you pass to standard output.

What happens if you stop the server? Try it—type Ctrl-C on terminal 1 and make sure you get back to a command prompt.

Now, back on terminal 2, call there.puts again.

  ​# [Terminal 2]​
  ​irb(main):005:0> there.puts('Hello, again.')​
  ​DRb::DRbConnError: druby://localhost:12345 - #<Errno::ECONNREFUSED....​

dRuby raised an exception. DRbConnError means that there is a communication error between dRuby processes. The client failed to invoke the method because the server is stopped.

Let’s start the server again in terminal 1.

  ​# [Terminal 1]​
  ​% ruby puts00.rb druby://localhost:12345​
  ​druby://localhost:12345​

Try there.puts again.

  ​# [Terminal 2]​
  ​irb(main):006:0> there.puts('Hello, again.')​
  ​=> nil​

This time, there is no exception, and you should see “Hello, again.” printed on terminal 1.

  ​# [Terminal 1]​
  ​% ruby puts00.rb​
  ​druby://localhost:12345​
  ​Hello, again.​

Let’s stop irb for now.

  ​# [Terminal 2]​
  ​irb(main):007:0> exit​

Creating the Script Version of the Client

As a final “Hello, World” experiment, let’s rewrite this as a script.

hello00.rb
  ​require 'drb/drb'
  ​​
  ​uri = ARGV.shift​
  ​there = DRbObject.new_with_uri(uri)​
  ​there.puts('Hello, World.')​

As you can see, this script contains most of the same code that you typed into irb, except it gets the URI from the command line. Let’s try it. Run hello00.rb in terminal 2.

  ​# [Terminal 2]​
  ​% ruby hello00.rb druby://localhost:12345​

You should see “Hello, World” appear on terminal 1.

So far, we’ve experimented with a simple dRuby example, and we’ve seen how easy it is to write a client-server model script. It’s time to go a little further.

The dRuby URI, Services, and Clients

In the previous example, we used druby://localhost:12345 as a URI, but we haven’t seen what this actually means. In this section, we’ll learn about the relationship between the dRuby URI and the URI specified in DRb.start_service.

A dRuby URI defines the path to a dRuby server. It consists of the protocol (always druby), an optional hostname, and an optional port number.

  ​druby://[hostname]:[port number]​

When you create a service (using DRb.start_service), you give it a URI. dRuby arranges things so that clients that subsequently specify that URI will be connected to this service. Each active DRbServer has one unique URI.

  ​DRb.start_service(uri, front)​

To connect a client to a service, pass that service’s URI to DRbObject.new_with_uri.

  ​there = DRbObject.new_with_uri(uri)​

An object that’s associated with the URI is called the front object because it acts as an entrance to the service (see Figure 2, The front object is the gateway to the application). All the method calls that are created by DRbObject.new_with_uri() go to this front object. When you write an actual application, you don’t directly associate the model object of the application; rather, you have a proxy object that handles access control or batches multiple operations.

images/d2front3.png

Figure 2. The front object is the gateway to the application.