User Datagram Protocol Exercise |
The data link layer can provide reliable and unreliable data transfer between the network layer of hosts. Several protocols based on datagrams are discussed and implemented using C by Tannenbaum though the underlying physical layer services are not implemented, required for actual experimentation. The simpler C protocol algorithms (Protocol 1-4) were implemented in Java using datagrams as the underlying protocol. As many of you noted, few if any errors were observed on the LAN, making the unreliable datagram in fact reasonably reliable. Even Protocol 2, which could deadlock on errors rarely would given the low error rate typical of LANs. As an example of a familiar program, Protocol 1 has been implemented below using datagrams directly. The key elements of Java for inserting datagrams onto and receiving datagrams from the network are discussed below.
A key advantage of Python for network applications is the relative simplicity of communication using datagram and connection services. The following discusses the key parts of Java for implementing datagram service.
Port - A workstation is uniquely identified by its IP number such as 149.160.51.113 associated with an NIC. Even though only one NIC is used the host may be communicating with multiple sites simultaneously. To maintain each communication separately, datagrams are sent and received through unique (to the host) ports. A few applications, such as SMTP (port 25), HTTP (port 80), FTP, etc. have well-known port numbers that are generally reserved. In our example, we used one port to send (sendPort=999) and another to receive (receivePort=666). Though it is possible to use one bi-directional port, because the sender and receiver are running on the same host, each bi-directional communication must use a different port number, that is only one use of a port number is allowed per computer host. It is important to note that the sender must send to the receiver port number in order for the two to communicate. In the chat implementations below, two different ports are used when the same machine is used for both sides of the chat.
Socket - A datagram socket is somewhat similar to a file handle except that provides a bi-directional access to send or receive datagrams via the network rather than a file system. Under TCP/IP it must be associated with a unique port number for that host. receiverSocket.bind(('',999)) creates a new socket identified with port 999, the object ds can be used to send or receive datagram packets through port 999.
Receiving - Receive on socket ds using port 999 data and address of sender.
ds=socket(AF_INET, SOCK_DGRAM)
ds.bind(('',999))
data, address = ds.recvfrom(1024)
|
Sending - A new datagram containing a string (e.g. 'Hello world') is
sent via socket ds to port 999 of the destination machine
cs1.ius.edu through the network:
ds.sendto('Hello World', ('cs1.ius.edu', 999))
|
Protocol 1 from Tannenbaum has a weakness in that communication is simplex with no coordination between sender and receiver to prevent overflow of the receiver. The following implements Protocol 1 using datagrams. The diagram at right illustrates the port communication.
# Protocol 1 sender using Python datagrams
from socket import *
senderSocket=socket(AF_INET, SOCK_DGRAM)
while True :
userIn=raw_input() # from_network
if not userIn : break # to_physical
senderSocket.sendto(userIn, ('localhost', 999))
senderSocket.close()
|
# Protocol 1 receiver using Python datagrams from socket import * receiverSocket=socket(AF_INET, SOCK_DGRAM) receiverSocket.bind(('',999)) while True : # from_physical data, address = receiverSocket.recvfrom(1024) if not data : break print data # to_network receiverSocket.close() |
ipconfig
ioSocket.sendto(userIn, ('localhost', ioPort))
where localhost is the IP or name of host to chat with.
# chat0
from socket import *
ioSocket=socket(AF_INET, SOCK_DGRAM)
ioPort = 111
receiverPort=ioPort
toName = 'localhost'
if toName == 'localhost' : receiverPort = 222
try :
ioSocket.bind(('',ioPort))
except Exception :
ioPort = 222
receiverPort = 111
ioSocket.bind(('',ioPort))
print 'chat0 on port ', ioPort, '. Blank line to quit.'
while True :
userIn = raw_input()
ioSocket.sendto(userIn, (toName, receiverPort))
data, address = ioSocket.recvfrom(1024)
if not data : break
print data
ioSocket.close()
|
Problems - Obviously the chatters should not be forced to take turns
chatting.
Solution - The reasonable solution is to create separate threads
of execution for sending and receiving packets allowing multiple packets to be
sent from a single, talkative chatter.
Improvement over chat0 is that it can receive and send at any time using two separate threads, the main thread for sending and a new thread for the receiving. Python supports lightweight threads that execute pseudo-parallel with the main thread. The main thread will continue to handle user typing, a separate thread will handle receiving and displaying datagrams; both threads operate independently avoiding the lockstep, turn-taking of the chat0. Daemon threads are used because they terminate when the main thread terminates.

ipconfig
c:\python23\python chat1.py
ioSocket.sendto(userIn, ('localhost', ioPort))
where localhost is the IP or name of host to chat with.
# chat1 from socket import * import threading
ioSocket=socket(AF_INET, SOCK_DGRAM)
ioPort = 111
receiverPort=ioPort
toName = 'localhost'
if toName == 'localhost' : receiverPort = 222
try :
ioSocket.bind(('',ioPort))
except Exception, msg :
ioPort = 222
receiverPort = 111
ioSocket.bind(('',ioPort))
print 'chat1 on port ', ioPort, '. Blank line to quit.'
while True : # Send in main thread userIn = raw_input() if not userIn : break ioSocket.sendto(userIn, (toName, receiverPort)) ioSocket.close() |
Problem - While two chatters can chat to one another, assuming that they each knew the others IP address, can a new chatter join the chat group? Unfortunately no since new chatters are not learned and remembered as they make contact. Currently the chatter host must be designated in the Python code (i.e. toName='localhost').
Consider that A contacts B and B contacts A, then A and B can chat. But if C contacts A, C can chat with A but A can't chat with C!
Solution - An apparently reasonable solution is to add the IP of any new chatters making contact to a chatters list. Chat packets are then sent to all chatters in the list. If A and B are chatting A maintains a list of: A(B) and B the list of chatters B(A). When C contacts A, the lists are now: A(B,C), B(A), C(A).
Exercise 1
Problem - While several chatters can now chat to one another, and a new
chatter can chat with one of the original chatters, the new chatter does not
chat with other chatters in the group. Consider that A contacts B and B contacts
A, then A and B can chat. When C contacts A, A adds C to a list of chatters that
it will send packets to but A does not send B chat from C or C chat from B. The
lists maintained would be: A(B,C), B(A), C(A).
Solution - A apparently reasonable solution is to add the IP of
any new chatters making contact to a list and to forward to all chatters in the
list any chat received.
To avoid loops an exception is made not to forward to the sender
of the chat. If A is contacted by B and C, the lists become A(B,C), B(A), C(A).
When C chats with A, A forwards any messages to B and will do the same for B
chatting with A. A could serve as the contact point for all chatters,
effectively acting as a chat server might. Or two chatters that act as servers
(e.g. A and C) might contact each and other chatters contact the server (e.g. A
contacted by B and C contacted by D). The lists would be: A(B,C), B(A), C(A,D),
D(C). The corresponding graph would appear as at right where B chat with A would
be forwarded to C which forwards to D.
Two requirements are necessary to avoid loops:
Exercise 2
A key advantage of Java for network applications is the relative simplicity of communication using datagram and connection services. The following discusses the key parts of Java for implementing datagram service.
Port - A workstation is uniquely identified by its IP number such as 149.160.51.113 associated with an NIC. Even though only one NIC is used the host may be communicating with multiple sites simultaneously. To maintain each communication seperately, datagrams are sent and received through unique (to the host) ports. A few applications, such as SMTP (port 25), HTTP (port 80), FTP, etc. have well-known port numbers that are generally reserved. In our example, we used one port to send (sendPort=999) and another to receive (receivePort=666). Though it is possible to use one bi-directional port, because the sender and receiver are running on the same host, each bi-directional communication must use a different port number, that is only one use of a port number is allowed per computer host. It is important to note that the sender must send to the receiver port number in order for the two to communicate. In the chat implementations below, two different ports are used when the same machine is used for both sides of the chat.
Socket - A datagram socket is somewhat similar to a file handle except that provides a bi-directional access to send or receive datagrams via the network rather than a file system. Under TCP/IP it must be associated with a unique port number for that host. DatagramSocket ds =new DatagramSocket(999) creates a new socket identified with port 999, the object ds can be used to send or receive datagram packets through port 999.
Packet - The datagram information is contained in a packet that is sent or received through a socket.
Receiving - Packet to receive must have a byte array for storage of received data and the array length as in:
byte buffer = new byte[20]; DatagramSocket ds = new DatagramSocket(999); DatagramPacket r = new DatagramPacket( buffer, 20); ds.receive(r); |
defines a packet that can receive a maximum of 20 bytes of data into the array buffer. The datagram r is received through socket ds on port 999 defined above. Note that receive is blocking, that is it waits until a datagram is received before returning.
Sending - A new datagram packet containing the 2 bytes of data (e.g.
OK) in buffer of count bytes is sent via socket ds at
port number 999 from a machine to port 666 of the destination machine
cs1.ius.edu through the network:
byte buffer = { 'O', 'K'};
DatagramSocket ds = new DatagramSocket(999);
ds.send(new DatagramPacket( buffer, 2, InetAddress.getByName("cs1.ius.edu" ), 666));
|
where buffer is the data to be sent, 2 is the number of bytes of data, 999 is the sender port and 666 is the receive port on host cs1.ius.edu.
InetAddress - The IP address representation of a host.
- InetAddress(getLocalHost()) returns the address of the host machine executing the statement.
- InetAddress.getByName("www.sun.com") returns the address of the host www.sun.com.
- InetAddress.getByName("149.160.23.145") returns the address of the host 149.160.23.145.
Protocol 1 from Tannenbaum has a weakness in that communication is simplex with no coordination between sender and receiver to prevent overflow of the receiver. The following implements Protocol 1 using datagrams. The diagram at right illustrates the port communication.
v:\common\user\b438\forJava
javac *.java
where host is the IP or name of host to send the file p1sender.java.
Protocol 1 Sender and Receiver Comparison
// Protocol 1 sender using Java datagrams, Tanenbaum
import java.net.*;
import java.io.*;
class p1sender {
public static void main(String args[]) throws Exception {
byte senderBuffer[] = new byte[128];
String toName, userIn;
BufferedReader in = new BufferedReader(
new InputStreamReader(System.in));
DatagramSocket senderSocket = new DatagramSocket(999);
if (args.length > 0) toName = args[0];
else toName = "localhost";
while ((userIn=in.readLine())!=null ){
senderBuffer=userIn.getBytes();
senderSocket.send(
new DatagramPacket( senderBuffer, senderBuffer.length,
InetAddress.getByName(toName), 666));
}
}
}
|
// Protocol 1 receiver using Java datagrams, Tanenbaum
import java.net.*;
class p1receiver {
public static void main(String args[]) throws Exception {
byte receiveData[] = new byte[128];
DatagramSocket receiveSocket = new DatagramSocket(666);
DatagramPacket receivePacket = new DatagramPacket(receiveData, 128);
while (true) {
receiveSocket.receive(receivePacket);
System.out.println(
new String(
receivePacket.getData(), 0, receivePacket.getLength()));
}
}
}
|
v:\common\user\b438\forJava
javac *.java
ipconfig
java -cp . simpleChat host
where host is the IP or name of host to chat with.
import java.net.*;
import java.io.*;
public class simpleChat {
public static void main(String args[]) throws Exception {
DatagramSocket ioSocket;
String toName, userIn;
byte sendBuffer[] = new byte[128];
byte receiveBuffer[] = new byte[128];
DatagramPacket sendPacket = new DatagramPacket(sendBuffer, 128);
DatagramPacket receivePacket = new DatagramPacket(receiveBuffer, 128);
BufferedReader in = new BufferedReader(
new InputStreamReader( System.in));
int ioPort = 111, receiverPort = ioPort;
if (args.length > 0) toName = args[0];
else toName = "localhost";
if (toName.equals("localhost")) // Use port 111 or 222 whether
receiverPort = 222; // chatting with localhost or remote
try { ioSocket = new DatagramSocket(ioPort); }
catch (Exception e) { // Switch ports when port 111 in use
ioPort = 222; receiverPort = 111;
ioSocket = new DatagramSocket(ioPort);
}
System.out.println("simpleChat on port " + ioPort + ". Ctrl Z to quit.");
while ((userIn=in.readLine())!=null) { // Read user input until Ctrl Z
sendBuffer= userIn.getBytes(); // Convert string to byte array
sendPacket = new DatagramPacket( sendBuffer, sendBuffer.length,
InetAddress.getByName(toName), receiverPort);
ioSocket.send(sendPacket); // Send packet
ioSocket.receive(receivePacket); // Wait to receive packet
// Print contents of received packet
System.out.println( "Received " + new String(receivePacket.getData(),
0, receivePacket.getLength()));
}
}
}
|
Problems - Obviously the chatters should not be forced to take turns
chatting.
Solution - The reasonable solution is to create separate threads
of execution for sending and receiving packets allowing multiple packets to be
sent from a single, talkative chatter.
Improvement over simpleChat is that it can receive and send at any time using two separate threads, the main thread for sending and a new thread for the receiving.

v:\common\user\b438\forJava
javac *.java
ipconfig
java -cp . chat
java -cp . chat <IP or DNS>
import java.net.*;
import java.io.*;
public class chat {
public static void main(String args[]) throws Exception {
DatagramSocket ioSocket;
String toName = "localhost";
String userIn;
byte sendBuffer[] = new byte[128];
DatagramPacket sendPacket = new DatagramPacket(sendBuffer, 128);
BufferedReader in = new BufferedReader(
new InputStreamReader(System.in));
int ioPort = 111, receiverPort = ioPort;
if (args.length > 0) toName = args[0];
else toName = "localhost";
if (toName.equals("localhost")) // Use two ports for localhost
receiverPort = 222;
try { ioSocket = new DatagramSocket(ioPort); }
catch (Exception e) {
ioPort = 222; receiverPort = 111;
ioSocket = new DatagramSocket(ioPort);
}
System.out.println("chat on port " + ioPort + ". Ctrl Z to quit.");
while ((userIn=in.readLine())!=null) { // Read user input until Ctrl Z
sendBuffer = userIn.getBytes();
sendPacket = new DatagramPacket( sendBuffer, sendBuffer.length,
InetAddress.getByName(toName), receiverPort);
ioSocket.send(sendPacket); // Send datagram
}
System.exit(0); // Exit, stopping receiver
}
}
|
Problem - While two chatters can chat to one another, assuming that they each knew the others IP address, can a new chatter join the chat group? Unfortunately no since new chatters are not learned and remembered as they make contact. Currently only the chatter host designated on the command line is contacted (i.e. java -cp . 149.160.24.23 ).
Consider that A contacts B and B contacts A, then A and B can chat. But if C contacts A, C can chat with A but A can't chat with C!
Solution - An apparently reasonable solution is to add the IP of any new chatters making contact to a chatters list. Chat packets are then sent to all chatters in the list. If A and B are chatting A maintains a list of: A(B) and B the list of chatters B(A). When C contacts A, the lists are now: A(B,C), B(A), C(A).
Exercise 1
Problem - While several chatters can now chat to one another, and a new
chatter can chat with one of the original chatters, the new chatter does not
chat with other chatters in the group. Consider that A contacts B and B contacts
A, then A and B can chat. When C contacts A, A adds C to a list of chatters that
it will send packets to but A does not send B chat from C or C chat from B. The
lists maintained would be: A(B,C), B(A), C(A).
Solution - A apparently reasonable solution is to add the IP of
any new chatters making contact to a list and to forward to all chatters in the
list any chat received.
To avoid loops an exception is made not to forward to the sender
of the chat. If A is contacted by B and C, the lists become A(B,C), B(A), C(A).
When C chats with A, A forwards any messages to B and will do the same for B
chatting with A. A could serve as the contact point for all chatters,
effectively acting as a chat server might. Or two chatters that act as servers
(e.g. A and C) might contact each and other chatters contact the server (e.g. A
contacted by B and C contacted by D). The lists would be: A(B,C), B(A), C(A,D),
D(C). The corresponding graph would appear as at right where B chat with A would
be forwarded to C which forwards to D.
Two requirements are necessary to avoid loops:
Exercise 2
Most of the Java ideas transfer in some form to C, whether using Windows, Unix, or other operating system. However, changes are needed to adapt to the specific operating system, this only discusses Windows. The relevant points to running a UDP application such as the following simpleChat are:
Returns the name of the local host.
Returns a hostent data structure pointer constructed for the host name parameter.
Initializes the sockaddr_in data structure used for referring to the receiver.
Initializes the sockaddr_in data structure used for communiating by local host.
Constructs a UDP socket.
Binds the socket ioSocket to the socket address of local.
Send buffer to receiver using ioSocket.
Receive buffer using ioSocket, receiver holds the source address. This is a blocking call, it waits for a datagram before returning.
Port - The concept of a port is common on all TCP/IP implementations whether Java or C. In our example, we used one port to send (ioPort) and receive on the local host. If both sides of the chat are on the same machine receivePort is a different number.
Socket - ioSocket = socket(AF_INET, SOCK_DGRAM,0); creates a new socket unidentified with any port. The varible ioSocket can be used to send or receive datagram packets after being bound to a port.
Binding - bind(ioSocket,(struct sockaddr*)&local,sizeof(local) ); binds ioSocket to the port defined in data structure local. The socket can now be used for sending and receiving datagrams.
Packet - The datagram information is contained in a packet that is sent or received through a socket.
Receiving - Receive buffer using ioSocket, receiver holds the source address. This is a blocking call, it waits for a datagram before returning.
recvfrom(ioSocket,buffer,sizeof(buffer),0, (struct sockaddr *)&receiver,&receiverlen);
Sending - Send buffer to receiver using ioSocket. A new datagram packet containing the data in buffer is sent from the local machine via socket ioSocket through the network to the receiver by:
sendto(ioSocket,buffer,strlen(buffer)+1,0, (struct sockaddr *)&receiver,receiverlen);
- Symetric, one version runs on each of two hosts.
- Each host can send one message and then must wait to receive one message before continuing.
- Message is sent as raw text string. A message is considered to be terminated by any one of a line feed ('\n'), a carriage return ('\r'), or a carriage return followed immediately by a linefeed.
- Terminated by sending empty message (Control Z).
Visual Studio 6.0 requires adding the following to the project: C:\Program Files\Microsoft Visual Studio\VC98\Lib\ws2_32.lib
Visual C++ .NET requires adding the following to the project: C:\Program Files\Microsoft Visual Studio .NET\Vc7\PlatformSDK\lib\ws2_32.lib
| // simpleChat.cpp - Use: simpleChat
<IP> or <DNS> // Visual C++ Project | Settings | Link | Object/Library modules | ws2_32.lib #include "stdafx.h" int main(int argc, char* argv[]) WSAStartup(0x202,&wsaData); // Startup gethostname(localName, sizeof(localName));
// Check if chatting with self if (!host) addr = inet_addr(argv[1]); // IP used instead of a name? if ((!host) && (addr == INADDR_NONE) ) {
// Failed to resolve name or IP local.sin_family = AF_INET; ioSocket = socket(AF_INET, SOCK_DGRAM,0); if (ioSocket == INVALID_SOCKET){ if (bind(ioSocket,(struct sockaddr*)&local,sizeof(local) ) ==
SOCKET_ERROR) { cout << argv[0] << " listening on " << ioPort << " protocol
UDP\n"; while(cin >> buffer) { retval =
sendto(ioSocket,buffer,strlen(buffer)+1,0, if (retval == SOCKET_ERROR) retval =
recvfrom(ioSocket,buffer,sizeof(buffer),0, cout << "Received " << buffer << "\n";
|
VB UDP is through the WinSock object described in Homework 6. A workstation is uniquely identified by its IP number such as 149.160.51.113 associated with an NIC. Though only one NIC is used the host may be communicating with multiple sites simultaneously. To maintain each communication seperately, datagrams are sent and received through unique (to the host) ports. A few applications, such as SMTP (port 25), HTTP (port 80), FTP, etc. have well-known port numbers that are generally reserved. In our example, we used one port to send (sendPort) and another to receive (receivePort), even though it is possible to use one bi-directional port. It is important to note that the sender port number on one host must match the receiver port number on the other host in order for the machines to communicate. In the chat implementations below, two different ports are used when the same machine is used for both sides of the chat.
The key points and examples, assuming that the name of the WinSock object is WinSock1 are:
Protocol - Winsock1.Protocol = sckUDPProtocol - Defines the protocol as UDP.
IP or DNS of remote host- WinSock1.RemoteHost = "localhost" - Defines the name of the remote host.
Local Port - WinSock1.Bind 111 - Binds to the local port 111.
Port of remote host - WinSock1.RemotePort = 222 - Port to send datagrams to on the remote host.
Private Sub WinSock1_DataArrival (ByVal bytesTotal As Long)
Dim strData As String
WinSock1.GetData strData
txtReceive.Text = strData
End Sub
Packet - The datagram information is contained in a packet that is sent
or received through the WinSock1 object.
Receiving - An arriving packet generates an event which should be handled by the WinSock_DataArrival_(ByVal bytesTotal As Long) subroutine. Packet to receive must have storage of received data. A complete subroutine is:
Private Sub WinSock1_DataArrival _(ByVal bytesTotal As Long)
Dim strData As String
WinSock1.GetData strData
txtReceive.Text = strData ' Put
received data in a text box
End Sub
Sending - Data sent from a machine (through port 111) to the
destination machine at port number 222 through the network by:
WinSock1.SendData "Hello"
Note that the following contains portions from the Microsoft VB 6.0 documentation.
Protocol description Symetric, one version runs on each of two hosts. Each host can send one character to the other at any time due to the event driven natural of VB. Message is sent as raw text string. A message is not considered to be terminated by anything. Protocol not terminated by anything.
- Use
- Execute in two different VB windows.
- Execute on two different machines by changing the line:
WinSock1.RemoteHost = "localhost"
where localhost is the IP or name of host to chat with.UDP Basics
In contrast to TCP, the UDP protocol doesn't require an explicit connection. To send data between two controls, three steps must be completed (on both sides of the connection):
- Set the RemoteHost property to the name of the other computer.
- Set the RemotePort property to the LocalPort property of the second control.
- Invoke the Bind method specifying the LocalPort to be used. (This method is discussed in greater detail below.) Because both computers can be considered "equal" in the relationship, it could be called a peer-to-peer application. To demonstrate this, the code below creates a "chat" application that allows two people to "talk" in real time to each other:
To create a UDP Peer
- Create a new Standard EXE project.
- Draw a Winsock control on the form
, it will be named WinSock1 automatically.
To place on toolbar:
- Project | Components | Check Microsoft Winsock Control
- Add two TextBox controls to the form. Name the one txtSend, and the other txtReceive.
- Add the code below for Peer 1.
UDP Peer 1 and 2 ' UDP Peer 1 Private Sub Form_Load( )
WinSock1.RemoteHost = "localhost"
Winsock1.Protocol = sckUDPProtocol
WinSock1.RemotePort = 222 ' Port to connect to.
WinSock1.Bind 111 ' Bind to the local port.
End SubPrivate Sub txtSend_Change( )
' Send text as soon as it's typed.
WinSock1.SendData txtSend.Text
End SubPrivate Sub WinSock1_DataArrival (ByVal bytesTotal As Long)
Dim strData As String
WinSock1.GetData strData
txtReceive.Text = strData
End Sub'UDP Peer 2 Private Sub Form_Load( )
WinSock1.RemoteHost = "localhost"
Winsock1.Protocol = sckUDPProtocol
WinSock1.RemotePort = 111 ' Port to connect to.
WinSock1.Bind 222 ' Bind to the local port.
End SubPrivate Sub txtSend_Change( )
' Send text as soon as it's typed.
WinSock1.SendData txtSend.Text
End SubPrivate Sub WinSock1_DataArrival (ByVal bytesTotal As Long)
Dim strData As String
WinSock1.GetData strData
txtReceive.Text = strData
End SubTo create a second UDP Peer
- Run another copy of VB.
- Add a standard form to the project.
- Follow the same steps as above except reverse port assignments to allow running on one host
To try the example, press F5 to run the project, and type into the txtSend TextBox on either form. The text you type will appear in the txtReceive TextBox on the other form.
About the Bind Method
As shown in the code above, you must invoke the Bind method when creating a UDP application. The Bind method "reserves" a local port for use by the control to receive datagram packets. For example, when you bind the control to port number 1001, no other application can use that port to "listen" for packets.The Bind method also features an optional second argument. If there is more than one network adapter present on the machine, the LocalIP argument allows you to specify which adapter to use. If you omit the argument, the control uses the first network adapter listed in the Network control panel dialog box of the computer's Control Panel Settings.
When using the UDP protocol, you can freely switch the RemoteHost and RemotePort properties while remaining bound to the same LocalPort. However, with the TCP protocol, you must close the connection before changing the RemoteHost and RemotePort properties.
Race game
Peer configurations typically interact more freely than the interaction characterized by a client and server taking turns as exemplified by a Web browser and server. A simple example where either peer can initiate an action is given below as a racing game where a peer moves on a grid, each attempting to crash into the other first. In this particular example the peers are identical so only one program exists. While the command/response interaction model typical of a client/server is relatively simple to program it is inappropriate where high interactivity is required, as is this case where either peer should be able to make multiple moves without waiting on the other to move.
A description of the racing game following unrestricted model.
- Racer - Using UDP, a datagram protocol, allows one system to send a datagram without waiting for a
connection. Each peer initializes the race by placing the two racers on the grid at the same coordinates. A SenderThread and ReceiverThread are created to handle sending and receiving racer moves and notifying the race object of moves so that the race is updated.
- SenderThread - Reads keyboard until keys I, J, K, M for moves up, left, right, and down respectively, sends the legal key to the other racer, and notifies the race object of the move.
- ReceiverThread - Reads the connection for the move from the other racer and notifies the race object of the move.
- Race - Maintains the race game state as the x, y coordinate of two racers on a 20x20 grid, displays the game grid showing the currents racers location, and determines when the race is over.
// Racer.java - Use: java -cp . Racer <IP or DNS> import java.net.*; import java.io.*; public class Racer { public static void main(String args[]) throws Exception { int ioPort = 111, toPort = ioPort; String toName = "localhost"; DatagramSocket ioSocket; Race race; // If both on same machine if (args.length > 0) // use two port numbers toName = args[0] == null ? "localhost" : args[0]; if (toName.equals("localhost")) toPort = 222; try { ioSocket = new DatagramSocket(ioPort); } catch (Exception e) { // 222 already receiverPort ioPort = 222; toPort = 111; // use 111 instead ioSocket = new DatagramSocket(ioPort); } System.out.println("Racer listening on port " + ioPort); if (ioPort == 111) race = new Race('C', 0, 0, 'S', 2, 2); // Start of race else race = new Race('S', 2, 2, 'C', 0, 0); // Start of race ReceiverThread receiver = new ReceiverThread(ioSocket, race); SenderThread sender = new SenderThread(ioSocket, toName, toPort, race); } }
import java.net.*; import java.io.*; class SenderThread implements Runnable { DatagramSocket out; String toName; int toPort; Race race; public SenderThread(DatagramSocket out, String toName, int toPort, Race race) { this.out = out; this.toName = toName; this.toPort = toPort; this.race = race; new Thread(this).start(); } public void run() { while(!race.over()) send(); } public void send() { char c; byte sendBuffer[] = new byte[1]; DatagramPacket sendPacket; try { if (System.in.available() > 0 ) { c=(char) System.in.read(); switch(c) { case 'I': case 'J': case 'K': case 'M': sendBuffer[0] = (byte) c; sendPacket = new DatagramPacket( sendBuffer, 1, InetAddress.getByName(toName), toPort); out.send(sendPacket); race.moveme(c); } } } catch(Exception e) { } } }
import java.net.*; import java.io.*; class ReceiverThread implements Runnable { DatagramSocket in; Race race; public ReceiverThread(DatagramSocket in, Race race) { this.in = in; this.race = race; try { in.setSoTimeout(1000); } catch(Exception e) {}; new Thread(this).start(); } public void run() { while(!race.over()) receive(); } public void receive() { char c; byte receiveBuffer[] = new byte[1]; DatagramPacket receivePacket = new DatagramPacket(receiveBuffer, 1); try { in.receive(receivePacket); c = (char) receiveBuffer[0]; race.movethem(c); } catch(Exception e) {} } }
class Race { int xme, yme, xthem, ythem; char me, them; char winner; public Race(char me, int xme, int yme, char them, int xthem, int ythem) { this.me = me; this.xme = xme; this.yme = yme; this.them = them; this.xthem = xthem; this.ythem = ythem; winner='?'; System.out.print("Keys\n I\nJ K\n M\n"); print(); } public void print() { for(int y=0; y < 20; y++) { for(int x=0; x<20; x++) if (x==xme && y==yme) System.out.print(me); else if (x==xthem && y==ythem) System.out.print(them); else System.out.print('.'); System.out.println(); } if(over()) System.out.println(winner + " wins"); System.out.println(); } public synchronized void moveme(char direction) { switch (direction) { case 'I' : if (yme > 0) yme--; break; case 'J' : if (xme > 0) xme--; break; case 'K' : if (xme < 19) xme++; break; case 'M' : if (yme < 19) yme++; break; } if(xme == xthem && yme == ythem) { winner = me; them = '*'; me = '*'; } print(); } public synchronized void movethem(char direction) { switch (direction) { case 'I' : if (ythem > 0) ythem--; break; case 'J' : if (xthem > 0) xthem--; break; case 'K' : if (xthem < 19) xthem++; break; case 'M' : if (ythem < 19) ythem++; break; } if(xme == xthem && yme == ythem) { winner = them; them = '*'; me = '*';} print(); } public boolean over() { return winner != '?'; } }Use:
- Copy the Racer files from above.
- Racer.java
- Race.java
- SenderThread.java
- ReceiverThread.java
- Open two Command Prompt windows.
- Compile in one window with:
- v:\common\user\b438\forJava
- javac *.java
- Execute in different windows by:
- java -cp . Racer <IP or DNS>
Document last modified: