briantheprogrammer

Home of the O2 Programming Language

An Echo Server In O2

Posted by Brian Andersen on June 12, 2012

Hello everyone I am back after a brief posting hiatus. I have spent the last few weeks working on O2′s facility for distributed computing. In my last series on concurrency I demonstrated how O2 can be used to build highly concurrent programs that share state using O2′s blackboard. O2′s concurrency facility makes it possible to write programs that communicate with themselves. But what about programs that communicate with the outside world, especially other O2 programs? In the next series of posts I will demonstrate how O2 programs can communicate with other from a distance, that is, without use of a common blackboard.

Introducing Bots

The basic unit of distributed computation in O2 is a bot. The wikipedia article on bots lists some examples of how the term bot is used in computing:

  • Web crawler or Web spider, a computer program that does automated tasks
  • Internet bot, a computer program that does automated tasks
  • Chatterbot or Chat bot, a computer program that converses in natural language
  • Internet Relay Chat bot, a computer program connected to an Internet Relay Chat server as a user, but providing special services or performing special functions
  • Computer game bot, a computer-controlled player or opponent
  • Bots (edi), an open-source EDI software
  • BOTS, a computer game
  • Bot, a line of budget desktop PCs manufactured by Alienware

So basically, a bot is some program that sits on a computer network, sends and receives messages with other programs, and hopefully does some useful and beneficial task. In terms specific to O2, a bot is a collection of fibers all connected to a single blackboard. You can create multiple bots within a single instance of O2 running on a single computer, but they will not be able to communicate using the blackboard because each will have its own blackboard that is isolated from the others. Communication between bots needs to happen via other means.

Introducing Send and Receive, Accept and Reply, Listen and Open

In my concurrency programs I made heavy use of the operators read and write, along with their variants like dispatch, throttle, peek, and gawk. In a similar vein, I am creating a suite of operators for inter-bot messaging. It is based on a paradigm of asynchronous request and response. A bot can initiate communication with another bot using the send operator to send a message, and the receive operator to get a response back. Or it can wait for someone to send a message to it using the accept operator, and can then respond using the reply operator. This is an asynchronous paradigm because sending messages out is a separate operation from receiving responses. This affords more control to the programmer than the synchronous, Remote Procedure Call model where sending messages out and getting responses back is a single operation. For example, a single bot can fire off several requests to other bots and let them work in parallel while waiting for all the results to come back, rather than doing one request at a time. But O2′s innovative syntax allows you to keep the code concise regardless of which messaging pattern you want to follow.

In the example below you will see two other operators; listen and open which allow you to listen for client connections and open connections to a server. I will show you how all of these operators work together in the context of a simple echo client and server. The client sends out requests to the server and the server echo’s the messages back as it sees them.

The Code

{ server:

This is going to be the definition of the server bot. Creating a new bot is just as easy as creating a fiber.

  { echo:
    { message:$L accept 1l
      :$message.id reply eval {text:"ECHO:" + $message.body.text}
      <-$L echo $R
    }

As I mentioned above, a bot is just a collection of fibers tied to a single blackboard. So within the definition for a bot is where you will define and start fibers for that bot. This is the echo operator which will be launched as a new fiber within the server bot. The left argument to the echo operator is the integer handle to the server socket. So the accept operator waits for messages from the server. The message that is received is a block that has at least two children; an id which is a symbol that you can use to reply back to that message, and a body which is the content sent from the client. So echo uses the reply operator, passing the id on the left and the body of the reply on the right. At the end it calls itself in order to process the next message.

    :fiber {<-(listen $L) echo $R}
    <-wait 0l
  }

Whenever you define a bot, the initial fiber that is created for that bot will have the number zero. So in this bot we launch a new child fiber for the echo process, and wait forever for the initial fiber to finish. Notice that we call the listen operator using the left argument to the bot, which will be a symbol indicating the network protocol and port number to use. Now let's look at the definition of the client bot.

  client:
  { talk:
    { message:"hello? is anyone there?"
      :cwrite (string $R) + " SEND:" + $message
      response:receive $L send eval {text:$message}
      :cwrite (string $R) + " RECV:" + $response.body.text
      <-$L talk $R
    }

Following a similar pattern to the server. The client bot has one child fiber called "talk". It uses the send operator to send the message in a block to the server. In this case the left argument $L contains the integer handle of the client connection that will be setup before entering the talk fiber. On the other hand, receive waits for a response to the message id returned by the send operator. Once the response arrives, the talk fiber prints out the text of the response on the console and repeats.

    :fiber {<-(open $L) talk $R}
    <-wait 0l
  }

The client bot creates a child fiber for the talk operator and then waits forever in its main fiber, whose number is always 0l. open is used to create a connection to the server host and port configured in the left argument of the client bot. You will see how this works below.

  :bot {<-#tcp,localhost,9090l client #talk,0l}
  :bot {<-#tcp,localhost,9090l client #talk,1l}
  :bot {<-#tcp,localhost,9090l client #talk,2l}
  :bot {<-#tcp,9090l server #echo,0l}
  <-wait 0l
}

This is where we create the four bots, three clients and two servers. As you can see creating a bot is very much like creating a fiber. You just pass a block to the bot operator containing the code which you want the new bot to execute. The listen and open operators both take symbols which describe the network protocol to be used along with additional information such as the host and port number. In this case we are using the tcp interface. I am working on other interfaces for http, disk files, and so on. Here is the complete echo server program, client and server:

{
  server:
  { echo:
    { message:$L accept 1l
      :$message.id reply eval {text:"ECHO:" + $message.body.text}
      <-$L echo $R
    }
    :fiber {<-(listen $L) echo $R}
    <-wait 0l
  }
  client:
  { talk:
    { message:"hello? is anyone there?"
      :cwrite (string $R) + " SEND:" + $message
      response:receive $L send eval {text:$message}
      :cwrite (string $R) + " RECV:" + $response.body.text
      <-$L talk $R
    }
    :fiber {<-(open $L) talk $R}
    <-wait 0l
  }
  :bot {<-#tcp,localhost,9090l client #talk,0l}
  :bot {<-#tcp,localhost,9090l client #talk,1l}
  :bot {<-#tcp,localhost,9090l client #talk,2l}
  :bot {<-#tcp,9090l server #echo,0l}
  <-wait 0l
}

Example Output

Welcome to O2, ask brian if you need help
O2>eval fload "../../demo/messaging/echo.o2"
#talk,0l SEND:hello? is anyone there?
#talk,1l SEND:hello? is anyone there?
#talk,2l SEND:hello? is anyone there?
#talk,0l RECV:ECHO:hello? is anyone there?
#talk,1l RECV:ECHO:hello? is anyone there?
#talk,0l SEND:hello? is anyone there?
#talk,1l SEND:hello? is anyone there?
#talk,2l RECV:ECHO:hello? is anyone there?
#talk,2l SEND:hello? is anyone there?
#talk,0l RECV:ECHO:hello? is anyone there?
#talk,1l RECV:ECHO:hello? is anyone there?
#talk,0l SEND:hello? is anyone there?
#talk,1l SEND:hello? is anyone there?
#talk,2l RECV:ECHO:hello? is anyone there?
#talk,2l SEND:hello? is anyone there?
#talk,0l RECV:ECHO:hello? is anyone there?
#talk,0l SEND:hello? is anyone there?
#talk,1l RECV:ECHO:hello? is anyone there?
#talk,1l SEND:hello? is anyone there?
#talk,2l RECV:ECHO:hello? is anyone there?
#talk,2l SEND:hello? is anyone there?
#talk,0l RECV:ECHO:hello? is anyone there?
#talk,0l SEND:hello? is anyone there?
#talk,1l RECV:ECHO:hello? is anyone there?
#talk,1l SEND:hello? is anyone there?
#talk,2l RECV:ECHO:hello? is anyone there?
#talk,2l SEND:hello? is anyone there?
#talk,0l RECV:ECHO:hello? is anyone there?
#talk,1l RECV:ECHO:hello? is anyone there?
#talk,0l SEND:hello? is anyone there?
#talk,1l SEND:hello? is anyone there?
#talk,2l RECV:ECHO:hello? is anyone there?
#talk,2l SEND:hello? is anyone there?
#talk,0l RECV:ECHO:hello? is anyone there?
#talk,0l SEND:hello? is anyone there?
#talk,1l RECV:ECHO:hello? is anyone there?
#talk,1l SEND:hello? is anyone there?
#talk,2l RECV:ECHO:hello? is anyone there?
#talk,2l SEND:hello? is anyone there?
#talk,0l RECV:ECHO:hello? is anyone there?

Conclusion

In this demo we explored O2's distributed messaging operators and the basic unit of distributed computation; bots. An echo server is about the closest I can get to a "Hello World" of distributed computing. Using bots and the basic messaging operators introduced in this article, we can develop some sophisticated distributed applications.

Leave a Comment

Your email address will not be published. Required fields are marked *

*

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>