UDP Example

Let's examine a sample UDP application. Take a look at the sample Visual Basic project SockUDP.vbp in the Chapter 15 directory on the CD. When the project is compiled and run, you will see a dialog similar to the one illustrated in Figure 15-1. This sample application is both a sender and a receiver of UDP messages, and therefore you can use just one instance to send and receive messages. Additionally, all the code behind the form, buttons, and Winsock controls is given in Figure 15-2.

click to view at full size.

Figure 15-1. Sample UDP application

When you look at the form, you see two Winsock controls. One control is used to send datagrams, and the other is used to receive datagrams. You can also see three group boxes: one for the sender, one for the receiver, and one for general Winsock information. For the sender, you need somewhere to put the recipient's host name or IP address. When you set the RemoteHost property, you can use either the machine's textual name or a string representation of the dotted-decimal numeric IP address. The control resolves the name if needed. You also need the remote port to which you will send the UDP packets. Also, notice the text box for the local port, txtSendLocalPort. For the sender, it doesn't really matter which local port you send the data on, only which port you're sending to. If you leave the local port set to 0, the system assigns an unused port. The last text box, txtSendData, is for the string data to be sent. Additionally, there are two command buttons: one for sending the data and one for closing the socket. To send datagrams, you must bind the Winsock control to a remote address, a remote port, and a local port before you can send any data. This means that if you want to change any one of these three parameters, you need to close the socket first and then rebind to the new parameters. That is why the form has a Close Socket button.

Figure 15-2. UDP sample code

 Option Explicit Private Sub cmdExit_Click() Unload Me End Sub Private Sub cmdSendDgram_Click() ' If the socket state is closed, we need to bind to a ' local port and also to the remote host's IP address and port If (sockSend.State = sckClosed) Then sockSend.RemoteHost = txtRecipientIP.Text sockSend.RemotePort = CInt(txtSendRemotePort.Text) sockSend.Bind CInt(txtSendLocalPort.Text) cmdCloseSend.Enabled = True End If ' ' Now we can send the data ' sockSend.SendData txtSendData.Text End Sub Private Sub cmdListen_Click() ' Bind to the local port ' sockRecv.Bind CInt(txtRecvLocalPort.Text) ' ' Disable this button since it would be an error to bind ' twice (a close needs to be done before rebinding occurs) ' cmdListen.Enabled = False cmdCloseListen.Enabled = True End Sub Private Sub cmdCloseSend_Click() ' Close the sending socket, and disable the Close button ' sockSend.Close cmdCloseSend.Enabled = False End Sub Private Sub cmdCloseListen_Click() ' Close the listening socket ' sockRecv.Close ' Enable the right buttons ' cmdListen.Enabled = True cmdCloseListen.Enabled = False lstRecvData.Clear End Sub Private Sub Form_Load() ' Initialize the socket protocols, and set up some default ' labels and values ' sockSend.Protocol = sckUDPProtocol sockRecv.Protocol = sckUDPProtocol lblHostName.Caption = sockSend.LocalHostName lblLocalIP.Caption = sockSend.LocalIP cmdCloseListen.Enabled = False cmdCloseSend.Enabled = False Timer1.Interval = 500 Timer1.Enabled = True End Sub Private Sub sockSend_Error(ByVal Number As Integer, _ Description As String, ByVal Scode As Long, _ ByVal Source As String, ByVal HelpFile As String, _ ByVal HelpContext As Long, CancelDisplay As Boolean) MsgBox Description End Sub Private Sub sockRecv_DataArrival(ByVal bytesTotal As Long) Dim data As String ' Allocate a string of sufficient size, and get the data; ' then add it to the list box data = String(bytesTotal + 2, Chr$(0)) sockRecv.GetData data, , bytesTotal lstRecvData.AddItem data ' Update the remote IP and port labels ' lblRemoteIP.Caption = sockRecv.RemoteHostIP lblRemotePort.Caption = sockRecv.RemotePort End Sub Private Sub sockRecv_Error(ByVal Number As Integer, _ Description As String, ByVal Scode As Long, _ ByVal Source As String, ByVal HelpFile As String, _ ByVal HelpContext As Long, CancelDisplay As Boolean) MsgBox Description End Sub Private Sub Timer1_Timer() ' When the timer goes off, update the socket status labels ' Select Case sockSend.State Case sckClosed lblSenderState.Caption = "sckClosed" Case sckOpen lblSenderState.Caption = "sckOpen" Case sckListening lblSenderState.Caption = "sckListening" Case sckConnectionPending lblSenderState.Caption = "sckConnectionPending" Case sckResolvingHost lblSenderState.Caption = "sckResolvingHost" Case sckHostResolved lblSenderState.Caption = "sckHostResolved" Case sckConnecting lblSenderState.Caption = "sckConnecting" Case sckClosing lblSenderState.Caption = "sckClosing" Case sckError lblSenderState.Caption = "sckError" Case Else lblSenderState.Caption = "unknown" End Select Select Case sockRecv.State Case sckClosed lblReceiverState.Caption = "sckClosed" Case sckOpen lblReceiverState.Caption = "sckOpen" Case sckListening lblReceiverState.Caption = "sckListening" Case sckConnectionPending lblReceiverState.Caption = "sckConnectionPending" Case sckResolvingHost lblReceiverState.Caption = "sckResolvingHost" Case sckHostResolved lblReceiverState.Caption = "sckHostResolved" Case sckConnecting lblReceiverState.Caption = "sckConnecting" Case sckClosing lblReceiverState.Caption = "sckClosing" Case sckError lblReceiverState.Caption = "sckError" Case Else lblReceiverState.Caption = "unknown" End Select End Sub 

Sending UDP Messages

Now that you know the sender's general capabilities, let's look at the code behind the scenes. First take a look at the Form_Load routine. The first step is to set the Protocol property of the sockSend Winsock control to UDP by using the sckUDPProtocol enumerated type. The other commands in this routine don't apply to the sending functionality except for disabling the cmdCloseSend command button. We do this for completeness because calling the Close method on an already closed control does nothing. Note that the default state of the Winsock control is closed.

Next look at the cmdSendDgram_Click routine, which is triggered by clicking on the Send Data button. This is the heart of sending a UDP message. The first step in the code is to check the socket's state. If the socket is in the closed state, the code binds the socket to a remote address, a remote port, and a local port. Once the code binds a UDP Winsock control with these parameters, the state of the control changes from sckClosed to sckOpen. If the code doesn't perform this check and attempts to bind the socket on every send, the run-time error 40020, "Invalid operation at current state," will be generated. Once a socket is bound, it remains bound until it is closed. This is why the code enables the Close Socket button for the sending socket once the control is bound. The last step is to call the SendData method with the data the user wants to send. When the SendData method returns, the code has finished sending data.

Only two other subroutines are associated with sending UDP messages. The first is cmdCloseSend, which as its name implies closes the sending socket, allowing the user to change the remote host, remote port, or local port parameter before sending data again. The other routine is sockSend_Error, which is a Winsock event. This event is triggered whenever a Winsock error is generated. Because UDP is unreliable, few errors will be generated. If an error does occur, the code simply prints out the error's description. The only message a user might see in this application is a destination unreachable message.

Receiving UDP Messages

As you can see, sending a UDP packet with the Winsock control is simple and straightforward. Receiving UDP packets is even easier. Let's go back to the Form_Load routine to see what needs to be done to receive a UDP message. As we saw with the sending Winsock control, the code sets the Protocol property to UDP. The code also disables the Close Listen button. Again, closing an already closed socket won't hurt, but the code does it for the sake of completeness. Also, it's always a good idea to think, "What could happen if I call method X?" at different points in the program. This is the source of most of the problems developers encounter with the control: calling a method when the state of control is invalid. An example of this is calling the Connect method on a Winsock control that is already connected.

To listen for incoming UDP packets, let's look at the cmdListen_Click routine. This is the handler for the Listen button. The only necessary step is to call the Bind method on the receiving Winsock control, passing the local port on which the user wants to listen for incoming UDP datagrams. When listening for incoming UDP packets, the code needs only the local port—the remote port on which the data was sent is not relevant. After the code binds the control, it disables the cmdListen button—this prevents the possibility of the user clicking the Listen button twice. Trying to bind an already bound control will fail with a run-time error.

At this point, the sockRecv control is registered to receive UDP data. When the control receives UDP data on the port it's bound to, the DataArrival event is triggered. This event is implemented in the sockRecv_DataArrival routine. The parameter passed into the event, bytesTotal, is the number of bytes available to be read. The code allocates a string slightly larger than the amount of data being read. Then it calls the GetData method, passing the allocated string as the first parameter. The second parameter defaults to the Visual Basic type vbString, and the third parameter specifies the number of bytes that need to be read, which, in this example, is the value bytesTotal. If the code requests to read a smaller number of bytes than that specified by the bytesTotal parameter, a run-time error is generated. Once the data is read into the character buffer, the code adds it to the list box of messages read. The last few steps in this subroutine set the label captions for the remote host's IP address and port number. Upon receipt of each UDP packet, the RemoteHostIP and RemotePort properties are set to the remote host's IP address and port number for the packet just received. Therefore, if the program receives multiple UDP packets from several hosts, the values of these properties will change often.

The last two subroutines associated with receiving UDP messages are cmdCloseListen_Click and sockRecv_Error. The user invokes the cmdCloseListen_Click handler by clicking the Close Listen button. The routine simply calls the Close method on the Winsock control. Closing a UDP control frees the underlying socket descriptor. The sockRecv_Error event is called whenever a Winsock error is generated. As we mentioned previously in the UDP send section, few UDP errors are generated to begin with because of their unreliable nature.

Obtaining Winsock Information

The last part of our UDP example is the Winsock Information group box. The local name and local IP labels are set at form load time. As soon the form loads and Winsock controls are instantiated, the properties LocalHostName and LocalIP are set to the host name and IP address of the host machine and can be read at any time. The next two labels, Sender State and Receiver State, display the current state of the two Winsock controls used by the application. The state information is updated every half second. This is where the Timer control comes in. Every 500 milliseconds, the timer control triggers the Timer handler, which queries the socket states and updates the labels. We print the socket states for informative purposes only. The last two labels, Remote IP and Remote Port, are set whenever a UDP message is received, as discussed in the previous paragraph.

Running the UDP Example

Now that you understand how to send and receive UDP messages, let's take a look at the example as it runs. The best way to test it is to run an instance of the application on three separate machines. On one of the applications, click the Listen button. On the other two, set the Recipient's Name/IP field to the name of the machine on which the first application is running. This can be either a host name or an IP address. Now click the Send Datagram button a few times, and the messages should appear in the receiver's message window. Upon receipt of each message, the Winsock Information fields should be updated with the IP address of the sender and the port number on which the message was sent. You can even use the Sender commands on the same application as the receiver to send messages on the same machine.

Another interesting test is using either subnet-directed broadcasts or broadcast datagrams. Assuming that you're testing all three machines on the same subnet, you can send a datagram to a specified subnet and all listening applications receive the message. For example, on our test machines we have two single-homed machines with IP addresses 157.54.185.186 and 157.54.185.224. The last machine is multihomed, with IP addresses 169.254.26.113 and 157.54.185.206. As you can see, all three machines share the subnet 157.54.185.255. Let's digress for a moment to discuss an important detail. If you want to receive UDP messages, you must implicitly bind to the first IP address stored in the network bindings when you call the Bind method. This is sufficient if your machine has only one network card. In some cases, however, a machine has more than one network interface and therefore more than one IP address. In these cases, the second parameter to the Bind method is the IP address on which to bind. Unfortunately, the Winsock control property LocalIP returns only one IP address, and the control provides no other method for obtaining other IP addresses associated with the local machine.

Now let's try some broadcasting. Close each sending or listening socket on each of the instances running. On the two single-homed machines, click the Listen button so that each machine can receive datagram messages. We don't use the multihomed machine because we aren't binding to any particular IP address in the code. On the third machine, enter the recipient's address as 157.54.185.255 and click on the Send Data button a few times. You should see the message being received by both listening applications. If your sending machine is also multihomed, you might be wondering how it knows which network interface to send the datagram over. It is one of the routing table's functions to determine the best interface to send the message over, given the message's destination address and the address of each interface on the local machine. If you would like to learn more about subnets and routing, consult a book on TCP/IP such as TCP/IP Illustrated Volume 1, by W. Richard Stevens (Addison-Wesley, 1994) or TCP/IP: Architecture, Protocols, and Implementation with IP v6 and IP Security, by Dr. Sidnie Feit (McGrawHill, 1996). The last test to try is to close the sender's socket on the third machine, enter the recipient's address as 255.255.255.255, and click the Send Datagram button a few times. The results should be the same: the other two listening programs should receive the message. However, the only difference on a multihomed machine is that the UDP message is being broadcast on each network attached to the machine.

UDP States

You might be a bit confused by the order in which method calls should be made to successfully send or receive datagrams. As mentioned earlier, the most common mistake when programming the Winsock control is to call a method whose operation is not valid for the current state of the control. To help alleviate this kind of mistake, take a look at Figure 15-3, which is a state diagram of the socket states when you are using UDP messages. Notice that the default starting state is always sckClosed, and no errors are generated for invalid host names.

click to view at full size.

Figure 15-3. UDP state diagram



Network Programming for Microsoft Windows
Linux Server Hacks, Volume Two: Tips & Tools for Connecting, Monitoring, and Troubleshooting
ISBN: 735615799
EAN: 2147483647
Year: 1998
Pages: 159

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net