CS 2500 Lab 11: Universe

In the beginning...

In this lab, you'll be writing a chat server. In addition, you'll be converting an existing chat client, which only works on a single machine, to interact with your chat server. Download the initial chat client, and try running it in DrScheme.

The rest of this lab will be spent doing three things:

  1. altering this existing chat client to work with an existing chat server,
  2. writing a compatible chat server, and
  3. altering the protocol between the two to allow for easier extensions.
Afterwards, we provide several possible extensions for you to try implementing, or feel free to implement your own!

Altering the client for the chat server

The Universe teachpack allows for client/server programs—that is, programs where a single server mediates between multiple clients. Clients and the server communicate through a series of messages. To start a client, we use big-bang as before. To start a server, the universe form is used instead. A longer description of client/server programming can be found on the Help Desk page for 2htdp/universe.

Client programs look very similar to the kinds of interactive programs you've already written. There are just a few changes:

  1. new clauses to connect to a server, and
  2. new protocols for sending information to and receiving information from the server.

For specifying a server, we use the register clause. It takes either a hostname or IP address and, when the big-bang form is executed, the teachpack attempts to connect to that machine. If the connection fails, the program will still run, but no communication will occur.

In addition, a client should specify a name using the name clause. It takes a string which will be used as an identifying name to the server. In our chat program, we will use this name as the nickname of the connecting user.

To send messages to the server, functions that originally returned a new World can instead return a new package. For example, the on-key contract was originally:

World KeyEvent -> World
But now we'll treat it as:

World KeyEvent -> [Or World Package]

An [Or X Y] is one of:

A Package consists of two items, a new world and a message to send to the server. To create a package, the make-package function is used, whose contract is:
World Message -> Package
As with World, what makes up a Message is up to the server and its clients. At first, we will use Strings as Messages.

To receive messages from the server, the on-receive clause is used. It takes a function with the following contract:

World Message -> [Or World Package]

We already have a chat server running at IP dublin.ccs.neu.edu. The first part of this lab is changing the client to interact with this server. This requires the client to send the editor line to the server whenever the user hits Enter and to receive messages from the server whenever a client sends out an update and use those messages to update its own display. (Unlike the current client, it should only update the display with new messages from the server. The server will send the messages from a given client to all clients, including the original sender.)

Problem 1: Update the handle-key function to return a Package instead of a World whenever the user hits Enter. As stated in the parenthetical above, it should not add the new text to the buffer like the current version, but instead just send it to the server.

Problem 2: Design a handle-msg function that can be used with the on-receive clause. This new function should take a Message (here, a String) and add it to the end of the buffer. Once you have designed it, add it to the big-bang form.

Problem 3: Add appropriate receive and name clauses to your big-bang form. Notice that the run function already takes a nickname as an argument.

Now try running your program—it should now function as a chat client, and you may see other people connected at the same time (and can send them messages). We'll put a connected client up on the overhead so you can see people connect, send messages, and disconnect as they work on the lab.

Writing the server

To write a server, we will be using the universe form. It is similar to big-bang, and we'll be interested in the following events: new connections, disconnections, and messages. Here's how we'll be using those events:

An IWorld is an datatype internal to the universe teachpack that describes a client connection. For the purpose of this lab, there is one operation you can perform on an IWorld, which is iworld-name. iworld-name takes an IWorld to a String which represents a name for the client.

A Bundle is a collection of three things: a (new) UniverseState, a list of Mails (described later) to be sent to clients, and a list of IWorlds to be disconnected. For our purposes, the latter will either be empty (in most cases) or the singleton list (for handling disconnections). A Bundle is created using the make-bundle function, whose contract is:

UniverseState [Listof Mail] [Listof IWorld] -> Bundle

The server we design will keep track of the connected clients, and any time a client sends a message to the server, which will be a String, the server sends a Mail to each connected client (including the sending client) that contains a String containing both the client's name and the message sent by the client.

To create a Mail value, the make-mail function is used, whose contract is:
IWorld Message -> Mail

For example, if the client with the name "sstrickl" sends the message "hello" to the server, then each client should receive a Mail value from the server that contains the String "sstrickl: hello".

In addition, the server should send a Message (as before, a String) to each connected client whenever a client connects or disconnects. For example, if the client "sstrickl" connects, each client will receive the message "sstrickl connected.", and if it later disconnects, each (still-connected) client will receive the message "sstrickl disconnected."

Problem 4: Design a UniverseState data defintion to be used by the server.

Problem 5: Design a function handle-new that handles new connections to the server.

Problem 6: Design a function handle-disconnect that handles clients that disconnect from the server.

Problem 7: Design a function handle-msg that handles new messages from clients to the server.

Altering the protocol

Right now the server and its clients send around Strings as Messages. It would make sense to make the protocol more extensible, to deal with future extensions to either. We will do this by changing Messages to be a non-empty list of Strings. The first String defines what type of Message is being described, and the meaning of any other strings in the list depend on the type. For example:

  1. The Message (list "JOIN" <name>) describes that a client with the name <name> has connected.
  2. The Message (list "PART" <name>) describes that a client with the name <name> has disconnected.
  3. The Message (list "MSG" <name> <msg>) describes that the client with the name <name> sent the given message.
To avoid spoofing, this protocol is used by the server to communicate to the clients. When clients send "MSG" Messages to the server, they do not send a <name>—the server adds the appropriate client name before sending the messages to clients. In other words, the only Message sent from clients to the server at this point is (list "MSG" <msg>); the other Messages are generated server-side for connections and disconnections. For other, later extensions, the server should add names as appropriate to Messages. We suggest that in future extensions, the second position in the list (i.e. after the Message type) is reserved for the client name, so that adding it is a uniform operation.

Problem 8: Change the protocol as described. In the parts of the client and server that deal with receiving messages, make it so that Messages which are not understood are dropped.

Problem 9: Add actions (messages which denote an action, not normal text). The user specifies actions by starting a message with "/me " (which is not part of the actual action). This will require changes to the client and server for full functionality. Use the string "ACTION" for the Message type.

It will be useful to first design a string-prefix? function, that takes two strings and checks to see if the second string is a prefix of the first and a string-remove-prefix function that takes two strings and returns a new string that is the same as the first string except with the second string removed as a prefix (substring is useful for both).

Possible extensions

Now that clients have the list of connected clients and the protocol is more extensible, you can change the client (and server) in many ways: