Reputation: 23218
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?
Upvotes: 1
Views: 4739
Reputation: 8414
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...
Upvotes: 1
Reputation: 596497
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:
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.
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.
Upvotes: 5
Reputation: 13099
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;
}
Upvotes: 1