Allowing only a single
connection might work, especially in cases where the requests come from
either a single source or at intervals that help ensure that two
resources cannot compete for a connection at the same time. However, in
some circumstances a single instance of a service could be used to
generate requests for multiple resources. A good example of this is when
you have a server that is multi-homed for several subnets, and receives
requests for data from any or all of these subnets—and possibly from
multiple computers or processes on each subnet. In this case, a single
point connection would not be beneficial; however, you still want to
limit how many connections you allow for performance reasons.
Extending the Listener Class
This
will require us to create up to 10 client sockets to accept the incoming
requests, as well as telling our listener socket to accept up to 10
client requests before it returns an error to the client stating that it
is unable to connect. Listener sockets only accept one socket at a
time. Our code uses synchronous sockets with a backlog of up to 10
socket requests. Remember, though, that our configuration file allows us
to specify the maximum number of sockets at any given time. Therefore,
it is not a hard-coded value.
We’ll use ThreadPool to take care of the multiple connections that our class will now be able to accept. Let’s review the new modifications.
The <SocketThread> Method
We need to create a
secondary thread method that will act as our socket processing thread.
Since we will allow more than one connection at a time, having only a
single thread won’t work. Listing 1 shows the newly created thread method.
Listing 1. The <SocketThread> thread method.
Private Sub SocketThread(ByVal args As Object)
Dim lSocket As Socket = CType(args, Socket)
Try
Dim bytes() As Byte = New [Byte](1024) {}
Dim Data As String = Nothing
Dim bError As Boolean = False
While Not m_ThreadAction.StopThread
If Not m_ThreadAction.Pause Then
Dim iStart As Long = Now.Ticks
bytes = New Byte(1024) {}
Dim bytesRec As Integer = lSocket.Receive(bytes)
Data += Encoding.ASCII.GetString(bytes, 0, bytesRec)
If ((Now.Ticks - iStart) / 10000000) > 30 Then
Try
lSocket.Shutdown(SocketShutdown.Both)
Catch ex As Exception
End Try
Try
lSocket.Close()
Catch ex As Exception
End Try
lSocket = Nothing
Exit Sub
End If
If Data.IndexOf("<EOF>") > -1 Then
Dim pszOut As String = Nothing
Try
Call ProcessCommand(Data, pszOut)
lSocket.Send(Encoding.ASCII.GetBytes(pszOut),
Encoding.ASCII.GetBytes(pszOut).Length, SocketFlags.None)
Catch ex As Exception
'clean up
lSocket.Shutdown(SocketShutdown.Both)
lSocket.Close()
lSocket = Nothing
Return
End Try
End If
Exit While
End If
End While
Catch ex As Exception
lSocket = Nothing
Finally
'clean up
Try
lSocket.Shutdown(SocketShutdown.Both)
lSocket.Close()
lSocket = Nothing
Catch ex As Exception
lSocket = Nothing
Finally
lSocket = Nothing
End Try
End Try
End Sub
|
The method shown in the preceding listing will be created as a temporary thread by our <StartListener>
method, which will listen for incoming connections and then create a
temporary thread to process the request. The temporary thread accepts a
socket as a parameter.
Next, we call the <ProcessCommand>
method, just as we did earlier, and then send the response to the
caller. Last, we close the socket and consider the communication closed.
Although this requires us to continually recreate new sockets for the
same client calling many different methods, or calling the same method
many times, it is for demonstration purposes only. We are not required
to drop this socket at all. We could simply make the temporary thread go
back into a receive blocking state waiting for a new command from the
client.
We could also
consider using asynchronous sockets instead of synchronous sockets;
however, depending on the workload required, synchronous sockets work
for a fast, small service that has limited functional requirements and
connections.
Updating the <StartListener> Method
We need to update
our primary thread method because we only want it to listen for incoming
connections now and not process them. Let’s review the new code, shown
in Listing 2.
Listing 2. The new <StartListener> method.
Private Sub StartListener()
While Not m_ThreadAction.StopThread
If Not m_ThreadAction.Pause Then
Try
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)
While Not m_ThreadAction.StopThread
Dim tmpSocket As Socket
tmpSocket = m_Listener.Accept
Dim tmpThread As New Thread(AddressOf SocketThread)
tmpThread.IsBackground = True
tmpThread.Name = "Socket Thread"
tmpThread.Start(tmpSocket)
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
|
Now
that we have our processing thread, this code just needs to wait for an
incoming socket connection and then hand that socket off to our
processing thread. It simply creates a local background thread and then
passes it the socket that it just accepted from the client. Notice that
our error handling is not as exhaustive as it could or should be. You
should be very careful when deciding to run any type of service,
especially when it comes to error handling and resources.
Service Validation
Install the new
service and make sure that your configuration file has at least two
separate entries for ports to listen on, with more than a single
connection as its MaxConnections property.
Run
multiple instances of the test client, assigning different ports to the
test client to cover all possible listening server ports you
configured. For each client you should receive a response from the
server on the specified port. You can even run multiple instances of the
client against the same port and each client will still receive its own
response. Remember that the accept
method can only accept one socket connection at a time per server port,
so you won’t be able to receive a response at the same instant that you
do on another—the server has to process the incoming connection and
hand it off to the processing thread. Also, remember that your MaxConnections
property specifies how many connections can wait in the backlog of
queued requests to the server. So if you launch, for example, 11 or more
clients and expect them all to work at the same time, you’ll be
disappointed. When more than 10 clients are queued, client connections
will start to fail.