Click here to Skip to main content
15,860,972 members
Articles / Desktop Programming / MFC

A High Performance TCP/IP Socket Server COM Component for VB

Rate me:
Please Sign up or sign in to vote.
3.50/5 (11 votes)
25 Jun 2002CPOL6 min read 406.7K   11.7K   61   142
Although socket based TCP/IP servers using IO Completion Ports are often written in C++, it's sometimes useful to write such a server in Visual Basic.

The following source was built using Visual Studio 6.0 SP5 and Visual Studio .NET. You need to have a version of the Microsoft Platform SDK installed.

Overview

Although socket based TCP/IP servers using IO Completion Ports are often written in C++, it's sometimes useful to write such a server in Visual Basic. You can use the Winsock control for this, but it deals with the Windows Sockets interface at a very low level and you have to write lots of VB code. This article presents a simple COM object that wraps the high performance socket server framework that we developed in previous articles. The COM object provides a simple, yet powerful, interface that allows you to easily construct high performance TCP/IP servers with a minimal amount of VB code.

The article presents the Socket Server COM object and a simple sample VB server application and describes how you use the COM object. The next article will deal with how the COM object was constructed and the design decisions and coding of the object itself. Please note that this object can only be used on Windows NT/2000/XP - it does not support Windows 9x.

A High Performance TCP/IP Socket Server COM Object

The socket server COM object provides a factory object for creating TCP/IP socket servers. The factory object is marked as an appobject so you can use it without an explicit reference as in the following example:

VBScript
Dim server As COMSOCKETSERVERLib.server
Set server = CreateSocketServer(5001)

The code above creates a server that will listen on port 5001. If you have a multi-homed machine, then you can optionally specify a single IP address for the server to listen on:

VBScript
Dim server As COMSOCKETSERVERLib.server
Set server = CreateSocketServer(5001, "192.168.0.1")

Once you have your server object, you can start accepting connections by calling StartListening and you can stop accepting connections by calling StopListening. It's quite OK to call StartListening again after you have stopped listening - you might do this if you wanted to resume the server after pausing it to prevent it accepting new connections. The server also has a read only LocalAddress property which will return the IP address and port of the server. The main coding for your server occurs in the three event handlers for the events that the socket server fires to inform you of client connection, client data arrival and client disconnection. The events are as follows:

VB
Private Sub m_server_OnConnectionClosed(ByVal socket As COMSOCKETSERVERLib.ISocket)

End Sub

Private Sub m_server_OnConnectionEstablished(ByVal socket As COMSOCKETSERVERLib.ISocket)

End Sub

Private Sub m_server_OnDataReceived( _
     ByVal socket As COMSOCKETSERVERLib.ISocket, _
     ByVal data As COMSOCKETSERVERLib.IData)

End Sub

Notice that each event passes you a Socket object. The socket object represents the connected client socket and provides you with methods and properties to manipulate its state. You can write data to the client in two ways; the Write method allows you to transmit an array of bytes and WriteString allows you to send a string. WriteString takes an optional boolean flag which determines if the string is sent as a UNICODE string or not. Both write methods also take an optional boolean flag that allows you to specify that this is the last write that you will be performing on this socket and that the send side of the socket should be shut down after the write completes. You can read data from the client by using the asynchronous RequestRead method - when the read completes, the OnDataReceived event will be fired and you'll be passed the data that was read. When you have finished with the socket, you can shut down the connection with the Shutdown method. This allows you to shut down the read and write sides of the connection together or independently. You can also forcibly close the socket with the Close method but beware, this may cause partially transmitted data to be lost.

The Socket object has two properties: the RemoteAddress property provides read only access to the address associated with the client end of the connection. The UserData property provides a place to store your own "per-connection" data, this can be anything you like, but a common use for it would be as a way of associating a class that you use to store session state for the connection. Your user data is stored in the Socket and since you are passed the Socket each time data arrives and when the connection is closed, you can retrieve your per session state whenever you need it.

The final object involved is the Data object that you are passed when a read completes and the OnDataReceived event is fired. This has two methods, Read which returns the data as an array of bytes and ReadString which returns the data as a string. Bear in mind that you cannot guarantee to receive data that a client may have sent as a single block with a single call to RequestRead and subsequent firing of the OnDataReceived event. The data may be fragmented into multiple packets and you will need to handle reassembly yourself by making another call to RequestRead and buffering the data yourself. Because of this danger of packet fragmentation, ReadString does not have any way of specifying that the data being read is already a UNICODE string. If your protocol involves sending and receiving UNICODE strings, then you must read your data using the Read method and convert from the byte array to a string yourself. This is because it is inherently unsafe to do the conversion below this level due to the potential of packet fragmentation causing the receipt of an incomplete UNICODE character. You can call a combination of Read and ReadString multiple times on the same Data object and you will retrieve the same data each time.

An Example Server in VB

As a simple example, we will develop an echo server that stores some per-connection state and can optionally shut down the connection in a variety of ways after echoing a certain number of data packets.

Our echo server consists of two forms. The first allows you to specify the port that the server should listen on and also set some parameters.

Image 1

When a server is created, the second form is displayed, this contains a list control and handles the events that the server fires. After creating a server, you can switch back to the first form, change the port number and create another server. All servers can operate independently.

Image 2

The code behind the first form is fairly simple and mostly consists of managing the user interface elements and passing parameters to the second form. The work required to create the socket server itself is very simple.

VBScript
Option Explicit

Private Sub Command1_Click()

    Dim server As JBSOCKETSERVERLib.server
    Set server = CreateSocketServer(CLng(Text1.Text))
    
    Dim frm As Form2
    Set frm = New Form2
    
    frm.SetServer server
    frm.ShowDataPackets.Value = ShowDataPackets.Value
    frm.DataIsBytes.Value = DataIsBytes.Value
    frm.DataIsString.Value = DataIsString.Value
    frm.SignOnAsUnicode.Value = SignOnAsUnicode.Value
    
    If ShutdownEnabled.Value Then
    
        If ShutdownAfterWrite.Value Then
            frm.ShutdownAfterWrite = CLng(ShutdownAfter.Text)
        ElseIf ShutdownSocket.Value Then
            frm.ShutdownAfter = CLng(ShutdownAfter.Text)
        ElseIf CloseSocket.Value Then
            frm.CloseAfter = CLng(ShutdownAfter.Text)
        End If
    
    End If
    
    server.StartListening
    
    frm.Show , Me

End Sub

Private Sub ShowDataPackets_Click()

    DataIsFrame.Enabled = ShowDataPackets.Value
    DataIsBytes.Enabled = ShowDataPackets.Value
    DataIsString.Enabled = ShowDataPackets.Value

End Sub

Private Sub ShutdownEnabled_Click()

    ShutdownAfterWrite.Enabled = ShutdownEnabled.Value
    ShutdownSocket.Enabled = ShutdownEnabled.Value
    CloseSocket.Enabled = ShutdownEnabled.Value
    ShutdownAfter.Enabled = ShutdownEnabled.Value
    
End Sub

The second form is slightly more complex. The code that handles the socket server is as follows:

VB
Option Explicit

Dim WithEvents m_server As JBSOCKETSERVERLib.server
Public ShutdownAfterWrite As Integer
Public ShutdownAfter As Integer
Public CloseAfter As Integer

Private ListWidth As Integer
Private ListHeight As Integer

Public Sub SetServer(server As JBSOCKETSERVERLib.server)
 
    Set m_server = server
    
    Caption = "Socket server listening on: " & server.LocalAddress.Port
    
End Sub

Private Sub m_server_OnConnectionClosed(ByVal Socket As JBSOCKETSERVERLib.ISocket)

    If ShowDataPackets.Value Then
        AddToList "OnConnectionClosed : " & GetAddressAsString(Socket)
    End If
    
    Dim counter As Class1
    Set counter = Socket.UserData

    If ShowDataPackets.Value Then
        AddToList "User data = " & counter.GetCount()
    End If
    
    Socket.UserData = 0

End Sub

Private Sub m_server_OnConnectionEstablished(ByVal Socket As JBSOCKETSERVERLib.ISocket)

    If ShowDataPackets.Value Then
        AddToList "OnConnectionEstablished : " & GetAddressAsString(Socket)
    End If
    
    Dim counter As Class1
    Set counter = New Class1
    
    Socket.UserData = counter
    
    Socket.WriteString "Welcome to VB echo server" & vbCrLf, SignOnAsUnicode.Value

    Socket.RequestRead

End Sub

Private Sub m_server_OnDataReceived( _
    ByVal Socket As JBSOCKETSERVERLib.ISocket, _
    ByVal Data As JBSOCKETSERVERLib.IData)

    Dim counter As Class1
    Set counter = Socket.UserData
    
    counter.IncrementCount

    If DataIsBytes.Value Then
    
        OnReceivedBytes Socket, Data, counter.GetCount
    
    ElseIf DataIsString.Value Then
    
        OnReceivedString Socket, Data, counter.GetCount

    End If

    Socket.RequestRead

    If ShutdownAfter <> 0 And ShutdownAfter = counter.GetCount Then
        Socket.Shutdown ShutdownBoth
    End If
    
    If CloseAfter <> 0 And CloseAfter = counter.GetCount Then
        Socket.Close
    End If

End Sub

Private Sub OnReceivedBytes( _
    ByVal Socket As JBSOCKETSERVERLib.ISocket, _
    ByVal Data As JBSOCKETSERVERLib.IData, _
    counter As Integer)

    Dim Bytes() As Byte
    Bytes = Data.Read()

    If ShowDataPackets.Value Then
    
        Dim stringRep As String
        
        Dim i As Integer

        For i = LBound(Bytes) To UBound(Bytes)

            stringRep = stringRep & CLng(Bytes(i)) & " "

        Next i
    
        AddToList "OnDataReceived : " & GetAddressAsString(Socket) & " - " & stringRep
    
    End If
        
    Dim thenShutdown As Boolean
    thenShutdown = False
    
    If ShutdownAfterWrite <> 0 And ShutdownAfterWrite = counter Then
        thenShutdown = True
    End If
    
    Socket.Write Bytes, thenShutdown

End Sub

Private Sub OnReceivedString( _
    ByVal Socket As JBSOCKETSERVERLib.ISocket, _
    ByVal Data As JBSOCKETSERVERLib.IData, _
    counter As Integer)

    Dim theData As String
    theData = Data.ReadString
    
    If ShowDataPackets.Value Then
        AddToList "OnDataReceived : " & GetAddressAsString(Socket) & " - " & theData
    End If
    
    Dim thenShutdown As Boolean
    thenShutdown = False
    
    If ShutdownAfterWrite <> 0 And ShutdownAfterWrite = counter Then
        thenShutdown = True
    End If
    
    Socket.WriteString theData, False, thenShutdown
    
End Sub

Private Function GetAddressAsString(Socket As JBSOCKETSERVERLib.ISocket) As String

    GetAddressAsString = Socket.RemoteAddress.Address & " : " & Socket.RemoteAddress.Port

End Function

The code is made more complex by the fact that we can display and echo the data and shut down the connection in all of the possible ways. Notice how we initialise our user data in the m_server_OnConnectionEstablished event and store it in the Socket. We then retrieve and use it in the m_server_OnDataReceived and m_server_OnConnectionClosed events.

Revision History

  • 11th June, 2002 - Initial revision
  • 26th June, 2002 - Removed call to ReuseAddress() during the creation of the listening socket as it is not required - thanks to Alun Jones for pointing this out to me

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer (Senior) JetByte Limited
United Kingdom United Kingdom
Len has been programming for over 30 years, having first started with a Sinclair ZX-80. Now he runs his own consulting company, JetByte Limited and has a technical blog here.

JetByte provides contract programming and consultancy services. We can provide experience in COM, Corba, C++, Windows NT and UNIX. Our speciality is the design and implementation of systems but we are happy to work with you throughout the entire project life-cycle. We are happy to quote for fixed price work, or, if required, can work for an hourly rate.

We are based in London, England, but, thanks to the Internet, we can work 'virtually' anywhere...

Please note that many of the articles here may have updated code available on Len's blog and that the IOCP socket server framework is also available in a licensed, much improved and fully supported version, see here for details.

Comments and Discussions

 
GeneralRe: Not able to compile the source code Pin
Len Holgate24-Nov-03 20:59
Len Holgate24-Nov-03 20:59 
GeneralRe: Not able to compile the source code Pin
eyedia24-Nov-03 23:37
eyedia24-Nov-03 23:37 
GeneralRe: Not able to compile the source code Pin
kimopennywise11-Apr-04 12:26
kimopennywise11-Apr-04 12:26 
GeneralRe: Not able to compile the source code Pin
Len Holgate14-Apr-04 0:21
Len Holgate14-Apr-04 0:21 
GeneralRe: Not able to compile the source code Pin
BuonHiu11-Aug-04 4:29
BuonHiu11-Aug-04 4:29 
GeneralRe: Not able to compile the source code Pin
Len Holgate11-Aug-04 11:07
Len Holgate11-Aug-04 11:07 
GeneralRe: Not able to compile the source code Pin
BuonHiu1-Sep-04 6:50
BuonHiu1-Sep-04 6:50 
GeneralCCOMSocketServer does not fire disconnect event Pin
thanatos14-Oct-03 14:19
thanatos14-Oct-03 14:19 
GeneralRe: CCOMSocketServer does not fire disconnect event Pin
Anonymous (Shannara)23-Oct-03 8:21
sussAnonymous (Shannara)23-Oct-03 8:21 
GeneralRe: CCOMSocketServer does not fire disconnect event Pin
Len Holgate23-Oct-03 8:35
Len Holgate23-Oct-03 8:35 
QuestionAnybody have a Collections Sample? Pin
Shannara16-Sep-03 9:59
Shannara16-Sep-03 9:59 
QuestionThe use of CCOMSocketServer::COMEventThread? Pin
sam lu10-Sep-03 5:06
sam lu10-Sep-03 5:06 
AnswerRe: The use of CCOMSocketServer::COMEventThread? Pin
Len Holgate10-Sep-03 6:46
Len Holgate10-Sep-03 6:46 
GeneralRe: The use of CCOMSocketServer::COMEventThread? Pin
sam lu11-Sep-03 3:13
sam lu11-Sep-03 3:13 
GeneralRe: The use of CCOMSocketServer::COMEventThread? Pin
Len Holgate11-Sep-03 3:37
Len Holgate11-Sep-03 3:37 
QuestionHow to Create Com component of UDP server? Pin
sam lu6-Sep-03 15:37
sam lu6-Sep-03 15:37 
AnswerRe: How to Create Com component of UDP server? Pin
Len Holgate6-Sep-03 23:57
Len Holgate6-Sep-03 23:57 
GeneralMajor bug, and thanks. Pin
Shannara16-Aug-03 6:23
Shannara16-Aug-03 6:23 
GeneralRe: Major bug, and thanks. Pin
Len Holgate16-Aug-03 22:05
Len Holgate16-Aug-03 22:05 
QuestionMissing Usability? Pin
Shannara25-Jun-03 20:45
Shannara25-Jun-03 20:45 
QuestionMissing Usability? Pin
Shannara25-Jun-03 20:43
Shannara25-Jun-03 20:43 
QuestionMissing Usability? Pin
Shannara25-Jun-03 20:41
Shannara25-Jun-03 20:41 
GeneralMaintaining Read/Write ordering in a reusable multithreaded TCP component for VB Pin
Pawn Takes King26-Mar-03 9:40
Pawn Takes King26-Mar-03 9:40 
GeneralRe: Maintaining Read/Write ordering in a reusable multithreaded TCP component for VB Pin
Pawn Takes King26-Mar-03 11:28
Pawn Takes King26-Mar-03 11:28 
GeneralRe: Maintaining Read/Write ordering in a reusable multithreaded TCP component for VB Pin
Len Holgate26-Mar-03 12:39
Len Holgate26-Mar-03 12:39 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.