Results 1 to 3 of 3

Thread: VB^ - SimpleServer

  1. #1

    Thread Starter
    Fanatic Member
    Join Date
    Dec 2012

    VB^ - SimpleServer

    Note: Title should be VB6 - SimpleServer (can't edit)
    CSocket was originally developed by Oleg Gdalevich as a replacement for MSWinSck. Emiliano Scavuzzo improved upon it with CSocketMaster, and I converted it to support IPv6 with cSocket2. With NewSocket, I attempted to streamline the code. SimpleSock is a complete rewrite designed to further simplify and streamline socket code. SimpleServer is designed to allow a single listening socket to support multiple connections without having to resort to using a control array. Like all socket tools, it must be used properly to be effective.

    In theory, SimpleServer could be used for all socket access, but for only a few sockets, SimpleSock is simpler and easier to use. Understanding how SimpleServer works will allow you to make better use of it. There are 8 events or call-backs associated with a socket.
    1. CloseSck
    2. Connect
    3. ConnectionRequest
    4. DataArrival
    5. EncrDataArrival
    6. Error
    7. SendComplete
    8. SendProgress
    In SimpleServer, there is another one called WndProc, but it is not used and only provides access to the other events. With one exception (CloseSck), these routines are not called directly. They simply provide information to the calling program.

    The calling program Implements SimpleServer. That means that any procedure declared as Public in SimpleServer will be implemented in the calling program, and that includes the 8 routines noted above. When SimpleServer is first implemented, the individual routines have to be activated. This is accomplished by clicking on each one. As you do so, they will go from plain to bold text. Routines that we want to access from the calling program but we do not want to implement, are declared as Friend instead of Public.

    When we add an instance of a class with call-backs, we simply define the procedure "WithEvents" and add a new instance. With Implements, we can't do that. So we have to do the following instead:
    Implements SimpleServer
    Private mServer() As New SimpleServer
        Dim lNum As Long
        ReDim mServer(MaxClients)
        For lNum = 0 To MaxClients
            Set mServer(lNum).Callback(lNum) = Me
            mServer(lNum).IPvFlg = 4
        ReDim RecLen(MaxClients)
        ReDim RecType(MaxClients)
    Adding the IPvFlg is not strictly necessary, because SimpleServer defaults to IPv4. But it is a good practice to get into. With SimpleServer, the listening socket is always the first instance.
    If SimpleServer was to be used to make a connection to another host, it would call "mServer(lIndex).TCPConnect Destination, PortConnect". Once the connection is established, SimpleServer would receive an "FD_CONNECT" and fire off a "Connect" message to the calling program. That would leave the calling program ready to start sending data.

    When a client attempts to connect, an "FD_ACCEPT" is received by SimpleServer, and it fires off a "ConnectionRequest" message to the calling program. If acceptable, the calling program sends "mServer(lIndex).Accept(requestID, RemotePort, RemoteHostIP)". If it is not acceptable, it sends "mServer(lIndex).Accept(requestID, 0, "")", and SimpleServer interprets the "0" port as invalid.

    Data is received by a socket in packets of approximately 1500 bytes. Of this, a maximum of 1460 bytes is actual data. Winsock assembles those packets into blocks of data that vary with the system. Windows Vista has a block size of 8,192 bytes, and Win 8.1 has a block size of 65,536 bytes. Winsock doesn't necessarily use all that space, it is just a maximum. Whatever criteria the OS uses, when it is ready it will send an "FD_READ" message to SimpleServer. For TCP, SimpleServer will add that data to it's own buffer (m_bRecvBuffer) and remove it from the Winsock buffer. It then fires off a "DataArrival"/"EncrDataArrival" message to the calling program along with the number of bytes just received. For UDP, SimpleServer will leave the data in the Winsock buffer, and notify the calling program of the bytes received.

    How the calling program handles this information depends on the program itself. SimpleServer will keep adding data to "m_bRecvBuffer" (TCP) until the calling program gives it instructions. In the sample program I have provided, I have used a header to provide more information about the data being sent. It includes a Record Type and Record Length. The Record Length tells the receiving program how much data to expect. Because the data length does not include the header itself, the header is removed from the buffer using the statements "Call mServer(Index).RecoverData(8)" & "RecHeader = mServer(Index).bInBuffer". The (8) is an optional number telling SimpleServer to only remove 8 bytes. If it was left out, SimpleServer would remove all bytes. If the Record Length includes the header, it can be recovered using the "PeekData" command and left in the buffer.

    All the data could be removed and analyzed in the "DataArrival"/"EncrDataArrival" routines, but that would mean separate buffers would be required for each connection, and I don't know how to create an array of byte arrays. Instead, we simply allow the data to accumulate in the "m_bRecvBuffer" in each instance of SimpleServer, and remove the desired amount when it is exceeded.

    Sending of data is similar. All the data is added to "m_bSendBuffer" regardless of size. When the calling program issues a TCPSend, it enters a loop. SimpleServer copies from "m_bSendBuffer" a maximum of the block size of the Winsock output buffer and forwards it to the Winsock API. If the API is successful in sending the data, it returns the number of bytes sent and they are removed from "m_bSendBuffer". It remains in the loop until all the bytes are sent. Should the API return an error "WSAEWOULDBLOCK", it means that the API is still busy sending the previous block. A message is sent to "SendProgress" with the total bytes sent and the bytes remaining, and the loop exited. When the Winsock output buffer is once again ready to send data, it sends an "FD_WRITE" message to SimpleServer, and SimpleServer calls TCPSend once again. When all the data has been sent, messages are sent to both "SendProgress" and "SendComplete".

    All SimpleServer errors (with the exception of "WSAEWOULDBLOCK") are forwarded to the calling program for action. Normally, in a server application errors are logged, so as to prevent holding up the program itself.

    That leaves the "CloseSck" event. There are 2 ways of closing the socket. Should the far end close the socket, Winsock will send an "FD_CLOSE" message to SimpleServer. SimpleServer will forward a message to "CloseSck" and change the socket state to "sckClosing". CloseSck will call "mServer(Index).CloseSocket" which will actually close the socket on the server side and change the socket state to "sckClosed". To close the socket from the server end, users should refrain from calling "CloseSocket" directly. This can cause the socket to remain in the "sckClosing" state and become unusable. Always call "CloseSck" in the calling program. As an added note, always include a routine in the "Form_Unload" event to close all sockets. Failure to do so can cause a port to become unusable.

    J.A. Coutts
    Attached Files Attached Files
    Last edited by couttsj; Mar 6th, 2019 at 02:46 PM.

  2. #2
    Join Date
    Mar 2015

    Re: VB^ - SimpleServer

    Nice to see alternative winsock replacements used. I have been long thinking of making a small student test program, in which students complete some test questions in random order. Something to play around with. Many thanks couttsj (i always like your programs).

  3. #3

    Thread Starter
    Fanatic Member
    Join Date
    Dec 2012

    Re: VB^ - SimpleServer

    Ran into a problem with SimpleSock. When using SimpleSock as a listening socket, the handle to the listening socket is restored when the connected socket is closed. Normally, that listening socket is retained, ready to receive the next connection request. On an application that I am currently working on, I needed to close the listening socket as well. When I attempted to do that, I received an unknown error (-1), and the program would stop functioning. Closing of a socket takes a little time as it waits for straggling packets, so I added a 1 second time delay between closing of the connected socket and closing of the listening socket.

    That solved one problem, but introduced another problem. Now I received an Error 10038 (WSAENOTSOCK) when attempting to close the listening socket after a connected socket was used. When SimpleSock accepts a connection request on a listening socket, it sets a flag (m_bAcceptClass). That flag was not reset when the listening socket itself was closed. Due to the way that a socket gets closed, there are actually 2 trips to the CloseSocket function. On the second pass, the API function attempted to close a non-existent socket. The problem was solved by resetting the "m_bAcceptClass" flag at the time the connected socket was closed (m_bAcceptClass = False) in the code below.
    Public Function CloseSocket() As Boolean
        Const Routine As String = "SimpleSock.CloseSocket"
        Dim lErrorCode As Long
        Dim lRet As Long
        Dim bTmp() As Byte
        If Not m_hSocket = SOCKET_ERROR Then
            lRet = API_CloseSocket(m_hSocket)
            If lRet = -1 Then
                m_State = sckError
                lErrorCode = Err.LastDllError
                RaiseEvent Error(lErrorCode, "Could not Close Socket!", Routine)
                Call modSocket.UnregisterSocket(m_hSocket)
                m_hSocket = SOCKET_ERROR
                RaiseEvent CloseSck
                CloseSocket = True
                If m_bAcceptClass Then
                    m_hSocket = m_hListen
                    m_State = scklistening
                    Call PrintDebug(CStr(m_hSocket) & ": scklistening")
                    m_bAcceptClass = False
                    m_State = sckClosed
                    Call PrintDebug("STATE: sckClosed")
                End If
                m_sLocalIP = vbNullString
                m_sRemoteHostIP = ""
                m_bRecvBuffer = bTmp
                m_bSendBuffer = bTmp
                m_lSendBufferLen = 0
                m_lRecvBufferLen = 0
            End If
        End If
    End Function
    The SimpleSock download has not been adjusted, and SimpleServer does not experience the same issue because all sockets are numbered, including the listening socket (socket 0).

    J.A. Coutts

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts


Click Here to Expand Forum to Full Width