Making the Connection: Multiuser Syntax
September 14, 1999
by Doug Brown
A discussion on actually creating an application with the multiuser functions in Director really needs to start with a distinction between the two different methods of implementation. The term "multiuser server" is used a lot in relation to the multiuser technology included with the latest incarnations of Director. The truth is that the server is only half of the story. The other half is the multiuser Xtra.
The server is really only necessary for hosting larger communities and doing data management related to a community. Each application connects to the server through an instance of the Xtra and logs in. Then the server acts as traffic cop routing messages from a sender to the appropriate recipients. This type of scenario is referred to as a "client-server" connection, and is typical of chat environments and game lobbies. The maximum number of connections allowed is really only limited by the scalability of the server and the number of connection licenses installed. (In case you haven't run across this yet, there are additional charges associated with running more than the fifty user license included with Director 7 Internet Studio)
A different type of connection called a "peer to peer" connection is also possible with the multiuser Xtra. In this scenario one of the client applications acts as a mini-server, accepting connections from other users and routing messages between senders and recipients. In this type of connection there are no community data functions available though, and the maximum number of users connected to the host is sixteen. One thing to remember about this limit is that this is per Xtra instance, so if you create sixteen Xtra instances listening on different ports, you'd actually be able to handle 16 x 16 = 256 connections.
Starting any multiuser connection does require an Xtra instance as mentioned above. This is done with a simple new(xtra "multiuser") lingo call, and immediately returns an Xtra object for manipulation. One of the useful things that can be done with this is to set the maximum size allowed for a given message, and total size for the message buffer. This is done with the setNetBufferLimits call, and must be done before actually connecting to a server. Attempting to make a change to these values after connecting to a server doesn't return an error, but doesn't actually do anything either.
on changeBufferSizes me global connObj connObj = new(xtra "multiuser") setNetBufferLimits(connObj, 16 * 1024, 350 * 1024, 100) end
This call sets the Xtra instance to read 16k of information from the TCP/IP stack at a time, will accept a message up to 350k in size, and permits up to 100 unread messages in the queue at a time before dropping additional incoming messages. A max message size of 350k is really probably pretty unnecessary on the Internet for a multiuser application, but it's been tested with messages as large as 4MB without a problem.
Another handler for configuring an instance of the Xtra is the setNetMessageHandler call. This call was covered pretty thoroughly in the previous article, so suffice to say if at least one callback handler isn't set, the Xtra won't have anywhere to send the incoming messages for processing and nothing will happen in the application.
Once the Xtra instance is set with appropriate buffer sizes, connecting to a server happens with a connectToNetServer call. The parameters for this call are the user name and password to use in logging on to the server, the IP address and port of the server, and the name of the movie being connected. The value for password must at least be SPACE, and EMPTY is not accepted as a valid parameter. Macromedia has officially registered port number 1626 for the multiuser server, but any port number is valid as long as the targeted server is listening on that port.
There are also two additional parameters for mode and encryption key. The encryption key is used for scrambling logon information and must have a matching key set on the server. The mode parameter is set to zero by default, but if set to a non-zero value a text-only connection will be open for communicating with other server types, such as SMTP or IRC.
on connectServer global connObj connectToNetServer(connObj, "Doug", "password", ¬ "192.168.0.4", 1626, "testMovie" [, mode, encryptionFlag]) end
Confirmation of the connectToNetServer call is returned from the server with the subject "connectToNetServer". If an error occurs while trying to connect, it will be contained in this return message.
Once connected to a server, the application can send messages to other users and the system, and will constantly be receiving messages from those sources as well. Messages are sent using the sendNetMessage command with parameters for recipients, message subject, and message contents. There are actually two formats that can be used for passing the parameters.
sendNetMessage(connObj, recipient string or ¬ list, subject string, string or list) sendNetMessage(connObj, [#recipients:string or ¬ list, #subject:string, #contents:string or list])
So what if you're in a chat environment with one thousand other users? Browsing the Internet can turn up online chats moving so quickly that nothing can be read, resulting in the visual equivalent of trying to listen to an entire crowd at the same time. To handle this the multiuser server allows for creating user groups, similar to IRC.
By default, every user attached to a server belongs to the " / at / AllUsers" group and can't leave it. For a smaller application it may be reasonable to have users messaging through this group, but for most instances this is best avoided since every user will see messages sent here. To break off into smaller communities use the joinGroup command, and its corollary leaveGroup to exit. A new group need not be created explicitly (actually it can't be) but is implicitly created when the first user joins the group. Once the last user leaves a group it is instantly disposed of by the server.
sendNetMessage(connObj, "System", "joinGroup", " / at / ChatOnly") sendNetMessage(connObj, "System", "leaveGroup", " / at / ChatOnly")
It's important to note that user groups are tracked by application and are not global to all connections on the server. For instance, six users are connected to a server through a movie named "Risk" and another six are connected through a movie named "Clue". A user in the Risk game sending a message to the " / at / AllUsers" group will only be heard by the users connected to the Risk game, and not the users in the Clue game.
Messages can also be sent to individual users rather than a whole group. In the sample chat application associated with these multiuser articles, the announceJoin/receiveJoin sequence of events uses this for passing avatars between users. When a new user joins the chat, an announcement is made to the whole group and the new user's avatar information is sent out as well. All of the other users in the chat are already aware of each other, so it would be a waste of bandwidth for each of them to send their information to the whole group again. Instead, the other users all send their information back exclusively to the new user in the chat.
The system also exists as a separate user called "System", and messages can be sent directly to it. To avoid timing out and being disconnected, a chat can send a "keep alive" message periodically to the System. Any valid server command is good for this, with getServerTime being an excellent candidate since its return data is a rather small, fixed size.
As mentioned earlier, peer to peer connections work the same as a client-server connection with the exception that there is no dedicated server application. In this case, one of the client machines acts as a "peer host" in the connection. These types of connections are very handy in gaming environments. Rather than tie up resources (and user licenses) by passing all game related messages through the server, users can meet in a lobby, create a game session, and then break off from the server into a peer connection.
The chain of events for this type of activity might look something like:
- Users congregate in lobby
- A set of users go into a separate room (group) to begin a game session
- One of the users is elected to serve as the peer host
- All users leave the server and connect to the peer host
- Game is played out
- All users reconnect to server to report scores/update stats
The real key in this script is electing a user to be the peer host, and that user accepting connections from the other users. Acting as a peer host involves the waitForNetConnection function of the multiuser Xtra. In this way the user becomes a mini-server, routing messages the same as the full server but without any of the normal system commands.
waitForNetConnection(connObj, "UserName", ¬ 1627, 8, "some encryption string")
The waitForNetConnection call accepts five parameters, although only the Xtra instance, user name, and port number are required. Additionally, the maximum number of connections allowed can be set, as well as an encryption key used in the same way as the full server. By default the maximum number of connections allowed is 16. If it is known that an application can't accept more than eight however, setting the number of connections lower will prevent a need for handling additional requests.
Once waitForNetConnection is called on a multiuser instance it immediately begins listening on the specified port for requests. To handle these incoming requests, it's a good idea to set a callback for the "connectToNetServer" subject similar to that in the code below. When a request to logon is received, the Xtra will then forward the message to the callback for processing. The connectToNetServer message will contain the user name, password, and movie name being used in the attempt. The peer host may use any logic then to decide whether to allow the requester to log on to the session. If the decision is to let the requester log on, the value TRUE must be returned. Otherwise a value of FALSE should be returned to deny the connection. Not returning anything will cause the requester to sit idle and eventually time out, which is unnecessary and somewhat impolite.
on validateConnection me global connObj newMsg = getNetMessage(connObj) connectRequest = newMsg.content if senderID = "Bob" then return TRUE else return FALSE end if end
Acting as a peer client doesn't really require much discussion because it's exactly the same as being part of a normal client-server session, with the exception of not being able to access any system functions. Note that the capacity to create separate groups is not available, although the " / at / AllGroups" group is still usable for sending a message to all participants in the peer to peer session.
Depending on the intent of the peer to peer activity some additional error handling might be warranted for the occasions when the peer host unexpectedly is dropped from the connection. This type of scenario leads to crashes in Quake deathmatches, or the odd circumstance in Unreal and other games where your opponents stop moving, but can't be killed.
One way to handle this is to have all users trade IP address information at the outset of the peer to peer activity, and identically sort them. Then each member calls waitForNetConnection to accept incoming requests and attempts to connect to the first user in the IP list. Should the peer host disappear at some point, all remaining participants then automatically delete that user from the IP list and attempt to connect to the next. Once a successful connection is made the activity can resume.
This is also a successful strategy for managing situations where certain users are behind firewalls, and therefore cannot act as a peer host. After a certain amount of time passes in which users are unsuccessful in connecting to a potential host, that user is deleted from the IP list and the next user is attempted.
Getting your local IP address involves a getNetAddressCookie call. Here's the Lingo that creates an instance of the Xtra and retrieves the local machine's IP address.
on createMUXtra me global connObj connObj = new(xtra "multiuser") tempIP = getNetAddressCookie(connObj, 0) end
The second parameter in the call is an encryption flag for scrambling the result. If set to zero the return is a clear string containing the address.
While these have so far proven some of the most useful bits of client-server and peer-peer syntax to know about, this has by no means been a comprehensive discussion of the multiuser functions. There are several others dealing with group management and messaging and message data handling that can be used to create user lists, chat room interfaces, and other features typical of multiuser applications.
The resources available at http://poppy.macromedia.com/~sallen/multiuser also continue to expand regularly. Stop by for regular chats and to browse through a number of different examples of the multiuser server in action.
Copyright 1997-2024, Director Online. Article content copyright by respective authors.