Receive Data

Essentials

This section summarises the content on this page (read on for the full tutorial):

In this tutorial we are considering receive over TCP connections. If you are writing a UDP (connectionless) application you need to look at the recvfrom request but the principles remain similar to those listed below.

On UNIX oriented APIs receive is synonymous with read (although read does not support flags, see below). Read has the potential to allow applications to be written as if handling file IO, more an aim of the TLI and XTI APIs that also support an open request in place of socket.

Receive Parameter Definition

Across language implementations the following parameters will typically be required:

Socket numberThe receive request is on a per socket basis, you must provide the socket number returned by the api on the original socket request, thus identifying the connection for which the receive is being made (some APIs support a global receive function and return the connection identifier but not the sockets API).
Buffer addressThis is the address of an application owned area of storage where the Stack can place the data received over the connection defined by the socket number. With interpreted languages this may not be required with the function return value being a composite of return codes and data or if supplied will be the name of a variable that will, on completion, have assigned to it a value equal to the received data.
Buffer lengthThe length of the storage area i.e. the maximum amount of data that can be received. Interpreted languages may set a maximum.
FlagsAPI specific flags to modify the basic receive function e.g. you can use peek to look at the data without removing it from the stacks queues.

Returns: A numeric code as follows:

PositiveThe length of the data received, either into the specified buffer or variable. Note if the length returned is the maximum allowed by "buffer length" there may be more data waiting to be received.
0 (zero)Following the UNIX file IO analogy, known as EOF (end of file), the connection has been shutdown in an orderly manner by the other side.
NegativeUsually -1 indicating an error occurred, refer to the ERRNO value for an explanation of the error. Some codes indicate that the connection may be lost, in this case due to an error condition rather than shutdown e.g. if the other end simply closes the socket without first making a shutdown request this may be picked up as such an error.

Receiving a Stream

Remember in the socket create request we specified SOCK_STREAM, the consequence is that the receive may complete with only half or less of our expected data, or even worse we could receive multiple requests/responses on the same receive (when not a simple client server connection e.g. some sort of message concentrator where the other side sends multiple data independent of our responses). Neither is there any guaranteed relationship between the contents sent in a single send and the data received in a single receive. Processing network/link layers may choose to split or combine data request packets. Note that this is unlikely to happen while testing within a same host or single LAN environment but may well happen across a broader IP network.

An application level protocol is needed to determine when a complete message is received e.g. HTTP 1.0 will drop the connection on completion, HTTP 1.1 will use a 'Content-Length: nnn' record. Other applications could use fixed length records e.g. always 80 bytes or a just a fixed length header containing the actual message length. ASN.1 even requires that for lengths greater than 127 bytes we prefix a binary length field with the length of that length field.

Protocol support means that not only must receive processing check for errors and normal disconnection but also that the receive request be embedded in an application protocol specific loop that breaks only when an entire message has been received. When supporting multiplexed connections this may even mean moving excess received data into a second message buffer.

A final note. If your protocol does use fixed length headers containing a length field, do ensure you have received an entire header before assuming that the data length is correct.

What if the Other Side Never Returns Data

If we simply issue a socket request followed by connect, send and then receive our application will wait/hang until data is received, this could be forever if the other side neither sends data nor breaks the connection. This is the purpose of "non-blocking" mode, set using the fcntl request. A socket in non-blocking mode that issues a receive when no data is present has the request rejected (ret code -1) with ERRNO EWOULDBLOCK or EAGAIN. The application can now notify the end-user, perform other work and/or wait before trying again or killing the connection.

Select

The sockets Select function allows us to wait until data is received on our socket or until a specified time limit is reached. When select completes we can either re-issue the receive to pick up available data or close the socket (and connection) if we have timed out.

 

Continue with the Tutorial

Server
Connections
Next
Sockets
Contents
Contents
Send
Data
Prev

 

Example Receives

C
Unix
Lua
ooREXX
REXX
IBM TSO
IBM HPNS
HLASM

Receive: C - Generic UNIX

This example illustrates a simple receive function that is passed a connection block (structure) containing the socket file descriptor, work areas and a buffer into which the data will be received. Since we are supporting a simple HTTP 1.0 protocol the receive routine inludes detection of the full message i.e. the server disconnects.

Note how using a buffer header to prefix the data, we need only change the value of pad to set the total length of all received data and then also have the next available byte position for additional to be received data.

You can see the full program here


/***
  Receive all data from server i.e. until connection terminated (HTTP 1.0 protocol).
  Note all data is expected to fit within the available buffer.
***/
int doRecv( struct snys_conn *pConn) {
  struct snys_glob *pGlob;
  struct snys_buf *pBuf;
  ssize_t rc = 1;                                    // =1 forces entry into while loop
  char *p1, *p2;
  char needle[3] = "\x0A";

  pGlob = pConn->pGlob;
  pBuf = pConn->pRcvBuf;

  while ( rc > 0) {
     rc = recv( pConn->fd, pBuf->data + pBuf->pad, (size_t)(sizeof( pBuf->data) - pBuf->pad), 0);
     if ( rc > 0) {
        doMessage( pGlob, 6, rc, pBuf->pad + rc, &pConn->serverName);
        if ( pConn->status & conn_status_dump)
           doDump( pGlob, pBuf->data + pBuf->pad, rc );
        p1 = pBuf->data+pBuf->pad;
        while ( p2 = strstr( p1, needle)) {
           if ( p2[-1] == 0x0D)
              p2[-1] = 0x00;
           else
              p2[0] = 0x00;
           printf( "     %s\n", p1);
           p1 = p2 + 1;
           if (p1 >= pBuf->data+pBuf->pad+rc)
              break;
        }
        pBuf->pad += rc;
        if ( pBuf->pad >= sizeof( pBuf->data)) {  // check for space in the receive buffer
           rc = -1;                               // none: break out of while loop
        }
     }
  }
  pBuf->dlen = pBuf->pad;
  pBuf->pad = 0;
  if ( rc < 0) {
      if ( pBuf->dlen >= sizeof( pBuf->data))     // check for space in the receive buffer
          doMessage( pGlob, 8);                   // None issue message 8
      else                                        // other error issue message 2
          doMessage( pGlob, 2, "receive", errno, strerror(errno));
      return -1;
  } else
      doMessage( pGlob, 7, &pConn->serverName);
  pConn->status &= 0xFF - conn_status_conn;       // set status NOT connected
  return 0;
}

Receive: Lua

A simple Lua receive loop based on use of HTTP 1.0 protocol i.e. server closes connection when all data sent.


 -- cstat set by connect call
   while ( cstat) do
      local s, status = clnt:receive(4096);
      if ( status ~= "closed" ) then
         print( s);
      else
         cstat = nil; 
      end
   end;

Receive: ooREXX

A simple receive function that is passed a socket number and returns the received data or exits. Note the drop of buffer before the receive. If buffer was already defined the receive length would be implicitly set to the length of the current variable buffer.


/*********************************************************************/
/*  Receive data                                                     */
/*********************************************************************/
  do while rcvLen <> 0
      drop buffer
      RcvLen = SockRecv(socNum, buffer, 8192)
      if rcvLen <= 0 then do
          if rcvLen = -1 then do
              if errno <> 'ECONNRESET' then do
                  say "Error receiving data, ERRNO:" errno
                  rcvLen = 0
              end
          end
          say 'Connection on socket' sockNum 'terminated'
      end
      else
          call DATA_TRACE buffer
  end

Receive: IBM TSO REXX

A more complicated receive that processes an HTTP 1.1 response. In this case the HTTP header is character delimited by CRLF-CRLF i.e. a null line. the http header contains a Content-Length: which specifies the size of the html that follows. The html therfore being a length delineated message.

You can see the full program here


/*********************************************************************/
/*  Receive HTTP header (probably more). The header is delimited by  */
/*  a CRLF-CRLF sequence (i.e. a null line.                          */
/*********************************************************************/

   tdata = ''                           /* total received data       */
   tlen = 0                             /* total length              */
   dlen = 1                             /* loop control              */
   clen = 99999999                      /* If Content-length missing */
   do while dlen <> 0                   /* at least once             */
      Resp = SOCKET( 'RECV', socNum)
      parse var resp retc dlen data
      if retc <> 0 then
      do
          say 'Error receiving data'
          say resp
          exit
      end
      if p.trace = 'Y' then
      do
         say 'Received:'
         call DO_TRACE data
      end
      tdata = tdata || data
      tlen = tlen + dlen
      ndx = POS( '0D250D25'x, tdata)
      if ndx <> 0 then
         dlen = 0
   end

   http = SUBSTR( tdata, 1, ndx+2)
   do while http <> ''
      parse var http line'0D25'x http
      if p.verbose = 'Y' then
         say 'http' line
      parse var line kywd': 'val .
      if TRANSLATE(kywd) = 'CONTENT-LENGTH' then
         clen = val-0
   end

/*********************************************************************/
/*  Receive HTML (if not already received)                           */
/*********************************************************************/

   html = SUBSTR( tdata, ndx+4)
   dlen = 1
   do while ((LENGTH( html) < clen) & (dlen <> 0))
      Resp = SOCKET( 'RECV', socNum)
      parse var resp retc dlen data
      if retc <> 0 then
      do
          say 'Error receiving data'
          say resp
          exit
      end
      if p.trace = 'Y' then
      do
         say 'Received:'
         call DO_TRACE data
      end
      html = html || data
   end

Receive: IBM HPNS API

In this example we see a mainline routine

You can see the full program here


********************************************************************** 
* Receive response (into same buffer used by send)                   * 
********************************************************************** 
         USING BUF_BLOCK,R8
MN_DO_RECEIVE DS 0H                    ******************************* 
         MVC   CWA_RBUF,CWA_SBUF       * Use same buffer as send     * 
         XC    CWA_SBUF,CWA_SBUF       * Clear send buffer addr      * 
         MVC   BUF_DOFF,=H'16'         * Reset data offset           * 
         XC    BUF_DLEN,BUF_DLEN       * Reset data length           * 
         JAS   R14,DO_RECEIVE          *                             * 
         LTGR  R15,R15                 *                             * 
         JNZ   MN_SHUTDOWN             *                             * 
         LGF   R15,CWA_RETCD           * Length received             * 
         LTGR  R15,R15                 * Any data ?                  * 
         JZ    MN_DISCONN              * N - disconnected            * 
         AGF   R15,BUF_DLEN            * Previous length             * 
         ST    R15,BUF_DLEN            * Save in buffer              * 
         J     MN_SHUTDOWN             *                             * 
MN_DISCONN DS  0H                      *                             * 

The receive subroutine "DO_RECEIVE" is located in a base register addressable portion of the app (a requirement of EZASMI). The passed connection work area, CWA, addresses a receive buffer where the header points to the current location and length available for received data. This allows a higher level routine to determine if all data has been received and if not, simply return to this receive routine with an updated buffer header containing a new data location and available length.


********************************************************************** 
* Receive data from target system                                    * 
********************************************************************** 
         USING CWA_BLOCK,R9 
DO_RECEIVE DS  0H                      ******************************* 
         STMG  R0,R15,W1_SAV2          * Save callers regs           * 
         MVC   CWA_REQNM,=CL12'RECEIVE'  Request name                * 
         NI    CWA_STATE,255-CWA_STATE_SRTY ensure retry off         * 
         L     R4,CWA_RBUF             * Pick up send buffer         * 
         USING BUF_BLOCK,R4            *                             * 
         LA    R2,BUF_DATA             * R2 addr for received data   * 
         XC    BUF_DLEN,BUF_DLEN       * No data received so far     * 
         LR    R3,R4                   * R3 buffer address           * 
         AH    R3,BUF_BLEN             * R3 end of buffer            * 
         SLR   R3,R2                   * R3 available space          * 
         ST    R3,W1T_COUNT            * R3 available space          * 
         MVC   CWA_PLIST(MODEL_EZASMI_LENGTH),MODEL_EZASMI init plst * 
         EZASMI TYPE=RECV,             *                             * X 
               S=CWA_SOCNO,            *                             * X 
               BUF=(R2),               * address of receive buffer   * X 
               NBYTE=W1T_COUNT,        * length of buffer space      * X 
               TASK=W1T_TASK_WA,       *                             * X 
               ERRNO=CWA_ERRNO,        *                             * X 
               RETCODE=CWA_RETCD,      *                             * X 
               MF=(E,CWA_PLIST)        *                             * 
         LGF   R15,CWA_RETCD           * Pick up return code         * 
         LTGR  R15,R15                 * Check value                 * 
         JM    RECV_REQ_ERR            * Negative - record error     * 
         XGR   R15,R15                 *                             * 
         J     RECV_RET                * return                      * 
****************************************                             * 
* EZASMI call error processing, issue message set return code        * 
****************************************                             * 
RECV_REQ_ERR DS 0H                     *                             * 
         JAS   R14,DO_REQ_ERR          * Go report error             * 
         LA    R15,16                  * Return code                 * 
RECV_RET DS    0H                      *                             * 
         LMG   R0,R14,W1_SAV2          * Restore callers regs        * 
         BR    R14                     * Return                      * 
*                                      ******************************* 
         DROP  R9 

 

Continue with the Tutorial

Server
Connections
Next
Sockets
Contents
Contents
Send
Data
Prev