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

I read the man pages and I understand that if write() does not work and sets errno to EAGAIN or EINTR , I can write() again, so I came up with the following code:

  ret = 0; while(ret != count) { write_count = write(connFD, (char *)buf + ret, count); while (write_count < 0) { switch(errno) { case EINTR: case EAGAIN: write_count = write(connFD, (char *)buf + ret, count -ret); break; default: printf("\n The value of ret is : %d\n", ret); printf("\n The error number is : %d\n", errno); ASSERT(0); } } ret += write_count; } 

I execute read() and write() on sockets and handle read() same way as described above. I am using Linux with the gcc compiler.

+6
source share
4 answers

You have some β€œdon't repeat yourself” problem - there is no need for two separate write calls or 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 
+6
source

EINTR and EAGAIN processing often should be slightly different. EAGAIN is always some kind of temporary error representing the state of the socket buffer (or rather, that your operation may be blocked).

As soon as you press EAGAIN , you most likely want a sleeping bit or return the control to the event loop (if you use it).

With EINTR situation is slightly different. If your application receives signals non-stop, this may be a problem in your application or environment, and for this reason I have some kind of internal counter eintr_max , so I was not stuck in a theoretical situation, when I just continue to loop endlessly on EINTR .

Alnitak's answer (sufficient for most cases) should also keep errno somewhere, as it can be tailored to perror() (although it can be omitted for brevity).

+3
source

I would prefer to poll descriptor in the case of EAGAIN rather than just go in for a loop and burn the CPU for no good reason. This is a kind of "blocking shell" for the 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 do not actually check poll results other than the return value; I believe the next write will fail if there is a persistent error in the handle.

You can enable EINTR as a repeated error by simply adding it to the conditions using EAGAIN , but I prefer it to actually interrupt I / O.

+2
source

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

If you only have a file descriptor, you can always transfer it to FILE* using fdopen() so that you can use it with the functions listed above.

However, there is one mistake: FILE* streams are usually buffered. This can be a problem if you are communicating with any other program and expect its response. This can slow down both programs, although there is no logical error, simply because fprintf() decided to postpone the corresponding write() bit. You can disable buffering or fflush() output streams when you really need write() calls that need to be executed.

0
source

Source: https://habr.com/ru/post/914261/


All Articles