Lecture 19: More Nonblocking I/O
- The outbound-file-test.cc presented earlier can be used to confirm the
OutboundFileclass implementation works as expected.- The nonblocking aspect of the doesn't really buy us anything.
- Only one copy of the source file is being syndicated, so no harm comes from blocking, since there's nothing else to do.
- To see why nonblocking I/O might be useful, consider the following nonblocking server implementation, presented over several slides.
- Our server is a static file server and responds to every single request—no matter how that request is structures—with the same payload. That payload is drawn from an HTML called "expensive-server.cc.html".
- Presented below is the portion of the server implementation that establishes the executable as a server that listens to port 12345 and sets the server socket to be nonblocking.
// expensive-server.html is expensive because it's always using the CPU, even when there's nothing to do
static const unsigned short kDefaultPort = 12345;
static const string kFileToServe("expensive-server.cc.html");
int main(int argc, char **argv) {
int server = createServerSocket(kDefaultPort);
assert(server != kServerSocketFailure);
setAsNonBlocking(server);
cout << "Static file server listening on port " << kDefaultPort << "." << endl;
}Lecture 19: More Nonblocking I/O
- As with all servers, our static file server loops interminably and aggressively accepts incoming connections as quickly as possible.
- The first part of the while loop calls and immediately returns from
accept. The return is immediate, because server has been configured to be nonblocking. - The code immediately following the accept call branches in one of two directions.
- If
acceptreturns a -1, we verify the -1 isn't something to be concerned about. - If
acceptsurfaces a new connection , we create an newOutboundFileon its behalf and append it to the runningoutboundFileslist of clients currently being served.
- If
- The first part of the while loop calls and immediately returns from
list<OutboundFile> outboundFiles;
while (true) {
// part 1: below
int client = accept(server, NULL, NULL);
if (client == -1) {
assert(errno == EWOULDBLOCK); // sanitycheck to confirm -1 doesn't represent a true failure
} else {
OutboundFile obf;
obf.initialize(kFileToServe, client);
outboundFiles.push_back(obf);
}
// part 2: presented on next slide
Lecture 19: More Nonblocking I/O
- As with all servers, our static file server loops interminably and aggressively accepts incoming connections as quickly as possible.
- The second part executes whether or not part 1 produced a new client connection and extended the
outboundFileslist. - It iterates over every single
OutboundFilein the list and attempts to send some or all available data out to the client.- If sendMoreData returns true, the loop advances on to the next client via
++iter. - If sendMoreData returns false, the relevant OutboundFile is removed from
outboundFilesbefore advancing. (Fortunately,erasedoes precisely what we want, and it returns the iterator addressing the nextOutboundFilein the list.)
- If sendMoreData returns true, the loop advances on to the next client via
- The second part executes whether or not part 1 produced a new client connection and extended the
list<OutboundFile> outboundFiles;
while (true) {
// part 1: presented and discussed on previous slide
// part 2: below
auto iter = outboundFiles.begin();
while (iter != outboundFiles.end()) {
if (iter->sendMoreData()) ++iter;
else iter = outboundFiles.erase(iter);
}
}
}Lecture 19: More Nonblocking I/O
- The code for
setAsNonblockingis fairly low-level.- It relies on a function called
fcntlto do surgery on the relevant file session in the open file table. - That surgery is little more that toggles some 0 bit to a 1, as can be inferred from the last line of the three line implementation.
- It relies on a function called
- The code for
setAsNonblockingand a few peer functions are presented below.
void setAsNonBlocking(int descriptor) {
fcntl(descriptor, F_SETFL, fcntl(descriptor, F_GETFL) | O_NONBLOCK); // preserve other set flags
}
void setAsBlocking(int descriptor) {
fcntl(descriptor, F_SETFL, fcntl(descriptor, F_GETFL) & ~O_NONBLOCK); // suppress blocking bit, preserve others
}
bool isNonBlocking(int descriptor) {
return !isBlocking(descriptor);
}
bool isBlocking(int descriptor) {
return (fcntl(descriptor, F_GETFL) & O_NONBLOCK) == 0;
}Lecture 19: More Nonblocking I/O
- We've been using the
OutboundFileabstraction without understanding how it works behind the scenes.- We really should see the implementation (or at least part of it) so we have some sense how it works and can be implemented using nonblocking techniques.
- The full implementation includes lots of spaghetti code.
- In particular, true file descriptors and socket descriptors need to be treated differently in a few places—in particular, detecting when all data has been flushed out to the sink descriptor (which may be a local file, a console, or a remote client machine) isn't exactly pretty.
- However, my implementation is decomposed well enough that I think many of the methods—the ones that I'll show in lecture, anyway—are easy to follow and provide a clear narrative.
- At the very least, I'll convince you that the
OutboundFileimplementation is accessible to someone just finishing up CS110.
Lecture 19: More Nonblocking I/O
- Here's is the condensed interface file for the OutboundFile class.
-
sourceandsinkare nonblocking descriptors bound to the data source and recipientbufferis a reasonably sized character array that helps shovel bytes lifted from source viareadcalls over to thesinkviawritecalls. -
numBytesAvailablestores the number of meaningful characters inbuffer. -
numBytesSenttracks the portion ofbufferthat's been pushed to the recipient. -
isSendingtracks whether all data has been pulled fromsourceand pushed tosink.
class OutboundFile {
public:
OutboundFile();
void initialize(const std::string& source, int sink);
bool sendMoreData();
private:
int source, sink;
static const size_t kBufferSize = 128;
char buffer[kBufferSize];
size_t numBytesAvailable, numBytesSent;
bool isSending;
// private helper methods discussed later
};Lecture 19: More Nonblocking I/O
-
sourceis a nonblocking file descriptor bound to some local file
-
- Note that the source file is opened for reading (
O_RDONLY), and the descriptor is configured to be nonblocking (O_NONBLOCK) right from the start. - For reasons we'll discussed, it's not super important that source be nonblocking, since it's bound to a local file.
- But in the spirit of a nonblocking example, it's fine to make it nonblocking anyway. We just shouldn't expect very many (if any) -1's to come back from our
readcalls.
- Note that the source file is opened for reading (
-
sinkis explicitly converted to be nonblocking, since it might be blocking, andsinkwill very often be a socket descriptor that really should be nonblocking.
- The implementations of the constructor and initialize are straightforward:
OutboundFile::OutboundFile() : isSending(false) {}
void OutboundFile::initialize(const string& source, int sink) {
this->source = open(source.c_str(), O_RDONLY | O_NONBLOCK);
this->sink = sink;
setAsNonBlocking(this->sink);
numBytesAvailable = numBytesSent = 0;
isSending = true;
}Lecture 19: More Nonblocking I/O
- The first line decides if all data has been read from
sourceand written tosink, and if so, it returnstrueunless it further confirms all of data written tosinkhas arrived at final destination, in which case it returnsfalseto state that syndication is complete.
- The first call to
dataReadyToBeSentchecks to see ifbufferhouses data yet to be pushed out. If not, then it attempts toreadMoreData. If after reading more data the buffer is still empty—that is, a single call toreadresulted in a-1/EWOULDBLOCKpair, then we returntrueas a statement that there's no data to be written, no need to try, but come back later to see if that changes. - The call to
writeMoreDatais an opportunity to push data out to sink.
- The implementation of sendMoreData is less straightforward:
bool OutboundFile::sendMoreData() {
if (!isSending) return !allDataFlushed();
if (!dataReadyToBeSent()) {
readMoreData();
if (!dataReadyToBeSent()) return true;
}
writeMoreData();
return true;
}Lecture 19: More Non-blocking I/O
By Chris Gregg
Lecture 19: More Non-blocking I/O
- 1,020