by Jon Salz
Oh, by the way, the last five lines are repeated about 25 times.../nmstl/thread: In copy constructor `nmstl::guard<Lock>::guard(const nmstl::guard<Lock>&) [with Lock = nmstl::mutex]': ../nmstl/seda:277: instantiated from here ../nmstl/thread:302: no match for `std::ostream& << nmstl::guard<nmstl::mutex>::my_thread&' operator /home/jsalz/gcc3/include/c++/3.2/bits/ostream.tcc:55: candidates are: std::basic_ostream<_CharT, _Traits>& std::basic_ostream<_CharT, _Traits>::operator<<(std::basic_ostream<_CharT, _Traits>&(*)(std::basic_ostream<_CharT, _Traits>&)) [with _CharT = char, _Traits = std::char_traits<char>]
Reading such error messages may leave you scratching your head, or perhaps standing on your chair yelling such phrases as "WTF?!!" (What's The Faux-pas?)
Anyone who has experienced this problem, which is to say anyone who uses STL, may want to take a look at "WTF", a little Perl hack which makes these error messages a lot more easily decipherable and gives you some context, like:
../nmstl/thread:302:no match for `ostream& << my_thread&' operator
300>
301> my_thread t;
302> cout << t;
303> }
304> guard& operator = (const guard& g) {
N.B.: You probably need to use an NMSTL_TO_STRING(my_thread);
macro immediately following your class declaration for my_thread.
Much nicer eh? You can find wtf in the bin
directory of the distribution - I find it useful for nearly any
C or C++ development.
to_string(t) function stringifies any type,
including scalars (booleans, ints, floats, etc.), and STL container
types such as vectors and maps.
You can make your own classes and structs stringifiable
by declaring a method with signature string as_string() const,
and including the macro NMSTL_TO_STRING(class_name) in
your header file, outside the class declaration:
class my_class {
int foo;
map bar;
public:
string as_string() {
return "my_class{" + foo +
", " + bar + "}";
}
};
NMSTL_TO_STRING(my_class);
to_human_readable returns a "human-readable" representation
of a chunk of data, replacing sequences of unreadable control characters with dots.
For example, the C string "ABC\1\2\3\4\5DEF" would be converted to
"ABC...DEF".
to_hex_string returns a chunk of data as a hexadecimal
string, e.g., "4142430102030405444546" for the above example.
to_escaped_string returns a valid C-string representation
of a chunk of data. For example,
cout << to_escaped_string("ABC\1\2\3\4\5DEF") << endl;
would print out (literally)
ABC\1\2\3\4\5DEF
<nmstl/debug> contains a simple debugging framework.
There are six debug levels, and six corresponding macros to generate
debug messages:
You can pass any sorts of arguments to these macros, in the typical iostreams fashion (although they're not technically iostreams). Each argument will be stringified, if necessary, usingDEBUG << "foo is " << foo INFO << "Accepted connection from " << addr; WARN << "..."; ERROR << "..."; FATAL << "Assertion failed"; // Also kills program COREDUMP << "Assertion failed"; // Kills program, leaving core dump
to_string.
To determine which messages are printed, the debugging code reads
the DEBUG environment variable. Warnings, errors, fatal
errors, and core dumps are always displayed. Info messages are
displayed only if the DEBUG environment variable is set
to 1 or higher; debug messages are displayed only if the
DEBUG environment variables is set to 2 or higher.
ntime class encapsulates a point in time, or a duration
in time. (As with time_t, a point in time is represented
as a number of seconds such the UNIX epoch, 1 January 1970.) ntimes
have microsecond precision.
You can create time objects with the following static methods:
ntime::now()
ntime::now_plus_secs(n) or
ntime::now_plus_msecs(n) or
ntime::now_plus_usecs(n)
ntime::secs(n)
or ntime::msecs(n)
or ntime::usecs(n).
ntime objects can also be added and subtracted to each other,
and multiplied and divided by scalars.
T, a ptr<T>
is a reference-counted pointer of type T. An object
is automatically deleted once the last ptr to it is
destructed.
ptrs are threadsafe, by the way.
read or write:
NMSTL provides abstractions over the memory-address/length tuple:ssize_t read(int fd, void *mem, size_t length); ssize_t read(int fd, const void *mem, size_t length);
constbuf is a data buffer that you can read from
but not write to (i.e., it's immutable). It is basically a const
void* memory address plus a length.
databuf is a data buffer that you can both read
from and write to (i.e., it's mutable). It is basically a
void* memory address plus a length, plus a maximum
length to which the buffer can grow.
dynbuf is a reference-counted data buffer that you
can both read from and write to. The buffer is automatically deallocated
when the last dynbuf pointing to it is deleted.
iohandle class defined by
NMSTL. An iohandle is a reference-counted file descriptor;
the descriptor is automatically closed when the last iohandle
to it is deleted.
For instance, here's how to read data from an iohandle:
void printbuf(constbuf buf) {
cout << "I just read " << buf.length() << " bytes" << endl;
}
void read_and_print(iohandle ioh) {
iohandle ioh = ...;
dynbuf buf(2048); // 2048-byte buffer
ioh.read(buf);
printbuf(buf);
// buffer is automatically deallocated when it goes out of scope
}
Note that printbuf takes a constbuf as
input - that's because it doesn't need to change the data in the buffer.
If it needed to change the buffer, it could take a databuf
as input. If it needed to keep a copy of the buffer, it could take
a dynbuf as input.
A dynbuf can be implicitly casted to a databuf,
and a databuf can be implicitly casted to a
constbuf, just as a void * can be implicitly
casted to a const void *.
status objects. A
status is simply a boolean value (true for
"good" or false for "bad") plus an optional detail string
describing the reason for failure. A status can be
cast to a boolean, so you can say, for example,
if (!my_ioh.stat()) {
cout << "Error: " << my_ioh.stat();
}
address is a network address; an inet_address
is an IP address (optionally with a port). A socket is an
iohandle with extra socket-type features. A tcpsocket
is a socket for, you guessed it, TCP connections; to
establish a TCP connection, use construct it as
tcpsocket(address_to_connect_to) for a blocking connect;
tcpsocket(address_to_connect_to, socket::nonblocking) for a nonblocking connect; or
tcpsocket(address_to_connect_to, socket::acceptor) to listen for connections on a particular address/port.
io_event_loop is
an asynchronous I/O event loop. For asynchronous-callback-driven
servers, you'll usually have one of these, and a slew of
io_handler objects to handle each individual event
source (e.g., the network).
An io_handler handles events for a single file
descriptor. To write such a handler, you'd subclass
io_handler; use set_ioh to specify the
file descriptor; call want_read and/or want_write
to specify which events you're interested in; and override
ravail and wavail to handle those events.
You'll find several useful networking handlers in
<nmstl/netioevent>.
tcp_acceptor<T>
listens for connections on a particular port, and instantiates handler
objects of type <T> to handle incoming connections.
net_handler is the one you'll usually use to implement
stream-oriented network services. You can subclass it and override
incoming_data, which is invoked whenever data arrives
on the connection; you can either consume the data or leave in in the
buffer.
connected, which is invoked when a connection attempt
on the associated socket succeeds or fails.
end_data, which is invoked when the other end closes
the stream.
msg_handler sends and receives data in discrete, variable-sized
chunks called "messages." You can write a message to the peer using
the msg_handler's write method;
incoming_message is invoked whenever a (complete) message
is received.
msg_handler is actually a template msg_handler<Header>,
where Header is whatever structure you'd like to use as your message
header (any struct is permissible, as long as it's serializable [see Serialization]
and has a length field).
term_handler, an asynchronous
command-line handler which uses GNU Readline and GNU History to
provide editing, key rebinding, history, and command completion
(not to mention automatic help functions). Much prettier than
cin >> foo (and safe to use in an asynchronous server!).
oserial is the base class for all serializers.
oserialstream is a subclass that serializes data to a stream, and
oserialstring is a subclass that serializes data to a string.
oserial object of the
appropriate type, and use the << operator.
oserial is the base class for all deserializers.
iserialstream is a subclass that serializes data from a stream, and
iserialstring is a subclass that serializes data from a string.
iserial object of the
appropriate type, and use the >> operator.
oserial class for details.
callback encapsulates
a function or method, plus some state to be passed to that function
or method when it is actually invoked.
thread class,
as well as mutex and a condition classes.
You can use the locking macro to acquire and release
a mutex automatically inside a particular scope.
Another useful abstraction in servers is a threaded queue
(tqueue, defined in <nmstl/tqueue>), allowing
writer threads to post items to a queue and readers to wait for items
to consume.
class Thread to class thread and
class IOEventLoop to class io_event_loop?
The NMSTL directory provides all the NMSTL APIs with
MixedCase names instead of stl_like_names,
so you can use whichever happens to match your coding conventions.
To use it, just use
#include <NMSTL/ioevent>
instead of
#include <nmstl/ioevent>
chatclient.cc and chatserver.cc
in the examples directory to see how it all fits together!