broos72482 - 2022-01-09

I have downloaded an example project for a TCP socket server running on a Raspberry Pi. Code copied from the example for socket communication Linux SL.
See the code below:

TON1(IN:=xOpen, PT:=TIME#30S);
IF hMySocket = SysSocket.SysSocket_Interfaces.RTS_INVALID_HANDLE  AND TON1.Q THEN
    // create a socket for an IPv4 TCP stream
    hMySocket := SysSockCreate( iAddressFamily  := SOCKET_AF_INET, (* SOCKET_AF_INET for ip4 connection. We do it locally, so it is a loopback *)
                                diType          := SOCKET_STREAM,
                                diProtocol      := SOCKET_IPPROTO_TCP,
                                pResult         := ADR(result));
    xIsBound := FALSE;
    xOpen := FALSE;
END_IF  

// go through the process of binding the port
IF hMySocket <> SysSocket.SysSocket_Interfaces.RTS_INVALID_HANDLE AND NOT xIsBound THEN 

    // set up port to reuse address so we can bind to ANY ip (0.0.0.0)
    result := SysSockSetOption( hSocket         := hMySocket,
                                diLevel         := SOCKET_SOL, // socket level
                                diOption        := SOCKET_SO_REUSEADDR, // allow using an addres (e.g. localhost or ANY) multiple times
                                pdiOptionValue  := ADR(diReuseAddr), // enable
                                diOptionLen     := SIZEOF(diReuseAddr));


    // fill out the struct used to pass along the binding information                       
    socketAddress.sin_family := SOCKET_AF_INET; // same as on creation 
    //result:=SysSockInetAddr('192.168.0.15', ADR(socketAddress.sin_addr.ulAddr)); // we bind to 192.168.0.15
    socketAddress.sin_addr.ulAddr := SOCKET_INADDR_ANY; // we bind to any interface
    socketAddress.sin_port := SysSockHtons(8000); // the port number we chose, converted to ethernet byte order

    // now do the actual binding
    result1 := SysSockBind(     hSocket         := hMySocket,
                                pSockAddr       := ADR(socketAddress),
                                diSockAddrSize  := SIZEOF(socketAddress));

    xIsBound := result1 = Errors.ERR_OK;    

    // set socket to start listening for incoming connections
    result := SysSockListen(    hSocket         := hMySocket,
                                diMaxConnections:= 1);
    xIsBound := xIsBound AND result = Errors.ERR_OK;    

    // set the option for non-blocking so accept() returns immediatly instead of waiting until a connection is made
    diNonblocking := 1;
    result := SysSockIoctl(     hSocket         := hMySocket,
                                diCommand       := SOCKET_FIONBIO, // non-blocking option
                                pdiParameter    := ADR(diNonblocking)); // set it to 1
 xIsBound := xIsBound AND result = Errors.ERR_OK;                                               
END_IF

// accept incoming connection
IF hMySocket <> SysSocket.SysSocket_Interfaces.RTS_INVALID_HANDLE AND xIsBound AND NOT xIsConnected THEN

    // accept the connection if possible. As an output we get information about the socket we are connectiong to
    hAcceptedSocket := SysSockAccept(   hSocket         := hMySocket,
                                        pSockAddr       := ADR(saConnectionPartner),
                                        pdiSockAddrSize := ADR(diSaConnectionPartnerSize),
                                        pResult         := ADR(result));

    xIsConnected := hAcceptedSocket <> SysSocket.SysSocket_Interfaces.RTS_INVALID_HANDLE;

    // set the option for non-blocking so accept() returns immediatly instead of waiting until a connection is made
    diNonblocking := 1;
    result := SysSockIoctl(     hSocket         := hAcceptedSocket,
                                diCommand       := SOCKET_FIONBIO, // non-blocking option
                                pdiParameter    := ADR(diNonblocking)); // set it to 1
END_IF

// use the handle of the accepted connection to read the data that is arriving.
IF hAcceptedSocket <> SysSocket.SysSocket_Interfaces.RTS_INVALID_HANDLE AND xIsConnected THEN
    // build the data structure needed to read a socket
    socketSet.fd_count := 1;
    socketSet.fd_array[0] := hAcceptedSocket;

    // check if there is anyhting to be read
    result := SysSockSelect(    pfdRead := ADR(socketSet), // pointer to set of the sockets we want to select on
                    pfdWrite := 0, // not used
                    pfdExcept := 0, // not used
                    diWidth := SOCKET_FD_SETSIZE, // maximum number of sockets in socketSet
                    ptvTimeout := ADR(tvSelectTimeout),
                    pdiReady := ADR(diSocketsReadyToReceive));

    IF diSocketsReadyToReceive > 0 THEN
        // read the bytes waiting in the socket.
        xiBytesReceived := SysSockRecv(     hSocket         := hAcceptedSocket, // the connected socket to read from
                                            pbyBuffer       := pReadData, // where we read to
                                            diBufferSize    := SizeReadData, // how much we read
                                            diFlags         := SOCKET_MSG_NONE, // no flags
                                            pResult         := ADR(result));
        // Check amount of data is received correctly
        IF xiBytesReceived=SizeReadData AND result=Errors.ERR_OK  THEN
            // Check the checksum of the data
            InitChecksumReceivedData:=CRC32Init();
            CheckSumReceivedData:=CRC32Update(InitChecksumReceivedData, pReadData, SizeReadData - 4);
            // If checksum correct, send a reply
            IF ChecksumReceivedData = pReadData^.CRC32 THEN
                pWriteData^.CRC32:=CRC32Init();
                pWriteData^.CRC32:=CRC32Update(pWriteData^.CRC32, pWriteData, SizeWriteData - 4);
                xiBytesSend:=SysSockSend(hAcceptedSocket, pWriteData, SizeWriteData, 0, ADR(resultSend));
            END_IF  
        END_IF
    END_IF              
    // if we don't have a timeout or we would not block, something failed, so connection must have dropped                      
    IF result <> Errors.ERR_OK AND result <> Errors.ERR_TIMEOUT AND result <> Errors.ERR_SOCK_WOULDBLOCK AND result <> Errors.ERR_SOCK_TIMEDOUT THEN
        xIsConnected := FALSE;
        SysSockClose(hSocket := hAcceptedSocket);
        hAcceptedSocket := SysSocket.SysSocket_Interfaces.RTS_INVALID_HANDLE;
    END_IF
END_IF


// close the socket.
IF hMySocket <> SysSocket.SysSocket_Interfaces.RTS_INVALID_HANDLE AND xClose THEN
    SysSockClose(hSocket := hMySocket);
    SysSockClose(hSocket := hAcceptedSocket);
    xClose := FALSE; 
    xIsBound := FALSE;
    xIsConnected := FALSE;
    hMySocket := SysSocket.SysSocket_Interfaces.RTS_INVALID_HANDLE;
    hAcceptedSocket := SysSocket.SysSocket_Interfaces.RTS_INVALID_HANDLE;
END_IF

The problem with this code arrises when bind is called after a complete download of my project. I've connected xOpen and xClose to the start en stop system events of the controller (see TaskConfiguration.png). So after a download xOpen will become active for 30 sec. and it will start the server. This works when the controller is stopped and started.
The problem is that after a download a created socket will remain active on the OS. So we use the socket option SOCKET_SO_REUSEADDR. However the bind function will fail with error code 519 Address in use. So it doesn't reuse the address. You can also specify SOCKET_SO_REUSEPORT however that results in an error.
I thought i solved the problem by creating a System event stopping the server just before a download commences. With the idea in mind that the socket will also be released on the OS.
However I've tried several system events without any result. The system events i've tried are: PrepareDownload and PrepareExitTasks and PrepareOnlineChange.

Found the problem:
I've stored the socket handles in a %MD100 and %MD104 DWORD. Now I'm able to close the socket in the system event routines before a download.