Serializing all the complex objects is done with Boost serialization.
The complete manual is generated by doxygen in the doc directory.
-Changes
--------
+Changes from 0.3
+----------------
+* Add Client-Wrappers, see examples-codegen/example1-client-wrapper how to
+ use them
+* API-incompatible change:
+ command_client now takes a client_connection pointer instead of a reference
* Fix build of documentation for "out of tree" builds
* Fixed a bug with nested namespaces
* Fixed a bug with incomplete writes (Robert Huitl)
- \anchor notes2
[2] remote procedure call (RPC), http://en.wikipedia.org/wiki/Remote_procedure_call and "THE RPC MODEL" http://www.faqs.org/rfcs/rfc1050.html
- \anchor notes3
- [3] Figure: Remote procedure call overview, http://jan.netcomp.monash.edu.au/webservices/rpc_stub.png, Jan Newmarch "Web services" http://jan.netcomp.monash.edu.au/webservices/tutorial.html
+ [3] Figure: Remote procedure call overview, http://jan.newmarch.name/webservices/rpc_stub.png , Jan Newmarch "Web services" http://jan.newmarch.name/webservices/tutorial.html
- \anchor notes4
[4] in fact gccxml is used to parse the C++ code and the XML output of gccxml is used as input for the code generator
- \anchor notes5
\section server Example server program and client library
\par The procedure to export (input for the code generator - libt2n-codegen): t2nexample.cpp:
- First the procedure to export is defined. It is marked for export with the attribute macro "LIBT2N_EXPORT". In this example the procedure throws an exception if the input string is "throw". The exception is passed back to the client transparently. Otherwise some text is appended and returned.
+ First the procedure to export is defined. It is marked for export with the attribute macro \ref LIBT2N_EXPORT. In this example the procedure throws an exception if the input string is "throw". The exception is passed back to the client transparently. Otherwise some text is appended and returned.
\include example1/t2nexample.cpp
\par Required includes go into the group header file: t2nexample.hxx:
$ kill %1
$ rm socket
\endverbatim
+
+ \section wrapper The Client-Wrapper
+ The interfaces can be called directly in the way outlined above. But this means you have to take care of connection errors between client and server
+ at each call, possibly try to reconnect and so on. Libt2n provides the Client-Wrapper to ease this. It is a way to select a error handling strategy
+ once and use it automatically for all calls invoked through the Wrapper. Tough error-handling is the common usecase, the Client-Wrapper could be used
+ to execute any user-provided code before and after a call to the server is made.
+
+ The other feature that the Client-Wrapper provides is a connection-singleton. T2n (currently) only offers single-threaded servers. So if you use methods of a T2n-server in a program, you usually only want to maintain one common connection to this server - even if it is accessed from different parts/modules/classes/... of your program. The Client-Wrapper is initialized with a \ref libt2n::ConnectionWrapper.
+
+ This \ref libt2n::ConnectionWrapper takes the error-handling strategy (e.g. reconnect-then-throw) and everything needed to establish a connection (e.g. socket name or host and tcp-port) as parameters. A connection is established at the first actual request to the server and re-used for following requests. You don't need to pass around client-handles and the like to your classes or methods, finding the right wrapper is done via the global singleton created for each server-interface initialized for the wrapper.
+
+ This example shows how to use the Client-Wrapper:
+
+ \include example1-client-wrapper/client.cpp
+
+The details of the Client-Wrapper can be found in the \ref libt2n::T2nSingletonWrapper, but beware, the code is full of ugly templates and template-construction-defines.
+
*/
# automatically generated by ./test-build-install-use
-EXTRA_DIST = $(srcdir)/example1/* $(srcdir)/example1-client/* $(srcdir)/example2/* $(srcdir)/example2-client/* $(srcdir)/README
+EXTRA_DIST = $(srcdir)/example1/* $(srcdir)/example1-client/* $(srcdir)/example1-client-wrapper/* $(srcdir)/example2/* $(srcdir)/example2-client/* $(srcdir)/README
wraptype::set_connection(std::auto_ptr<libt2n::ConnectionWrapper>
(new libt2n::ReconnectSocketWrapper("./socket")));
- std::string result;
-
// execute a function via t2n. The wrapper will open a connection to the server if none
// exists or try to reconnect if the existing one doesn't answer
std::cout << t2n_exec(&cmd_group_t2nexample_client::testfunc)("hello") << endl;
calls using this connection with an error-handling strategy (e.g. to reconnect when
the connection broke). The source looks very complicated due to heavy use of templates,
look at the 3rd codegen example to see how to use it.
+
+ @par Example
+ Calling remote methods is usually done via t2n_exec, this saves you from always
+ specifying which T2nSingletonWrapper-template to use when calling T2nSingletonWrapper::exec
+ @code
+ t2n_exec(&cmd_group_t2nexample_client::testfunc)("the answer is %d",42)
+ @endcode
*/
template< class Client >
class T2nSingletonWrapper : public T2nSingletonWrapperMessages
static std::auto_ptr<T2nSingletonWrapper> SingletonObject;
static std::auto_ptr<ConnectionWrapper> WrappedConnection;
+ /// @cond
// create a prep-method for each possible number of parameters
#define _GEN_ARG(z,n,d) Arg ## n arg ##n
#define _GEN_PREP(z,n,d) \
#undef _GEN_PREP
#undef _GEN_ARG
+ /// @endcond
T2nSingletonWrapper(std::auto_ptr<Client> stub)
{
public:
+ /** @brief tell the wrapper which connection to use
+ @param wrappedConnection the connection to establish when needed
+ */
static void set_connection(std::auto_ptr<ConnectionWrapper> wrappedConnection)
{
WrappedConnection=wrappedConnection;
if (SingletonObject.get() != NULL)
SingletonObject.reset();
}
+
+ /// return a pointer to the ConnectionWrapper currently in use
static ConnectionWrapper* get_connection_wrapper(void)
{ return WrappedConnection.get(); }
+ /// manually establish the connection without actually executing a call
static void ensure_singleton_there(void)
{
if (SingletonObject.get() == NULL)
init();
}
+ /// @cond
// create an exec-method for each possible number of parameters
#define _GEN_PLACEHOLDER(z,n,d) BOOST_PP_CAT(_,BOOST_PP_ADD(n,1))
#define _GEN_EXEC(z,n,d) \
#undef _GEN_EXEC
#undef _GEN_PLACEHOLDER
+ /// @endcond
};
+/// @cond
// create an t2n_exec-method for each possible number of parameters
#define _GEN_EXEC(z,n,d) \
template< class Client, typename R BOOST_PP_COMMA_IF(n) BOOST_PP_ENUM_PARAMS(n,typename Arg) > \
#undef _GEN_EXEC
#undef _GEN_PLACEHOLDER
+/// @endcond
}
#endif
/** @brief handle incoming commands
@param[in,out] usec_timeout wait until new data is found, max timeout usecs.
-1: wait endless, 0: instant return
+ @param[out] usec_timeout_remaining microseconds from the timeout that were not used
*/
void command_server::handle(long long usec_timeout, long long* usec_timeout_remaining)
{
@retval true if new data was found (does not mean that the received data
is a complete packet though)
*/
- virtual bool fill_buffer(long long usec_timeout=-1, long long* timeout_remaining=NULL)=0;
+ virtual bool fill_buffer(long long usec_timeout=-1, long long* usec_timeout_remaining=NULL)=0;
void cleanup();
@param[in,out] usec_timeout wait until new data is found, max timeout usecs.
-1: wait endless
0: return instantly
+ @param[out] usec_timeout_remaining microseconds from the timeout that were not used
*/
bool socket_handler::data_waiting(long long usec_timeout,long long* usec_timeout_remaining)
{
@param[in,out] usec_timeout wait until new data is found, max timeout usecs.
-1: wait endless
0: return instantly
+ @param[out] usec_timeout_remaining microseconds from the timeout that were not used
*/
-bool socket_handler::fill_buffer(std::string& buffer, long long usec_timeout, long long *timeout_remaining)
+bool socket_handler::fill_buffer(std::string& buffer, long long usec_timeout, long long *usec_timeout_remaining)
{
// fast path for timeout==0
- if (usec_timeout==0 || data_waiting(usec_timeout,timeout_remaining))
+ if (usec_timeout==0 || data_waiting(usec_timeout,usec_timeout_remaining))
return fill_buffer(buffer);
else
return false;
socket_type_value socket_type;
- bool data_waiting(long long usec_timeout,long long *timeout_remaining=NULL);
+ bool data_waiting(long long usec_timeout,long long *usec_timeout_remaining=NULL);
void wait_ready_to_write(int socket, long long write_block_timeout);
protected:
virtual void close();
- bool fill_buffer(std::string& buffer, long long usec_timeout, long long*timeout_remaining=NULL);
+ bool fill_buffer(std::string& buffer, long long usec_timeout, long long* usec_timeout_remaining=NULL);
bool fill_buffer(std::string& buffer);
public:
namespace libt2n
{
+/** @brief a basic implementation of ConnectionWrapper
+
+ This is a basic version of a ConnectionWrapper which does not do any fancy
+ error handling or anything, it justs executes the regular calls. Use this
+ wrapper if you only want to use the singleton-feature of T2nSingletonWrapper.
+*/
class BasicSocketWrapper : public ConnectionWrapper
{
protected:
void set_logging(std::ostream *_logstream, log_level_values _log_level);
};
+/** @brief a wrapper implementing reconnect-then-throw
+
+ This ConnectionWrapper tries to reconnect to the server if something with the connection
+ goes wrong. If even reconnecting max_retries times does not help, an exception is thrown.
+*/
class ReconnectSocketWrapper : public BasicSocketWrapper
{
public:
bool handle(command_client* stubBase, boost::function< void() > f);
};
+/// a placeholder-client_connection which is closed all the time
class dummy_client_connection : public client_connection
{
private:
{ return false; }
};
+/** @brief a wrapper implementing reconnect-then-ignore
+
+ This ConnectionWrapper tries to reconnect to the server if something with the connection
+ goes wrong. If even reconnecting max_retries times does not help, the complete t2n-call is
+ ignored. The return value of the call will be created with the default constructor.
+*/
class ReconnectIgnoreFailureSocketWrapper : public ReconnectSocketWrapper
{
private: