3.3. The send() and recv() API calls

Once we've made a connection, we'll want to use that connection to send and receive data across the network. We'll do that using the send() and recv() APIs.

IBM's manual page for the send() API can be found at this link: http://publib.boulder.ibm.com/pubs/html/as400/v4r5/ic2924/info/apis/send.htm

It tells us that the prototype for the send() API looks like this:

         int send(int socket_descriptor,
                  char *buffer,
                  int buffer_length,
                  int flags)
     

Yes, the procedure is called 'send', and it accepts 4 parameters. Those parameters are an integer, a pointer, an integer and another integer. The send() API also returns an integer. Therefore, the RPG prototype for this API is:

         D send            PR            10I 0 ExtProc('send')
         D   sock_desc                   10I 0 value
         D   buffer                        *   value
         D   buffer_len                  10I 0 value
         D   flags                       10I 0 value
     

You may have noticed that for other 'char *' definitions, we put the 'options(*string)' keyword in our D-specs, but we didn't this time. Why? Because the send() API doesn't use a trailing null-character to determine the end of the data to send. Instead, it uses the buffer_length parameter to determine how much data to send.

That is a useful feature to us, because it means that we are able to transmit the null-character over the network connection as well as the rest of the string, if we so desire.

The flags parameter is used for 'out of band data', and for sending 'non-routed' data. You'll almost never use these flags. Why? Because 'out of band data' has never been widely adopted. Many TCP/IP stacks don't even implement it properly. In fact, for a long time, sending 'out-of-band' data to a Windows machine caused it to crash. The popular program called 'winnuke' does nothing more than send some out-of-band data to a Windows machine. The other flag, 'dont route' is really only used when writing routing applications. In all other situations, you want your packets to be routed! Therefore, it's very rare for us to specify anything but a 0 in the flags parameter.

The return value of the send() API will be the number of bytes sent, or a negative number if an error occurred.

Consequently, we typically call the send() API like this:

         D miscdata        S             25A
         D rc              S             10I 0
    
         C                   eval      miscdata = 'The data to send goes here'
         C                   eval      rc = send(s: %addr(miscdata): 25: 0)
         c                   if        rc < 25
         C*  for some reason we weren't able to send all 25 bytes!
         C                   endif
     

The recv() API is used to receive data over the network. IBM has documented this API here: http://publib.boulder.ibm.com/pubs/html/as400/v4r5/ic2924/info/apis/recv.htm

recv() is very similar to send(). In fact, the prototype for recv is nearly identical to send(), the only difference is the name of the procedure that you call. The prototype looks like this:

            int recv(int socket_descriptor,
                     char *buffer,
                     int buffer_length,
                     int flags)
     

And, just like send, the RPG prototype looks like this:

         D recv            PR            10I 0 ExtProc('recv')
         D   sock_desc                   10I 0 value
         D   buffer                        *   value
         D   buffer_len                  10I 0 value
         D   flags                       10I 0 value
     

The obvious difference between send() and recv() is what the system does with the memory pointed to by the 'buffer' parameter. When using send(), the data in the buffer is written out to the network. When using recv(), data is read from the network and is written to the buffer.

Another, less obvious, difference is how much data gets processed on each call to these APIs. By default, when you call the send() API, the API call won't return control to your program until the entire buffer has been written out to the network. By contrast, the recv() API will receive all of the data that's currently waiting for your application.

By default, recv() will always wait for at least one byte to be received. But, if there are more bytes, it will return them all, up to the length of the buffer that you've requested.

In the send() example above, 25 bytes are always written to the network unless an error has occurred. In the recv() example below, we can receive anywhere from 1 to 25 bytes of data. We have to check the return code of the recv() API to see how much we actually received.

Here's a quick example of calling recv():

         D miscdata        S             25A
         D rc              S             10I 0
    
         C                   eval      rc = recv(s: %addr(miscdata): 25: 0)
         c                   if        rc < 1
         C*  Something is wrong, we didnt receive anything.
         C                   endif