This chapter documents the interface of the various MUSES classes, and the relationships among them.
All MUSES classes are part of the muses
namespace; their names should
be fully qualified (as in muses::Server
), imported as needed in the
global namespace (via `using muses::CLASSNAME;'), or imported as a whole
(using namespace muses;
).
These classes are common to all the possible MUSES servers. Among them, only
the Server
class needs to be instantiated by the end user. The framework
will create and destroy ServerPort
and Connection
objects when
needed; such objects can be accessed via the Server
. Moreover, the
current Connection
is passed as a parameter to user-defined event
handlers.
A Server
represents the entire MUSES server. It can perform a few
high-level operations, the most important of which are:
void addPort(unsigned int portnumber, ConnectHandler *handler)
: start
listening to a new port for connections. The handler will be called whenever
some client connects. If the handler is missing, new connections will not be
initialized; this is not recommended, as client input will be silently thrown
away.
void run(unsigned int timeout = 0, TimeoutHandler *handler = 0)
:
scan all ports and all connections, do whatever needs to be done. The
timeout is in microseconds; with a timeout of zero, run()
returns
immediately after polling the network.
void shutdown()
: shut down the server, its ports and all connections.
When the Server
ceases to exist, shutdown()
is called
automatically by the destructor. Once you shut the server down, you should
not use any of the server-provided objects (Connection
and
ServerPort
) any longer.
The Server
class provides other, less frequently needed, methods:
const std::list<ServerPort*>& ports() const
: return the list of
ServerPort
objects to which the server is listening for new connections.
void removePort(ServerPort *port)
: shut down a specific listen port,
and all the connections that were accepted by that port. If you only want to
disable a port temporarily, you should instead change its
ConnectHandler
with a handler that sends an appropriate message to the
client, and then calls disconnect()
. After shutting down a port, you
should not refer to any of its Connections
any longer.
This class represents a port on which the server is listening; it manages all the connections currently open on that port.
Users should seldom be interested in this class; the main reasons to use it
directly are reading the list of active connections, and replacing the
current ConnectHandler
.
ServerPort
objects are created and destroyed by their Server
;
they can be accessed via the Server
or via a Connection
they
accepted.
ServerPort
provides the following methods:
Server *server()
: retrieve the Server
to which this port belongs.
unsigned int portNumber()
: return the number of the port to which the
server is listening to for new connections.
unsigned int socketNumber()
: return the number of the socket that is
bound to this port.
ConnectHandler *connectHandler()
: return the handler that is currently
being used to initialize new connections.
ConnectHandler *setConnectHandler(ConnectHandler *handler)
: replace
the current handler with a new one, return the old handler.
const std::list<Connection*>& connections()
: access the list of active
connections.
void disconnect(Connection *conn)
: close a client's connection.
Direct usage of this method is not very elegant; you should call the
connection's disconnect()
method instead.
void shutdown()
: disconnect all clients that are connected to this
port, shut the port down, and remove it from the Server
. This method
is equivalent to Server::removePort(port)
. The same caveat applies:
after shutting down a port, the related Connection
objects should
not be used any longer.
A Connection
is a client's connection to a given ServerPort
.
The list of connections can be obtained via a ServerPort
; most handlers
are passed the current Connection
when invoked.
Its most important features:
ostream
, it can be used to send data to the client,
via the << operator:
myConnection << "Hello world!" << endl;Such data can be formatted via the usual
ostream
manipulators.
ReceiveHandler
and DisconnectHandler
can be accessed and
replaced in runtime, via receiveHandler()
, setReceiveHandler()
,
disconnectHandler()
and setDisconnectHandler()
.
void disconnect()
forces an immediate disconnection; the
Connection
object should not be used any longer afterwards.
RH_Telnet
and RH_LineSplitter
),
it is necessary to call some code that might close the current
Connection
. In this case, as soon as the unknown code returns, the
current Connection
object should be implicitly convert to bool
,
in order to make sure the connection is still open.
const char *buffer()
; the length
of pending data is unsigned int pendingSize()
. A ReceiveHandler is
expected to make use of both these methods.
ConnectionData
can be attached to the
connection via setData(ConnectionData*)
and retrieved via a call to
ConnectionData *data()
and a dynamic_cast
.
Some other, possibly less useful methods:
Connection
's ServerPort
can be retrieved via port()
.
unsigned int socketNumber()
returns the connection's socket.
void removeFromBuffer(unsigned int offset, unsigned int size)
removes
some data from the Connection
's input buffer. This can be useful, for
example, to remove escape sequences in an input filter like RH_Telnet
,
before forwarding the buffer to another handler.
unsigned int bufferSize()
and void setBufferSize(unsigned int)
.
The buffer size cannot be decreased below the current pending size. Also,
take care not to make the buffer too small: when the buffer is full, the
ReceiveHandler
is asked to remove some data from it, and if no data
is removed the Connection
shuts down.
setSendFilter()
method, or (more easily)
by dumping a new filter onto the Connection
via the <<
operator.
MUSES provides only a framework for internet servers, and thus only implements the features that are common to most kinds of servers. The specific aspects that make a server different from another have to be implemented via event handlers. MUSES requires several different kinds of handlers, corresponding to the abstract base classes described below.
MUSES also provides a standard way of attaching custom data to a Connection:
Finally, it is possible to preprocess the contents of the output buffer, right before sending them to the client, in a way that is transparent to the application:
ConnectHandler
objects tell a ServerPort
what to do when a
client tries to connect. They are expected to set up appropriate handlers
on the newly-made Connection
, and possibly to greet the client.
The entry point to a ConnectHandler
is
void operator () (muses::Connection& newConnection)
A Connection
's ReceiveHandler
is invoked whenever some new data
reaches the Connection
's input buffer. The handler is expected to
return the number of bytes that have been read and that can be dropped from
the buffer.
The entry point to a ReceiveHandler
is given as arguments the current
connection and the number of bytes that are waiting to be read:
unsigned int operator () (muses::Connection& connection, unsigned int len)
The default (null) handler throws away everything it receives without doing anything else.
If the input buffer ever becomes full, and the ReceiveHandler
does not
manage to handle some data, the current connection is automatically dropped.
The ready-made handler RH_LineSplitter
splits incoming data into lines
and parses each line separately. This parsing is done by calling a
LineHandler
.
The entry point to a LineHandler
is as usual operator()
:
void operator () (muses::Connection& connection, std::string& line)
This kind of handler tells a Connection
what to do when link is closed.
Usually, some kind of cleanup will be needed.
The entry point is as usual operator()
:
void operator () (muses::Connection& deadConnection)
When the handler is invoked, the Connection
is already closed; it is
safe to read whatever remains in its buffer, but of course it is pointless
to send it further data.
This handler is invoked when the network remains idle long enough. The handler
is passed the current Server
:
void operator () (Server& server)
Any kind of data can be attached to a MUSES connection. This is done by
storing such data in a subclass of ConnectionData
, and by linking it
to a Connection
via Connection::setData()
.
The attached data can be retrieved as follows:
MyData *data = dynamic_cast<MyData*>(myConnection.data());
data will contain a pointer to the stored data, or zero if no data of the appropriate type is available.
The ConnectionData
class does not contain any data member, and it does
not define any method apart from a constructor and a virtual destructor.
Therefore, it is an excellent candidate as a mix-in base class. A concrete
data class can inherit from both ConnectionData
and any pre-existing
class, without incurring in most of the disadvantages of multiple inheritance.
MUSES stores the server output in a buffer before sending it to its clients.
Just before flushing the buffer, MUSES can perform any kind of operation upon
its contents, in a way that is transparent to the application. This is done
by using a SendFilter
. This feature can be used to implement
communication protocols, to perform on-the-fly stream encryption...
A SendFilter
must be capable of performing three operations:
SendFilter *clone() const; unsigned int length(const char *buf, unsigned int len); unsigned int filter(const char *buf, unsigned int len, char *dest);
length()
, and must not exceed the
previously declared output buffer size, or Bad Things will happen.
Connection::setSendFilter(SendFilter*)
attaches a SendFilter
to a Connection
. This can be done more easily via <<
.
The newly specified filter will work until replaced by a new one. You may
safely deallocate a SendFilter
after passing it to a Connection
,
since the Connection
will make its own copy. For the same reason, it
may be unwise to store plenty of data into a SendFilter
.
If you want to disable output translation, just use the globally predefined
SendFilter
raw:
myConnection << muses::raw << "This text will not be filtered." << endl;
Beside providing a framework for internet servers, MUSES contains some ready-made event handlers to be used in common situations. More ready-made handlers might become available in the future.
This ReceiveHandler
splits the incoming data into lines, and passes
each line to a LineHandler
to be parsed.
The active LineHandler
can be determined in the constructor, and can
be changed at any time via setLineHandler(handler*)
.
This ReceiveHandler
implements a very minimal version of the
telnet
protocol, and forwards filtered data to another
ReceiveHandler
.
RH_Telnet
only unescapes incoming IAC characters, refuses all
options (answering DONT
to WILL
and WONT
to DO
),
and ignores other commands. Derived classes may override the handleIAC()
method and implement a more detailed version of the protocol.
The sub-handler to which filtered data is passed can be chosen when creating
the RH_Telnet
object, and can be changed at any time via
setReceiveHandler(handler*)
. A null handler will throw away data
without doing anything.
This SendFilter
converts outgoing IAC
characters into
IAC IAC
pairs. A telnet-compliant client will convert the pair
back to a single IAC before doing anything else with it.
Since this SendFilter
is likely to be used quite frequently, MUSES
provides a global instance of it, called sf_telnet. Just pass it to
a Connection
in order to enable outbound telnet conversion:
myConnection << sf_telnet;
If you want to send a raw IAC character (perhaps because you're sending a telnet command!), you should disable outbound filtering and then re-enable it:
myConnection << raw << IAC << my_usual_telnet_filter;
To make things easier, MUSES provides a iac global that does the same:
myConnection << iac;
In case some critical error happens, MUSES throws exceptions for the caller to catch. This is done when the error would not allow the server to start or to continue working, or when a serious bug is detected. Recoverable error conditions are handled more gently, usually by disconnecting the user that is having trouble.
All MUSES exceptions derive from MusesException
, which is in turn a
std::exception
. Therefore, their what()
method returns an
error std::string
describing the problem. Apart from this, some
MUSES exceptions provide further troubleshooting data.
ServerException
is thrown when a Server encountered a fatal error
during operation. The server()
method provides a reference to the
server. Recommended action: attempt to notify any clients, and shut down
gracefully.
AddPortException
is a ServerException
that is only thrown during
a failed addPort()
. This exception is most commonly thrown when you
try to open a port that some other process is already using. Methods
portNumber()
and connectHandler()
provide access to the
parameters passed to addPort()
.
ServerPortException
reports a serious problem with one of the server's
ports (returned by port()
).
Go to the first, previous, next, last section, table of contents.