Section 23.6. ClientServer Interaction with Stream-Socket Connections


23.6. Client/Server Interaction with Stream-Socket Connections

Figures 23.1 and 23.2 use the classes and techniques discussed in the previous two sections to construct a simple client/server chat application. The server waits for a client's request to make a connection. When a client application connects to the server, the server application sends a String to the client, indicating that the connection was successful. The client then displays a message notifying the user that a connection has been established.

Figure 23.1. Server portion of a client/server stream-socket connection.

  1  ' Fig. 23.1: FrmChatServer.vb  2  ' Set up a server that will receive a connection from a client, send a  3  ' string to the client, chat with the client and close the connection.  4  Imports System.Threading    5  Imports System.Net          6  Imports System.Net.Sockets  7  Imports System.IO           8  9  Public Class FrmChatServer 10     Private connection As Socket ' Socket for accepting a connection       11     Private readThread As Thread ' Thread for processing incoming messages 12     Private socketStream As NetworkStream ' network data stream            13     Private writer As BinaryWriter ' facilitates writing to the stream     14     Private reader As BinaryReader ' facilitates reading from the stream   15 16     ' initialize thread for reading 17     Private Sub FrmChatServer_Load(ByVal sender As System.Object, _ 18        ByVal e As System.EventArgs) Handles MyBase.Load 19 20        readThread = New Thread(New ThreadStart(AddressOf RunServer)) 21        readThread.Start()                                            22     End Sub ' FrmChatServer_Load 23 24     ' close all threads associated with this application 25     Private Sub FrmChatServer_FormClosing(ByVal sender As System.Object, _ 26        ByVal e As System.Windows.Forms.FormClosingEventArgs) _ 27        Handles MyBase.FormClosing 28 29        System.Environment.Exit(System.Environment.ExitCode) 30     End Sub ' FrmChatServer_FormClosing 31 32     ' Delegate that allows method DisplayMessage to be called 33     ' in the thread that creates and maintains the GUI 34     Private Delegate Sub DisplayDelegate(ByVal message As String) 35 36     ' method DisplayMessage sets txtDisplay's Text property 37     ' in a thread-safe manner 38     Private Sub DisplayMessage(ByVal message As String) 39        ' if modifying txtDisplay is not thread safe 40        If txtDisplay.InvokeRequired Then 41           ' use inherited method Invoke to execute DisplayMessage 42           ' via a Delegate                                        43           Invoke(New DisplayDelegate(AddressOf DisplayMessage), _ 44              New Object() {message})                               45       ' OK to modify txtDisplay in current thread 46       Else 47            txtDisplay.Text &= message 48         End If 49      End Sub ' DisplayMessage 50 51      ' Delegate that allows method DisableInput to be called 52      ' in the thread that creates and maintains the GUI 53      Private Delegate Sub DisableInputDelegate(ByVal value As Boolean) 54 55      ' method DisableInput sets txtInput's ReadOnly property 56      ' in a thread-safe manner 57      Private Sub DisableInput(ByVal value As Boolean) 58         ' if modifying txtInput is not thread safe 59         If txtInput.InvokeRequired Then 60            ' use inherited method Invoke to execute DisableInput      61            ' via a Delegate                                           62            Invoke(New DisableInputDelegate(AddressOf DisableInput), _ 63               New Object() {value})                                   64         ' OK to modify txtInput in current thread 65         Else 66            txtInput.ReadOnly = value 67         End If 68      End Sub ' DisableInput 69 70      ' send the text typed at the server to the client 71      Private Sub txtInput_KeyDown(ByVal sender As System.Object, _ 72         ByVal e As System.Windows.Forms.KeyEventArgs) _ 73         Handles txtInput.KeyDown 74         ' send the text to the client 75         Try 76              If e.KeyCode = Keys.Enter And txtInput.ReadOnly = False Then 77               writer.Write("SERVER>>>" & txtInput.Text) 78               txtDisplay.Text &= vbCrLf & "SERVER>>> " & txtInput.Text 79 80               ' if the user at the server signaled termination 81               ' sever the connection to the client 82               If txtInput.Text = "TERMINATE" Then 83                  connection.Close() 84               End If 85               txtInput.Clear() ' clear the userí-s input 86            End If 87         Catch ex As SocketException 88            txtDisplay.Text &= vbCrLf & "Error writing object" 89         End Try 90      End Sub ' txtInput_KeyDown 91 92      ' allows a client to connect; displays text the client sends 93        Public Sub RunServer() 94         Dim listener As TcpListener 95         Dim counter As Integer = 1 96 97         ' wait for a client connection and display the text 98         ' that the client sends 99         Try 100           ' Step 1: create TcpListener                          101           Dim local As IPAddress = IPAddress.Parse("localhost") 102           listener = New TcpListener(local, 50000)              103 104           ' Step 2: TcpListener waits for connection request 105           listener.Start()                                   106 107           ' Step 3: establish connection upon client request 108            While True 109               DisplayMessage("Waiting for connection" & vbCrLf) 110 111               ' accept an incoming connection      112               connection = listener.AcceptSocket() 113 114               ' create NetworkStream object associated with socket 115               socketStream = New NetworkStream(connection)         116 117               ' create objects for transferring data across stream 118               writer = New BinaryWriter(socketStream)              119               reader = New BinaryReader(socketStream)              120 121               DisplayMessage( _ 122                 "Connection " & counter & " received." & vbCrLf) 123 124               ' inform client that connection was successfull 125               writer.Write("SERVER>>> Connection successful") 126 127               DisableInput(False)' enable txtInput 128               Dim theReply As String = "" 129 130               ' Step 4: read string data sent from client 131               Do 132                  Try 133                     ' read the string sent to the server 134                     theReply = reader.ReadString() 135 136                     ' display the message 137                     DisplayMessage(vbCrLf & theReply) 138                  Catch ex As Exception 139                     ' handle exception if error reading data 140                     Exit Do 141                  End Try 142               Loop While theReply <> "CLIENT>>> TERMINATE" And _ 143                  connection.Connected 144 145              DisplayMessage(vbCrLf & "User terminated connection" & vbCrLf) 146 147              ' Step 5: close connection 148              writer.Close()             149              reader.Close()             150              socketStream.Close()       151              connection.Close()         152 153              DisableInput(True) ' disable txtInput 154              counter += 1 155           End While 156        Catch ex As Exception 157           MessageBox.Show(ex.ToString()) 158        End Try 159     End Sub ' RunServer 160  End Class ' FrmChatServer_Load 

Figure 23.2. Client portion of a client/server stream-socket connection.

  1  ' Fig. 23.2: FrmChatClient.vb  2  ' Set up a client that will send information to and  3  ' read information from a server.  4  Imports System.Threading    5  Imports System.Net.Sockets  6  Imports System.IO           7  8  Public Class FrmChatClient  9     Private output  As NetworkStream ' stream for receiving data           10     Private writer  As BinaryWriter ' facilitates writing to the stream    11     Private reader As BinaryReader ' facilitates reading from the stream   12     Private readThread As Thread ' Thread for processing incoming messages 13     Private message As String = "" 14 15    ' initialize thread for reading 16    Private Sub FrmChatClient_Load(ByVal sender As System.Object, _ 17       ByVal e As System.EventArgs) Handles MyBase.Load 18 19       readThread = New Thread(New  ThreadStart(AddressOf RunClient)) 20       readThread.Start()                                             21    End Sub ' FrmChatClient_Load 22 23     ' close all threads associated with this application 24     Private Sub FrmChatClient_FormClosing(ByVal sender As  System.Object, _ 25        ByVal e As System.Windows.Forms.FormClosingEventArgs) _ 26        Handles MyBase.FormClosing 27 28        System.Environment.Exit(System.Environment.ExitCode) 29     End Sub ' FrmChatClient_FormClosing 30 31     ' Delegate that allows method  DisplayMessage to be called 32     ' in the thread that creates and  maintains the GUI 33     Private Delegate Sub DisplayDelegate(ByVal  message As String) 34 35     ' method DisplayMessage sets txtDisplay's Text property 36     ' in a thread-safe manner 37     Private Sub DisplayMessage(ByVal  message As String) 38        ' if modifying txtDisplay is not thread safe 39          If txtDisplay.InvokeRequired Then 40             ' use inherited method Invoke to execute DisplayMessage  41             ' via a Delegate                                         42             Invoke(New DisplayDelegate(AddressOf  DisplayMessage), _ 43                New Object()  {message})                            44          ' OK to modify txtDisplay in current thread 45          Else 46             txtDisplay.Text & = message 47          End If 48     End Sub ' DisplayMessage 49 50     ' Delegate that allows method  DisableInput to be  called 51     ' in the thread that creates and  maintains the GUI 52     Private Delegate Sub DisableInputDelegate(ByVal value  As Boolean ) 53 54     ' method DisableInput sets txtInput's ReadOnly property 55     ' in  a thread-safe manner 56     Private Sub DisableInput(ByVal value  As Boolean) 57        ' if modifying txtInput is not thread safe 58        If txtInput.InvokeRequired Then 59           ' use inherited method Invoke to execute DisableInput      60           ' via a Delegate                                           61           Invoke(New DisableInputDelegate(AddressOf DisableInput), _ 62              New Object() {value})                                   63        ' OK to modify txtInput in current thread 64        Else 65           txtInput.ReadOnly = value 66        End If 67     End Sub ' DisableInput 68 69     ' sends text the user typed to server 70     Private Sub txtInput_KeyDown(ByVal sender As System.Object, _ 71        ByVal e As System.Windows.Forms.KeyEventArgs) _ 72        Handles    txtInput.KeyDown 73 74        Try 75           If e.KeyCode = Keys.Enter And txtInput.ReadOnly = False Then 76              writer.Write("CLIENT>>> " & txtInput.Text) 77              txtDisplay.Text &= vbCrLf & "CLIENT>>> " & txtInput.Text 78              txtInput.Clear() 79           End If 80        Catch ex As SocketException 81           txtDisplay.Text &= vbCrLf & "Error writing object" 82        End Try 83     End Sub ' txtInput_KeyDown 84 85     ' connect to server and display server-generated text 86     Public Sub RunClient() 87        Dim client As TcpClient 88 89         ' instantiate TcpClient for sending data to server 90         Try 91         DisplayMessage("Attempting connection" & vbCrLf) 92 93           ' Step 1: create TcpClient and connect to server 94           client = New TcpClient()                         95           client.Connect("localhost", 50000)               96 97           ' Step 2: get NetworkStream associated with TcpClient 98           output = client.GetStream()                           99 100          ' create objects for writing and reading across stream 101          writer = New BinaryWriterx(output)                     102          reader = New BinaryReader(output)                      103 104          DisplayMessage(vbCrLf  & "Got I/O streams" & vbCrLf) 105          DisableInput(False) ' enable txtInput 106 107          ' loop until server signals termination 108          Do 109             ' Step 3: processing phase 110             Try 111                ' read message from server       112                message = reader.ReadString()    113                DisplayMessage(vbCrLf & message) 114             Catch ex As Exception 115               ' handle exception if error in reading server data 116               System.Environment.Exit(System.Environment.ExitCode) 117            End Try 118         Loop While message <> "SERVER>>> TERMINATE" 119 120           ' Step 4: close connection 121           writer.Close()             122           reader.Close()             123           output.Close()             124           client.Close()             125 126           Application.Exit() 127        Catch ex As Exception 128           ' handle exception if error in establishing connection 129            MessageBox.Show(ex.ToString(), "Connection Error"  , _ 130               MessageBoxButtons.OK, MessageBoxIcon.Error) 131            System.Environment.Exit(System.Environment.ExitCode) 132        End Try 133     End Sub ' RunClient 134  End Class ' FrmChatClient 

(a)

(b)

(c)

(d)

(e)

(f)

(g)

The client and server applications both contain TextBoxes that enable users to type messages and send them to the other application. When either the client or the server sends the message "TERMINATE," the connection between the client and the server terminates. The server then waits for another client to request a connection. Figure 23.1 and Fig. 23.2 provide the code for classes FrmChatServer and FrmChatClient, respectively. Figure 23.2 also contains screen captures displaying the execution between the client and the server.

FrmChatServer Class

In class FrmChatServer (Fig. 23.1), FrmChatServer_Load (lines 1722) creates a Thread that will accept connections from clients (line 20). The THReadStart delegate object that is passed as the THRead constructor's argument specifies which method the Thread executes. Line 21 starts the Thread, which uses the ThreadStart delegate to invoke method RunServer (lines 93159). This method initializes the server to receive connection requests and process connections. Line 102 instantiates a TcpListener object to listen for a connection request from a client at port 50000 (Step 1). Line 105 then calls TcpListener method Start, which causes the TcpListener to begin waiting for requests (Step 2).

Accepting the Connection and Establishing the Streams

Lines 108155 are an infinite loop that begins by establishing the connection requested by the client (Step 3). Line 112 calls method AcceptSocket of the TcpListener object, which returns a Socket upon successful connection. The thread in which method Accept-Socket is called blocks (i.e., stops executing) until a connection is established. The returned Socket object manages the connection. Line 115 passes this Socket object as an argument to the constructor of a NetworkStream object, which provides access to streams across a network. In this example, the NetworkStream object uses the streams of the specified Socket. Lines 118119 create instances of the BinaryWriter andBinaryReader classes for writing and reading data. We pass the NetworkStream object as an argument to each constructorBinaryWriter can write bytes to the NetworkStream, and BinaryReader can read bytes from NetworkStream. Line 121 calls DisplayMessage, indicating that a connection was received. Next, we send a message to the client indicating that the connection was received. BinaryWriter method Write has many overloaded versions that write data of various types to a stream. Line 125 uses method Write to send the client a String notifying the user of a successful connection. This completes Step 3.

Receiving Messages from the Client

We now begin the processing phase (Step 4). Lines 131143 loop until the server receives the message CLIENT>>> TERMINATE, which indicates that the connection should be terminated. Line 134 uses BinaryReader method ReadString to read a String from the stream. Method ReadString blocks until a String is read. This is why we execute method RunServer in a separate Thread (created in lines 2021, when the Form loads). This THRead ensures that the application's user can continue to interact with the GUI to send messages to the client, even when this thread is blocked while awaiting a message from the client.

Modifying GUI Controls from Separate Threads

Windows Form controls are not thread safea control that is modified from multiple threads is not guaranteed to be modified correctly. The Visual Studio 2005 Documentation recommends that only the thread which created the GUI should modify the controls.[1] Class Control provides method Invoke to help ensure this. Invoke takes two argumentsa Delegate representing a method that will modify the GUI and an array of Objects representing the parameters of the method. (Delegates were introduced in Section 13.3.3.) At some point after Invoke is called, the thread that originally created the GUI will (when not executing any other code) execute the method represented by the Delegate, passing the contents of the Object array as the method's arguments.

[1] The MSDN article "How to: Make Thread-Safe Calls to Windows Forms Controls" can be found at msdn2.microsoft.com/en-us/library/ms171728.aspx.

Line 34 declares a Delegate type named DisplayDelegate, which represents methods that take a String argument and do not return a value. Method DisplayMessage (lines 3849) meets these requirementsit receives a String parameter named message and does not return a value. The If statement in line 40 tests txtdisplay's InvokeRequired property (inherited from class Control), which returns true if the current thread is not allowed to modify this control directly and returns False otherwise. If the current thread executing method DisplayMessage is not the thread that created the GUI, the If condition evaluates to true and lines 4344 call method Invoke, passing it a new DisplayDelegate representing the method DisplayMessage itself and a new Object array consisting of the String argument message. This causes the thread that created the GUI to call method DisplayMessage again at a later time with the same String argument as the original call. When that call occurs from the thread that created the GUI, the method is allowed to modify txTDisplay directly, so the Else body (line 47) executes and appends message to txtdisplay's Text property.

Lines 5368 define the Delegate, DisableInputDelegate, and a method, DisableInput, to allow any thread to modify the ReadOnly property of txtInput using the same techniques. A thread calls DisableInput with a Boolean argument (true to disable; False to enable). If DisableInput is not allowed to modify the control from the current thread, DisableInput calls method Invoke. This causes the thread that created the GUI to call DisableInput at a later time to set txtInput.ReadOnly's value to the Boolean argument.

Terminating the Connection with the Client

When the chat is complete, lines 148151 close the BinaryWriter, BinaryReader, NetworkStream and Socket (Step 5) by invoking their respective Close methods. The server then waits for another client connection request by returning to the beginning of the loop (line 108).

Sending Messages to the Client

When the server application's user enters a String in the TextBox and presses the Enterkey, event handler txtInput_KeyDown (lines 7190) reads the String and sends it via method Write of class BinaryWriter. If a user terminates the server application, line 83 calls method Close of the Socket object to close the connection.

Terminating the Server Application

Lines 2530 define event handler FrmChatServer_FormClosing for the FormClosing event. The event closes the application and calls method Exit of class Environment with parameter ExitCode. Method Exit terminates all threads associated with the application.

FrmChatClient Class

Figure 23.2 lists the code for class FrmChatClient. Like the FrmChatServer object, the FrmChatClient object creates a Thread (lines 1920) in its constructor to handle all incoming messages. FrmChatClient method RunClient (lines 86133) connects to the FrmChatServer, receives data from the FrmChatServer and sends data to the FrmChatServer. Lines 9495 instantiate a TcpClient object, then call its Connect method to establish a connection (Step 1). The first argument to method Connect is the name of the serverin our case, the server's name is "localhost", meaning that the server is located on the local computer. The localhost is also known as the loopback IP address and is equivalent to the IP address 127.0.0.1. This value sends the data transmission back to the sender's IP address. The second argument to method Connect is the server port number. This number must match the port number at which the server waits for connections. [Note: The host name localhost is commonly used to test networking applications on one computer. This is particularly useful if you don't have separate computers on which to execute the client and server. Normally, localhost would be replaced with the hostname or IP address of another computer.]

The FrmChatClient uses a NetworkStream to send data to and receive data from the server. The client obtains the NetworkStream in line 98 through a call to TcpClient method GetStream (Step 2). Lines 108118 loop until the client receives the connectiontermination message (SERVER>>> TERMINATE). Line 112 uses BinaryReader method ReadString to obtain the next message from the server (Step 3). Line 113 displays the message, and lines 121124 close the BinaryWriter, BinaryReader, NetworkStream and TcpClient objects (Step 4).

Lines 3367 declare DisplayDelegate, DisplayMessage, DisableInputDelegate and DisableInput just as in lines 3468 of Fig. 23.1. These once again are used to ensure that the GUI is modified only by the thread that created the GUI controls.

When the user of the client application enters a String in the TextBox and presses the Enter key, event handler txtInput_KeyDown (lines 7083) reads the String from the TextBox and sends it to the server via BinaryWriter method Write.

In this client/server chat program, the FrmChatServer receives a connection, processes it, closes it and waits for the next one. In a real-world application, a server would likely receive a connection, set up the connection to be processed as a separate thread of execution and wait for new connections. The separate threads that process existing connections could then continue to execute while the server concentrates on new connection requests.



Visual BasicR 2005 for Programmers. DeitelR Developer Series
Visual Basic 2005 for Programmers (2nd Edition)
ISBN: 013225140X
EAN: 2147483647
Year: 2004
Pages: 435

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