OutboundFile
class implementation works as expected.
// 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;
}
accept
. The return is immediate, because server has been configured to be nonblocking.accept
returns a -1, we verify the -1 isn't something to be concerned about.accept
surfaces a new connection , we create an new OutboundFile
on its behalf and append it to the running outboundFiles
list of clients currently being served.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
outboundFiles
list.OutboundFile
in the list and attempts to send some or all available data out to the client.
++iter
.outboundFiles
before advancing. (Fortunately, erase
does precisely what we want, and it returns the iterator addressing the next OutboundFile
in the list.) 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);
}
}
}
setAsNonblocking
is fairly low-level.
fcntl
to do surgery on the relevant file session in the open file table.setAsNonblocking
and 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;
}
OutboundFile
abstraction without understanding how it works behind the scenes.
OutboundFile
implementation is accessible to someone just finishing up CS110.source
and sink
are nonblocking descriptors bound to the data source and recipient buffer
is a reasonably sized character array that helps shovel bytes lifted from source via read
calls over to the sink
via write
calls.numBytesAvailable
stores the number of meaningful characters in buffer
.numBytesSent
tracks the portion of buffer
that's been pushed to the recipient.isSending
tracks whether all data has been pulled from source
and pushed to sink
.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
};
source
is a nonblocking file descriptor bound to some local fileO_RDONLY
), and the descriptor is configured to be nonblocking (O_NONBLOCK
) right from the start.read
calls.sink
is explicitly converted to be nonblocking, since it might be blocking, and sink
will very often be a socket descriptor that really should be nonblocking.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;
}
source
and written to sink
, and if so, it returns true
unless it further confirms all of data written to sink
has arrived at final destination, in which case it returns false
to state that syndication is complete.dataReadyToBeSent
checks to see if buffer
houses data yet to be pushed out. If not, then it attempts to readMoreData
. If after reading more data the buffer is still empty—that is, a single call to read
resulted in a -1/EWOULDBLOCK
pair, then we return true
as a statement that there's no data to be written, no need to try, but come back later to see if that changes.writeMoreData
is an opportunity to push data out to sink.bool OutboundFile::sendMoreData() {
if (!isSending) return !allDataFlushed();
if (!dataReadyToBeSent()) {
readMoreData();
if (!dataReadyToBeSent()) return true;
}
writeMoreData();
return true;
}