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:
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:
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 -> WorldBut now we'll treat it as:
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 KeyEvent -> [Or World Package]
An [Or X Y] is one of:
- X
- Y
World Message -> PackageAs 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.
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:
New connection events happen when a client connects to the server, and are handled by the on-new clause.
The contract for functions given to on-new is:
UniverseState IWorld -> Bundle
Disconnections happen when a client disconnects from the server, and are handled by the on-disconnect clause.
The contract for functions given to on-disconnect is:
UniverseState IWorld -> Bundle
Messages are sent from the clients to the server, and are handled by the on-msg clause.
The contract for functions given to on-msg is:
UniverseState IWorld Message -> Bundle
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.
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:
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).