Is there a cleaner way to use the write() function reliably?

前端 未结 4 1756
悲哀的现实
悲哀的现实 2021-02-09 16:15

I read the man pages, and my understanding is that if write() fails and sets the errno to EAGAIN or EINTR, I may

相关标签:
4条回答
  • 2021-02-09 16:41

    Yes, there are cleaner ways to use write(): the class of write functions taking a FILE* as an argument. That is, most importantly, fprintf() and fwrite(). Internally, these library functions use the write() syscall to do their job, and they handle stuff like EAGAIN and EINTR.

    If you only have a file descriptor, you can always wrap it into a FILE* by means of fdopen(), so you can use it with the functions above.

    However, there is one pitfall: FILE* streams are usually buffered. This can be a problem if you are communicating with some other program and are waiting for its response. This may deadlock both programs even though there is no logical error, simply because fprintf() decided to defer the corresponding write() a bit. You can switch the buffering off, or fflush() output streams whenever you actually need the write() calls to be performed.

    0 讨论(0)
  • 2021-02-09 16:42

    EINTR and EAGAIN handling should often be slightly different. EAGAIN is always some kind of transient error representing the state of the socket buffer (or perhaps, more precisely, that your operation may block).

    Once you've hit an EAGAIN you'd likely want to sleep a bit or return control to an event loop (assuming you're using one).

    With EINTR the situation is a bit different. If your application is receiving signals non-stop, then it may be an issue in your application or environment, and for that reason I tend to have some kind of internal eintr_max counter so I am not stuck in the theoretical situation where I just continue infinitely looping on EINTR.

    Alnitak's answer (sufficient for most cases) should also be saving errno somewhere, as it may be clobbered by perror() (although it may have been omitted for brevity).

    0 讨论(0)
  • 2021-02-09 16:43

    You have a bit of a "don't repeat yourself" problem there - there's no need for two separate calls to write, nor for two nested loops.

    My normal loop would look something like this:

    for (int n = 0; n < count; ) {
        int ret = write(fd, (char *)buf + n, count - n);
        if (ret < 0) {
             if (errno == EINTR || errno == EAGAIN) continue; // try again
             perror("write");
             break;
        } else {
            n += ret;
        }
    }
    
    // if (n < count) here some error occurred
    
    0 讨论(0)
  • 2021-02-09 16:55

    I would prefer to poll the descriptor in case of EAGAIN instead of just busy looping and burning up CPU for no good reason. This is kind of a "blocking wrapper" for a non-blocking write I use:

    ssize_t written = 0;
    
    while (written < to_write) {
        ssize_t result;
        if ((result = write(fd, buffer, to_write - written)) < 0) {
            if (errno == EAGAIN) {
                struct pollfd pfd = { .fd = fd, .events = POLLOUT };
                if (poll(&pfd, 1, -1) <= 0 && errno != EAGAIN) {
                    break;
                }
                continue;
            }
            return written ? written : result;
        }
        written += result;
        buffer += result;
    }
    return written;
    

    Note that I'm not actually checking the results of poll other than the return value; I figure the following write will fail if there is a permanent error on the descriptor.

    You may wish to include EINTR as a retryable error as well by simply adding it to the conditions with EAGAIN, but I prefer it to actually interrupt I/O.

    0 讨论(0)
提交回复
热议问题