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:
Much nicer eh? You can find../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.
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; mapbar; 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.) ntime
s
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.
ptr
s 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
:
Note thatvoid 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 }
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!