emNet TCP EchoServer (Sample)

From SEGGER Knowledge Base
Jump to navigation Jump to search
IP_TCP_Echo_Server_Sample.c
Requires modifications No
Download IP_TCP_Echo_Server_Sample.c

This Sample starts a TCP echo server and waits for a IP_TCP_Echo_Client_Sample.c to connect and send a payload to echo back.

Code

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

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

Purpose : Sample program for embOS & emNet
          Starts a TCP echo server on port SERVER_PORT and waits for
          the IP_TCP_Echo_Client.c to connect to it and send a payload.

          The server tries to read one chunk as big as possible from the
          stream and echoes the data back. Using this method the payload
          sent can be as small as a single byte.

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

          .
          .
          .
          0:017 Server - Waiting for a client to connect on port 1234 .
          5:037 Server - New client connected.
          .
          .
          .
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.

//
// Server sample.
//
#define SERVER_PORT     1234                               // Remote port the server listens on.
#define TIMEOUT         5000                               // Timeout after which the server kicks an idle client [ms].
#define USE_ZERO_COPY   0                                  // Whether to use TCP receive zero-copy API or not.

//
// Task priorities.
//
enum {
   TASK_PRIO_IP_SERVER = 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 _ServerStack[(1024 + APP_TASK_STACK_OVERHEAD)/sizeof(int)];  // Stack of the server task.
static OS_TASK         _ServerTCB;                                                  // Task-Control-Block of the server task.

//
// Sample specific.
//
#if (USE_ZERO_COPY == 0)
static U32      _abData[2048 / sizeof(U32)];  // Enough to fit a full Ethernet packet of 1514 (+ 4 bytes CRC that we do not get).
                                              // U32 to make sure we have a word alignment.
#else
static OS_EVENT _RxEvent;
#endif
static int      _Status;
static int      _NumBytesToSend;

/*********************************************************************
*
*       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) {
  int NumBytes;

  if (Code < 0) {  // Error or close ?
    _Status         = SOCKET_ERROR;
    _NumBytesToSend = SOCKET_ERROR + 1;  // The application task compares _Status and _NumBytesToSend .
  } else {
    //
    // Send the response.
    //
    NumBytes        = pPacket->NumBytes;
    _NumBytesToSend = NumBytes;
    _Status         = send(hSock, (const char*)pPacket->pData, NumBytes, 0);
  }
  //
  // Signal task that something has happened.
  //
  OS_EVENT_Set(&_RxEvent);
  return IP_OK;
}
#endif

/*********************************************************************
*
*       _ServerTask()
*
* Function description
*   Initializes a socket and starts the server. Then waits to receive
*   a TCP payload to echo it back to the client.
*/
static void _ServerTask(void) {
  struct sockaddr_in InAddr;
  U32  Timeout;
  int  hSockListen;
  int  hSock;
  int  Error;
#if (USE_ZERO_COPY != 0)
  char IsTimedOut;

  OS_EVENT_Create(&_RxEvent);
#endif
  Timeout = TIMEOUT;

  //
  // Open parent socket.
  //
  hSockListen = socket(AF_INET, SOCK_STREAM, 0);
  if (hSockListen <= 0) {
    getsockopt(hSockListen, SOL_SOCKET, SO_ERROR, &Error, sizeof(Error));
    IP_Warnf_Application("Error opening parent socket: %s", IP_Err2Str(Error));
    while (1) {
      BSP_ToggleLED(0);
      OS_Delay(20);
    }
  }
  InAddr.sin_family      = AF_INET;
  InAddr.sin_port        = htons(SERVER_PORT);
  InAddr.sin_addr.s_addr = INADDR_ANY;
  (void)bind(hSockListen, (struct sockaddr*)&InAddr, sizeof(InAddr));
  (void)listen(hSockListen, 1);  // Allow one client connection.
#if (USE_ZERO_COPY != 0)
  //
  // Set callback for the parent socket.
  // In newer emNet versions the callback is inherited to child sockets, making sure that
  // even very early data received from peer ends up in the callback instead of the socket buffer.
  //
  setsockopt(hSockListen, SOL_SOCKET, SO_CALLBACK, (void*)_cbOnRx, 0);          // Register Rx callback to process data.
#endif
  //
  // Retry loop in case of remote-disconnect/timeout.
  //
  for (;;) {
    IP_Logf_Application("Waiting for a client to connect on port %d .", SERVER_PORT);
    //
    // Accept new client
    //
    hSock = accept(hSockListen, NULL, NULL);
    if (hSock == SOCKET_ERROR) {
      continue;  // Error but do not care and wait for next client.
    }
    IP_Logf_Application("New client connected.");
#if (USE_ZERO_COPY != 0)
    setsockopt(hSock, SOL_SOCKET, SO_CALLBACK, (void*)_cbOnRx, 0);          // Register Rx callback to process data.
#endif
    setsockopt(hSock, SOL_SOCKET, SO_RCVTIMEO, &Timeout, sizeof(Timeout));  // Set timeout to wait for an echo request.
    //
    // Inner receive/send loop.
    //
    for (;;) {
#if (USE_ZERO_COPY == 0)
      //
      // Receive data.
      //
      _Status = recv(hSock, (char*)&_abData[0], sizeof(_abData), 0);
      if (_Status <= 0) {  // Error, disconnect or timeout ?
        IP_Logf_Application("Receive error!");
        if (_Status == SOCKET_ERROR) {
          getsockopt(hSock, SOL_SOCKET, SO_ERROR, &Error, sizeof(Error));
          IP_Logf_Application("Error: %s", IP_Err2Str(Error));
        }
        break;
      }
      //
      // Send data.
      //
      _NumBytesToSend = _Status;
      _Status         = send(hSock, (const char*)&_abData[0], _NumBytesToSend, 0);
      if (_Status != _NumBytesToSend) {  // Error or disconnect ?
        IP_Logf_Application("Send error!");
        if (_Status == SOCKET_ERROR) {
          getsockopt(hSock, SOL_SOCKET, SO_ERROR, &Error, sizeof(Error));
          IP_Logf_Application("Error: %s", IP_Err2Str(Error));
        }
        break;
      }
      if (_Status != _NumBytesToSend) {  // Error or disconnect ?
        IP_Logf_Application("Send error!");
        if (_Status == SOCKET_ERROR) {
          getsockopt(hSock, SOL_SOCKET, SO_ERROR, &Error, sizeof(Error));
          IP_Logf_Application("Error: %s", IP_Err2Str(Error));
        }
        break;
      }
#else
      //
      // Receive data in callback and send back from 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.
      if ((IsTimedOut != 0) || (_Status != _NumBytesToSend)) {  // Error or disconnect ?
        IP_Logf_Application("Receive/send error!");
        if (_Status == SOCKET_ERROR) {
          getsockopt(hSock, SOL_SOCKET, SO_ERROR, &Error, sizeof(Error));
          IP_Logf_Application("Error: %s", IP_Err2Str(Error));
        }
        break;
      }
#endif
    }
    closesocket(hSock);
    IP_Logf_Application("Client disconnected.");
  }
}

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

/*********************************************************************
*
*       MainTask()
*
* Function description
*   Main task executed by the RTOS to create further resources and
*   running the main application.
*/
void MainTask(void) {
  IP_ConfigDoNotAddLowLevelChecks();
  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(&_ServerTCB, "Server"   , _ServerTask, TASK_PRIO_IP_SERVER , _ServerStack);  // Start the server 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 ****************************/