I am reading CSAPP recently. In section 10.9, it said that standard I/O should not be used with socket because of the reasons as follows:
(1) The restrictions of standard I/O
Restriction 1: Input functions following output functions. An input function cannot follow an output function without an intervening call to fflush, fseek, fsetpos, or rewind. The fflush function empties the buffer associated with a stream. The latter three functions use the Unix I/O lseek function to reset the current file position.
Restriction 2: Output functions following input functions. An output function cannot follow an input function without an intervening call to fseek, fsetpos, or rewind, unless the input function encounters an end-of-file.
(2) It is illegal to use the lseek function on a socket.
Question 1: What would happen if I violate the restriction? I wrote a code snippet and it works fine.
Question 2: To walk around restriction 2, one approach is as follows:
File *fpin, *fpout;
fpin = fdopen(sockfd, "r");
fpout = fdopen(sockfd, "w");
/* Some Work Here */
fclose(fpin);
fclose(fpout);
In the text book, it said,
Closing an already closed descriptor in a threaded program is a recipe for disaster.
Why?
Your workaround does not work as written, due to the double-close bug you cited. Double-close is harmless in single-threaded programs as long as there are no intervening operations which could open new file descriptors (the second close will just fail harmlessly with
EBADF
) but they are critical bugs in multi-threaded programs. Consider this scenario:close(n)
.open
and it returnsn
which gets stored asint fd1
.close(n)
again.open
again and it returnsn
again, which gets stored asfd2
.fd1
and actually writes into the file opened by the second call toopen
instead of the one first opened.This can lead to massive file corruption, information leak (imagine writing a password to a socket instead of a local file), etc.
However, the problem is easy to fix. Instead of calling
fdopen
twice with the same file descriptor, simply usedup
to copy it and pass the copy tofdopen
. With this simple fix, stdio is perfectly usable with sockets. It's not suitable for asynchronous event loop usage still, but if you're using threads for IO, it works great.Edit: I think I skipped answering your question 1. What happens if you violate the rules about how to switch between input and output on a stdio stream is undefined behavior. This means testing it and seeing that it "works" is not meaningful; it could mean either:
The C implementation you're using provides a definition (as part of its documentation) for what happens in this case, and it matches the behavior you wanted. In this case, you can use it, but your code will not be portable to other implementations. Doing so is considered very bad practice for this reason. Or,
You just got the result you expected by chance, usually as a side effect of how the relevant functionality is implemented internally on the implementation you're using. In this case, there's no guarantee that it doesn't have corner cases that fail to behave as you expected, or that it will continue to work the same way in future releases, etc.