0

I need to write a small client/server application in C on Linux.

I have built a short example in order to explore a little bit more since i am new to network programming.

I am basically trying to send an array of double dynamically allocated by the client.

I found the following way to do it ( client side ) :

write(sd,&datas.size,sizeof(int)); /* send size */
write(sd,datas.yi,datas.size*sizeof(double));/* send array */

and on the server side :

read(sd_cli,&datas.size,sizeof(int)); /* receive size */
datas.yi=(double *)malloc(datas.size*sizeof(double));
read(sd_cli,datas.yi,datas.size*sizeof(double)); /* receiving datas */

At first sight my code seems to work fine.

But since the write calls are non blocking, i ask myself if the read sequence can receive , for example the array of double before its size ?

Is there any guarantee that this can never happen ?

Thanks.

IbliSS
  • 25
  • 1
  • 7
  • 1
    It's ok, but be careful with endianess. And [Do not cast `malloc()`](http://stackoverflow.com/a/605858/1983495) – Iharob Al Asimi Mar 04 '15 at 17:23
  • 1
    TCP is stream-oriented, things never arrive out of order. – Ben Voigt Mar 04 '15 at 17:29
  • 4
    TCP guarantees the correct order on delivery. However make sure to test the return value of `read()` not only against error, but on how many bytes actually where read, as `read()` might read less from a socket as it was told to do. This propably happens rarely for small chunks of data, but just try sending some millions of doubles and you see what I am referring to. Read `read()`'s documentation closely. – alk Mar 04 '15 at 18:03
  • Indeed i have seen this after few tests and i prevent it ( but did not mentionned the whole code here ). Thanks anyway to remind it. – IbliSS Mar 04 '15 at 18:36
  • You have to not only test the return value of read, for a non-blocking socket (especially) you have to test the value of write() as well! Granted, for most situations you will just write into the network stack buffers even if the data doesn't send yet. – Chris Stratton Mar 04 '15 at 18:46
  • "successively"? Do you mean "consecutively"? I am not aware of the word "successively", but it sounds like doing anything that way would always be a good idea! How could it fail? – William Pursell Mar 04 '15 at 18:52
  • @WilliamPursell, check your dictionary, or [this one](http://dictionary.reference.com/browse/successively). Or take my word for it that you have correctly inferred the definition of a perfectly good word from its context. – John Bollinger Mar 04 '15 at 18:56
  • @John Bollinger. That is odd. 4 hours later reading my comment, I can't imagine why I didn't recognize the word. The brain is a funny thing! – William Pursell Mar 04 '15 at 22:57

1 Answers1

1

Sockets of type SOCK_STREAM provide reliable, in-order data transmission, though details depend on the nature of the underlying transport. The reader will receive all the data successfully written (if it in fact chooses to read them all), byte-for-byte in the order they were written, but not necessarily in the same size chunks.

Blocking vs. non-blocking has nothing to do with it, though I don't actually see what makes you say your writes are non-blocking. Perhaps you're remarking on the fact that neither write() nor read() promises to transfer the full number of bytes requested on any given call. That in itself provides no guarantee against blocking, but you absolutely do need to account for it correctly, especially with sockets, and even more especially if you really have put one or both ends of the socket in non-blocking mode. The original version of your question seemed to claim that you do account for it.

In any case, barring some kind of kernel bug, your client will never read the array size after any part of the array, nor otherwise receive bytes in a different relative order than they were written.

To be perfectly clear, however, here are reasonable implementations for reading and writing variable-size double arrays via a stream socket. They assume that sender and receiver have identical representations of type double, which will certainly be the case for UNIX-domain sockets. They are not at all trivial, though the helper functions comprising around half the code are suitable for reuse:

/******
 * helper functions
 */

/*
 * Returns the number of bytes written, which may be zero, or a number
 * less than zero on failure.
 */
ssize_t write_fully(int fd, const void *buf, size_t count) {
    const unsigned char *next = buf;
    size_t remaining = count;

    while (remaining) {
        ssize_t n_written = write(fd, next, remaining);

        if (n_written < 0) {
            /* error */
            return n_written;
        } else {
            assert(n_written <= remaining);
            next += n_written;
            remaining -= n_written;
        }
    }

    /* all bytes successfully written */
    return count;
}

/* 
 * Returns the number of bytes read on success, or a number less
 * than zero on error.  It is accounted "success" if the end of the stream
 * is reached before the requested number of bytes is read; that case
 * can be distinguished by the return value, but no recovery is
 * possible.
 */
ssize_t read_fully(int fd, void *buf, size_t count) {
    unsigned char *next = buf;
    size_t remaining = count;

    while (remaining) {
        ssize_t n_read = read(fd, next, remaining);

        if (n_read < 0) {
            /* error */
            return n_read;
        } else if (n_read) {
            assert(n_read <= remaining);
            next += n_read;
            remaining -= n_read;
        } else {
            /* premature end of file */
            return count - remaining;
        }
    }

    /* all bytes successfully read */
    return count;
}

/******
 * Array-transfer functions
 */

/* returns 0 on success, else nonzero */
int write_double_array(int fd, unsigned n, double d[n]) {
    ssize_t bytes_written;

    bytes_written = write_fully(fd, &n, sizeof(n));
    if (bytes_written < 0) return bytes_written;

    bytes_written = write_fully(fd, d, n * sizeof(double));
    return (bytes_written < 0) ? bytes_written : 0;
}

/*
 * returns 0 on success, else nonzero.
 * On success, the caller takes responsibility for freeing the
 * dynamically-allocated result array.
 */
int read_double_array(int fd, unsigned *n, double **d) {
    unsigned temp_n;
    ssize_t bytes_read = read_fully(fd, &temp_n, sizeof(temp_n));

    if (bytes_read < 0) {
        return -1;
    } else if (bytes_read != sizeof(temp_n)) {
        return 1;
    } else if (temp_n) {
        size_t n_bytes = temp_n * sizeof(double);
        double *temp = malloc(n_bytes);

        if (!temp) return -1;  /* allocation failure */
        if (read_fully(fd, temp, n_bytes) < n_bytes) {
            free(temp);
            return -1;
        }

        /* success */
        *d = temp;
    }

    *n = temp_n;
    return 0;
}

You could implement the array-transfer protocol differently, but that approach sends the data in the same form that you claim to do. You cannot safely do it any more simply than that.

John Bollinger
  • 160,171
  • 8
  • 81
  • 157
  • My comment applies to any stream socket. I observe, however, that if you specifically want a TCP/IP-flavored stream socket, then it should be set up with domain `AF_INET` or maybe `AF_INET6`, *not* `PF_UNIX`. – John Bollinger Mar 04 '15 at 18:19
  • Yes you are right, i am not using TCP, the question was actually related to `SOCK_STREAM`, since i set up my socket with `socket(PF_UNIX,SOCK_STREAM,0)`. I have just rectified this post. Thanks. – IbliSS Mar 04 '15 at 18:23
  • Stream sockets are reliable with respect to in-order delivery, but *only* if the API is used correctly, which is not something the present code which ignores write() and read() return values does. In the present code, the write of the array size could theoretically fail, and then the write of the data succeed if buffer space is available by then. Or the reverse, from reading too soon. **The socket is reliable, but the non-blocking calls are not** – Chris Stratton Mar 04 '15 at 18:48
  • @ChrisStratton, you are right, but the original version of the question asserted that the return values of `read()` and `write()` are indeed accounted for correctly, with appropriate measures to write and read fully, even though that is not evident in the code presented. That assertion is re-made in comments on the original question. – John Bollinger Mar 04 '15 at 18:52
  • Inexplicable why they pulled that out - I'm rather tempted to put it back in, but hesitate only in wondering if perhaps they removed it because they realized they might not be fully accomplishing reliable writes and reads with the attempted mechanism. Reading less than all of the bytes of a double (because they are not yet all available), for example, could be a bit messy to handle. – Chris Stratton Mar 04 '15 at 19:00
  • I didn't want to post the whole code as i needed to focus on the sequence of `read` and `write` functions assuming that all bytes were transferred and received which is not always the case. I thought that calling `write` two times from the client (sending size and then array) could possibly lead to read bytes in a wrong order for the server but you said it can never happen here. However Chris pointed out another question i had concerning the wrapping function (reading less than all the bytes of a double) but that's another topic i guess. – IbliSS Mar 04 '15 at 22:15
  • @IbliSS, the code I presented handles `double` elements being split across two (or more) reads and / or writes. I cannot think of a simpler way to approach the problem that ensures complete, reliable data transfer. – John Bollinger Mar 04 '15 at 22:28
  • @John, i did not pay enough attention to your code indeed. My mistache ;) I have included some modification in my code based on your example (for `read` and `write` functions) and it seems to work well. I have sent up to 8GB of datas without loosing any Bytes between client and server. Thanks again. – IbliSS Mar 05 '15 at 14:23