Socket remains active on Raspbian OS after stopping / starting the controller

broos72482
2022-01-09
2022-05-06
  • 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.

     
  • yangzhihai - 2022-05-04

    When I use CODESYS V3.5 SP18.0 for socket programming, the SysSockBind() function returns "16#0207" error.
    The help file says "ERR_SOCK_ADDRINUSE, The provided address is already in use."
    I also want to know how to solve this problem, can you explain it, I need your help. Can you give me an example of how to solve it?
    Thank you.

     
  • broos72482 - 2022-05-06

    Certainly,
    You need to execute the following steps:
    1. First place the socket variable at a %MD and also xopen and xclose should be placed at a%M.

    2. Secondly define some eventroutines when the socket should be closed to prevent it from being reoped / reused. You can do this in Taskconfiguration->System events.

    3. Now yo need to define the event routines
    Start socket server

    Stop the socket server - called before the application stops

    Stop the socket server - called before an online change

    Stop the socket server - called before a download

    Now sockets will be closed when the soft PLC is stopped or changed

     

    Last edit: broos72482 2022-05-06

Log in to post a comment.