|
A Simple VB .NET Asynchronous Class Example
In this example, we will create a shared asynchronous packet class that will be used by the VB .NET server and client programs that will be created later. Create a new class library project and you might want to use AsyncPacketClass as the project and AsyncPacketClassVB as the solution names as shown in the following Figure.
|
Rename the source file to AsyncPacket to reflect the application that we will build.
Add the following Imports directives.
Imports System Imports System.Net Imports System.Net.Sockets Imports System.Collections Imports System.Threading |
In the class add an IoPacketType enum.
'Indicates the type of asynchronous operation. Public Enum IoPacketType Accept Connect Send SendTo Receive ReceiveFrom End Enum |
Next, add the IoPacket class.
' Describes an IO asynchronous IO request. This class contains the delegate which ' is fired when the specified operation completes. It also allocates the data ' buffer used by a send or receive operation. Public Class IoPacket Public ioType As IoPacketType Public connObject As SocketConnection Public ioCallback As AsyncCallback Public dataBuffer() As Byte
Public IoPacketDefaultBufferSize As Integer = 4096
' Initialize member variables and allocate the data buffer if this IO packet ' describes an asynchronous send or receive operation. ' <param name="connObj">Connection object to which this IO packet belongs to</param> ' <param name="packetType">Operation type being created</param> Public Sub New(ByVal connObj As SocketConnection, ByVal packetType As IoPacketType) ioType = packetType connObject = connObj ioCallback = New AsyncCallback(AddressOf IoCompleteCallback)
' If this packet describes a send or receive operation create the ' data buffer. If (packetType <> IoPacketType.Accept) Then ReDim dataBuffer(IoPacketDefaultBufferSize) End If End Sub
' Posts an asynchronous connect operation on the underlying TCP socket. ' <param name="serverEndPoint"></param> Public Sub PostConnect(ByVal serverEndPoint As IPEndPoint) Try connObject.tcpSocket.BeginConnect(serverEndPoint, ioCallback, Me) connObject.IncrementOutstanding(ioType) 'Console.WriteLine("Posting a BeginConnect...") Catch err As SocketException Console.WriteLine("IoPacket.PostConnect: {0}", err.Message) Throw End Try End Sub
' Posts an asynchronous accept operation on the underlying TCP socket. Public Sub PostAccept() Try ' Post the async accept and increment the accept count connObject.tcpSocket.BeginAccept(ioCallback, Me) connObject.IncrementOutstanding(ioType) 'Console.WriteLine("Posting a BeginAccept...") Catch err As SocketException Console.WriteLine("IoPacket.PostAccept: {0}", err.Message) Throw End Try End Sub
' Posts an asynchronous receive operation on the underlying TCP socket. Public Sub PostReceive() Try ' Post the async receive and increment the accept count connObject.tcpSocket.BeginReceive( _ dataBuffer, _ 0, _ dataBuffer.Length, _ SocketFlags.None, _ ioCallback, _ Me _ ) connObject.IncrementOutstanding(ioType) 'Console.WriteLine("Posting a BeginReceive...") Catch err As SocketException Console.WriteLine("IoPacket.BeginReceive: {0}", err.Message) Throw End Try End Sub
' Posts an asynchronous send operation on the underlying TCP socket. Public Sub PostSend() Try ' Post an async send and increment the send count connObject.tcpSocket.BeginSend( _ dataBuffer, _ 0, _ dataBuffer.Length, _ SocketFlags.None, _ ioCallback, _ Me _ ) connObject.IncrementOutstanding(ioType) 'Console.WriteLine("Posting a BeginSend...") Catch err As SocketException Console.WriteLine("IoPacket.BeginSend: {0}", err.Message) Throw End Try End Sub ' This is the asynchronous delegate that is called when an operation on the posted ' IO object completes. This method simply calls the owning objects HandleIo method. ' Only object derived from the SocketConnection class can use this IoPacket class ' for describing async IO operations. ' <param name="ar">Asynchronous context information</param> Sub IoCompleteCallback(ByVal ar As IAsyncResult) Dim ioOperation As IoPacket = CType(ar.AsyncState, IoPacket)
ioOperation.connObject.DecrementOutstanding(ioOperation.ioType) ioOperation.connObject.HandleIo(ioOperation, ar) End Sub End Class |
Then, add the SocketConnection class.
' Base class from which the TcpServer and ClientConnection objects are derived ' from. Public MustInherit Class SocketConnection Public tcpSocket As Socket Public tcpAddress As IPEndPoint Protected receiveOutstanding As Integer Protected receiveFromOutstanding As Integer Protected sendOutstanding As Integer Protected sendToOutstanding As Integer Protected connectOutstanding As Integer Protected acceptOutstanding As Integer Protected sendByteCount As Integer Protected receiveByteCount As Integer
' Simple constructor to initialize counters to zero. Public Sub New() receiveOutstanding = 0 receiveFromOutstanding = 0 sendOutstanding = 0 sendToOutstanding = 0 connectOutstanding = 0 acceptOutstanding = 0 sendByteCount = 0 receiveByteCount = 0 End Sub
' Returns the total bytes sent on the socket. Public ReadOnly Property TotalSendBytes() As Integer Get Return sendByteCount End Get End Property
' Returns the total bytes received on the socket. Public ReadOnly Property TotalReceiveBytes() As Integer Get Return receiveByteCount End Get End Property
' This method handles the IO operation that completed. ' <param name="io">Object describing the IO operation that completed</param> ' <param name="ar">Asynchronous context information</param> Public MustOverride Sub HandleIo(ByVal io As IoPacket, ByVal ar As IAsyncResult)
' Removes the indicated IO packet from any lists or queues the packet may be in ' <param name="io">IO object to remove from any lists</param> Public MustOverride Sub RemoveIo(ByVal io As IoPacket)
' Increments the count for the outstanding operation type. This is implemented such ' that the IoPacket post routines can make a call to increment the count such that ' the caller of each post routine doesn't have to make the increment call. ' <param name="ioType">Operation type to increment count for</param> Public Sub IncrementOutstanding(ByVal ioType As IoPacketType) Select Case ioType Case IoPacketType.Connect Interlocked.Increment(connectOutstanding) Case IoPacketType.Accept Interlocked.Increment(acceptOutstanding) Case IoPacketType.Receive Interlocked.Increment(receiveOutstanding) Case IoPacketType.ReceiveFrom Interlocked.Increment(receiveFromOutstanding) Case IoPacketType.Send Interlocked.Increment(sendOutstanding) Case IoPacketType.SendTo Interlocked.Increment(sendToOutstanding) Case Else End Select End Sub
' Decrements the count for the outstanding operation type. This is implemented such ' that the IoPacket post routines can make a call to decrement the count such that ' the caller of each post routine doesn't have to make the decrement call. ' <param name="ioType">Operation type to decrement count for</param> Public Sub DecrementOutstanding(ByVal ioType As IoPacketType) Select Case ioType Case IoPacketType.Connect Interlocked.Decrement(connectOutstanding) Case IoPacketType.Accept Interlocked.Decrement(acceptOutstanding) Case IoPacketType.Receive Interlocked.Decrement(receiveOutstanding) Case IoPacketType.ReceiveFrom Interlocked.Decrement(receiveFromOutstanding) Case IoPacketType.Send Interlocked.Decrement(sendOutstanding) Case IoPacketType.SendTo Interlocked.Decrement(sendToOutstanding) Case Else End Select End Sub End Class |
Add the ClientConnection class.
' This class identifies a client connection. It keeps track of all outstanding ' async send and receive operations and handles completed IO operations. Public Class ClientConnection Inherits SocketConnection Private ownerList As ArrayList Private ownerEvent As ManualResetEvent Private sendList As ArrayList Private recvList As ArrayList Private sendCountLeft As Integer Private connectPacket As IoPacket
' This is the constructor for the client which takes only the client socket ' object. It creates the lists of outstanding send and receive operations ' and posts the initial BeginReceive. ' <param name="tcpClient">Socket object representing the client connection</param> ' <param name="serverObject">Reference to the server object that owns this client connection</param> Public Sub New(ByVal serverEndPoint As IPEndPoint, ByVal clientList As ArrayList, ByVal clientEmpty As ManualResetEvent) MyBase.New() ownerList = clientList ownerEvent = clientEmpty sendList = New ArrayList recvList = New ArrayList sendCountLeft = 15
tcpSocket = New Socket( _ serverEndPoint.AddressFamily, _ SocketType.Stream, _ ProtocolType.Tcp _ )
connectPacket = New IoPacket(Me, IoPacketType.Connect)
Try connectPacket.PostConnect(serverEndPoint) Catch err As Exception Console.WriteLine("ClientConnection: PostConnect failed: {0}", err.Message) End Try End Sub
' This overrided routine handles all asynchronous IO that completes on the ClientConnection ' object. Since the client only sends and receives only the Send and Receive commands are ' handled. The client connection first receives a request from the client until the client ' shuts down the send path. At this point the client connection will receive zero bytes ' indicating the client is done sending. This is followed by the client sending a response ' to the client. Once the requested number of send operations are performed to the client ' the client socket is closed and resources are freed (by closing the socket handle and ' indicating to the owning TcpServer to remove the ClientConnection object from its list of connections). ' <param name="io">IO packet object that describes the completed operation</param> ' <param name="ar">Asynchronous context information for the completed operation</param> Public Overrides Sub HandleIo(ByVal io As IoPacket, ByVal ar As System.IAsyncResult) Dim rc As Integer
Select Case io.ioType Case IoPacketType.Connect ' Connect operation completed Try tcpSocket.EndConnect(ar)
' Add the IoPacket to the send list sendList.Add(io) ' Make it a send operation and post a send operation io.ioType = IoPacketType.Send io.PostSend() Catch err As Exception Console.WriteLine("Socket connect failed: {0}", err.Message) tcpSocket.Close() ownerList.Remove(Me) ownerEvent.Set() End Try
Case IoPacketType.Send ' An async send operation completed, first decrement the number of sends left Interlocked.Decrement(sendCountLeft)
Try ' Get the results from the send rc = tcpSocket.EndSend(ar) ' Update counters sendByteCount += rc 'Console.WriteLine("Client wrote {0} bytes", rc)
' Check to see if we're done sending, if so close the socket and clean ' up the IoPacket used. If (sendCountLeft = 0) Then 'Console.WriteLine("Shutting down connection") tcpSocket.Shutdown(SocketShutdown.Send) RemoveIo(io) io.ioType = IoPacketType.Receive recvList.Add(io) io.PostReceive() Else ' If there are still sends left, post another async send operation io.PostSend() End If
Catch err As Exception Console.WriteLine("ClientConnection.HandleIo: Send op failed: {0}", err.Message) tcpSocket.Close() ownerList.Remove(Me) ownerEvent.Set() End Try
Case IoPacketType.Receive ' An async receive operation completed Try ' Get the results from the operation rc = tcpSocket.EndReceive(ar) ' Update the byte counters receiveByteCount += rc
' Console.WriteLine("Client read {0} bytes", rc)
' If zero bytes were returned, the client has shutdown its send side. This ' side then initiates sending data back to the client. If (rc = 0) Then 'Console.WriteLine("Client connection shutdown...") ' Once the entire request is received start the response RemoveIo(io) tcpSocket.Close() ownerList.Remove(Me) ownerEvent.Set() Else ' Client hasn't finished sending data so post another async receive io.PostReceive() End If Catch err As Exception ' Clean up the client connection if a receive fails Console.WriteLine("ClientConnection.HandleIo: Receive op failed: {0}", err.Message) tcpSocket.Close() ownerList.Remove(Me) ownerEvent.Set() End Try Case Else ' Should never be here Console.WriteLine("ClientConnection.HandleIo: Accept indication received ?!?!") End Select End Sub
' Removes the given IoPacket from the appropriate ArrayList depending on the operation ' type. Public Overrides Sub RemoveIo(ByVal io As IoPacket) Select Case io.ioType Case IoPacketType.Receive recvList.Remove(io) Case IoPacketType.Send sendList.Remove(io) Case Else End Select io = Nothing End Sub End Class |
Add the AcceptConnection class.
' This class identifies a client connection. It keeps track of all outstanding ' async send and receive operations and handles completed IO operations. Public Class AcceptConnection Inherits SocketConnection
Private sendList As ArrayList Private recvList As ArrayList Private tcpParent As TcpServer Private sendCountLeft As Integer
' This is the constructor for the client which takes only the client socket ' object. It creates the lists of outstanding send and receive operations ' and posts the initial BeginReceive. ' <param name="tcpClient">Socket object representing the client connection</param> ' <param name="serverObject">Reference to the server object that owns this client connection</param> Public Sub New(ByVal tcpClient As Socket, ByVal serverObject As TcpServer) MyBase.New() tcpSocket = tcpClient sendList = New ArrayList recvList = New ArrayList tcpParent = serverObject
sendCountLeft = 15 Console.WriteLine("Client connection from: {0}", tcpSocket.RemoteEndPoint.ToString())
' Post the initial BeginReceive on the client socket Dim recvPacket As IoPacket = New IoPacket(Me, IoPacketType.Receive)
recvList.Add(recvPacket)
Try recvPacket.PostReceive() Catch err As Exception Console.WriteLine("AcceptConnection.ClientConnection: PostReceive failed: {0}", err.Message) End Try End Sub
' This overrided routine handles all asynchronous IO that completes on the ClientConnection ' object. Since the client only sends and receives only the Send and Receive commands are ' handled. The client connection first receives a request from the client until the client ' shuts down the send path. At this point the client connection will receive zero bytes ' indicating the client is done sending. This is followed by the client sending a response ' to the client. Once the requested number of send operations are performed to the client ' the client socket is closed and resources are freed (by closing the socket handle and ' indicating to the owning TcpServer to remove the ClientConnection object from its list ' of connections). ' <param name="io">IO packet object that describes the completed operation</param> ' <param name="ar">Asynchronous context information for the completed operation</param> Public Overrides Sub HandleIo(ByVal io As IoPacket, ByVal ar As System.IAsyncResult) Dim rc As Integer
Select Case io.ioType Case IoPacketType.Send ' An async send operation completed, first decrement the number of sends left Interlocked.Decrement(sendCountLeft)
Try ' Get the results from the send rc = tcpSocket.EndSend(ar) ' Update counters sendByteCount += rc tcpParent.IncrementByteCount(rc, 0)
' Console.WriteLine("Client wrote {0} bytes", rc)
' Check to see if we're done sending, if so close the socket and clean ' up the IoPacket used. If (sendCountLeft = 0) Then tcpSocket.Shutdown(SocketShutdown.Send) tcpSocket.Close() RemoveIo(io) io = Nothing tcpSocket = Nothing
' Console.WriteLine("Connection finished...") Else ' If there are still sends left, post another async send operation io.PostSend() End If Catch err As Exception Console.WriteLine("AcceptConnection.HandleIo: Send op failed: {0}", err.Message) tcpSocket.Close() tcpSocket = Nothing Finally ' Check to see if the client connection is done, if so indicate to the TcpServer ' parent to remove the client from its list.
' Console.WriteLine("sendCountLeft = {0} | sendOutstanding = {1} | recvOutstanding = {2}", _ ' sendCountLeft, _ ' sendOutstanding, _ ' MyBase.receiveOutstanding _ ' )
If (IsNothing(tcpSocket) And (sendCountLeft = 0) And _ (sendOutstanding = 0) And (receiveOutstanding = 0) _ ) Then tcpParent.RemoveClient(Me) End If End Try
Case IoPacketType.Receive ' An async receive operation completed Try ' Get the results from the operation rc = tcpSocket.EndReceive(ar) ' Update the byte counters receiveByteCount += rc tcpParent.IncrementByteCount(0, rc)
' Console.WriteLine("Client read {0} bytes", rc)
' If zero bytes were returned, the client has shutdown its send side. This ' side then initiates sending data back to the client. If (rc = 0) Then ' Console.WriteLine("Client connection shutdown...") ' Once the entire request is received start the response RemoveIo(io) ' Switch the packet type to sending io.ioType = IoPacketType.Send sendList.Add(io) ' Post a send operation to the client io.PostSend() Else ' Client hasn't finished sending data so post another async receive io.PostReceive() End If Catch err As Exception ' Clean up the client connection if a receive fails Console.WriteLine("ClientConnection.HandleIo: Receive op failed: {0}", err.Message) tcpSocket.Close() tcpSocket = Nothing tcpParent.RemoveClient(Me) End Try Case Else ' Should never be here Console.WriteLine("AcceptConnection.HandleIo: Accept indication received ?!?!") End Select End Sub
' Removes the given IoPacket from the appropriate ArrayList depending on the operation ' type. ' <param name="io">IO object to remove from any lists</param> Public Overrides Sub RemoveIo(ByVal io As IoPacket) Select Case (io.ioType) Case IoPacketType.Receive recvList.Remove(io) Case IoPacketType.Send sendList.Remove(io) Case Else End Select io = Nothing End Sub End Class |
Finally, add the TcpServer class.
' This class encapsulates functionality for the TCP server object. This ' class keeps track of all clients accepted on the listening socket ' as well as the outstanding asynchronous accept operations. The class ' keeps track the send and receive byte counters and client counts. Public Class TcpServer Inherits SocketConnection Private clientList As ArrayList Private clientSyncList As ArrayList Private acceptList As ArrayList Private serverShutdown As Integer Public serverDone As ManualResetEvent Public totalClientCount As Integer Public currentClientCount As Integer
' Simple constructor for the TcpServer class. This initializes the counters ' and creates the listening TCP socket. ' <param name="listenEndPoint">Endpoint to create the listening socket on</param> ' <param name="asyncListenLimit">Maximum number of async accepts outstanding</param> Public Sub New(ByVal listenEndPoint As IPEndPoint, ByVal asyncListenLimit As Integer) MyBase.New()
Dim optval As Integer = 1
' Initialize some member variables serverShutdown = 0 totalClientCount = 0 currentClientCount = 0 serverDone = New ManualResetEvent(False)
' Create the listening socket tcpSocket = New Socket( _ listenEndPoint.AddressFamily, _ SocketType.Stream, _ ProtocolType.Tcp _ )
' Set exclusive use so that the port we bind to cannot be stolen by anyone else tcpSocket.SetSocketOption( _ SocketOptionLevel.Socket, _ SocketOptionName.ExclusiveAddressUse, _ optval _ )
' Bind the socket to the specified address and port tcpSocket.Bind(listenEndPoint) ' Set the socket to listening tcpSocket.Listen(Integer.MaxValue) ' Indicate what address/port socket is listening on Console.WriteLine("Listening on {0}", listenEndPoint.ToString()) ' Create list for outstanding client connections clientList = New ArrayList clientSyncList = ArrayList.Synchronized(clientList) ' Create list for outstanding BeginAccept operations acceptList = New ArrayList ' Post the initial BeginAccepts on the listening socket Dim i As Integer For i = 0 To asyncListenLimit - 1 Dim acceptPacket As IoPacket = New IoPacket(Me, IoPacketType.Accept)
acceptList.Add(acceptPacket) Try acceptPacket.PostAccept() Catch err As Exception Console.WriteLine("TcpServer.TcpServer: PostAccept failed: {0}", err.Message) End Try Next End Sub
' This method removes an accepted client object (AcceptConnection) from the ' list of accepted connections. It decrements the current client count. ' <param name="clientObject">Accepted client object to remove</param> Public Sub RemoveClient(ByVal clientObject As AcceptConnection) clientSyncList.Remove(clientObject) clientObject = Nothing Interlocked.Decrement(currentClientCount) End Sub
' This method marks the TcpServer as shutting down. This flag is set before all the ' listening sockets are closed. Public Sub Shutdown() Interlocked.Increment(serverShutdown) End Sub
' This routine handles all IO completion events initiated by the TcpServer ' listening socket which will consist of BeginAccept events. Before accessing ' the socket, a check is made to make sure the server is not shutting down ' in which case the listening socket have already been closed. ' <param name="io">IO packet describing the operation that completed</param> ' <param name="ar">Asynchronous context information</param> Public Overrides Sub HandleIo(ByVal io As IoPacket, ByVal ar As System.IAsyncResult) Select Case io.ioType Case IoPacketType.Accept 'Console.WriteLine("TcpServer.HandleIo: Accept completed: outstanding {0}", acceptOutstanding )
If (serverShutdown <> 0) Then io.connObject.RemoveIo(io) Exit Sub End If Try Dim clientSocket As Socket Dim clientConn As AcceptConnection
Try ' Handle the accept that completed clientSocket = tcpSocket.EndAccept(ar) clientConn = New AcceptConnection(clientSocket, Me) clientSyncList.Add(clientConn) ' Update the counters Interlocked.Increment(totalClientCount) Interlocked.Increment(currentClientCount) Catch Console.WriteLine("Accept: Client aborted connection most likely...") End Try
' Repost the accept io.PostAccept()
Catch err As Exception Console.WriteLine("TcpServer.HandleIo: Unknown error: {0}", err.Message) acceptList.Remove(io) io = Nothing End Try Case Else End Select End Sub
' This method removes the IoPacket from the appropriate list. ' <param name="io">The IoPacket to remove from the correct list</param> Public Overrides Sub RemoveIo(ByVal io As IoPacket) Select Case io.ioType Case IoPacketType.Accept acceptList.Remove(io) io = Nothing Case Else End Select End Sub
' Increments the sent and received byte counts for all client connections established ' on this TCP server socket. Each AcceptConnection has a reference to the TcpServer ' on which the client connection was accepted -- it then calls this method to ' increment the byte counters. ' <param name="sendCount">Number of bytes sent to increment counter by</param> ' <param name="recvCount">Number of bytes received to increment counter by</param> Public Sub IncrementByteCount(ByVal sendCount As Integer, ByVal recvCount As Integer) MyBase.sendByteCount += sendCount MyBase.receiveByteCount += recvCount End Sub End Class |
Build the project and make sure there is no error that can be seen in the Output window.
![]() |
|
The generated DLL that we will refer later in other project is shown below.
The namespace that will be used as reference to the class created is AsyncPacketClass as can be seen in the Property page.