programming4us
programming4us
ENTERPRISE

Microsoft Visual Basic 2008 : Services That Listen - Listening with TCP/IP

- How To Install Windows Server 2012 On VirtualBox
- How To Bypass Torrent Connection Blocking By Your ISP
- How To Install Actual Facebook App On Kindle Fire
9/13/2012 12:57:17 AM
When using TCP/IP to accept incoming connections and requests, you must account for several things before designing your service. These depend on the solution you are trying to build or the problem you are trying to resolve.

Design Points for Service Listeners

Many different types of service listeners are available. Whether the protocol is TCP, HTTP, UDP, FTP, or some custom variation, you need to consider some important questions before you start coding your service, including the following:

  • Which server port (or list of ports) will requests come in on?

  • How many active connections will you allow at one time?

  • What type of security will you implement? Will you use network authentication, implied security, or clear-text authentication with user names and passwords stored in a secured place such as Microsoft SQL Server?

  • If your service must perform actions on behalf of that caller, will it do so in the context of the caller, or in its own security context? Will your service have more or less security authorization than the caller?

  • Will the connections be synchronous or asynchronous?

  • Will connections have a time limit?

  • Will each request from a caller require a new connection or can connections be static after a user is connected successfully?

  • How will the service handle invalid connection attempts?

  • In what format does the service expect the requests?

  • What format will you use for communications between the service and the client?

Creating the First Listener Service

The preceding list shows some important characteristics of a service that you must carefully consider before design. In this section, we’ll use a single server port to listen for connections. When connections arrive, they will be authenticated using a very simple, basic text authentication scheme. The user name and password will be hard-coded for now. When the connection is authenticated and a secondary socket has been created to service clients’ requests, we’ll wait for requests to come in from the client. All of these requests will be standard text-based requests with a standard delimiter. When the request comes in, the service will process the request and then return the information to the caller. After the caller acknowledges receipt of the information or the request times out, the connection will be dropped and the resources for that connection and any work it did will be cleaned up.

Coding the Service Listener

The code base gives us our standard service framework with the ability to start, stop, pause, continue, and shut down the service.

Adding a Configuration File

We need to add a configuration file that our application can use to create single or multiple listeners. In the first example, shown in Listing 1, we’ll only use a single listener.

Listing 1. Configuration file schema.
<?xml version="1.0" encoding="utf-8" ?>
<Configuration>
  <Listeners>
    <Listener>
      <Port>15000</Port>
      <MaxConnections>1</MaxConnections>
    </Listener>
  </Listeners>
</Configuration>

The configuration file will allow us to have as many listeners as we want. The listeners themselves consist of the following two properties:

  • Port represents the server side port to listen on. For most systems this is a number between 1 and 65,000. Ensure that the port you want to listen on is not already in use.

  • MaxConnections represents how many client connections you can have at one time. Remember that connections do not stay connected on the server port that the client initially connected to. You have to create a server-side socket to hold the client’s connection.

Creating a Listener Class

We need to create a class that can support multiple listeners. Although we won’t be creating multiple services, we will be creating the ability for multiple entry points into this service. We could also extend our configuration file to include information that would tell the Listener class instance to do a specific task. If a single service could have multiple actions, you would want to have separate server ports, threads, and worker data for each possible action. You could optimize your service even more by separating the workload of each task that your service is capable of. Let’s review our Listener class, shown in Listing 2.

Listing 2. The Listener class.
Imports System.Threading
Imports System.IO
Imports System.Text
Imports System.Net.Sockets
Imports System.Net
Imports System.ServiceProcess

Public Class Listener
    Public m_Incoming As Thread
    Private m_ThreadAction As ThreadActionState
    Private m_Listener As Socket = Nothing
    Private m_ClientSocket As Socket = Nothing
    Private m_MaxConnections As Integer
    Private m_Port As Integer

    Public Sub New(ByRef threadaction As ThreadActionState)
        m_ThreadAction = threadaction
    End Sub

    Public Sub Start()
        m_Incoming = New Thread(AddressOf StartListener)
        m_Incoming.Priority = ThreadPriority.Normal
        m_Incoming.IsBackground = True
        m_Incoming.Start()
    End Sub

    Private Sub StartListener()
        While Not m_ThreadAction.StopThread
            If Not m_ThreadAction.Pause Then
                Try
                    'We need to set up our Port listener and the ability
                    'to accept an incoming call.
                    Dim localEndPoint As IPEndPoint = Nothing

                    m_Listener = New Socket(AddressFamily.InterNetwork, _
                                  SocketType.Stream, ProtocolType.Tcp)

                    Dim ipHostInfo As IPHostEntry = Dns.GetHostEntry(Dns.GetHostName())
                    Dim ipAddress As IPAddress = ipHostInfo.AddressList(0)

                    localEndPoint = New IPEndPoint(ipAddress.Any, Me.Port)

                    m_Listener.Bind(localEndPoint)
                    m_Listener.Listen(Me.MaxConnections)

                    Dim bytes() As Byte = New [Byte](1024) {}

                    While True
  ' Program is suspended while waiting for an incoming connection.
                        m_ClientSocket = m_Listener.Accept

                        Dim Data As String = Nothing
                        Dim bError As Boolean = False
                        ' An incoming connection needs to be processed.
                        While True
                            Dim iStart As Long = Now.Ticks
                            'Create a Byte Buffer to receive data on.
                            bytes = New Byte(1024) {}

                            Dim bytesRec As Integer = _
                            m_ClientSocket.Receive(bytes)
                            Data += Encoding.ASCII.GetString(bytes, 0, _
                                                              bytesRec)

                            If ((Now.Ticks - iStart) / 10000000) > 30 Then
            'We have timed out based on a 30 second timeout
                                Try
                       m_ClientSocket.Shutdown(SocketShutdown.Both)
                                Catch ex As Exception
                                    'do nothing
                                End Try

                                Try
                                    m_ClientSocket.Close()
                                Catch ex As Exception
                                    'do nothing
                                End Try

                                Exit While
                            End If
   'if we have not timed out yet then let us   see if a command
                            'has come in and process it
                            If Data.IndexOf("<EOF>") > -1 Then
  'If we have found an EOF then we need to process that information
 'we could reset our timeout variable also if we have a command so it
                                'does not time out falsely
                                'Process the Command
                                Dim pszOut As String = Nothing

                                Try
                                    WriteLogEvent(Data, 15, _
                 EventLogEntryType.Information, My.Resources.Source)
                                    Call ProcessCommand(Data, pszOut)
                                    m_ClientSocket.Send(Encoding.ASCII.GetBytes(pszOut), _
                      Encoding.ASCII.GetBytes(pszOut).Length, SocketFlags.None)
                                Catch ex As Exception
                                    Exit While
                                End Try
                                'clean up
                                Try
m_ClientSocket.Shutdown(SocketShutdown.Both)
                                Catch ex As Exception
                                End Try

                                Try
                                    m_ClientSocket.Close()
                                Catch ex As Exception
                                End Try
                            End If

                            Exit While
                        End While
                    End While
                Catch nex As SocketException
               WriteLogEvent(My.Resources.ThreadErrorMessage + "_" + _
               nex.ToString + "_" + Now.ToString, THREAD_ERROR, _
                              EventLogEntryType.Error, My.Resources.Source)
                Catch tab As ThreadAbortException
               WriteLogEvent(My.Resources.ThreadAbortMessage + "_" + _
               tab.ToString + "_" + Now.ToString, THREAD_ABORT_ERROR, _
                      EventLogEntryType.Error, My.Resources.Source)
                Catch ex As Exception
                WriteLogEvent(My.Resources.ThreadErrorMessage + "_" + _
                   ex.ToString + "_" + Now.ToString, THREAD_ERROR, _
                   EventLogEntryType.Error, My.Resources.Source)
                End Try
            End If

            If Not m_ThreadAction.StopThread Then
                Thread.Sleep(THREAD_WAIT)
            End If
        End While

    End Sub

    Private Shared Sub WriteLogEvent(ByVal pszMessage As String, _
         ByVal dwID As Long, ByVal iType As EventLogEntryType, _
         ByVal pszSource As String)
        Try
            Dim eLog As EventLog = New EventLog("Application")
            eLog.Source = pszSource

  Dim eInstance As EventInstance = New EventInstance(dwID, 0, iType)
            Dim strArray() As String

            ReDim strArray(1)
            strArray(0) = pszMessage
            eLog.WriteEvent(eInstance, strArray)

            eLog.Dispose()
        Catch ex As Exception
            'Do not Catch here as it doesn't do any good for now
        End Try
    End Sub

    Private Function ProcessCommand(ByVal pszCommand As String, _
                  ByRef pszOut As String) As Boolean
        Try
            If pszCommand Is Nothing Then
                Return False
            End If

            'Get the Data and clear out our ending delimiter
            Try
                pszCommand = pszCommand.Remove(pszCommand.Length - 5,
                  "<EOF>".Length)
            Catch ex As Exception
                Return False
            End Try

            'Split the Command and find out which one we are doing
            Dim pszArray() As String = Split(pszCommand, "##")

            Select Case UCase(pszArray(0))
                Case "GETDATETIME"
                    pszOut = GetDateTime()
                Case "GETSERVICESTATUS"
                    pszOut = GetServiceStatus(pszArray(1))
                Case "GETPROCESSLIST"
                    pszOut = GetProcessList()
                Case Else
                    pszOut = Nothing
                    Return False
            End Select

            Return True
        Catch ex As Exception
            Return False
        End Try
    End Function

    Private Function GetDateTime() As String
        Try
            Return Now.ToString
        Catch ex As Exception
            Return Nothing
        End Try
    End Function

    Private Function GetServiceStatus(ByVal pszService As String) As String
        Try
            Dim tmpService As New ServiceController(pszService)
            Dim pszOut As String = Nothing

            Select Case tmpService.Status
                Case ServiceControllerStatus.ContinuePending
                    pszOut = "ContinuePending"
                Case ServiceControllerStatus.Paused
                    pszOut = "Paused"
                Case ServiceControllerStatus.PausePending
                    pszOut = "PausePending"
                Case ServiceControllerStatus.Running
                    pszOut = "Running"
                Case ServiceControllerStatus.StartPending
                    pszOut = "StartPending"
                Case ServiceControllerStatus.Stopped
                    pszOut = "Stopped"
                Case ServiceControllerStatus.StopPending
                    pszOut = "StopPending"
                Case Else
                    pszOut = "Unknown"
            End Select

            Try
                tmpService.Close()
                tmpService.Dispose()
                tmpService = Nothing
            Catch ex As Exception
                'Do nothing
            End Try
            Return pszOut
        Catch ex As Exception
            Return "Unknown"
        End Try
    End Function

    Private Function GetProcessList() As String
        Try
            Dim pszOut As String = Nothing
            Dim tmpProcesses() As Process = Process.GetProcesses
            Dim objProcess As Process
            For Each objProcess In tmpProcesses
                If pszOut Is Nothing Then
                    pszOut = objProcess.ProcessName
                Else
                    pszOut += "##" + objProcess.ProcessName
                End If
            Next

            objProcess = Nothing
            tmpProcesses = Nothing

            Return pszOut
        Catch ex As Exception
            Return Nothing
        End Try
    End Function

    Public Property Port() As Integer
        Get
            Return m_Port
        End Get
        Set(ByVal value As Integer)
            m_Port = value
        End Set
    End Property

    Public Property MaxConnections() As Integer
        Get
            Return m_MaxConnections
        End Get
        Set(ByVal value As Integer)
            m_MaxConnections = value
        End Set
    End Property

    Public ReadOnly Property Incoming() As Thread
        Get
            Return m_Incoming
        End Get
    End Property
End Class

					  

The following sections review this code.

Listener Class Properties

Listener has two properties that we read from our configuration file. First is the Port property, which tells us which server port to use for this instance. Second is the MaxConnections property, which tells us how many clients can connect to this instance at one time. Each property must be set before the <Start> method of the class instance is called.

The <StartListener> Method

If you’ve never worked with sockets and TCP/IP before, it’s especially important that you review this code.

The first thing I do is create an endpoint, shown in Listing 3. An endpoint defines the binding information used by the socket to bind to the local server and socket instance based on the port and server IP address.

Listing 3. Define local endpoint used by listener socket.
Dim localEndPoint As IPEndPoint = Nothing

Next I create a listener socket, shown in Listing 4. The listener socket waits on the endpoint for incoming requests.

Listing 4. Define listener socket used by service.
m_Listener = New Socket(AddressFamily.InterNetwork, _
                        SocketType.Stream, ProtocolType.Tcp)

I am using the standard TCP protocol. This indicates that I want to create a connection-based socket.

Next, as shown in Listing 5, I create the required IPHostEntry and IPAddress instances that are used by IPEndPoint to create the binding information for the listener socket. I use the Dns.GetHostName method to get the list of IP addresses of the local computer. I actually get back a list of IP addresses, but I only care about the first one. I could, of course, iterate through the list if I had multiple adapters and wanted to bind to a specific adapter.

Listing 5. Define listener socket attributes used to bind to server local port.
Dim ipHostInfo As IPHostEntry = Dns.GetHostEntry(Dns.GetHostName())
Dim ipAddress As IPAddress = ipHostInfo.AddressList(0)
localEndPoint = New IPEndPoint(ipAddress, Me.Port)
m_Listener.Bind(localEndPoint)
m_Listener.Listen(Me.MaxConnections)

Last, you will see that I use IPEndPoint to bind the listener socket, and then I use the Socket.Listen method to start listening for incoming connections. You should also notice that I am using the MaxConnections property from the configuration file to tell the listener how many client sockets can be connected at any time before it will return an unavailable connection error to the clients.

The <StartListener> Processing Loop

Once we have the service listener socket running, we are waiting for a client connection request. When a request comes in, we want to process that request. Let’s review the code that does this, shown in Listing 6.

Listing 6. The <StartListener> processing loop.
Dim bytes() As Byte = New [Byte](1024) {}
While True
' Program is suspended while waiting for an incoming connection.
    m_ClientSocket = m_Listener.Accept

Dim Data As String = Nothing
Dim bError As Boolean = False
' An incoming connection needs to be processed.
    While True
Dim iStart As Long = Now.Ticks
'Create a Byte Buffer to receive data on.
        bytes = New Byte(1024) {}
Dim bytesRec As Integer = m_ClientSocket.Receive(bytes)
        Data += Encoding.ASCII.GetString(bytes, 0, bytesRec)
        If ((Now.Ticks - iStart) / 10000000) > 30 Then
'We have timed out based on a 30 second timeout
            Try
                m_ClientSocket.Shutdown(SocketShutdown.Both)
            Catch ex As Exception
            End Try

            Try
                m_ClientSocket.Close()
            Catch ex As Exception
            End Try
            Exit While
        End If
        If Data.IndexOf("<EOF>") > -1 Then
Dim pszOut As String = Nothing

            Try
                WriteLogEvent(Data, 15, EventLogEntryType.Information,
                                 My.Resources.Source)
                Call ProcessCommand(Data, pszOut)
                m_ClientSocket.Send(Encoding.ASCII.GetBytes(pszOut),
                                      Encoding.ASCII.GetBytes(pszOut).Length,
                                      SocketFlags.None)
            Catch ex As Exception
                Exit While
            End Try
'clean up
            Try
                m_ClientSocket.Shutdown(SocketShutdown.Both)
            Catch ex As Exception
            End Try

            Try
                m_ClientSocket.Close()
            Catch ex As Exception
            End Try
        End If

        Exit While
    End While
End While

					  

When a client connection request comes in, we use our single client socket and then use the listener socket’s accept method to assign the client to the client socket. Then we begin an inner loop to receive the request from the client. In this case I am requiring a 30-second window for the client to send its request. If the request doesn’t come within the allotted time, I consider a time-out has occurred, and then exit the loop and disconnect the client.

If the client does send its request in the time period allotted, I begin to peek the data and look for the <EOF>, which is required based on the communication specification for this client-server pair.

When I find the <EOF>, the data is read off into the allocated buffer and then sent to the <ProcessCommand> method. (I will go over this method shortly.) If the <ProcessCommand> call has no errors or issues, I use the client socket to send the response to the client, shut down the socket, close the socket, and then go back to listening for another connection request.

Listener Processing Methods

The Listener class has several processing methods that are used to parse, process, and respond to clients’ requests. In our service, we support three separate requests. Each request is covered by a separate processing method. Each processing method is wrapped around the <ProcessCommand> function, which takes the request from the client, parses it, calls the processing method, and then returns the data to the <StartListener> thread.

The <ProcessCommand> Method

<ProcessCommand> is the main method used by our service. The service will use this wrapper method to determine the type of client request and then call the appropriate method to handle the gathering of the client’s request. After the request is complete, the <ProcessCommand> method will return the data via a reference pointer to a string passed to it from the <StartListener> thread method. Each processing method will return back the appropriate string to <ProcessCommand>.

The <GetDateTime> Method

<GetDateTime> will return the current date and time of the local server. Although not an incredibly useful method, <GetDateTime> is good for demonstration purposes.

The <GetServiceStatus> Method

When <GetServiceStatus> is called, the user will pass in the short name of any service whose status it wants to validate. After this method is called, it will return the state of the requested service. In the case of an error, or if no service is found, <GetServiceStatus> will return an unknown status to the caller.

The <GetProcessList> Method

<GetProcessList> will return a comma-delimited list to the client of currently running processes on the server. The client can then parse the processes and get an alphabetized list of server processes.

Updating the <Tutorials.ThreadFunc> Method

We need to update the <ThreadFunc> method (as shown in Listing 7) so that we can read in the values from our configuration file.

Listing 7. The <ThreadFunc> method configuration code.
Private Sub ThreadFunc()
    Try
        'Load our Configuration File
        Dim Doc As XmlDocument = New XmlDocument()
        Doc.Load(My.Settings.ConfigFile)

        Dim Options As XmlNode
        'Get a pointer to the Outer Node
        Options = Doc.SelectSingleNode("//*[local-name()='Listeners']")

        If (Not Options Is Nothing) Then
            Dim tmpOptions As System.Xml.XPath.XPathNavigator = _
                        Options.FirstChild.CreateNavigator()

            If (Not tmpOptions Is Nothing) Then
                Dim children As System.Xml.XPath.XPathNavigator
                Do
                    Try
                        Dim tmpListener As New Listener(m_ThreadAction)

                        children = tmpOptions.SelectSingleNode("MaxConnections")
                        tmpListener.MaxConnections = Int32.Parse(children.Value)

                        children = tmpOptions.SelectSingleNode("Port")
                        tmpListener.Port = Int32.Parse(children.Value)

                        m_WorkerThreads.Add(tmpListener)
                        tmpListener.Start()
                    Catch ex As Exception
                        WriteLogEvent(ex.ToString(), CONFIG_READ_ERROR, _
                                      EventLogEntryType.Error, My.Resources.Source)
                    End Try
                Loop While (tmpOptions.MoveToNext)
            End If
        End If

    Catch ex As Exception
        WriteLogEvent(ex.ToString(), ONSTART_ERROR, _
                      EventLogEntryType.Error, My.Resources.Source)
        Me.Stop()
    End Try
End Sub

					  

As with all our previous services, we need to be able to read in the values from the configuration file and then assign them to the properties of the class instance. In this case, we are assigning both the MaxConnections and Port properties to the Listener class instance. We can have as many instances as we want, and we can listen on practically an unlimited number of ports. I would recommend, however, that you don’t use any ports below 1024 because these are usually associated with already existing applications or standards.

Updating the Tutorials <OnStop> Method

The service <OnStop> method needs to be updated to clean up the service threads properly for each class instance created, which is displayed in Listing 8.

Listing 8. The updated <OnStop> service method.
Protected Overrides Sub OnStop()
    ' Add code here to perform any tear-down necessary to stop your service.
    Try
        If (Not m_WorkerThread Is Nothing) Then
            Try
                WriteLogEvent(My.Resources.ServiceStopping, ONSTOP_INFO, _
                EventLogEntryType.Information, My.Resources.Source)

                m_ThreadAction.StopThread = True

                For Each listener As Listener In m_WorkerThreads
                    Me.RequestAdditionalTime(THIRTY_SECONDS)
                    Listener.Incoming.Join(TIME_OUT)
                Next
            Catch ex As Exception
                m_WorkerThread = Nothing
            End Try
        End If
    Catch ex As Exception
        'We Catch the Exception
        'to avoid any unhandled errors
        'since we are stopping and
        'logging an event is what failed
        'we will merely write the output
        'to the debug window
        m_WorkerThread = Nothing
        Debug.WriteLine("Error stopping service: " + ex.ToString())
    End Try
End Sub				  
Other  
  •  Seagate GoFlex Satellite Wireless
  •  Toshiba MQ01ABD100 1TB Hard Drive
  •  View Quest Retro WI-FI Radio
  •  Microsoft Enterprise Library : Non-Formatted Trace Listeners (part 3) - Adding Additional Context Information, Tracing and Correlating Activities
  •  Microsoft Enterprise Library : Non-Formatted Trace Listeners (part 2) - Logging to a Database, Testing Logging Filter Status
  •  Microsoft Enterprise Library : Non-Formatted Trace Listeners (part 1) - Creating and Using Logentry Objects, Capturing Unprocessed Events and Logging Errors
  •  Introducing Windows Presentation Foundation and XAML : Transforming Markup into a .NET Assembly
  •  Introducing Windows Presentation Foundation and XAML : Building a WPF Application using Only XAML
  •  Intel : We For One Welcome Our Apple Overlords
  •  Azure Table Storage (part 2) - Accessing Table Storage, Choosing a PartitionKey, Exception handling
  •  
    Top 10
    - Microsoft Visio 2013 : Adding Structure to Your Diagrams - Finding containers and lists in Visio (part 2) - Wireframes,Legends
    - Microsoft Visio 2013 : Adding Structure to Your Diagrams - Finding containers and lists in Visio (part 1) - Swimlanes
    - Microsoft Visio 2013 : Adding Structure to Your Diagrams - Formatting and sizing lists
    - Microsoft Visio 2013 : Adding Structure to Your Diagrams - Adding shapes to lists
    - Microsoft Visio 2013 : Adding Structure to Your Diagrams - Sizing containers
    - Microsoft Access 2010 : Control Properties and Why to Use Them (part 3) - The Other Properties of a Control
    - Microsoft Access 2010 : Control Properties and Why to Use Them (part 2) - The Data Properties of a Control
    - Microsoft Access 2010 : Control Properties and Why to Use Them (part 1) - The Format Properties of a Control
    - Microsoft Access 2010 : Form Properties and Why Should You Use Them - Working with the Properties Window
    - Microsoft Visio 2013 : Using the Organization Chart Wizard with new data
    REVIEW
    - First look: Apple Watch

    - 3 Tips for Maintaining Your Cell Phone Battery (part 1)

    - 3 Tips for Maintaining Your Cell Phone Battery (part 2)
    programming4us programming4us
    programming4us
     
     
    programming4us