so API Socket Example (Advanced)

Top  Previous  Next

//-----------------------------------------------------------------------------
// Program:     Socket example program using the so API.
//              Demonstrates both blocking and non-blocking mode.
//                
// Description:
// This program demonstrates how to establish a TCP/IP connection over the
// LAN network using the SO API in both blocking and non-blocking mode.
//
// Prerequisites:
// - A TCP Echo Server (e.g., the supplied Windows tool 'echos.exe') must be running 
//   on the target PC/Server to echo received data back to the RTCU.
// - The 'host' and 'server_port' variables below must be updated to match
//   the IP address and port of the PC running 'echos.exe'.
//
// Key Logic:
// 1. Network Setup: Initializes the network and waits for connection.
// 2. Socket Config: Creates a TCP stream and in either non-blocking or
//    blocking mode using 'soConfigNonblock'.
//    When NON_BLOCKING is defined the non-blocking mode will be used.
// 3. Data Handling:
//    - Checks 'soRecv' for data.
//    - If rc = 1, data is processed (Echoed back to server).
//    - If rc = -4, the program continues (no data waiting).
//    - If rc < 0 (other errors), the connection is reset.
// 4. Lifecycle: Connects to '192.168.1.100:5005', echoes 100 packets, then disconnects.
//
// I/O Mapping:
// - Output 'cnetwork': High when cellular network is connected.
// - Output 'toggle': Toggles state every time a data packet is received.
//-----------------------------------------------------------------------------
INCLUDE rtcu.inc
 
// Define this for non-blocking mode:
//#DEFINE  NON_BLOCKING
 
// Input variables
VAR_INPUT
END_VAR;
 
// Output variables
VAR_OUTPUT
   toggle   : BOOL; | LED that indicates when data is received from echo host
   cnetwork : BOOL; | LED that indicates that a network connection is present.
END_VAR;
 
// Global variables
VAR
   host        : STRING := "192.168.1.100"; 
   server_port : INT := 5005;
   
   // Test string
   teststr1    : STRING := "Hello world. This is a test of the RTCU so API Interface";
END_VAR;
 
//-----------------------------------------------------------------------------
// Main Program
//-----------------------------------------------------------------------------
PROGRAM test;
VAR
   handle      : SYSHANDLE; // Handle to the socket
   rc          : INT;       // Return codes
   buffer      : ARRAY[0..300] OF SINT; // Receive buffer
   
   net_addr    : STRING;    // Socket Address string
   iter        : INT;       // Iteration counter
   
   rx_len      : DINT;      // Length of received data (Output from soRecv)
   tx_len      : DINT;      // Length of sent data (Output from soSend)
   
   // State Flags
   connected      : BOOL := FALSE; // True if TCP connection is established
   socket_created : BOOL := FALSE; // True if soCreate has been called successfully
   await_connection : BOOL := FALSE; //True if non-blocking mode is waiting for connection.
#IFDEF NON_BLOCKING THEN   
  //Used for soStatus in non-blocking mode
  local,remote : STRING;     
#END_IF; 
END_VAR;
 
DebugMsg(message:="Network 'so' API Socket test-program started.");
 
 
//---------------------------------------------------------------------------
// 1. Network Setup: Initializes the network and waits for connection.
//---------------------------------------------------------------------------
// Open the LAN network connection
rc := netOpen(iface:=_NET_IFACE_LAN1);
DebugFmt(message:="netOpen()=\1", v1:=rc);
 
// Wait for network connected
WHILE NOT netConnected(iface:=_NET_IFACE_LAN1) DO
   DebugMsg(message:="Waiting for network connection");
   Sleep(delay:=2500);
END_WHILE;
 
BEGIN
  // Show the network connected status on an LED
  cnetwork := netConnected(iface:=_NET_IFACE_LAN1);
 
  //---------------------------------------------------------------------------
  // 2. Connection Logic
  //---------------------------------------------------------------------------
  //Check for not connected and network available:
  IF NOT connected AND cnetwork THEN
      // 2a. Create the socket
      IF NOT socket_created THEN
        // Create socket
        rc := soCreate(socket:=handle, type:=_SO_TYPE_STREAM, protocol:=_SO_PROT_TCP);
        IF rc=1 THEN
            socket_created := TRUE;
            
            #IFDEF NON_BLOCKING THEN
               // Configure Socket as NON-BLOCKING
               // This ensures soRecv returns immediately if no data is present.
               rc := soConfigNonblock(socket:=handle, enable:=TRUE);
               IF rc < 0 THEN
                   DebugFmt(message:="soConfigNonblock failed rc=\1", v1:=rc);
               ELSE
                   DebugMsg(message:="Socket configured as Non-Blocking.");
               END_IF;
            #END_IF
            
        ELSE
            DebugFmt(message:="soCreate failed rc=\1", v1:=rc);
        END_IF;
      END_IF;
 
      // 2b. Connect
      IF socket_created AND NOT await_connection THEN
         DebugMsg(message:="Connecting to " + host + "...");
         // Create address string (IP + Port)
         rc := soAddrInetSet(address:=net_addr, host:=host, port:=server_port);
 
         IF rc < 0 THEN
          DebugFmt(message:="soAddrInetSet failed rc=\1", v1:=rc);
         ELSE
           // Connect
           rc := soConnect(socket:=handle, address:=net_addr);
           
           IF rc = 1 THEN
              connected := TRUE;
              iter := 0;
              DebugMsg(message:="Socket Connected!");
              
              // "Prime" the conversation - Send initial data
              strToMemory(dst:=ADDR(buffer), str:=teststr1, len:=strLen(str:=teststr1));
              rc := soSend(socket:=handle, data:=ADDR(buffer), size:=strLen(str:=teststr1), sent:=tx_len);
              
           ELSIF rc = -4 THEN
              //"Would-block" in Non-blocking mode
            #IFDEF NON_BLOCKING THEN
              await_connection:=TRUE;
            #END_IF;
           ELSE
              // Connect failed
              DebugFmt(message:="soConnect()=\1", v1:=rc);
              soClose(socket:=handle);
              socket_created := FALSE;
              connected := FALSE;
           END_IF;
         END_IF;
      END_IF;
      
   #IFDEF NON_BLOCKING THEN
      IF await_connection THEN
         rc := soStatus(socket:=handle,local:=local,remote:=remote);
         IF rc = 4 THEN
            //awaiting connection. Throttle a bit.
            Sleep(delay:=250);
         ELSIF rc = 5 THEN
            //Connected.
            DebugMsg(message:="Socket Connected! local="+local+" remote="+remote);
            await_connection:=FALSE;
            connected:=TRUE;
            iter:=0;
            // "Prime" the conversation - Send initial data
            strToMemory(dst:=ADDR(buffer), str:=teststr1, len:=strLen(str:=teststr1));
            rc := soSend(socket:=handle, data:=ADDR(buffer), size:=strLen(str:=teststr1), sent:=tx_len);
         ELSE
            //everything we consider an error.
            soClose(socket:=handle);
            socket_created:=FALSE;
            await_connection:=FALSE;
        END_IF;
      END_IF;
   #END_IF;
  END_IF;
 
  //---------------------------------------------------------------------------
  // 3. Data Handling Logic (Non-Blocking)
  //---------------------------------------------------------------------------
  IF connected THEN
      // Receive data
      // In non-blocking mode soRecv will NOT wait.
      rc := soRecv(socket:=handle, data:=ADDR(buffer), maxsize:=SIZEOF(buffer), size:=rx_len);
      
      IF rc = 1 THEN
         // --- Success (Data received or Connection Closed) ---
         IF rx_len > 0 THEN
             // Data actually arrived
             iter := iter + 1;
             DebugFmt(message:="\1 - data received len=\4", v1:=iter, v4:=rx_len);
             toggle := NOT toggle;
             
             IF iter < 100 THEN
                // Echo data back
                rc := soSend(socket:=handle, data:=ADDR(buffer), size:=rx_len, sent:=tx_len);
                // Make a small pause. This is not necessary, but to avoid flooding the debug output window.
                Sleep(delay:=250);
             ELSE
                DebugMsg(message:="Limit reached. Disconnecting...");
                soClose(socket:=handle);
                socket_created := FALSE;
                connected := FALSE;
             END_IF;
         ELSE
             // rx_len = 0 with rc = 1 implies connection closed gracefully by remote
             DebugMsg(message:="Connection closed by remote (0 bytes).");
             soClose(socket:=handle);
             socket_created := FALSE;
             connected := FALSE;
         END_IF;
         
      ELSIF rc = -4 THEN
         // --- NO DATA AVAILABLE RIGHT NOW ---
         // This is the expected behavior in non-blocking mode when the buffer is empty.
         // We simply do nothing and loop around.
         
         // (DebugMsg is commented out to avoid flooding the log)
         // DebugMsg(message:="No data..."); 
         
         // IMPORTANT: Since we are not blocking, we must sleep a little to yield CPU time.
         // Without this, the loop spins infinitely fast.
         Sleep(delay:=100);
         
      ELSE
         // --- ERROR ---
         // Any other error code < 0 means a real failure
         DebugFmt(message:="Receive Error or Connection Lost rc=\1", v1:=rc);
         soClose(socket:=handle);
         socket_created := FALSE;
         connected := FALSE;
      END_IF;
      
      IF NOT cnetwork THEN
         DebugFmt(message:="Network connection down!");
         soClose(socket:=handle);
         socket_created := FALSE;
         connected := FALSE;
      END_IF;
         
  END_IF;
 
END;
 
END_PROGRAM;