emNet TCP EchoClient (Sample)

From SEGGER Knowledge Base
Jump to navigation Jump to search
IP_TCP_Echo_Client_Sample.c
Requires modifications Yes
Download IP_TCP_Echo_Client_Sample.c

This Sample connects to the IP_TCP_Echo_Server_Sample.c running on another Target and sends a payload to be echoed by the server. The client then prints the round-trip-time (RTT).

To run this sample, SERVER_IP_ADDR (and SERVER_PORT) must be modified to match the Echo Server.

Code

/*********************************************************************
*                   (c) SEGGER Microcontroller GmbH                  *
*                        The Embedded Experts                        *
*                           www.segger.com                           *
**********************************************************************

-------------------------- END-OF-HEADER -----------------------------

Purpose : Sample program for embOS & emNet
          Connects to the IP_TCP_Echo_Server.c running on SERVER_IP_ADDR
          and port SERVER_PORT and sends a TCP payload of PAYLOAD_SIZE .
          The server then echoes the data back and the round-trip-time (RTT)
          from sending to receiving data is displayed.

          The IP address of the server host (SERVER_IP_ADDR) has to be
          configured below.

          The following is a sample of the output to the terminal window:

          .
          .
          .
          3:017 Client - Waiting 2s for the link UP to become stable...
          5:017 Client - Connecting...
          5:017 Client - Successfully connected after 233us!
          5:118 Client - TCP socket API RTT: 180us!
          5:219 Client - TCP socket API RTT: 184us!
          5:320 Client - TCP socket API RTT: 184us!
          5:421 Client - TCP socket API RTT: 184us!
          5:522 Client - TCP socket API RTT: 184us!
          5:623 Client - TCP socket API RTT: 184us!
          5:724 Client - TCP socket API RTT: 184us!
          .
          .
          .
Notes   : For compatibility with interfaces that need to connect in
          any way this sample calls connect and disconnect routines
          that might not be needed in all cases.

          This sample can be used for Ethernet and dial-up interfaces
          and is configured to use the last registered interface as
          its main interface.
*/

#include "RTOS.h"
#include "BSP.h"
#include "IP.h"

/*********************************************************************
*
*       Configuration
*
**********************************************************************
*/

#define USE_RX_TASK     0  // 0: Packets are read in ISR, 1: Packets are read in a task of its own.

//
// Client sample.
//
#define SERVER_IP_ADDR  IP_BYTES2ADDR(192, 168,  88,  41)  // IP addr., for example 192.168.5.1 .
#define SERVER_PORT     1234                               // Remote port the server listens on.
#define TIMEOUT         5000                               // Timeout to wait for the response [ms].
#define PAYLOAD_SIZE    1                                  // Number of bytes to send for the TCP payload.
#define USE_ZERO_COPY   0                                  // Whether to use TCP receive zero-copy API or not.

//
// Task priorities.
//
enum {
   TASK_PRIO_IP_CLIENT = 150
  ,TASK_PRIO_IP_TASK          // Priority should be higher than all IP application tasks.
#if USE_RX_TASK
  ,TASK_PRIO_IP_RX_TASK       // Must be the highest priority of all IP related tasks.
#endif
};

//
// Task stack sizes that might not fit for some interfaces (multiples of sizeof(int)).
//
#ifndef   APP_TASK_STACK_OVERHEAD
  #define APP_TASK_STACK_OVERHEAD     0
#endif

/*********************************************************************
*
*       Static data
*
**********************************************************************
*/

static IP_HOOK_ON_STATE_CHANGE _StateChangeHook;
static int                     _IFaceId;

//
// Task stacks and Task-Control-Blocks.
//
static OS_STACKPTR int _IPStack[TASK_STACK_SIZE_IP_TASK/sizeof(int)];               // Stack of the IP_Task.
static OS_TASK         _IPTCB;                                                      // Task-Control-Block of the IP_Task.

#if USE_RX_TASK
static OS_STACKPTR int _IPRxStack[TASK_STACK_SIZE_IP_RX_TASK/sizeof(int)];          // Stack of the IP_RxTask.
static OS_TASK         _IPRxTCB;                                                    // Task-Control-Block of the IP_RxTask.
#endif

static OS_STACKPTR int _ClientStack[(1024 + APP_TASK_STACK_OVERHEAD)/sizeof(int)];  // Stack of the client task.
static OS_TASK         _ClientTCB;                                                  // Task-Control-Block of the client task.

//
// Sample specific.
//
#if (USE_ZERO_COPY != 0)
static OS_EVENT  _RxEvent;
#endif
static OS_TIMING _t;
static int       _Status;
static U8        _abData[PAYLOAD_SIZE];

/*********************************************************************
*
*       Prototypes
*
**********************************************************************
*/
#ifdef __cplusplus
extern "C" {     /* Make sure we have C-declarations in C++ programs */
#endif
void MainTask(void);
#ifdef __cplusplus
}
#endif

/*********************************************************************
*
*       Local functions
*
**********************************************************************
*/

/*********************************************************************
*
*       _OnStateChange()
*
* Function description
*   Callback that will be notified once the state of an interface
*   changes.
*
* Parameters
*   IFaceId   : Zero-based interface index.
*   AdminState: Is this interface enabled ?
*   HWState   : Is this interface physically ready ?
*/
static void _OnStateChange(unsigned IFaceId, U8 AdminState, U8 HWState) {
  //
  // Check if this is a disconnect from the peer or a link down.
  // In this case call IP_Disconnect() to get into a known state.
  //
  if (((AdminState == IP_ADMIN_STATE_DOWN) && (HWState == 1)) ||  // Typical for dial-up connection e.g. PPP when closed from peer. Link up but app. closed.
      ((AdminState == IP_ADMIN_STATE_UP)   && (HWState == 0))) {  // Typical for any Ethernet connection e.g. PPPoE. App. opened but link down.
    IP_Disconnect(IFaceId);                                       // Disconnect the interface to a clean state.
  }
}

#if (USE_ZERO_COPY != 0)
/*********************************************************************
*
*       _cbOnRx()
*
*  Function description
*    TCP zero-copy receive callback. Signals the main application
*    once a packet has been received or the status has been updated.
*
*  Parameters
*    hSock  : Socket handle.
*    pPacket: Packet with data received.
*    Code   : * == 0: O.K.
*             * <  0: Error or IP_ERR_SHUTDOWN if socket closed by peer.
*
*  Return value
*    IP_OK               : Application has taken all data out of packet.
*                          The packet is freed by the stack.
*    IP_OK_KEEP_PACKET   : Application now own the packet.
*    IP_OK_KEEP_IN_SOCKET: The packet is forwarded to the socket to
*                          be received using recv() .
*/
static int _cbOnRx(long hSock, IP_PACKET* pPacket, int Code) {
  IP_USE_PARA(hSock);

  OS_TIME_StopMeasurement(&_t);  // Take the time as soon as we are aware of a response.
  if (Code < 0) {                // Error or close ?
    _Status = SOCKET_ERROR;      // The application task only checks _Status .
  } else {
    //
    // Discard the response as we only care about a repsonse at all.
    //
    _Status = (int)pPacket->NumBytes;
  }
  //
  // Signal task that something has happened.
  //
  OS_EVENT_Set(&_RxEvent);
  return IP_OK;
}
#endif

/*********************************************************************
*
*       _ClientTask()
*
* Function description
*   Initializes a socket and tries to connect to the server. Then sends
*   a TCP payload that is echoed by the server.
*/
static void _ClientTask(void) {
  struct sockaddr_in Addr;
  U32 tDiff;
  U32 Timeout;
  int hSock;
  int Error;
  char IsTimedOut;

#if (USE_ZERO_COPY != 0)
  OS_EVENT_Create(&_RxEvent);
#endif
  Timeout    = TIMEOUT;
  IsTimedOut = 0;

  //
  // Wait until link is up and interface is configured.
  //
  while (IP_IFaceIsReadyEx(_IFaceId) == 0) {
    OS_Delay(50);
  }
  //
  // Link UP reported from the PHY does not always mean packets are sent out.
  // Grant a grace period to make sure our connect() succeeds on first attempt without retransmit.
  //
  IP_Logf_Application("Waiting 2s for the link UP to become stable...");
  OS_Delay(2000);
  //
  // Retry loop in case of remote-disconnect/timeout.
  //
  for (;;) {
    //
    // Open socket.
    //
    hSock = socket(AF_INET, SOCK_STREAM, 0);
    if (hSock <= 0) {
      IP_Warnf_Application("Error opening socket.");
      while (1) {
        BSP_ToggleLED(0);
        OS_Delay(20);
      }
    } else {
#if (USE_ZERO_COPY != 0)
      setsockopt(hSock, SOL_SOCKET, SO_CALLBACK, (void*)_cbOnRx, 0);  // Register Rx callback to process data.
#endif
      //
      // Connect to server.
      //
      Addr.sin_family      = AF_INET;
      Addr.sin_port        = htons(SERVER_PORT);
      Addr.sin_addr.s_addr = htonl(SERVER_IP_ADDR);
      IP_Logf_Application("Connecting...");
      OS_TIME_StartMeasurement(&_t);
      _Status = connect(hSock, (struct sockaddr*)&Addr, sizeof(Addr));
      OS_TIME_StopMeasurement(&_t);
      tDiff = OS_TIME_GetResultus(&_t);
      if (_Status == 0) {
        IP_Logf_Application("Succesfully connected after %luus!", tDiff);
        setsockopt(hSock, SOL_SOCKET, SO_RCVTIMEO, &Timeout, sizeof(Timeout));  // Set timeout to wait for the echo response.
        //
        // Inner send/receive loop.
        //
        for (;;) {
          memset(&_abData[0], 0xAA, sizeof(_abData));  // Fill with a pattern if checking the signal on a scope.
          //
          // "Small" delay between sending echo requests to make it easier to inspect
          // packets low-level if necessary/desired. Use the delay in front to give the
          // server a short window to register a potential zero-copy callback after
          // accepting the client.
          //
          IP_OS_Delay(100);
          //
          // Send data.
          //
          OS_TIME_StartMeasurement(&_t);
          _Status = send(hSock, (const char*)&_abData[0], sizeof(_abData), 0);
          if (_Status != sizeof(_abData)) {  // Error/disconnect ?
            getsockopt(hSock, SOL_SOCKET, SO_ERROR, &Error, sizeof(Error));
            IP_Logf_Application("Send error!");
            IP_Logf_Application("Error: %s", IP_Err2Str(Error));
            break;
          }
#if (USE_ZERO_COPY == 0)
          //
          // Receive data.
          //
          _Status = recv(hSock, (char*)&_abData[0], sizeof(_abData), 0);
          OS_TIME_StopMeasurement(&_t);
#else
          //
          // Receive data in callback.
          // Wait for signal from callback to evaluate the result.
          //
          IsTimedOut = OS_EVENT_WaitTimed(&_RxEvent, TIMEOUT);      // Wait for the callback to signal a status update or timeout to occur.
#endif
          if ((IsTimedOut != 0) || (_Status != sizeof(_abData))) {  // Error or disconnect ?
            IP_Logf_Application("Receive error/timeout!");
            if (_Status == SOCKET_ERROR) {
              getsockopt(hSock, SOL_SOCKET, SO_ERROR, &Error, sizeof(Error));
              IP_Logf_Application("Error: %s", IP_Err2Str(Error));
            }
            break;
          }
          tDiff = OS_TIME_GetResultus(&_t);
#if (USE_ZERO_COPY == 0)
          IP_Logf_Application("TCP socket API RTT: %luus!", tDiff);
#else
          IP_Logf_Application("TCP zero-copy API RTT: %luus!", tDiff);
#endif
        }
      } else {
        getsockopt(hSock, SOL_SOCKET, SO_ERROR, &Error, sizeof(Error));
        IP_Logf_Application("connect() failed after %luus!", tDiff);
        IP_Logf_Application("Error: %s", IP_Err2Str(Error));
      }
    }
    closesocket(hSock);
    IP_Logf_Application("Retry in %dms.", 2000);
    OS_Delay(2000);
  }
}

/*********************************************************************
*
*       Global functions
*
**********************************************************************
*/

/*********************************************************************
*
*       MainTask()
*
* Function description
*   Main task executed by the RTOS to create further resources and
*   running the main application.
*/
void MainTask(void) {
  IP_Init();
  IP_AddLogFilter(IP_MTYPE_APPLICATION);
  _IFaceId = IP_INFO_GetNumInterfaces() - 1;                                                 // Get the last registered interface ID as this is most likely the interface we want to use in this sample.
  OS_SetPriority(OS_GetTaskID(), TASK_PRIO_IP_TASK);                                         // For now, this task has highest prio except IP management tasks.
  OS_CREATETASK(&_IPTCB    , "IP_Task"  , IP_Task    , TASK_PRIO_IP_TASK   , _IPStack);      // Start the IP_Task.
#if USE_RX_TASK
  OS_CREATETASK(&_IPRxTCB  , "IP_RxTask", IP_RxTask  , TASK_PRIO_IP_RX_TASK, _IPRxStack);    // Start the IP_RxTask, optional.
#endif
  OS_CREATETASK(&_ClientTCB, "Client"   , _ClientTask, TASK_PRIO_IP_CLIENT , _ClientStack);  // Start the client task.
  IP_AddStateChangeHook(&_StateChangeHook, _OnStateChange);                                  // Register hook to be notified on disconnects.
  IP_Connect(_IFaceId);                                                                      // Connect the interface if necessary.
  OS_SetPriority(OS_GetTaskID(), 255);                                                       // Now this task has highest prio for real-time application. This is only allowed when this task does not use blocking IP API after this point.
  while (IP_IFaceIsReadyEx(_IFaceId) == 0) {
    OS_Delay(50);
  }
  while (1) {
    BSP_ToggleLED(1);
    OS_Delay(200);
  }
}

/*************************** End of file ****************************/