AgentXcpp  Revision:0.1.1
Internals Documentation
 All Classes Namespaces Files Functions Variables Enumerations Enumerator Friends Pages
connector.hpp
Go to the documentation of this file.
1 /*
2  * Copyright 2011-2012 Tanjeff-Nicolai Moos <tanjeff@cccmz.de>
3  *
4  * This file is part of the agentXcpp library.
5  *
6  * AgentXcpp is free software: you can redistribute it and/or modify
7  * it under the terms of the AgentXcpp library license, version 1, which
8  * consists of the GNU General Public License and some additional
9  * permissions.
10  *
11  * AgentXcpp is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14  * GNU General Public License for more details.
15  *
16  * See the AgentXcpp library license in the LICENSE file of this package
17  * for more details.
18  */
19 #ifndef _LOCAL_SOCKET_H_
20 #define _LOCAL_SOCKET_H_
21 
22 #include <boost/asio.hpp>
23 #include <boost/cstdint.hpp>
24 
25 #include "ResponsePDU.hpp"
26 
27 using boost::uint8_t;
28 using boost::uint32_t;
29 
30 namespace agentxcpp
31 {
32  /**
33  * \internal
34  *
35  * \brief This class provides connection to another agentXcpp entity via
36  * a unix domain socket.
37  *
38  * A connector object is always in one of the following states:
39  *
40  * -# connected
41  * -# disconnected
42  *
43  * When created, a connector object starts in disconnected state. The
44  * current state can be obtained using the is_connected() method. Some
45  * operations may throw a disconnected exception if the object is in
46  * disconnected state. Further, the connection may fail at any point in
47  * time, therefore a disconnected exception may also be thrown during a
48  * network operation.
49  *
50  * The socket needed for networking is created upon connect and destroyed
51  * upon disconnect. The reason is that closing the socket may throw a
52  * system_error exception, leaving the socket in an unknown state.
53  * Destroying it and creating a new one should be safe.
54  *
55  * Sending works as follows:
56  *
57  * - The user invokes the send() method.
58  * - The send() method sends the %PDU synchronously (but with a timeout).
59  *
60  * Note: The send() function invokes io_service->run_one() one or several
61  * times.
62  *
63  * \par Receiving %PDU's
64  *
65  * - Upon connecting to the remote entity, an asynchronous read operation
66  * is started to receive the %PDU header (fixed size). On disconnect, the
67  * asynchronous read operation is stopped again.
68  * - The boost::asio library writes a fixed amount of data (which is the
69  * %PDU header) into the header_buf member and invokes the
70  * receive_callback() function.
71  * - The receive_callback() method reads the payload length from header_buf
72  * and reveives the payload synchronously (but with a timeout).
73  * - The receive_callback() method then constructs a PDU object and
74  * delivers it to a user-provided handler object (which implements the
75  * connector::pdu_handler interface) by calling its handle_pdu()
76  * method.
77  * - The handler object processes the %PDU as needed. As soon as processing
78  * finishes, the receive_callback() method starts the next asynchronous
79  * read operation, so that it invoked again when the next header arrives.
80  *
81  * \note As a special case, ResponsePDU's are not delivered to the
82  * registered handler object, but are handled differently. see below
83  * for details.
84  *
85  * \note If no handler object is registered, the %PDU is received
86  * nevertheless and is silently discarded.
87  *
88  * \note The receive_callback() function, and thus the registered handler
89  * object's handle_pdu() method are executed in the io_service's
90  * run() context.
91  */
92  /**
93  * \internal
94  *
95  * \par Receiving %ResponsePDU's
96  *
97  * The function wait_for_response() supports the request-response
98  * communication model. After sending a request to the remote entity (using
99  * send()), wait_for_response() is used to wait for the response. It blocks
100  * while waiting for a responsePDU from the remote entity, which is then
101  * returned to the caller. The wait_for_response() function may seem to do
102  * synchronous operations only, but this is not true. In fact, it uses an
103  * asynchronous receive mechanism, because there may be other %PDU's in the
104  * queue before the given response is actually received.
105  * wait_for_response() therefore invokes io_service->run_one() one ore more
106  * times, until the response is received. This may also cause other
107  * asynchronous operations to finish. For example, the registered handler
108  * object may receive other %PDU types, or another asynchronous operation
109  * on the io_service object (outside this class or even outside the
110  * agentXcpp library) may be served. Here are the steps performed to
111  * receive a ResponsePDU:
112  *
113  * - The wait_for_response() function puts an empty boost::shared_ptr<>
114  * into the responses map, using the PacketID of the awaited ResponsePDU
115  * as key.
116  * - The wait_for_response() function invokes io_service->run_one() one or
117  * several times, which triggers receive_callback() if data becomes
118  * available.
119  * - When the receive_callback() function is invoked, it receives a single
120  * %PDU and processes it as described above.
121  * - If a ResponsePDU is received, the registered handler object is not
122  * informed by receive_callback(). Instead, the responses map is searched
123  * for an entry with the same PacketID as the received ResponsePDU. If
124  * found, the received ResponsePDU is stored in the map. Otherwise the
125  * ResponsePDU is silently discarded (as nobody waits for it).
126  * - The wait_for_response() checks its map entry after each run_one() call
127  * for a received ResponsePDU. If it finds one, the entry is erased from
128  * the map and returned to the caller.
129  *
130  * \par Timeout detection
131  *
132  * The same timeout value is used by all operations which deal with
133  * timeouts. The value is stored in the timeout member.
134  *
135  * For timeout detection, a boost deadline timer is used together with the
136  * timeout_status member. While the timer is not in use, it's expiry time
137  * is set to "infinite" and timeout_status is set to "unused". When
138  * starting operations which need timeout detection, the deadline timer's
139  * expiry is set to the according time, and timeout_status is set to
140  * "in_progress". If the timer expires, the callback function
141  * check_deadline() is invoked, which sets the expiry back to "infinite"
142  * and timeout_status to "expired". If the operation completes before the
143  * timer expires, the expiry is set back to "infinite" and timeout_status
144  * is reset to "unused".
145  *
146  * Note that the callback check_deadline() may be invoked errornously, e.g.
147  * in this situation:
148  * -# The network operation completes
149  * -# The timer expires in the backround and check_deadline() is scheduled
150  * for invokation at the next call to io_service::run()
151  * -# The network operation set the timer's expiry to "infinite"
152  * The operation thus completed without a timeout condition, but
153  * check_deadline() will nevertheless be invoked later. Therefore, it
154  * compares the expiry date of the timer with the current timestamp to
155  * determine whether the timer really expired.
156  *
157  * The check_deadline() callback also restarts the timer on each call to
158  * keep it running.
159  */
160  class connector
161  {
162  public:
163 
164  /**
165  * \internal
166  *
167  * \brief Interface for classes which can handle incoming PDU's.
168  *
169  * Classes which want to receive incoming %PDU's implement this
170  * interface. An object of type pdu_handler can then be registered
171  * with a connector object to indicate that it wants to receive
172  * %PDU's.
173  */
175  {
176  public:
177  /**
178  * \brief Handler method for incoming %PDU's.
179  *
180  * When a %PDU is received by the connector class, this
181  * method is called on the registered object. Note that
182  * ResponsePDU's are not handed over to the registered
183  * object.
184  *
185  * The 'error' parameter is used to report errors. Using
186  * exceptions is not possible because of the asynchronous
187  * nature of the callback-mechanism.
188  *
189  * \param pdu The PDU which was just received. This is an
190  * empty shared_ptr if an error occured.
191  *
192  * \param error The error code. 0 means success, -1 means
193  * "parse error", -2 means "version error".
194  *
195  * \todo Check whether usage of boost::system makes
196  * sense for error reporting.
197  */
198  virtual void handle_pdu(boost::shared_ptr<PDU> pdu,
199  int error) =0;
200 
201  /**
202  * \brief Destructor.
203  */
204  virtual ~pdu_handler()
205  {
206  }
207  };
208 
209  private:
210 
211  /**
212  * \brief The timeout in milliseconds, used in various contexts.
213  */
214  unsigned timeout;
215 
216  /**
217  * \brief The mandatory io_service object.
218  *
219  * This object is needed for boost::asio sockets. It is provided by
220  * the user of this class.
221  */
222  boost::asio::io_service* io_service;
223 
224  /**
225  * \brief The socket.
226  *
227  * The null pointer while disconnected.
228  */
229  boost::asio::local::stream_protocol::socket* socket;
230 
231  /**
232  * \brief The endpoint used for unix domain sockets.
233  */
234  boost::asio::local::stream_protocol::endpoint endpoint;
235 
236  /**
237  * \brief Callback function to receive a %PDU.
238  *
239  * See the class documentation to learn about the receive
240  * mechanism.
241  *
242  * \note Exceptions thrown by the user-provided handler (if any)
243  * are catched and discarded.
244  *
245  * The synchronous read operation (to read the payload) may time
246  * out, using the class' timeout value. If the read times out, the
247  * socket is destroyed and the connector object becomes
248  * disconnected.
249  *
250  * \param result The result of the asynchronous read operation
251  * (provided by boost::asio).
252  *
253  * \exception None.
254  */
255  void receive_callback(const boost::system::error_code& result);
256 
257  /**
258  * \brief The received, yet unprocessed ReponsePDU's.
259  *
260  * See the class documentation to learn about the receive
261  * mechanism.
262  *
263  * The wait_for_response() function stores a null pointer to this
264  * map to indicate that it is waiting for a certain response.
265  *
266  * When a response is received, the receive_callback() function
267  * stores it into the map, but only if a null pointer is found for
268  * the packetID of the received ResponsePDU. Otherwise, the
269  * received ResponsePDU is discarded.
270  *
271  * After a ResponsePDU was received and stored into the map, the
272  * wait_for_response() function processes it and erases it from the
273  * map.
274  *
275  * The map key is the packetID of the response which is awaited.
276  */
277  std::map< uint32_t, boost::shared_ptr<ResponsePDU> > responses;
278 
279  /**
280  * \brief Buffer to receive a PDU header.
281  *
282  * See the class documentation to learn about the receive
283  * mechanism.
284  *
285  * The %PDU header is placed here before receive_callback() is
286  * called by boost::asio.
287  *
288  * Since the AgentX-header is always 20 bytes in length, this
289  * buffer is 20 bytes in size.
290  */
291  // TODO: avoid magic numbers, even if they are documented.
292  uint8_t header_buf[20];
293 
294  /**
295  * \brief The handler object for incoming %PDU's.
296  *
297  * This handler object is informed by receive_callback() for each
298  * received PDU (except ResponsePDU's).
299  *
300  * The pointer may be null, which means that there is no handler
301  * object registered.
302  */
304 
305  /**
306  * \brief Hide standard constructor.
307  *
308  * We need an io_service object to function properly.
309  */
310  connector();
311 
312 
313  public:
314 
315  /**
316  * \brief The constructor
317  *
318  * This constructor initializes the connector object to be in
319  * disconnected state.
320  *
321  * \param io_service The io_service object needed for boost::asio
322  * operations. It may also be used by other parts
323  * of the program.
324  *
325  * \param unix_domain_socket The path to the unix_domain_socket.
326  *
327  * \param timeout The timeout, in milliseconds, for sending and
328  * receiving %PDU's. See the documentation of the
329  * respective methods for details.
330  *
331  * \exception None.
332  */
333  connector(boost::asio::io_service* io_service,
334  const std::string& unix_domain_socket,
335  unsigned timeout);
336 
337  /**
338  * \brief Wait with timeout for a reponse.
339  *
340  * This function blocks until a ResponsePDU with the given
341  * packetID is received or until the timeout expires, whichever
342  * comes first. The received ResponsePDU (if any) is returned.
343  *
344  * This function calls run_one() repeatedly on the io_service
345  * object until the desired ResponsePDU arrives or the timeout
346  * expires. This may cause other asynchronous operations to be
347  * served, as well.
348  *
349  * \param packetID The packetID to wait for.
350  *
351  * \exception timeout_exception If the timeout expired before the
352  * ResponsePDU was received. The
353  * connector object stays in
354  * connected state.
355  *
356  * \exception disconnected If disconnected. This is also thrown if
357  * the operation fails and the object gets
358  * disconnected for that reason.
359  *
360  * \return The received ResponsePDU.
361  */
362  boost::shared_ptr<ResponsePDU>
363  wait_for_response(uint32_t packetID);
364 
365 
366  /**
367  * \brief Register a handler object for received %PDU's.
368  *
369  * Every time a %PDU is received, the handler object's handle_pdu()
370  * method will be invoked with the %PDU as argument. This method is
371  * executed in the context of the io_service object's run() or
372  * run_one() method. Care should be taken to not block the call,
373  * e.g. by doing networking.
374  *
375  * After registering a handler it can be unregistered again by
376  * calling this function with a null pointer. While no handler
377  * object is registered, received %PDU's are silently discarded.
378  *
379  * \note Any exceptions thrown by the handler object are silently
380  * discarded.
381  *
382  * \note There can be only one handler object registered at a time.
383  *
384  * \param handler A pointer to the handler object, or null.
385  *
386  * \exception None.
387  */
389 
390  /**
391  * \brief Connect to the remote entity.
392  *
393  * This function connects to the remote entity and starts receiving
394  * %PDU's. If the object is already connected, the function does
395  * nothing.
396  *
397  * \note While no handler is registered, received %PDU's are
398  * silently discarded.
399  *
400  * \exception disconnected If connecting fails.
401  */
402  void connect();
403 
404  /**
405  * \brief Disconnect the remote entity.
406  *
407  * Stops receiving %PDU's and disconnects the remote entity.
408  *
409  * \exception None.
410  */
411  void disconnect();
412 
413  /**
414  * \brief Find out whether the object is currently connected.
415  *
416  * \return Whether the object is in state connected.
417  *
418  * \exception None.
419  */
421  {
422  return (this->socket != 0 ? true : false );
423  }
424 
425  /**
426  * \brief send a %PDU to the remote entity.
427  *
428  * The %PDU is sent to the remote entity. If the timeout expires
429  * during the send operation, a timeout_error is thrown and the
430  * object gets disconnected.
431  *
432  * \note The run_one() of the io_service object is called at least
433  * one time by this operation.
434  *
435  * \param pdu The PDU to send
436  *
437  * \exception timeout_error If a timeout error occurs. The function
438  * uses the timeout value given during
439  * construction. The objects gets
440  * disconnected in this case. Some data
441  * might be sent already.
442  *
443  * \exception disconnected If disconnected. This is also thrown if
444  * sending fails and the object gets
445  * disconnected for that reason. Some data
446  * might be sent already.
447  */
448  void send(const PDU& pdu);
449 
450  /**
451  * \brief Destructor
452  *
453  * \exception None.
454  */
456  {
457  // Disconnect if applicable. This also destroys the socket (if
458  // any).
459  disconnect();
460  }
461 
462  };
463 
464 }
465 
466 #endif
467