Announcement

Collapse
No announcement yet.

OVERLAPPED SOCKET - Anyone have a working demo?

Collapse
X
 
  • Filter
  • Time
  • Show
Clear All
new posts

  • OVERLAPPED SOCKET - Anyone have a working demo?

    Hi
    I am trying to cook up a simple overlapped socket demo. Overlapped being no-blocking asynchronous socket communications.

  • #2
    Hi, David.

    I have a couple of files that I was using to test an asynchronous UDP socket using overlapped I/O. They're kind of long so I've attached them to this thread (had actually posted them earlier in another thread). One file starts a socket, listens for incoming messages, and sends a reply. It's not refined but should give you some ideas. The other file is a simple GUI that allows you to send UDP messages to the socket app.

    Note that the real reason for this file was to test the use of multiple threads to service a single socket. I was looking at socket buffer overflow, CPU utilization and context switching under high load and different configurations. It seems to work quite well. This is used for high-throughput message switching server. Hopefully, it all makes sense.
    Attached Files

    Comment


    • #3
      Thanks Jerry! I will take a look!

      Comment


      • #4
        Jerry,

        Very nice example using UDP.
        The server is compiled using PBCC and the client "SendTestMessage.bas" is compiled using PBWIN.

        Change the send port to something other than 0 (PBWIN program).
        The server starts 40 threads.
        Changed to 1, just to see what would happen (and prevent everything scrolling off the PBCC window.
        Having problems converting the server to PBWIN, but after compiling with PBCC should find the errors of my ways.

        Not sure how to handle errors with UDP, but chugging along.
        May just adapt starting multiple threads in advance and not use overlapped I/O to speed up SQLiteningServer.

        I've noticed VPN's default to UDP, but would have to find a way to handle the errors like they do. Need to go back to school.

        I thought with asynchronous i/o a thread wouldn't be needed for each connection?
        Something like 1-thread reads everything and fills an array element for each socket connection.



        Comment


        • #5
          The sample code was derived from an IoT project - a message switching server and a client API. The server, which maintains simultaneous connections to as many as 50K remote remote clients, is designed to run as a service and use a single UDP socket for all connections (sessions). A layer above UDP handles the "multiplexing" of the separate sessions.

          A single thread can handle received messages from any remote client on that single socket. However, the processing of a message packet requires a database lookup, decryption/encryption, attack mitigation and other chores - which can be computationally expensive. Distributing the work to multiple threads/cores seems to provide a performance improvement - especially when handling up to 50K msgs/second.

          The console logging was added to the server sample code. The actual project logs events that can be used by DebugView for development/troubleshooting.

          Most errors that occur when processing messages are probably fatal socket errors (e.g. app stops, Winsock stops, socket is closed) which are reflected as some type of Windows error. Other errors, such as ICMP errors, may not be fatal but may cause other actions. For instance, a destination port unreachable probably indicates that a remote client has shut down, so that session can be cleared on the server. Of course, there can be discussion on this being used as a possible attack vector.

          Comment


          • #6
            Jerry

            I do not understand a word of this - ok I will spend some more time on it!

            It would be quite useful if experts like you developed an easy system for people like me to allow PB programs to take advantage of IoT technology and concepts. Is that a silly request?

            It seems to me that IoT is one of the ways of the future and that PB has excellent tools for dealing with large amounts of data.

            Kerry
            [I]I made a coding error once - but fortunately I fixed it before anyone noticed[/I]
            Kerry Farmer

            Comment


            • #7
              Hi Kerry

              Let me try to explain this...

              I have a product that is a communications hub for lots of small computers calling home to report in and get instructions - a client server thing, could be IoT but in my case it is a building security thing.

              My "server" listens on a TCP IP port for incoming connection requests then accepts them and has a conversation. A web server does a similar thing.

              There are different ways of programming the TCP IP side of the server. I am happy to show you code examples if you need something like this. Currently my server uses lots of threads. I have been reading that "overlapped IO" is a more efficient way of doing this and is totally asynchronous.

              I am going to build some tests to see if overlapped IO will make my things able to handle more simultaneous connections and lighten the load on the CPU.








              Comment


              • #8
                Thanks David

                That helped a lot

                Kerry
                [I]I made a coding error once - but fortunately I fixed it before anyone noticed[/I]
                Kerry Farmer

                Comment


                • #9
                  Hi, Kerry.

                  I'm not really an expert, however, I may have tinkered with IoT and sockets more than some folks (but not Mike Stefanik for sockets).

                  The sample code that I attached in post #2 provides just one method of wrapping Winsock API functions to support multiple UDP connections using a single socket, overlapped I/O for performance, asynchronous notification to work in a continuous loop, and the ability to adjust socket send/receive buffer sizes. This is just an example of one way to do this - and it may not be a good method for some applications. But, it does show some of the native Winsock API functions and how they can be implemented. Once you have a handle on how the API works, application code can be tailored for specific requirements - including IoT.

                  There are a number of fine examples of working with Winsock in this forum. There are also some very useful references available. The PB IDE help files provide a simple tutorial for TCP and UDP communications as well as some sample code. Microsoft provides a lot of details on using Winsock here. There's a lot of information floating around on the Internet. If one really wants to get down in the weeds, the reference books TCP/IP Illustrated Volumes 1 and 2 (Kevin R. Fall and W.Richard Stevens) provide a wealth of information. If someone just needs a networking toolkit to support their applications, I would think that tools available from Catalyst Development would be an incredibly good investment. I dug into this Winsock API stuff because I like to tinker.

                  There's also quite a bit of information on IoT on the Internet. Note that IoT is a general concept more than a clearly defined technology. If you search for IoT data protocols, you'll find lots of candidate protocols. But I suspect that MQTT and COAP are the dominant players right now. They're we'll documented publish/subscribe and/or query/response protocols and can be quite complex. But, there are some existing client APIs and servers/brokers available supporting those protocols. My particular interest is in very secure, very low-latency message switching between client applications supporting short-message telemetry applications. This is something that existing protocols may not natively do that well. It's a pet project that I've been fiddling with for a few years. But, it is definitely non-standard.

                  Comment


                  • #10
                    Would suspending/resuming threads work as well as the thread pool functions?

                    I am testing with normal PowerBASIC TCP (blocking)
                    1. Resume next available thread on connection.
                    2. Suspend thread on connection close.
                    Uses 1-thread per connection and not sure it is better than just creating a thread on each connection.
                    I see the benefit of UDT where all data is received or none at all, but doesn't consolidate packets like TCP.

                    Using overlapped could a single thread receive all data with an array of connected sockets?
                    Code:
                    tcp open server port %port as nserver timeout 3000
                    'tcp notify nserver, accept to cb.hndl as %tcp_accept
                    'case %tcp_accept
                    
                    'check for data all sockets (very rough, no error checking)
                    do
                     sleep 10
                     for x = 1 to ubound(socket)               'check each socket in array
                      tcp recv socket(x), buffersize, sbuffer  'replace with overlapped
                      if len(sbuffer) Then                     'data received
                        spacket(x) = spacket(x) + sbuffer      'put into element for this socket
                        if len(spacket(x)) = something then thread create process()
                      end if
                     next
                    loop until gDoneFlag
                    Mike Doty
                    Member
                    Last edited by Mike Doty; 24 Oct 2019, 12:12 PM.

                    Comment


                    • #11
                      Thanks Jerry

                      I need to do some work!

                      Kerry
                      [I]I made a coding error once - but fortunately I fixed it before anyone noticed[/I]
                      Kerry Farmer

                      Comment


                      • #12
                        I have been using a dedicated dialog just for TCP communications. The dialog runs in its own single thread. You can hide the dialog if you want. Normally I have a debug display so I can see the communications when needed. Here are the basic concepts. You need to have arrays to keep track of things but they are all indexed to the file number.


                        Works like this:

                        Make a form.
                        Have that form call a thread function that creates a form.
                        If you are a server, open a listening port.
                        Accept connection requests.
                        TCP NOTIFY each new connection to a %TCP_MASTER_EVENT on the communications dialog.

                        Code:
                        [B][FONT=courier new]CASE %API_TCP_MASTER_EVENT
                        
                                  SELECT CASE CB.LPARAM
                        
                                    CASE %FD_READ    ' 1
                                      TCP_RECEIVE CONVERT_FILE_HANDLE_TO_FILE_NUMBER(CB.WPARAM)
                        
                                    CASE %FD_ACCEPT  ' 8
                                      TCP_ACCEPT_CONNECTION
                        
                                    CASE %FD_CLOSE   ' 32
                                      TCP_CLOSE CONVERT_FILE_HANDLE_TO_FILE_NUMBER(CB.WPARAM)
                        
                                  END SELECT  [/FONT][/B]
                        Remember, CB.LPARAM is the FILE HANDLE so you need to convert it to a FILE NUMBER:

                        Code:
                        [B][FONT=courier new] FUNCTION CONVERT_FILE_HANDLE_TO_FILE_NUMBER(FILE_HANDLE AS DWORD) AS LONG
                        
                          'FILE_HANDLE IS PASSED IN FROM CALLBACK CB.WPARAM
                          'FILE_HANDLE_TO_NUMBER(CB.WPARAM)
                        
                           LOCAL Looper     AS LONG
                           LOCAL hFileOsTry AS LONG
                           LOCAL FileNumTry AS LONG
                        
                           Looper = 1                                'FILEATTR(Looper, 3) is one based
                           DO
                           FileNumTry = FILEATTR(Looper, 3)          '3 enumerates existing file numbers.
                           IF FileNumTry = -1 THEN                   'No more handles to scan
                             EXIT LOOP                               'FUNCTION will return zero, so nothing found, could also be setted to -1 if needed
                           ELSE
                             IF FILEATTR(FileNumTry, 0) THEN         '0 for "open state", return TRUE if file is open
                               hFileOsTry = FILEATTR(FileNumTry, 2)  'Get the coresponding operating system file handle
                               IF hFileOsTry = FILE_HANDLE THEN
                                 FUNCTION = FileNumTry
                               END IF
                             END IF
                           END IF
                           INCR Looper
                           LOOP
                        
                        END FUNCTION[/FONT][/B]

                        Comment


                        • #13
                          Are you using overlapped?
                          What do those statements look like for the open and recv?
                          Are you placing each recv into an array?
                          How do you concatenate the bytes into the array?
                          Do you exit on a number of bytes received?

                          How do you send information back since a TCP SEND would be a bottleneck?

                          Comment


                          • #14
                            Asynchronous UDP Server

                            Put server into PBFORMS instead of PBCC and replaced PRINT with LISTBOX ADD.
                            Great for learning the API of Winsock and asynchronous.
                            I was going to put the client into it, but decided if anyone is interested they can use your client program.
                            Buttons are not functional, but left them if anyone wants to add some code.
                            Jerry,
                            That was a lot of work. Nice code!!

                            See post #2 to get Jerry's original code. Server is in PBCC and client is in PBWIN10.

                            Code:
                            #PBFORMS CREATED V2.01
                            #COMPILE EXE      ' Default is an EXE file
                            #DIM ALL          ' All vars MUST be declared before use
                            #UNIQUE VAR ON    ' No duplicate var names except as local vars in separate SUBs/FUNCTIONs
                            #OPTION VERSION5  ' Only support Win2000 and beyond
                            #TOOLS OFF        ' Disable this if want to use TRACE, PROFILE or CALLSTK tools
                            GLOBAL gDone AS LONG    'original code is by Jerry Wilson code  modified a bit and putting into PBFORMS 10/26/19
                            '   https://forum.powerbasic.com/forum/user-to-user-discussions/programming/783120-overlapped-socket-anyone-have-a-working-demo
                            '------------------------------------------------------------------------------
                            '   ** Includes **
                            '------------------------------------------------------------------------------
                            #PBFORMS BEGIN INCLUDES
                            %USEMACROS = 1
                            #INCLUDE ONCE "WIN32API.INC"
                            #PBFORMS END INCLUDES
                            
                            '   ** Constants **
                            '------------------------------------------------------------------------------
                            #PBFORMS BEGIN CONSTANTS
                            
                            %IDD_DIALOG1        =    101
                            %LB_LISTBOX1        =   8197
                            %BTN_BUTTON1        =   8198
                            %BTN_BUTTON2        =   8199
                            %BTN_BUTTON3        =   8200
                            %BTN_BUTTON4        =   8201
                            %BTN_BUTTON5        =   8202
                            %BTN_BUTTON6        =   8203
                            #PBFORMS END CONSTANTS
                            GLOBAL ghDlg AS DWORD
                            '------------------------------------------------------------------------------
                            '   ** Declarations **
                            '------------------------------------------------------------------------------
                            #PBFORMS DECLARATIONS
                            
                            '**************************************************************************************************
                            ' Most errors that will be generated are Winsock errors. Microsoft provides these values at:
                            '   https://docs.microsoft.com/en-us/windows/win32/winsock/windows-sockets-error-codes-2
                            '**************************************************************************************************
                            %SOCK_NOFRAG      = 1
                            %SOCK_OVERLAPPED  = 2
                            
                            MACRO VAR16       = STRING * 16
                            MACRO SOCKADDR_V4 = SOCKADDR_IN
                            MACRO SOCKHNDL    = DWORD
                            
                            '-- Set this value to work with your application. It should be less than the path MTU if you
                            '   don't want to fragment packets. There is a lot of data on the Internet about this.
                            %WS_MAXPACKETSIZE = 1400
                            
                            '-- Non-Windows/Winsock errors (local to this library).
                            %WS_BASEERROR         = %APPLICATION_ERROR_MASK
                            %WS_NOERROR           = 0
                            %WS_BADARGUMENT       = %WS_BASEERROR + 1
                            %WS_DESTUNREACHABLE   = %WS_BASEERROR + 2
                            
                            '-- This value is required for the Winsock getsockopt funtion but does not seem to be in the
                            '   PB include files.
                            %SO_PROTOCOL_INFO  = &H2004
                            
                            '-- This struct is defined in ws2tcpip.h and is used with the IPv6 protocol stack. When that
                            '   include file is ported to a PB .inc file, this can be removed and we'll just include the
                            '   new file. The element names byte and word are specified in the source include file and
                            '   happen to be reserved words. Fortunately, those names can now be included in structs. The
                            '   fixed length string s is NOT included in the source but may be convenient for our use.
                            UNION V6_ADDR
                              nByte(0 TO 15)  AS BYTE
                              nWord(0 TO 7)   AS WORD
                              sAddr           AS VAR16  ' Not "officially" defined in the Winsock SDK.
                            END UNION
                            
                            '-- This struct replaces SOCKADDR_IN6 from Win32 for easier control of the IPv6 address.
                            TYPE SOCKADDR_V6
                              sin6_family   AS WORD
                              sin6_port     AS WORD
                              sin6_flowInfo AS LONG
                              sin6_addr     AS V6_ADDR  ' <-- This part is differrent!
                              sin6_scope_id AS LONG
                            END TYPE
                            
                            '==================================================================================================
                            ' Some PB function declarations only support IPv4 (such as SOCKADDR_IN). The following
                            ' declarations support v6 and v4.
                            '==================================================================================================
                            DECLARE FUNCTION wsGetSockName LIB "ws2_32.dll" ALIAS "getsockname" ( _
                                BYVAL s       AS SOCKHNDL, _
                                BYVAL sName   AS DWORD, _     ' Changed: Ptr to SOCKADDR_IN or SOCKADDR_IN6
                                BYREF namelen AS LONG _
                                ) AS LONG
                            
                            DECLARE FUNCTION wsRecvFrom LIB "ws2_32.dll" ALIAS "WSARecvFrom" ( _
                                BYVAL s                       AS SOCKHNDL, _
                                BYREF lpBuffers               AS WSABUF, _
                                BYVAL dwBufferCount           AS DWORD, _
                                BYREF lpNumberOfBytesReceived AS DWORD, _
                                BYREF lpFlags                 AS DWORD, _
                                BYVAL lpFrom                  AS DWORD, _     ' Changed: Ptr to socket address struct
                                BYREF lpFromLen               AS LONG, _
                                BYREF lpOverlapped            AS OVERLAPPED, _
                                BYVAL lpCompletionRoutine     AS DWORD _
                                ) AS LONG
                            
                            DECLARE FUNCTION wsSendTo LIB "ws2_32.dll" ALIAS "WSASendTo" ( _
                                BYVAL s                   AS SOCKHNDL, _
                                BYREF lpBuffers           AS WSABUF, _
                                BYVAL dwBufferCount       AS DWORD, _
                                BYREF lpNumberOfBytesSent AS DWORD, _
                                BYVAL dwFlags             AS DWORD, _
                                BYVAL lpTo                AS DWORD, _         ' Changed: Ptr to socket address struct
                                BYVAL iTolen              AS LONG, _
                                BYREF lpOverlapped        AS OVERLAPPED, _
                                BYVAL lpCompletionRoutine AS DWORD _
                                ) AS LONG
                            
                            DECLARE FUNCTION wsGetSockOpt LIB "ws2_32.dll" ALIAS "getsockopt" ( _
                                BYVAL s       AS SOCKHNDL, _
                                BYVAL level   AS LONG, _
                                BYVAL optName AS LONG, _
                                BYVAL optVal  AS LONG, _    'Changed: This is pointer to option value
                                BYREF optlen  AS LONG _
                                ) AS LONG
                            
                            DECLARE FUNCTION wsGetSockNamet LIB "ws2_32.dll" ALIAS "getsockname" ( _
                                BYVAL s         AS SOCKHNDL, _
                                BYVAL lpName    AS LONG, _      'Ptr to SOCKADDR struct
                                BYREF namesize  AS LONG _
                                ) AS LONG
                            
                            
                            '------------------------------------------------------------------------------
                            '   ** Main Application Entry Point **
                            '------------------------------------------------------------------------------
                            FUNCTION PBMAIN()
                              ShowDIALOG1 %HWND_DESKTOP
                              wsStopWinsock
                            END FUNCTION
                            '------------------------------------------------------------------------------
                            '   ** CallBacks **
                            '------------------------------------------------------------------------------
                            CALLBACK FUNCTION ShowDIALOG1Proc()
                            
                              SELECT CASE AS LONG CB.MSG
                                CASE %WM_INITDIALOG
                                  ' Initialization handler
                                  ghDlg = CB.HNDL
                                  PBMAIN2
                            
                                CASE %WM_CLOSE
                                  gDone = 1
                                  tPrint " %WM_CLOSE"
                                  DIALOG REDRAW CB.HNDL
                                  SLEEP 1100
                            
                                CASE %WM_NCACTIVATE
                                  STATIC hWndSaveFocus AS DWORD
                                  IF ISFALSE CB.WPARAM THEN
                                    ' Save control focus
                                    hWndSaveFocus = GetFocus()
                                  ELSEIF hWndSaveFocus THEN
                                    ' Restore control focus
                                    SetFocus(hWndSaveFocus)
                                    hWndSaveFocus = 0
                                  END IF
                            
                                CASE %WM_COMMAND
                                  ' Process control notifications
                                  SELECT CASE AS LONG CB.CTL
                                    CASE %LB_LISTBOX1
                            
                                    CASE %BTN_BUTTON1
                                      IF CB.CTLMSG = %BN_CLICKED OR CB.CTLMSG = 1 THEN
                                       ? "%BTN_BUTTON1"
                                      END IF
                            
                            
                                    CASE %BTN_BUTTON2
                                      IF CB.CTLMSG = %BN_CLICKED OR CB.CTLMSG = 1 THEN
                                        'DisplayTheReceivedPacketCountForEachThread
                                      END IF
                            
                                    CASE %BTN_BUTTON3
                                      IF CB.CTLMSG = %BN_CLICKED OR CB.CTLMSG = 1 THEN
                                        MSGBOX "%BTN_BUTTON3=" + FORMAT$(%BTN_BUTTON3), %MB_TASKMODAL
                                      END IF
                            
                                    CASE %BTN_BUTTON4
                                      IF CB.CTLMSG = %BN_CLICKED OR CB.CTLMSG = 1 THEN
                                        MSGBOX "%BTN_BUTTON4=" + FORMAT$(%BTN_BUTTON4), %MB_TASKMODAL
                                      END IF
                            
                                    CASE %BTN_BUTTON5
                                      IF CB.CTLMSG = %BN_CLICKED OR CB.CTLMSG = 1 THEN
                                        MSGBOX "%BTN_BUTTON5=" + FORMAT$(%BTN_BUTTON5), %MB_TASKMODAL
                                      END IF
                            
                                    CASE %BTN_BUTTON6
                                      IF CB.CTLMSG = %BN_CLICKED OR CB.CTLMSG = 1 THEN
                                        MSGBOX "%BTN_BUTTON6=" + FORMAT$(%BTN_BUTTON6), %MB_TASKMODAL
                                      END IF
                            
                                  END SELECT
                              END SELECT
                            END FUNCTION
                            
                            '------------------------------------------------------------------------------
                            '   ** Dialogs **
                            '------------------------------------------------------------------------------
                            FUNCTION ShowDIALOG1(BYVAL hParent AS DWORD) AS LONG
                              LOCAL lRslt  AS LONG
                            
                            #PBFORMS BEGIN DIALOG %IDD_DIALOG1->->
                              LOCAL hDlg   AS DWORD
                              LOCAL hFont1 AS DWORD
                            
                              DIALOG NEW hParent, "Dialog1", 264, 166, 637, 278, %WS_POPUP OR %WS_BORDER _
                                OR %WS_DLGFRAME OR %WS_CAPTION OR %WS_SYSMENU OR %WS_MINIMIZEBOX OR _
                                %WS_MAXIMIZEBOX OR %WS_CLIPSIBLINGS OR %WS_VISIBLE OR %DS_MODALFRAME OR _
                                %DS_3DLOOK OR %DS_NOFAILCREATE OR %DS_SETFONT, %WS_EX_CONTROLPARENT OR _
                                %WS_EX_LEFT OR %WS_EX_LTRREADING OR %WS_EX_RIGHTSCROLLBAR, TO hDlg
                              CONTROL ADD LISTBOX, hDlg, %LB_LISTBOX1, , 5, 25, 505, 230, %WS_CHILD OR _
                                %WS_VISIBLE OR %WS_TABSTOP OR %WS_VSCROLL OR %LBS_NOTIFY, _
                                %WS_EX_CLIENTEDGE OR %WS_EX_LEFT OR %WS_EX_LTRREADING OR _
                                %WS_EX_RIGHTSCROLLBAR
                              CONTROL ADD BUTTON,  hDlg, %BTN_BUTTON1, "Button1", 5, 5, 50, 15
                              CONTROL ADD BUTTON,  hDlg, %BTN_BUTTON2, "Button2", 61, 5, 50, 15
                              CONTROL ADD BUTTON,  hDlg, %BTN_BUTTON3, "Button3", 117, 5, 50, 15
                              CONTROL ADD BUTTON,  hDlg, %BTN_BUTTON4, "Button4", 173, 5, 50, 15
                              CONTROL ADD BUTTON,  hDlg, %BTN_BUTTON5, "Button5", 229, 5, 50, 15
                              CONTROL ADD BUTTON,  hDlg, %BTN_BUTTON6, "Button6", 285, 5, 50, 15
                            
                              FONT NEW "Lucida Console", 8, 0, %ANSI_CHARSET TO hFont1
                            
                              CONTROL SET FONT hDlg, %LB_LISTBOX1, hFont1
                            #PBFORMS END DIALOG
                            
                              DIALOG SHOW MODAL hDlg, CALL ShowDIALOG1Proc TO lRslt
                            
                            #PBFORMS BEGIN CLEANUP %IDD_DIALOG1
                              FONT END hFont1
                            #PBFORMS END CLEANUP
                            
                              FUNCTION = lRslt
                            END FUNCTION
                            
                            '-- Threadsafe screen logging so that multiple threads can write to the screen without
                            '   making a mess. This is REALLY basic but good enough for a demo.
                            SUB tPrint(sMsg AS STRING) THREADSAFE
                              LISTBOX ADD ghDlg,%LB_LISTBOX1, sMsg
                              CONTROL SEND ghDlg,%LB_LISTBOX1,%WM_VSCROLL,%SB_BOTTOM,0
                              '? sMsg
                            END SUB
                            
                            
                            '//////////////////////////////////////////////////////////////////////////////////////////////////
                            ' wsStartWinsock - Starts the Winsock library.
                            '
                            ' Starts the Winsock library after it verifies that the Winsock version is at least
                            ' 2.2 (prob most recent) but not later than 2.2 (not sure if our code will be compatible
                            ' with future versions). Returns 0 if no problems and the Winsock error code if there
                            ' is a problem. If version is invalid then call wsaCleanup since we won't be using
                            ' Winsock.
                            '
                            ' This function should never fail but we don't want to call wsaStartup more than one time
                            ' in the app. Otherwise, wsaCleanup would need to be called multiple times to shut down
                            ' Winsock.
                            '//////////////////////////////////////////////////////////////////////////////////////////////////
                            FUNCTION wsStartWinsock() THREADSAFE AS LONG
                              LOCAL ret     AS LONG
                              LOCAL wsData  AS WSADATA
                              STATIC init   AS LONG
                            
                              IF ISFALSE(init) THEN
                                ret = wsaStartup(MAKWRD(2, 2), wsData)
                                IF ret THEN
                                  FUNCTION = ret
                                ELSE
                                  init = %TRUE
                                END IF
                              END IF
                            
                            END FUNCTION  ' wsStartWinsock
                            
                            
                            '////////////////////////////////////////////////////////////////////////////////////////
                            ' wsStopWinsock() - Stop Winsock and release resources (possible future use).
                            '////////////////////////////////////////////////////////////////////////////////////////
                            FUNCTION wsStopWinsock() AS LONG
                              wsaCleanup
                            END FUNCTION  ' wsStopWinsock
                            
                            
                            '////////////////////////////////////////////////////////////////////////////////////////
                            ' wsGetRxBufferSize() - Get the size of the socket send buffer.
                            '
                            ' Return 0 if success or an Winsock error code if we fail (e.g. invalid socket). If fail
                            ' then set the return buffer size to zero (invalid in this library) as well.
                            '////////////////////////////////////////////////////////////////////////////////////////
                            FUNCTION wsGetRxBufferSize( _
                                BYVAL hSock AS SOCKHNDL, _  ' IN: Socket to get buffer size for
                                BYREF bSize AS DWORD _  ' OUT: Buffer size for target socket
                                ) AS LONG               ' RTN: 0=success otherwise Winsock error
                            
                              LOCAL zRet AS ASCIZ * 5   ' This is big enough for a LONG value plus the NULL byte.
                            
                              IF getsockopt(hSock, %SOL_SOCKET, %SO_RCVBUF, zRet, 4) THEN
                                bSize = 0
                                FUNCTION = wsaGetLastError
                              ELSE
                                bSize = PEEK(DWORD, VARPTR(zRet))
                              END IF
                            
                            END FUNCTION  ' wsGetRxBufferSize
                            
                            
                            '////////////////////////////////////////////////////////////////////////////////////////
                            ' wsGetTxBufferSize() - Same as wsGetRxBufferSize.
                            '////////////////////////////////////////////////////////////////////////////////////////
                            FUNCTION wsGetTxBufferSize ( _
                                BYVAL hSock AS SOCKHNDL, _  ' IN:  Winsock socket handle assigned to target socket
                                BYREF bSize AS LONG _   ' OUT: Size of socket TX buffer (bytes)
                                ) AS LONG               ' RTN: 0=success otherwise Winsock error
                            
                              LOCAL zRet  AS ASCIZ * 5
                            
                              IF getsockopt(hSock, %SOL_SOCKET, %SO_SNDBUF, zRet, 4) THEN
                                bSize = 0
                                FUNCTION = wsaGetLastError
                              ELSE
                                bSize = PEEK(DWORD, VARPTR(zRet))
                              END IF
                            
                            END FUNCTION  ' wsGetTxBufferSize
                            
                            
                            '//////////////////////////////////////////////////////////////////////////////////////////////////
                            ' wsSetTxBufferSize() - Sets the send buffer size for a socket (not same as max msg length).
                            '
                            ' Sets the send buffer size of a Windows socket hSock to size bSize. If function fails (e.g.
                            ' bad socket value), bSize will return value of zero. If success, bSize will be set to the
                            ' actual buffer size.
                            '
                            ' CAUTION: A value of zero in bSize will cause Winsock buffering to be disabled for the socket!!!
                            '//////////////////////////////////////////////////////////////////////////////////////////////////
                            FUNCTION wsSetTxBufferSize( _
                                BYVAL hSock AS SOCKHNDL, _  ' IN: Socket to set
                                BYREF bSize AS LONG _       ' IN/OUT: IN=request size OUT=allowed size
                                ) AS LONG                   ' Rtn: 0 if no error or error code if fail.
                            
                              LOCAL zRet AS ASCIZ * 5
                            
                              zRet = MKL$(bSize)
                              IF setsockopt(hSock, %SOL_SOCKET, %SO_SNDBUF, VARPTR(zRet), 4) THEN
                                FUNCTION = wsaGetLastError
                                EXIT FUNCTION
                              ELSE
                                wsGetTxBufferSize hSock, bSize
                              END IF
                            
                            END FUNCTION  ' wsSetTxBufferSize
                            
                            
                            '//////////////////////////////////////////////////////////////////////////////////////////////////
                            ' wsSetRxBufferSize() - Sets the recv buffer size for a socket (not same as max msg length).
                            '
                            ' Sets the receive buffer size of a Windows socket hSock to size bSize. If function fails (e.g.
                            ' bad socket value), bSize will return value of zero.
                            '
                            ' CAUTION: A value of zero in bSize will cause Winsock buffering to be disabled for the socket!!!
                            '//////////////////////////////////////////////////////////////////////////////////////////////////
                            FUNCTION wsSetRxBufferSize( _
                                BYVAL hSock AS SOCKHNDL, _  ' IN: Socket to set buffer size on
                                BYREF bSize AS DWORD _      ' IN: Requested buffer size
                                ) AS LONG                   ' RTN: 0=success otherwise the appropriate Winsock error
                            
                              LOCAL zRet AS ASCIZ * 5   ' This is big enough for a LONG value plus the NULL byte.
                            
                              zRet = MKL$(bSize)
                              IF setsockopt(hSock, %SOL_SOCKET, %SO_RCVBUF, VARPTR(zRet), 4) THEN
                                FUNCTION = wsaGetLastError
                              ELSE
                                wsGetRxBufferSize hSock, bSize
                              END IF
                            END FUNCTION  ' wsSetRxBufferSize
                            
                            
                            '//////////////////////////////////////////////////////////////////////////////////////////////////
                            ' wsStopSocket
                            '//////////////////////////////////////////////////////////////////////////////////////////////////
                            SUB wsStopSocket(BYREF hSock AS DWORD)
                              closeSocket hSock
                              hSock = 0
                            END SUB ' wsStopSocket
                            
                            
                            '//////////////////////////////////////////////////////////////////////////////////////////////////
                            ' wsStartSocket - COnvenience function that will start a socket.
                            '
                            ' sockProt = %SOCK_DGRAM for UDP or %SOCK_STREAM for TCP (no other options supported)
                            '
                            '//////////////////////////////////////////////////////////////////////////////////////////////////
                            FUNCTION wsStartSocket ( _
                                BYREF hSock       AS SOCKHNDL, _  'OUT: Handle to new socket (MUST BE A DWORD)
                                BYVAL sockFamily  AS LONG, _      'IN: IP address family (IPv4 or IPv6)
                                BYVAL sockProt    AS LONG, _      'IN: socket protocol (UDP or TCP)
                                BYREF locPort     AS WORD, _      'IN/OUT: IN=request port OUT=assigned port
                                BYVAL lpAddr      AS LONG, _      'IN: Ptr to local address (IPv4 or IPv6)
                                BYREF txBuffSize  AS DWORD, _     'IN/OUT: IN=request buff size OUT=actual buff size
                                BYREF rxBuffSize  AS DWORD, _     'IN/OUT: Same as above for recv buffer
                                BYVAL flags       AS DWORD _      'IN: Flags (options) used for wsaSocket
                                ) AS LONG                         'RTN: 0=success else an appropriate Winsock API error value
                            
                              LOCAL ret           AS LONG
                              LOCAL v6            AS VAR16
                              LOCAL sock4         AS SOCKADDR_V4  ' Renamed from SOCKADDR_IN in Win32 for naming consistency with...
                              LOCAL sock6         AS SOCKADDR_V6  ' Modified version or SOCKADDR_IN6 (struct in this include file)
                              LOCAL sockType      AS LONG
                              LOCAL sockFlags     AS LONG
                            
                              '-- Make sure that AF is set correctly. Only support IPv4 and IPv6.
                              IF (sockFamily <> %AF_INET) AND (sockFamily <> %AF_INET6) THEN
                                FUNCTION = %WSAEAFNOSUPPORT
                                EXIT FUNCTION
                              END IF
                            
                              '-- Make sure that protocol supported. Set socket type if proto is valid.
                              IF sockProt = %IPPROTO_TCP THEN
                                sockType = %SOCK_STREAM
                              ELSEIF sockProt = %IPPROTO_UDP THEN
                                sockType = %SOCK_DGRAM
                              ELSE
                                FUNCTION = %WSAEPROTONOSUPPORT
                                EXIT FUNCTION
                              END IF
                            
                              '-- Set flags for wsaSocket call. Only overlapped supported.
                              IF flags AND %SOCK_OVERLAPPED THEN sockFlags = %WSA_FLAG_OVERLAPPED
                            
                              '-- Create a socketf for either IPv4 or IPv6. The most possible (but unlikely) error that
                              '   may occur is if Winsock has not been started.
                              tPrint " MAIN: Starting socket"
                              hSock = wsaSocket( _
                                sockFamily, _   ' af
                                sockType, _     ' type
                                sockProt, _     ' protocol
                                BYVAL 0, _      ' lpProtocolInfo (use default settings)
                                0, _            ' g (socket groups not supported)
                                sockFlags _     ' dwFlags (only %SOCK_OVERLAPPED supported)
                                )               ' RTN: Socket handle if success else %INVALID_SOCKET (0xFFFFFFFF)
                            
                              '-- Note that hSock MUST be a DWORD since the value of INVALID_SOCKET is NOT -1. MSDN defines
                              '   socket handles as UINT (DWORD).
                              IF hSock = %INVALID_SOCKET THEN
                                ret = WSAGetLastError
                                FUNCTION = ret
                                EXIT FUNCTION
                              END IF
                            
                              '-- If a value has been provided for a send or recv socket buffer size the set that up. If
                              '   zero is provided then we don't make the call to setsockopt() as this would disable the
                              '   buffer for the socket. Since we have a valid socket, we shouldn't see any errors so don't
                              '   bother testing. The calling app may want to compare the value going into and coming out
                              '   of this fuction to make sure that it got the buffer space it was looking for. The default
                              '   buffer size in Win10 seems to be 65536 but can probably be adjusted in the registry.
                              IF txBuffSize THEN wsSetTxBufferSize(hSock, txBuffSize)
                              IF rxBuffSize THEN wsSEtRxBufferSize(hSock, rxBuffSize)
                              wsGetTxBufferSize(hSock, txBuffSize)
                              wsGetRxBufferSize(hSock, rxBuffSize)
                            
                              '-- Bind socket to an address/port. We already know that Winsock is started and we have a
                              '   good socket so the only thing that should fail is an invalid address or a null pointer
                              '   to the address. locPort is a WORD, which is a valid range for an IP port.
                              IF sockFamily = %AF_INET THEN
                                sock4.sin_family            = %AF_INET
                                sock4.sin_port              = htons(locPort)
                                sock4.sin_addr.s_un.s_addr  = PEEK(DWORD, lpAddr)
                                IF bind( _
                                    BYVAL hSock, _          ' s
                                    BYVAL VARPTR(sock4), _  ' *addr (sockaddr)
                                    SIZEOF(sock4) _         ' namelen
                                    ) THEN                  ' RTN: 0=success else SOCKET_ERROR (not FALSE)
                                  ret = WSAGetLastError   ' Test for error reason
                                  FUNCTION = ret
                                  wsStopSocket hSock
                                  EXIT FUNCTION
                                ELSE
                            'tPrint "Bind successful"
                                  wsGetSockName hSock, VARPTR(sock4), SIZEOF(sock4)
                                  locPort = ntohs(sock4.sin_port)  ' Convert from network byte order
                            'tPrint "locPort =" & str$(locPort)
                                END IF
                              ELSEIF sockFamily = %AF_INET6 THEN
                                MEMORY COPY lpAddr, VARPTR(V6), SIZEOF(V6)
                                sock6.sin6_family     = %AF_INET6
                                sock6.sin6_port       = htons(locPort)
                                sock6.sin6_flowinfo   = 0
                                sock6.sin6_addr.sAddr = v6
                                sock6.sin6_scope_id   = 0
                                IF bind( _
                                    BYVAL hSock, _
                                    BYVAL VARPTR(sock6), _
                                    SIZEOF(sock6) _
                                    ) THEN
                                  ret = WSAGetLastError   ' Test for error reason
                                  FUNCTION = ret
                                  wsStopSocket hSock
                                  EXIT FUNCTION
                                ELSE
                                  wsGetSockName hSock, VARPTR(sock6), SIZEOF(sock6)
                                  locPort = ntohs(sock6.sin6_port)  ' Convert from network byte order
                                END IF
                              END IF
                            
                            END FUNCTION  ' wsStartSocket
                            
                            
                            '//////////////////////////////////////////////////////////////////////////////////////////////////
                            ' wsProtoSocketManager - Prototype thread that can receive and respond to UDP messages.
                            '
                            '//////////////////////////////////////////////////////////////////////////////////////////////////
                            THREAD FUNCTION wsProtoSocketManager (BYVAL hSock AS SOCKHNDL) AS LONG
                              LOCAL sockAF    AS DWORD   ' if TRUE than IP family is IPv4 (AF_INET) othewise is IPv6 (AF_INET6)
                              LOCAL wsBuf     AS WSABUF
                              LOCAL wsOverlap AS OVERLAPPED
                              LOCAL ret       AS DWORD
                              LOCAL proto     AS WSAPROTOCOL_INFO
                              LOCAL rxBytes   AS DWORD
                              LOCAL txBytes   AS DWORD
                              LOCAL pSock     AS DWORD
                              LOCAL sockSize  AS LONG
                              LOCAL sock4     AS SOCKADDR_IN
                              LOCAL sock6     AS SOCKADDR_IN6
                              LOCAL nErr      AS LONG
                              LOCAL hMem      AS LONG
                            
                            'tPrint STR$(THREADID) & ": Starting with hSock =" & STR$(hSock)
                            
                            '==================================================================================================
                            '                              Initialization and socket validation
                            '==================================================================================================
                              '-- Determine IP family type. This will fail if the socket is invalid or Winsock has not been
                              '   started. Only IPv4 and IPv6 are supported. In the very unlikely event that some other AF is
                              '   used for the socket, then this also cause a failure.
                              IF wsGetSockOpt(hSock, %SOL_SOCKET, %SO_PROTOCOL_INFO, VARPTR(proto), SIZEOF(WSAPROTOCOL_INFO)) THEN
                                nErr = WSAGetLastError
                            'tPrint STR$(THREADID) & ": getsocketopt error =" & STR$(nErr)
                                GOSUB TestError
                              ELSE
                                IF proto.iAddressFamily = %AF_INET THEN
                                  sockAF = %AF_INET
                                  pSock = VARPTR(sock4)
                                  sockSize = SIZEOF(sock4)
                                ELSEIF proto.iAddressFamily = %AF_INET6 THEN
                                  sockAF = %AF_INET6
                                  pSock = VARPTR(sock6)
                                  sockSize = SIZEOF(sock6)
                                ELSE
                                  nErr = %WSAEAFNOSUPPORT
                                  GOSUB TestError
                                END IF
                            'tPrint STR$(THREADID) & ": sockAF =" & STR$(sockAF)
                              END IF
                            
                              '-- Create the OVERLAPPED struct and an IO completion signaling event. We will already fail if
                              '   Winsock is not started (above) so there is little chance that this will ever fail. But,
                              '   check anyway - just to make sure.
                              wsOverlap.hEvent = wsaCreateEvent
                              IF wsOverlap.hEvent = %WSA_INVALID_EVENT THEN
                                nErr = WSAGetLastError
                            tPrint STR$(THREADID) & ": WSACreateEvent failed with error" & STR$(nErr)
                                GOSUB TestError
                              END IF
                            
                              '-- Create the WSABUF struct for receiving messages. This buffer will be used for both Recv
                              '   and Send. Destroy the memory when the thread stops!
                              wsBuf.dLen = %WS_MAXPACKETSIZE              ' The wsBuf struct will store the buffer size
                              GLOBALMEM ALLOC %WS_MAXPACKETSIZE TO hMem   ' Allocate global memory to a buffer...
                              GLOBALMEM LOCK hMem TO wsBuf.buf            '   and store the buffer address in WSABUF (ptr)
                            'tPrint STR$(THREADID) & ": Socket message buffer memory allocated"
                            
                            '==================================================================================================
                            '                                    Receive packets
                            '==================================================================================================
                              DO
                            
                                '-- This is the Windows Winsock WSARecvFrom function. The PB include files declaration only
                                '   supports IPv4 so a modified declaration and function name is used in zWinsock.inc.
                                '
                                '   The socket is asynch and uses overlapped IO for speed. A rtn value of zero means a packet
                                '   was waiting in the socket buffer, so we need to call wsaGetOverlappedResult to get the
                                '   packet information. A non-zero return means some error occured. Some errors are not fatal
                                '   (WSAECONNRESET, WSA_IO_PENDING). Fatal errors will cause the TestError GOSUB code to
                                '   terminate the thread. Non-fatal errors will be processed (prob WSAECONNRESET) but there
                                '   is no message to process so loop and wait for next message.
                                RESET nErr
                                wsBuf.dLen = %WS_MAXPACKETSIZE   ' May have been changed if we sent a packet so reset this var
                            'tPrint "------------------------------------------------------------------------------"
                            'tPrint STR$(THREADID) & ": Calling wsRecvFrom"
                                IF wsRecvFrom ( _
                                    hSock, _      'IN: Socket handle
                                    wsBuf, _      'IN: Struct with data buffer info
                                    1, _          'IN: Only one data memory buffer (no gather/scatter)
                                    rxBytes, _    'OUT: Number of bytes received if IO immediate completion
                                    0, _          'IN: Flags (none used)
                                    pSock, _      'IN: Ptr to correct socket addr struct for this AF
                                    sockSize, _   'IN: Size of above socket
                                    wsOverlap, _  'IN: OVERLAPPED struct contains signaling event handle
                                    0 _           'IN: Completion routine address (not used in this app)
                                    ) THEN        'RTN: 0=ready to read else some error
                                  nErr = WSAGetLastError
                                  IF nErr <> %WSA_IO_PENDING THEN
                            tPrint STR$(THREADID) & ": wsRecvFrom error =" & STR$(nErr)
                                    GOSUB TestError
                                    ITERATE DO
                                  END IF
                            
                                  '-- Wait to be signaled if IO completion is not finished. This should never fail but
                                  '   check anyway. Gather/scatter is not used so we don't need to monitor multiple events.
                                 'tPrint STR$(THREADID) & ": Calling WaitForSingleObject (recv)"
                                  ret = WaitForSingleObject (wsOverlap.hEvent, %INFINITE)
                                  IF ret = %WAIT_FAILED THEN
                                    ret = wsaGetLastError
                                    tPrint STR$(THREADID) & ": WaitForSingleObject (recv) fail =" & CHR$(ret)
                                    GOTO StopThread
                                  END IF
                                END IF
                            
                                '-- Receive result is ready - either immediatly or after wait. This is the only way that we
                                '   access received host addr/port and message data. Note that error WSAECONNRESET will be
                                '   returned (nErr) if the destination port is not listening. This is not documented in
                                '   MSDN.
                                'tPrint STR$(THREADID) & ": Calling wsaGetOverlappedResult"
                                IF ISFALSE wsaGetOverlappedResult ( _
                                    hSock, _
                                    wsOverlap, _  'IN: Includes signaling event
                                    rxBytes, _    'OUT: This is the number of bytes we received
                                    BYVAL 0, _    'IN: Set to FALSE so this function will not block
                                    0 _           'IN: Flags (no flags used)
                                    ) THEN        'RTN: TRUE=success else some fatal error
                                  nErr = WSAGetLastError
                                  tPrint STR$(THREADID) & ": wsaGetOverlappedResult error =" & STR$(nErr)
                                  GOSUB TestError
                                  ITERATE DO    ' Error is not terminal so jump to wait for another message
                                END IF
                                GOSUB ProcPacket
                              LOOP
                            
                            
                            '==================================================================================================
                            '                          Message processing (your code goes here)
                            '==================================================================================================
                            ProcPacket:
                            
                              ThreadMon THREADID
                            
                              'tPrint STR$(THREADID) & ": Received a message with length" & STR$(rxBytes)
                              '-- The received message is in the memory buffer that starts at wsBuf.buf. Only the first
                              '   rxBytes in the buffer are useful.
                            
                            LOCAL farAddr AS LONG
                            LOCAL farPort AS WORD
                            LOCAL af      AS WORD
                            LOCAL sMsg    AS STRING
                            
                              '-- This version only supports IPv4. the SOCKADDR_IN struct is where we get the far IP
                              '   address and port.
                              IF sockAF = %AF_INET THEN
                                af      = sock4.sin_family
                                farAddr = sock4.sin_addr.s_un.s_addr
                                farPort = ntohs(sock4.sin_port)
                              END IF
                            
                              '-- Copy the message in the wsBuf buffer to a dynamic string.
                              sMsg = PEEK$(wsBuf.buf, rxBytes)
                              tPrint sMsg
                            
                              '-- Simulate some work here.
                            'LOCAL nTemp AS LONG
                            'LOCAL n AS LONG
                            'FOR n = 1 TO 1000
                            '  nTemp = RND() * 1000
                            'NEXT n
                            
                            'tPrint STR$(THREADID) & ":   sin_family =" & STR$(af)
                            'tPrint STR$(THREADID) & ":   farPort    =" & STR$(farPort)
                            'tPrint STR$(THREADID) & ":   farAddr    = " & zIp2String(farAddr)
                            'tPrint STR$(THREADID) & ":   RxMsg      = <" & sMsg & ">"
                            
                              '-- Send a reply message. If we're responding to the sending IP addr/port then we don't
                              '   need to reset the values in the sock4 struct, but we do it here just for practice.
                              '   Note that the port myst be in network byte order so convert it with htons.
                              sock4.sin_family = af
                              sock4.sin_port = htons(farPort)
                              sock4.sin_addr.s_un.s_addr = farAddr
                            
                              '-- Construct the response message. For this demo we'll simply prepend some data to the
                              '   received message.
                              sMsg = "You sent: " & sMsg
                              txBytes = LEN(sMsg)
                              POKE$ wsBuf.buf, sMsg
                            
                              '-- Jump to a GOSUB to send the message. If no error then we'll continue.
                              GOSUB PacketSend
                            
                            RETURN ' End or ProcPacket
                            
                            
                            '==================================================================================================
                            '                                        Send packet
                            '==================================================================================================
                            PacketSend:
                              '-- This is a GOSUB routine so it will return if a fatal error is not encountered.
                              RESET nErr
                              wsBuf.dLen = txBytes
                              'tPrint STR$(THREADID) & ": Calling wsSendTo"
                              IF wsSendTo ( _
                                  hSock, _      'IN: Socket handle
                                  wsBuf, _      'IN: Buffer struct (only one) with data to send and size of data
                                  1, _          'IN: Only one buffer
                                  txBytes, _    'OUT: Data in buffer that was sent
                                  0, _          'IN: No flags
                                  pSock, _      'IN: Ptr to correct socket addr struct for this AF
                                  sockSize, _   'IN: Sized of above socket address
                                  wsOverlap, _  'IN: Contains the event handle used for IO comp signaling
                                  %NULL _       'IN: No completion routine - we handle all of it here
                                  ) THEN        'RTN: 0=success else get error with WSAGetLastError
                            
                                '-- Some error occured. We may be waiting for IO operation to complete (%WSA_IO_PENDING)
                                '   or we may receive a Winsock error - probably WSAENOTSOCK (closed or bad socket) or
                                '   WSANOTINITIALIZED (Winsock not running).
                                nErr = wsaGetLastError
                                IF nErr <> %WSA_IO_PENDING THEN
                                  tPrint STR$(THREADID) & ": wsaSendTo error =" & STR$(nErr)
                                  GOSUB TestError
                                  RETURN
                                END IF
                              END IF
                            
                              'tPrint STR$(THREADID) & ": Calling WaitForSingleOpject (send)"
                              '-- If we get here then we're waiting for an overlap IO completion. The only error that we
                              '   should ever see here is a timeout error. And, that's not likely since this is all so
                              '   fast. If we can't send the packet, then we prob can't send any of the packet so
                              '   only check for fatal errors and don't bother testing for how many packets were already
                              '   sent (if no error, assume all).
                              ret = WaitForSingleObject (wsOverlap.hEvent, 100) '%MAX_UDPSENDTIME)
                              IF ret = %WAIT_FAILED THEN
                                nErr = WSAGetLastError
                                IF nErr = %WAIT_TIMEOUT THEN
                            tPrint STR$(THREADID) & ": Timed out waiting for overlapped send to complete"
                                  RETURN
                                ELSE
                            tPrint STR$(THREADID) & ": WaitForSingleObject (send) fatal error =" & STR$(nErr)
                                  GOTO StopThread
                                END IF
                              END IF
                              'tPrint STR$(THREADID) & ": Message sent"
                            RETURN  ' End of PacketSend
                            
                            
                            '==================================================================================================
                            '                                   Test and process errors
                            '==================================================================================================
                            TestError:
                              '-- Test errors. Most errors are fatal (e.g. socket closed, Winsock stopped). If the far port,
                              '   host or network is not available then an ICMP error may be returned. This is not fatal and
                              '   MAY be an indication that the far BSM module is not running. However, receiving one of
                              '   these ICMP errors can also be a DoS attack vector so we don't close an SML session based
                              '   on these errors but we may want to increment a counter so that we can look for floods.
                              IF (nErr = %WSAECONNRESET) OR _
                                 (nErr = %WSAENETUNREACH) OR _
                                 (nErr = %WSAEHOSTUNREACH) _
                                 THEN
                            tPrint STR$(THREADID) & ": Received ICMP dest unreachable message (non-fatal)"
                                nErr = %WS_DESTUNREACHABLE
                              ELSE
                                GOTO StopThread
                              END IF
                            RETURN  ' End of TestError
                            
                            
                            '==================================================================================================
                            '                                        Stop thread
                            '==================================================================================================
                            StopThread:
                            tPrint STR$(THREADID) & ": Stoppping thread with error" & STR$(nErr)
                              IF wsOverlap.hEvent THEN WSACloseEvent wsOverlap.hEvent
                              IF hMem THEN GLOBALMEM FREE hMem TO hMem
                            
                              '### Should probably log an event and use some method to signal the main app that we're stopping
                            
                            END FUNCTION   ' wsProtoSocketManager
                            
                            
                            '+++++++++++++++++ KEEP THIS IN PLACE UNTIL MIGRATED AWAY FROM zInternet.inc ++++++++++++++++++++++
                            '//////////////////////////////////////////////////////////////////////////////
                            ' zIP2String() - Converts 32-bit IPv4 address to a dotted notation string.
                            '
                            ' This function converts a 4-byte long representation of an IPv4 address to a
                            ' dotted notation string (spaces trimmed).  It works correctly with big endian
                            ' variable derived from IP network calls.  Only supports IPv4.
                            '//////////////////////////////////////////////////////////////////////////////
                            FUNCTION zIP2String ALIAS "zip2string" (BYVAL lAddr AS DWORD) EXPORT AS STRING
                              LOCAL pAddr AS BYTE PTR
                            
                              pAddr=VARPTR(lAddr)
                              FUNCTION = USING$("#_.#_.#_.#", @pAddr, @pAddr[1], @pAddr[2], @pAddr[3])
                            END FUNCTION
                            
                            
                            '//////////////////////////////////////////////////////////////////////////////
                            ' zString2IP() - Converts a dotted notation IPv4 address to a 32-bit value.
                            '
                            '   This function converts an incoming IP string address in the format
                            '   n.n.n.n to a long integer in network byte order (most significant byte
                            '   first or "big endian").  If the string is invalid (wrong number of numeric
                            '   inputs (must be four), invalid numbers (0-255)), the return value will be
                            '   0 (0.0.0.0 assumed to be invalid IP address).  Only supports IPv4.
                            '//////////////////////////////////////////////////////////////////////////////
                            FUNCTION zString2IP ALIAS "zstring2ip" (BYREF sIPAddr AS STRING) EXPORT AS LONG
                              REGISTER n      AS LONG
                              LOCAL sWork     AS STRING         '
                              LOCAL sTemp     AS STRING
                            
                              sIPAddr = REMOVE$(sIPAddr, ANY CHR$(0,32))    ' Make sure there's no spaces or nulls
                              sWork = SPACE$(4)                             ' Build a buffer for calculations
                              IF TALLY(sIPAddr,".") <> 3 THEN               ' Make sure we have exactly 3 periods
                                EXIT FUNCTION
                              ELSE
                                FOR n = 1 TO 4                              ' Process four values for IPv4
                                  sTemp = PARSE$(sIPAddr, ".", n)           ' Pointer value adjusted by ParseString
                            
                                  '---- Make sure data element is in range (num 0 to 255)
                                  IF LEN(sTemp) > 3 OR sTemp="" OR VERIFY(1, sTemp, "0123456789") OR VAL(sTemp) > 255 THEN
                                    EXIT FUNCTION                           ' Some problem with input string so get out
                                  ELSE
                                    ASC(sWork,n) = VAL(sTemp)               ' Looks OK.  Update output string.
                                  END IF
                                NEXT n
                              END IF
                              FUNCTION = CVL(sWork)                         ' Move data to return value.
                            
                            END FUNCTION  ' zString2IP
                            
                            '-- Struct to track tread receive msg count.
                            TYPE THREAD_COUNT
                              tid AS LONG
                              cnt AS DOUBLE
                            END TYPE
                            
                            GLOBAL gThreadCnt AS LONG
                            GLOBAL tc()       AS THREAD_COUNT
                            
                            '-- Keep track of received packet count for each thread. Add thread to array if
                            '   firt time.
                            SUB ThreadMon(BYVAL tid AS LONG)
                              REGISTER n AS LONG
                              IF UBOUND (tc) THEN
                                FOR n = 1 TO UBOUND(tc)
                                  IF tid = tc(n).tid THEN
                                    INCR tc(n).cnt
                                    EXIT SUB
                                  END IF
                                NEXT n
                                REDIM PRESERVE tc(1 TO n)
                                tc(n).tid = tid
                                INCR tc(n).cnt
                              ELSE
                                REDIM tc(1 TO gThreadCnt)
                                tc(1).tid = tid
                                tc(1).cnt = 1
                              END IF
                              tPrint " TMON: Added thread" & STR$(tid)
                            END SUB
                            
                            
                            '//////////////////////////////////////////////////////////////////////////////////////////////////
                            ' PbMain
                            '//////////////////////////////////////////////////////////////////////////////////////////////////
                            FUNCTION PBMAIN2 () AS LONG
                              LOCAL hSock   AS SOCKHNDL
                              LOCAL locPort AS WORD
                              LOCAL locAddr AS LONG
                              LOCAL ret     AS LONG
                              LOCAL txSize  AS LONG
                              LOCAL rxSize  AS LONG
                              LOCAL nTemp   AS LONG
                              REGISTER n    AS LONG
                              LOCAL msgCnt  AS DWORD
                            
                              '-- Set requested socket config paramters. A value of zero in any of these will cause
                              '   Winsock to use default params (e.g. socket buffer size of 65536).
                              HOST ADDR TO locAddr  ' Should be same as using 0 but you can see the address now
                              locPort = 9998        ' You can set port here so you know where to send test msgs
                              txSize = 0            ' Send Winsock buffer size
                              rxSize = 50000        ' Same for recv
                            
                              '-- Set the number of threads to launch for the socket.
                              gThreadCnt = 1
                            
                              '-- Have to start Winsock before we make any calls. Try skipping this and see what happens.
                            
                              wsStartWinsock
                            
                            
                              '-- Make this call to start the socket. Only UPD is supported at this time. Just
                              ret = wsStartSocket ( _
                                  hSock, _                  ' If no fail then this will be the socket handle
                                  %AF_INET, _               ' IPv4
                                  %IPPROTO_UDP, _           ' This will be UDP (only thing supported right now)
                                  locPort, _                ' Requested loc port
                                  BYVAL VARPTR(locAddr), _  ' Use default addr
                                  txSize, _                 ' IN is req size OUT is actual size of tx buffer
                                  rxSize, _                 ' Same for rx buffer
                                  %SOCK_OVERLAPPED _        ' Set this flag if using overlapped I/O
                                  )                         ' 0=success else Winsock error
                              IF ret THEN
                                tPrint " MAIN: wsStartSocket error =" & STR$(ret)
                                wsStopWinsock
                              END IF
                            
                              '-- Create threads to service messages received on the socket. Pass the
                              '   socket handle that you got above. If you started more than one socket
                              '   then call this multiple times with the associated socket handle. All
                              '   threads will services messages for the same socket in a reasonably
                              '   balanced fashon.
                              tPrint " MAIN: Starting socket management threads."
                              FOR n = 1 TO gThreadCnt
                                THREAD CREATE wsProtoSocketManager(hSock) TO ret
                                THREAD CLOSE ret TO ret
                              NEXT n
                            END FUNCTION
                            Attached Files

                            Comment


                            • #15
                              Wow Mike! Nice!

                              Comment


                              • #16
                                Never got around to torture testing.
                                Need to study UDP to harden server from error 10055.
                                Here is a simple client test followed by the torture.
                                Code:
                                #DIM ALL 'udtjerrywilsonclient.bas  1/13/22
                                $IpAddress                    = "192.168.0.2"
                                %PortNum                      = 9998
                                %Threads                      = 10   'under 64
                                %LoopsPerThread               = 10   'increasing these:
                                %NumberOfSendsPerLoop         = 10   'can cause error on server
                                %BytesPerSend                 = 1400 'maximum 1400-bytes per send
                                %MillisecondsToSleepAfterLoop = 10   '10-milliseconds prevents most error 10055's on server
                                GLOBAL gDone AS LONG                 'hack to exit early
                                THREADED ip,portnum,h,errnum AS LONG, sb AS ISTRINGBUILDERA
                                '____________________________________________________________________________
                                FUNCTION PBMAIN AS LONG
                                 LOCAL ip,portnum AS LONG,sTo,sFrom AS STRING
                                 portnum = %PortNum
                                 UDP OPEN AS #1
                                 HOST ADDR $IpAddress TO ip
                                 sTo = "Hello, world!"
                                 UDP SEND #1, AT ip,portNum,sTo
                                 UDP RECV #1,FROM ip,portnum,sFrom
                                 TCP CLOSE #1
                                 ? sFrom,,"Received"
                                '____________________________________________
                                 LOCAL x AS LONG 'start multiple threads
                                 REDIM hThread(1 TO %threads) AS LONG
                                 FOR x = 1 TO UBOUND(hThread):THREAD CREATE PbMain2(x) TO hThread(x):NEXT
                                 DO
                                  SLEEP 50
                                  IF gDone THEN ? "An error occurred",%MB_SYSTEMMODAL,"Error":EXIT DO
                                 LOOP UNTIL THREADCOUNT = 1
                                 FOR x = 1 TO UBOUND(hThread):THREAD CLOSE hThread(x) TO hThread(x):NEXT
                                 ? "Done"
                                END FUNCTION
                                '____________________________________________________________________________
                                THREAD FUNCTION pbmain2(BYVAL notused AS LONG) AS LONG
                                 IF %BytesPerSend > 1400 THEN BEEP:EXIT FUNCTION
                                 LOCAL x,loopcounter AS LONG
                                 LOCAL sDataToSend AS STRING
                                 sb = CLASS "StringBuilderA" 'threaded results
                                
                                 HOST ADDR "192.168.0.2" TO ip
                                 portnum = %PortNum
                                
                                 FOR loopcounter = 1 TO %LoopsPerThread
                                  h = FREEFILE
                                  UDP OPEN AS #h
                                  IF ERR THEN ? "Open Error",%MB_SYSTEMMODAL:gDone=1:EXIT FUNCTION
                                  sDataToSend = STRING$(%BytesPerSend,"X")
                                  FOR x = 1 TO %NumberOfSendsPerLoop
                                   IF SendIt(sDataToSend) THEN ? ERROR$(errnum),%MB_SYSTEMMODAL OR %MB_ICONERROR,"SendIt":UDP CLOSE #h:EXIT FUNCTION
                                  NEXT x
                                  UDP CLOSE #h                       'so we don't get too many connections error 10055 on server
                                  SLEEP %MillisecondsToSleepAfterLoop'give server some milliseconds
                                 NEXT loopcounter
                                
                                 IF gDone THEN UDP CLOSE #h:EXIT FUNCTION 'prevent some error message boxes
                                
                                 IF sb.len = (x-1) * LEN(sDataToSend) * (loopcounter-1) THEN
                                   '? USING$("Bytes received #,",sb.len),%MB_SYSTEMMODAL,"Correct"
                                 ELSE 'this will display if wrong number of bytes
                                   ? USING$("Only received #,",sb.len),%MB_ICONERROR,"Incorrect"
                                 END IF
                                END FUNCTION
                                '____________________________________________________________________________
                                FUNCTION SendIt(s AS STRING) AS LONG
                                 UDP SEND #h, AT ip,portnum,s
                                 'gDone only used while testing to prevent closing error message boxes
                                 IF ERR THEN errnum = ERR:FUNCTION = ERR:gDone=1:EXIT FUNCTION
                                 UDP RECV #h,FROM ip,portnum, s
                                 IF ERR THEN errnum = ERR:FUNCTION = ERR:gDone=1:EXIT FUNCTION
                                 sb.add s
                                END FUNCTION
                                '____________________________________________________________________________
                                'https://forum.powerbasic.com/forum/user-to-user-discussions/programming/783120-overlapped-socket-anyone-have-a-working-demo

                                Comment


                                • #17
                                  Originally posted by Mike Doty View Post
                                  Need to study UDP to harden server from error 10055
                                  Since UDP doesn't really establish a "session" like TCP, you have to rely on some other means to determine if the target UPD port is still alive and listening. You could monitor Dest Port Unreachable message but the far host may not send those messages or they could be blocked the OS or the network. And there are valid concerns about the sender even responding to those messages as they represent a possible DoS vector (nothing stops Black Hat from sending them to your port).

                                  I use short keep-alive messages that are sent if no valid message are received from the far port in a specific period (say seconds). This is also useful for keeping firewall pinholes active during periods of no user traffic.

                                  Comment


                                  • #18
                                    Jerry, thanks very much. Learning.
                                    My goal was to use wireguard to create a secure tunnel for the SQLitening TCP server port.
                                    I have wireguard working, but it wants only UDP.
                                    https://www.wireguard.com/known-limitations/

                                    I was using NordVPN, but now they want me to pay for each user with https://nordlayer.com/
                                    NordVPN modified wireguard and created NordLynx.
                                    I know this VPN topic is outside the topic of this thread, but is related since UDP is required with wireguard.
                                    I think I'll add encryption to the TCP send and receives. The secure tunnel would have been nice, but is another subject.

                                    Comment


                                    • #19
                                      You know, you can use Windows synchronization objects to help you share resources.

                                      Here's a recent demo: Win32: Memory Mapped Files/Control User Count April 26, 2001

                                      Ok, so maybe it's not quite so recent.. but the principle is still valid. You use a synchronization object (in the demo, a mutex) to provide temporary exclusive use of a resource (in the demo, a file).

                                      Maybe that kind of approach will work in your application. (Maybe not but at least you may consider it!)
                                      Michael Mattias
                                      Tal Systems (retired)
                                      Port Washington WI USA
                                      [email protected]
                                      http://www.talsystems.com

                                      Comment


                                      • #20
                                        Thanks Michael, I will go read up on that.

                                        Comment

                                        Working...
                                        X