| Echo Client Example | ||||||
|
The sample program that will be used throughout this document is a simple tool that can be used to connect with an echo server, a program which echoes back any data that's sent to it. Later on, we'll also cover how to implement your own echo server. To begin, you'll need to create a form that has three labels, three text controls, a button and the SocketWrench control. The form might look something like this:
When executed, the user will enter the name or IP address of the system in the Text1 control, the text that is to be echoed in the Text2 control, and the server's reply will be displayed in the Text3 control. The Command1 button will be used to establish a connection with the remote host. When you save your project, call it "Client". First we should initialize the controls in the Form's Load subroutine. Note that we want to disable the Text2 and Text3 controls, since they only should be usable once a connection to a server has been established. The code should look like this:
Private Sub Form_Load()
Command1.Caption = "Connect"
Command1.Enabled = True
Text1.Enabled = True
Text2.Enabled = False
Text3.Enabled = False
End Sub
The next step is to write the code that actually establishes a connection with the remote host in the Click event for the Command1 button. The code should look like this:
Private Sub Command1_Click()
If Not SocketWrench1.Connected Then
Dim strRemoteHost As String
Dim nError As Long
strRemoteHost = Trim(Text1.Text)
SocketWrench1.Blocking = False
SocketWrench1.Protocol = swProtocolTcp
nError = SocketWrench1.Connect(strRemoteHost, swPortEcho)
If nError <> 0 Then
MsgBox "Unable to connect to remote host", vbExclamation
Exit Sub
End If
Command1.Enabled = False
Else
SocketWrench1.Disconnect
Command1.Caption = "Connect"
Text2.Enabled = False
Text3.Enabled = False
End If
End Sub
The first SocketWrench property that we encounter is the Connected property. This is a boolean flag which tells us if the control has established a connection to a remote host. We're using this to allow the Command1 button to function in one of two ways: if no connection has been established, then pressing the button will cause the client to make a connection to the server entered in the Text1 control. However, if there is an active connection, then pressing the button will disconnect the client from the server. These next three SocketWrench properties are used to define some basic functions of the control, such as how host names are resolved and what network protocol is used. These properties are:
To establish the connection to the server, the Connect method is called, passing the name of the server to connect to and the port number of the echo server. It should be noted that there are a number of optional arguments to this method, but for the purposes of this example, only the host name and port number are needed. If the connection attempt is successful, the method will return a value of zero. However, if an error occurs the method will return a non-zero value which specifies an error code. If the connection attempt is successful, then the Command1 button is disabled. Because the socket is non-blocking (that is, the Blocking property is False), when the Connect method returns it does not mean that the connection has actually completed. Instead, it means that the connection process has begun, and completion is signaled by the control's OnConnect event firing. So between the time that the Connect method is called to establish a connection and the time that the OnConnect event is fired to indicate that the connection has been completed, the user should not be able to press the Command1 button because it would result in the Connect method being called again. To update our form when a connection has been established, we need to add some code to the control's OnConnect event. Remember, this event is only called after a connection attempt has completed on a non-blocking socket:
Private Sub SocketWrench1_OnConnect()
Command1.Caption = "Disconnect"
Command1.Enabled = True
Text2.Enabled = True
Text3.Enabled = True
MsgBox "Connect to remote host", vbInformation
End Sub
This will change the caption of our Command1 button to "Disconnect" (informing the user that when they press it, now it will disconnect the current session), and enable our Text2 and Text3 controls. We also display a message box indicating that the connection has completed. There is a possibility that the remote host may terminate our connection, and our client application needs to be able to handle this. If this happens, for example if the server is stopped, then the control's OnDisconnect event will fire. In our code, we'll reset our command button's caption, disable the Text2 and Text3 controls and display a message box indicating that the connection has been lost. The code would look like this:
Private Sub SocketWrench1_OnDisconnect()
SocketWrench1.Disconnect
Command1.Caption = "Connect"
Command1.Enabled = True
Text2.Enabled = False
Text3.Enabled = False
MsgBox "Disconnected from remote host", vbInformation
End Sub
The one thing that may seem strange here is calling the Disconnect method. After all, the connection has been closed, so this appears to be redundant. The thing to remember is that a socket is a communications endpoint; for every conversation between a client and a server, there are two sockets: one on your end (the client) and one on theirs (the server). When the OnDisconnect event fires, what the control is telling you is that the other socket, in this case the server's socket, has been closed. However, until you call the Disconnect method it will remain open on the client side. For the connection to be completely terminated, the sockets on both ends of the connection need to be closed. What happens if there is an error while the client attempts to connect to the server? It is possible for the Connect method to return zero (indicating success), and then once the connection attempt begins, an error occurs. For example, this can happen if there is no server listening on the specified port number. To be able to handle this, the control has an event called OnError which is fired whenever an error such as this occurs. Let's add some code to the event to report any errors:
Private Sub SocketWrench1_OnError(ByVal Error As Variant, ByVal Description As Variant)
If Error <> swErrorOperationWouldBlock Then
SocketWrench1.Disconnect
Command1.Caption = "Connect"
Command1.Enabled = True
Text2.Enabled = True
Text3.Enabled = True
MsgBox Description, vbExclamation, "Error " & CStr(Error)
End If
End Sub
The OnError event has two arguments passed to it, an
error code and a textual description of the error. The first thing
that we do is compare this error against one of our predefined
error constants Now that the code to establish the connection has been written, the next step is to actually send and receive data to and from the server. To do this, the Text2 control should have the following code added to its KeyPress event:
Private Sub Text2_KeyPress(KeyAscii As Integer)
If KeyAscii = 13 Then
Dim strBuffer As String
Dim cchBuffer As Long, nResult As Long
strBuffer = Text2.Text & vbCrLf
cchBuffer = Len(strBuffer)
Text2.Text = ""
KeyAscii = 0
nResult = SocketWrench1.Write(strBuffer, cchBuffer)
If nResult = -1 Then
MsgBox "Unable to send data to server"
Exit Sub
End If
End If
End Sub
The Write method is used to send data to the remote host. The first argument is the buffer that contains the data (in this case, a string variable) and the second argument is the number of bytes to write. Note that the second argument is optional and if it is omitted the entire buffer is written. For clarity, it is recommended that the buffer length be specified. Note that in addition to strings, the Write method will also accept bytes and byte arrays as parameters. Because our example is connecting to an echo service, once the data has been sent to the remote host, it immediately sends the data back to the client. This generates an OnRead event in SocketWrench, which should have the following code:
Private Sub SocketWrench1_OnRead()
Dim strBuffer As String
Dim nResult As Long
nResult = SocketWrench1.Read(strBuffer, 1024)
If nResult > 0 Then
Text3.Text = Text3.Text + strBuffer
End If
End Sub
The OnRead event indicates that data has arrived and is available to be read by the control. The Read method then reads the data sent by the server and stores it in the buffer specified in the first parameter. The second parameter specifies the maximum number of bytes to read. Note that in this case, it is an arbitrary value of 1,024 bytes. One important thing to note is that requesting to read a specified number of bytes does not guarantee that you will actually receive that amount. Because TCP is a stream-oriented protocol, there is no concept of a "message boundary" or a one-to-one relationship between the amount of data written to the socket and the amount of data read from it. In other words, the server sends four pieces of data in 512 byte blocks, there is no guarantee that your program will get four OnRead events for that number of bytes per read. Instead, you may get more than four events (in which the data sent is received in smaller blocks) or you may get fewer events, with the data being combined. This is the nature of how TCP/IP works, and must be accounted for in the design of you application. Typically this means buffering the data in the program and either looking for special "end of message" characters in the data stream, accumulating data in fixed sizes and processing it as the buffer is filled. A good rule of thumb to follow when considering your design is thinking about how your program would work if, after asking for some arbitrary number of bytes of data, it received only a single byte. If your program is robust enough to handle this situation, then it will function correctly under a wide variety of networking environments (such as low throughput or high latency networks). On the other hand, if your program expects that it will be able to read a specific number of bytes of data at any given time, then you may find that it works correctly in under most circumstances, but intermittently fails under low bandwidth or high network load conditions. The last piece of code to add to the sample is to handle closing the socket when the program is terminated by selecting Close on the system menu. The best place to put socket cleanup code is in the form's Unload event, such as:
Sub Form_Unload (Cancel As Integer)
If SocketWrench1.Connected Then
SocketWrench1.Disconnect
End If
End
End Sub
If the Connected property returns True, then a connection has been established and we should disconnect from the server before the program terminates. With all of the properties and event code needed for the sample client application completed, all that's left to do is run the program! Of course, in a real application you'd need to provide extensive error checking. SocketWrench errors start at 10,000 and correspond to the error codes used by the Windows Sockets API. Most errors will occur when setting the host name, address, service port or using one of the methods. Note that if you don't have access to an echo server, then you won't be able to test your client program just yet. But don't worry, the next step is building your own server application and you can use the client to communicate with it. |
||||||
|
Copyright © 2008 Catalyst Development Corporation. All rights reserved. |
||||||