Building the Client
Now that the server is built, it is time to build the client. Using Visual Studio 2005, I create a new Windows application (name it WinClient) and populate the default form with the controls shown in
Figure 7 and note the following:
- Set the Multiline property of txtMessageHistory to True and the ReadOnly property to True.
- Set the SelectionMode property of lstUsers to MultiExtended.
 | |
| Figure 7. Adding Controls: Populate the Windows Form with the various controls shown in the screen shot. |
Double-click on the form to switch to the code behind. Import the following namespaces:
Imports System.Net.Sockets
Imports System.IO
Within the Form1 class, define the following variables and constants:
'---get own IP address
Dim ips As Net.IPHostEntry = _
Net.Dns.Resolve(Net.Dns.GetHostName())
'---port nos and server IP address
Const PORTNO As Integer = 500
Const FTPPORTNO As Integer = 501
Const SERVERIP As String = "10.0.1.4"
Dim client As TcpClient
'--used for sending and receiving data
Dim data() As Byte
'---for FTP use
Dim fs As System.IO.FileStream
Dim filename As String
Dim fullfilename As String
When the user signs in, the client first connects to the server and sends the nickname of the user using the
SendMessage() subroutine (defined shortly). Then it begins reading data from the server asynchronously and changes the name of the Sign In button to "Sign Out." It will also ask for the list of names of users currently logged in.
When the user signs out of the chat application, you invoke the Disconnect() subroutine (defined shortly).
'--Sign in to server---
Private Sub btnSignIn_Click( _
ByVal sender As System.Object, _
ByVal e As System.EventArgs) _
Handles btnSignIn.Click
If btnSignIn.Text = "Sign In" Then
'---Sign in to the server
Try
client = New TcpClient
' client.NoDelay = True
'---connect to the server
client.Connect(SERVERIP, PORTNO)
ReDim data(client.ReceiveBufferSize - 1)
'---inform the server of your nick name---
' e.g. [Join][User1]
SendMessage("[Join][" & txtNick.Text & "]")
'---begin reading data asynchronously from
'the server
client.GetStream.BeginRead( _
data, 0, CInt(client.ReceiveBufferSize), _
AddressOf ReceiveMessage, Nothing)
'---change the button and textbox
btnSignIn.Text = "Sign Out"
btnSend.Enabled = True
txtNick.Enabled = False
'---get all users connected
' e.g. [Usrs]
System.Threading.Thread.Sleep(500)
SendMessage("[Usrs]")
Catch ex As Exception
MsgBox(ex.ToString)
End Try
Else
'---Sign off from the server
Disconnect()
lstUsers.Items.Clear()
'---change the button and textbox
btnSignIn.Text = "Sign In"
btnSend.Enabled = False
txtNick.Enabled = True
End If
End Sub
The Send button sends a message to the server. Note that you need to select a user in the ListBox before you can send a message.
'---Send Button
Private Sub btnSend_Click( _
ByVal sender As System.Object, _
ByVal e As System.EventArgs) _
Handles btnSend.Click
' e.g. [Talk][User2,User3,etc]User1>Hello world!
'---select users to chat
If lstUsers.SelectedItems.Count < 1 Then
MsgBox("You must select who to chat with.")
Exit Sub
End If
'---formulate the message
Dim Message As String = "[Talk]["
'---check who to chat with
Dim user As Object
For Each user In lstUsers.SelectedItems
Message += user & ","
Next
Message += "]" & txtNick.Text & ">" & txtMessage.Text
'---update the message history
txtMessageHistory.Text += txtNick.Text & _
">" & txtMessage.Text & vbCrLf
'---send message
SendMessage(Message)
txtMessage.Clear()
End Sub
The
SendMessage() subroutine, used in the code above, allows the client to send a message to the server:
'---Sends the message to the server
Public Sub SendMessage(ByVal message As String)
Try
'---send the text
Dim ns As System.Net.Sockets.NetworkStream
SyncLock client.GetStream
ns = client.GetStream
Dim bytesToSend As Byte() = _
System.Text.Encoding. _
ASCII.GetBytes(message)
'---sends the text---
ns.Write(bytesToSend, 0, bytesToSend.Length)
ns.Flush()
End SyncLock
Catch ex As Exception
MsgBox(ex.ToString)
End Try
End Sub
The
ReceiveMessage() subroutine asynchronously reads data sent from the server in a separate thread. When the data is received, it will display the data in the
txtMessageHistory control. As Windows controls are not thread-safe, you need to use a delegate,
delUpdateHistory(), to update the controls.
'---Receives a message from the server
Public Sub ReceiveMessage(ByVal ar As IAsyncResult)
Try
Dim bytesRead As Integer
bytesRead = client.GetStream.EndRead(ar)
If bytesRead < 1 Then
Exit Sub
Else
Dim messageReceived As String = _
System.Text.Encoding.ASCII.GetString( _
data, 0, bytesRead)
'---update the message history
Dim para() As Object = {messageReceived}
Me.Invoke(New delUpdateHistory(AddressOf _
Me.UpdateHistory), para)
End If
'---continue reading for more data
client.GetStream.BeginRead(data, 0, _
CInt(client.ReceiveBufferSize), _
AddressOf ReceiveMessage, Nothing)
Catch ex As Exception
' MsgBox(ex.ToString)
End Try
End Sub
The
delUpdateHistory() delegate is used to invoke the
UpdateHistory() function in the main thread:
'---delegate to update the textboxes in the main thread
Public Delegate Sub delUpdateHistory(ByVal str As String)
In the
UpdateHistory() subroutine, you examine the message format and perform the appropriate action. For example, if the user has left a chat (through the [Left] message), you must remove the user name from your ListBox.
Listing 2 shows the code for the
UpdateHistory() subroutine.
When the Send File button is clicked, check to see that a recipient user is selected and then prompt the user to select a file to send (see Listing 3).
The FTP_Send subroutine (see Listing 4) sends a file to the recipient through the TCP port 501. It sends files in blocks of 8192 bytes (the maximum buffer size).
The FTP_Receive subroutine (see Listing 5) receives an incoming file through TCP port 501. It writes the file to the c:\temp\ directory.
When the form is closed (by clicking on the "X" button on the window), disconnect the client from the server. This code handles the form close action:
Private Sub Form1_FormClosing( _
ByVal sender As Object, _
ByVal e As System.Windows.Forms.FormClosingEventArgs) _
Handles Me.FormClosing
Disconnect()
End Sub
The
Disconnect() subroutine handles the actual disconnection of the client from the server:
'---disconnect from the server
Public Sub Disconnect()
Try
client.GetStream.Close()
client.Close()
Catch ex As Exception
End Try
End Sub