An Internet Chat System This chapter develops a useful UDP version of an Internet chat system. Like other chat systems that might be familiar to you, the software consists of a server that manages multiple discussion groups called "channels." Users log into the server using a command-line client, join whatever channels they are interested in, and begin exchanging public messages. Any public message that a user sends is echoed by the server to all members of the user's current channel. The server also supports private messages, which are sent to a single user only by using his or her login name . The system notifies users whenever someone joins or departs one of the channels they are monitoring. A Sample Session Figure 19.1 shows a sample session with the chat client. As always, keyboard input is in a bold font and output from the program is in normal font. Figure 19.1. A session with the chat client We begin by invoking the client with the name of the server to connect to. The program prompts us for a nickname, logs in, and prints a confirmation message. We then issue the /channels command to fetch the list of available channels. This client, like certain other command-line chat clients , expects all commands to begin with the " / " character. Anything else we type is assumed to be a public message to be transmitted to the current channel. The system replies with the names of five channels, a brief description, and the number of users that belong to each one (a single user may be a member of multiple channels at once, so the sum of these numbers may not reflect the total number of users on the system). We join the Weather channel using the /join command, at which point we begin to see public messages from other users, as well as join and departure notifications. We participate briefly in the conversation and then issue the /users command to view the users who currently belong to the channel. This command lists users' nicknames, the length of time that they have been on the system, and the channels that they are subscribed to. We send a private message to one of the users using the /private command, /join the Hobbies channel briefly, and finally log out using /quit . In addition to the commands shown in the example (Figure 19.1), there's also a /part command that allows one to depart a channel. Otherwise, the list of subscribed channels just grows every time you join one. Chat System Design The chat system is message oriented. Clients send prearranged messages to the server to log in, join a channel, send a public message, and so forth. The server sends messages back to the client whenever an event of interest occurs, such as another user posting a public message to a subscribed channel. Event Codes In all our previous examples, we have passed information between client and server in text form. For example, in the travesty server, the server's welcome message was the text string "100." However, some Internet protocols pass command codes and other numeric data in binary form. To illustrate such systems, the chat server uses binary codes rather than human-readable ones. In this system, all communication between client and server is via a series of binary messages. Each message consists of an integer event code packed with a message string. For example, to create a public message using the SEND_PUBLIC message constant, we call pack() with the format "na*" : $message = pack("na*",SEND_PUBLIC,"hello, anyone here?"); To retrieve the code and the message string, we call unpack() with the same format: ($code,$data) = unpack("na*",$message); We use the " n " format to pack the event code in platform-independent "network" byte order. This ensures that clients and servers can communicate even if their hosts don't share the same byte order. The various event codes are defined as constants in a .pm file that is shared between the client and server source trees. The code for packing and unpacking messages is encapsulated in a module named ChatObjects::Comm. A brief description of each of the messages is given in Table 19.1. Table 19.1. Event Codes Code | Argument | Description | ERROR | <error message> | Server reports an error | LOGIN_REQ | <nickname> | Client requests a login | LOGIN_ACK | <nickname> | Server acknowledges successful login | LOGOFF | <nickname> | Client signals a signoff | JOIN_REQ | <title> | Client requests to join channel <title> | JOIN_ACK | <title> <count> | Server acknowledges join of channel <title> , currently containing <count> users | PART_REQ | <title> | Client requests to depart channel | PART_ACK | <title> | Server acknowledges departure | SEND_PUBLIC | <text> | Client sends public message | PUBLIC_MSG | <title> <user> <text> | User <user> has sent message <text> on channel <title> | SEND_PRIVATE | <user> <text> | Client sends private message <text> to user <user> | PRIVATE_MSG | <user> <text> | User <user> has sent private message <text> | USER_JOINS | <channel> <user> | User has joined indicated channel | USER_PARTS | <channel> <user> | User has departed indicated channel | LIST_CHANNELS | | Client requests a list of all channel titles | CHANNEL_ITEM | <channel> <count> <desc> | Sent in response to a LIST_CHANNELS request | | | Channel <channel> has <count> users and description <desc> | LIST_USERS | | Client requests a list of users in current channel | USER_ITEM | <user> <timeon> <channel 1> <channel 2>...<channel n> | Sent in response to a LIST_USERS request. User <user> has been online for <timeon> seconds and is subscribed to channels <channel 1> through <channel n> | User Information The system must maintain a certain amount of state information about each active user: the channels she has subscribed to, her nickname, her login time, and the address and port her client is bound to. While this information could be maintained on either the client or the server side, it's probably better that the server keep track of this information. It reduces the server's dependency on the client's implementing the chat protocol correctly, and it allows for more server-side features to be added later. For example, since the server is responsible for subscribing users to a channel, it is easy to limit the number or type of channels that a user can join. This information is maintained by objects of class ChatObjects::User. Channel Information One other item of information that the server tracks is the list of channels and associated information. In addition to the title, channels maintain a human-readable description and a list of the users currently subscribed. This simplifies the task of sending a message to all members of the channel. This information is maintained by objects of class ChatObjects::Channel. Concurrency We assume that each transaction that the server is called upon to handle ”logging in a user, sending a public message, listing channels ”can be disposed of rapidly . Therefore, the server has a single-threaded design that receives and processes messages on a first-come, first- served basis. Messages come in from users in any order, so the server must keep track of each user's address and associate it with the proper ChatObjects::User object. On the other end, the client will be communicating with only one server. However, it needs to process input from both the server and the user, so uses a simple select() loop to multiplex between the two sources of input. The object classes used by the server are designed for subclassing. This enables us to modify the chat system to take advantage of multicasting in the next chapter. |