1

I have a requirement to send commands and read responses from a device using IrDA sockets communication. Although packaging up the commands is fairly straight forward, determining the expected size of the response is not possible. For example the command "GET_ERRORS" results in the device returning data from 0 to n, \n delimited lines of up to 80 bytes each. I have read the post *here, but the ***header preceding the actual data block* is not provided to me by the device.

[EDIT]
Here is a typical response from the GET_ERRORS command (shorted for readability):

Date       Time     Fault
10/12/2000 02:00:00 3f46
10/12/2000 02:00:00 bcf5
10/12/2000 02:00:00 1312
10/12/2000 02:00:00 a334
10/12/2000 02:00:00 b212
10/12/2000 02:00:00 b212
10/12/2000 02:00:00 c43a
%

This example, (from a SO post HERE) works well if I know the length of data being returned:

int recv_all(int sockfd, void *buf, size_t len, int flags)
{
    size_t toread = len;
    char  *bufptr = (char*) buf;

    while (toread > 0)
    {
        ssize_t rsz = recv(sockfd, bufptr, toread, flags);
        if (rsz <= 0)
            return rsz;  /* Error or other end closed cnnection */

        toread -= rsz;  /* Read less next time */
        bufptr += rsz;  /* Next buffer position to read into */
    }

    return len;
}

But if I want to receive an unknown amount of data, the only thing I know to do is to declare a large buffer, then pass it to something like this:

int IrdaCommReceive(int irdaCommSocket, void* largeBuf, size_t len, int* rcvd)
{
    char *bufptr = (char *)largeBuf;

    int bytesRcvd = recv(irdaCommSocket, bufptr, len, 0);

    if (bytesRcvd < 0) return WSAGetLastError();
    *rcvd = bytesRcvd;

    return 0;
}

Is there a better way to write a receive function for sockets data of an indeterminate size?

Community
  • 1
  • 1
ryyker
  • 22,849
  • 3
  • 43
  • 87

3 Answers3

5

Socket messages must be framed in some way to guarantee the integrity of the communications. For instance, UDP datagram sockets send/receive self-contained messages, data cannot span across message boundaries. TCP stream sockets do not have that restriction, so logical messages must be delimited instead, either with a header that describes the message length, or a unique terminator that does not appear in the message data. IrDA sockets are no different.

Since you have not shown the actual data for a GET_ERRORS response, or any code for that matter, but do say that there is no header in the front of the response message, then that leaves only two possibilities:

  1. message framing at the transport level. This is handled for you if you are creating the IrDA socket using the SOCK_DGRAM type. If you recv() using a buffer that is too small, the message is discarded and you get a WSAEMSGSIZE error.

  2. message delimiting at the application level. You have to handle this yourself if you are creating the IrDA socket using the SOCK_STREAM type. You provide an arbitrary buffer, and recv() fills it with whatever data is available, up to the requested size. You simply call recv() again to receive more data, continuing as needed until you find the data you are looking for.

I am assuming the latter, since that is how IrDA is typically used with sockets. I cannot find any online documentation for any IrDA-based GET_ERRORS command (do you have such documentation?), but I have to assume that the data has a terminating delimiter that you are not accounting for. It may be sending 0-n lines to you, but I bet the last line has a length of 0, or equivalent. In which case, you would simply call recv() in a loop until you receive that terminator.

Remy Lebeau
  • 555,201
  • 31
  • 458
  • 770
  • Correct, I use SOCK_STREAM, and there is a delimited end of message character - `%'. (edited question with example). (I am accounting for it by the way) "GET_ERRORS" is one of over 300 device specific commands (not IrDA specific) that can be sent, each with unique response formats, the documentation includes everything but expected response length. If I read this right, your suggestion of looping until I see the terminator leaves me with two options: sending in a really big buffer, and hope its big enough or a reasonable sized buffer, and calling realloc from the calling function. – ryyker Sep 24 '13 at 20:56
  • 1
    use a reasonable sized buffer to read the data and then append any received data to your real buffer, realloc'ing it as needed. Since you don't know the length of the response, and you shouldn't read more than the total size of the message, you should either 1) read 1 byte at a time until you encounter the terminator, or 2) implement an intermediate buffer that you read all inbound data into unconditionally (so you can read using larger buffer sizes), and then extract only completed messages from that intermediate buffer as needed (you can search it for the next available terminator). – Remy Lebeau Sep 25 '13 at 00:38
  • Depending on your particular coding style, you could go either way. Reading 1 byte at a time is easier to code, but reading into an intermediate buffer is more efficient, and usually faster. – Remy Lebeau Sep 25 '13 at 00:44
  • Well, it is becoming clear that realloc is in my future. Barring the discovery of some sockets magic that I have not yet discovered, this is a workable approach. Thanks – ryyker Sep 25 '13 at 00:59
1

Just use realloc would be my naive suggestion. Here's the inner loop from some old code I have here, gathering dust.

///////////// step 3 - get received bytes ////////////////
    // Receive until the peer closes the connection
    hdr->contentLen = 0;
    while(1)
    {
        memset(hdr->readBuffer, 0, bufSize);
        hdr->thisReadSize = recv (hdr->conn, hdr->readBuffer, bufSize, 0);
        if ( hdr->thisReadSize <= 0 )
            break;

        // nasty - but seemingly necessary. Headers don't always include the content-length
        // without a known content length, we cannot accurately allocate the memory required to hold
        // the full (header+message) response. Therefore, we re-allocate our memory each time a successful read is performed.
        // the alternative would be to write directly to file, or allocate (hopefully) more memory than we
        // need (Too bad if we allocate 10 Mb & a resource is 10.1 Mb !)
        hdr->result = (char*)realloc(hdr->result, hdr->thisReadSize+hdr->contentLen);

        memcpy(hdr->result+hdr->contentLen, hdr->readBuffer, hdr->thisReadSize);
        hdr->contentLen += hdr->thisReadSize;
    }
enhzflep
  • 12,927
  • 2
  • 32
  • 51
1

You could try something like this.

Send your command to the device. Then inside a loop call epoll() or select() on the socket to test to see if the socket has become ready for receiving data. If select() returns indicating that the socket has data ready for reading, call recv() repeatedly until a \n is received, marking the end of a line. That recv_all() is fine for that purpose. Process that line as you wish, then go back to the beginning of the loop.

You could use the timeout on select() to allow your program to go off and do something else, to judge when no more data is forthcoming from the device, etc. An implementation of select that modifies the timeout parameter could be useful here.

Of course, that assumes that select() works on irda sockets on Windows...

bazza
  • 7,580
  • 15
  • 22
  • Although your suggestion does not address my primary concern, (handling return data of indeterminate size) I have not yet tried using select() or epoll() as you have described. If it works with an AF_IRDA socket, then it will solve another problem I have, i.e. determining the right time to call recv after having sent the command. Thanks. – ryyker Sep 24 '13 at 21:45
  • Well, it does address your problem, I just left it as an inference. It allows you to both handle whatever and however much comes back whilst not necessarily having to wait for an inconvenient period of time. The only trick is to know what command received data corresponds to. The line of text that makes up the column headers in the response would be a good demarcation. So you'd just receive lines of text whenever they happen to turn up and process them accordingly, rather than trying to get a whole block of lines first. – bazza Sep 25 '13 at 22:28