Jpp
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Pages
JLigier.cc
Go to the documentation of this file.
1 #include <iostream>
2 #include <iomanip>
3 #include <string>
4 #include <sstream>
5 #include <deque>
6 #include <set>
7 
8 #include "Jeep/JParser.hh"
9 #include "Jeep/JMessage.hh"
10 #include "JLang/JMemory.hh"
11 #include "JLang/JSharedCounter.hh"
12 #include "JNet/JSocket.hh"
13 #include "JNet/JSocketChannel.hh"
14 #include "JNet/JServerSocket.hh"
15 #include "JNet/JSelect.hh"
16 #include "JNet/JControlHost.hh"
18 
19 
20 namespace JNET {
21 
22 
23  using namespace JPP;
24 
25 
26  template<class T> class JMemory_t : public JMalloc<T> {}; // Memory manager
27 
28  typedef JPrefix JPrefix_t;
30 
31 
32  /**
33  * Get total size of internet packet.
34  *
35  * \param prefix prefix
36  * \return number of bytes
37  */
38  inline int getSizeOfPacket(const JPrefix_t& prefix)
39  {
40  return prefix.getSize() + sizeof(JPrefix_t);
41  }
42 
43 
44  /**
45  * Set total size of internet packet.
46  *
47  * \param size number of bytes
48  * \param prefix prefix
49  */
50  inline void setSizeOfPacket(const int size, JPrefix_t& prefix)
51  {
52  prefix.setSize(size - sizeof(JPrefix_t));
53  }
54 
55 
56  /**
57  * Data structure of a ControlHost message.
58  * This data structure consists of a copy of the ControlHost prefix and
59  * an array of bytes (including the ControlHost prefix).
60  * A new array of bytes is created by the appropriate constructor.
61  * The allocated memory is released upon destruction of the last object of this class.
62  */
63  class JDispatch :
64  public JPrefix_t,
65  public JSharedCounter
66  {
67  public:
68 
70 
71  static long long int MEMORY_TOTAL; //!< Total size of data [Bytes]
72  static long long int MEMORY_LIMIT; //!< Limit size of data [Bytes]
73 
74  /**
75  * Default constructor.
76  */
78  JPrefix_t(),
80  buffer(NULL)
81  {}
82 
83 
84  /**
85  * Constructor.
86  * Note that the input data should contain a copy of the prefix.
87  *
88  * \param prefix prefix
89  * \param data data
90  */
91  JDispatch(const JPrefix_t& prefix,
92  const char* data) :
93  JPrefix_t(prefix),
95  {
96  create();
97 
98  memcpy(this->buffer, data, size());
99  }
100 
101 
102  /**
103  * Constructor.
104  * Note that the given message is appended to a copy of the prefix.
105  *
106  * \param tag tag
107  * \param message message
108  */
110  const std::string& message) :
111  JPrefix_t(tag, message.size()),
113  {
114  create();
115 
116  memcpy(this->buffer, static_cast<const JPrefix_t*>(this), sizeof(JPrefix_t));
117 
118  memcpy(this->buffer + sizeof(JPrefix_t), message.data(), message.size());
119  }
120 
121 
122  /**
123  * Copy constructor.
124  *
125  * \param message message
126  */
127  JDispatch(const JDispatch& message)
128  {
129  static_cast<JPrefix_t&>(*this) = message;
130 
131  buffer = message.buffer;
132 
133  attach(message);
134  }
135 
136 
137  /**
138  * Destructor.
139  */
141  {
142  if (detach())
143  release();
144  }
145 
146 
147  /**
148  * Assignment operator.
149  *
150  * \param message message
151  * \return this JDispatch
152  */
153  JDispatch& operator=(const JDispatch& message)
154  {
155  if (buffer != message.buffer) {
156 
157  if (detach()) {
158  release();
159  }
160 
161  buffer = message.buffer;
162 
163  attach(message);
164  }
165 
166  return *this;
167  }
168 
169 
170  /**
171  * Type conversion operator.
172  *
173  * \return socket output buffer
174  */
175  operator JSocketOutputBuffer () const
176  {
177  return JSocketOutputBuffer(this->data(), this->size());
178  }
179 
180 
181  /**
182  * Get size.
183  *
184  * \return number of bytes
185  */
186  int size() const
187  {
188  return getSizeOfPacket(static_cast<const JPrefix_t&>(*this));
189  }
190 
191 
192  /**
193  * Get data.
194  *
195  * \return pointer to data
196  */
197  const char* data() const
198  {
199  return buffer;
200  }
201 
202 
203  protected:
204  /**
205  * Allocate memory.
206  */
207  void create()
208  {
210 
211  if (buffer != NULL) {
212 
214 
215  MEMORY_TOTAL += size();
216 
217  } else {
218 
219  throw JMallocException("Not enough space in memory.");
220  }
221  }
222 
223 
224  /**
225  * Release memory.
226  */
227  void release()
228  {
230 
231  MEMORY_TOTAL -= size();
232  }
233 
234  char* buffer;
235  };
236 
237 
238  /**
239  * ControlHost client manager.
240  */
241  class JClient :
242  public JSocket
243  {
244  public:
245 
246 
247  static unsigned int QUEUE_LIMIT; //!< Maximum number of messages in queue
248 
249 
250  /**
251  * Default constructor.
252  */
254  JSocket(),
255  in (*this),
256  out(*this),
257  requestAll(false),
258  requestCounter(0)
259  {}
260 
261 
262  /**
263  * Constructor.
264  *
265  * \param socket socket
266  */
267  JClient(const JSocket& socket) :
268  JSocket(socket),
269  in (*this),
270  out(*this),
271  requestAll(false),
272  requestCounter(0)
273  {}
274 
275 
276  /**
277  * Get nick name.
278  *
279  * \return nick name
280  */
281  const std::string& getNickname() const
282  {
283  return nick_name;
284  }
285 
286 
287  /**
288  * Set nick name.
289  *
290  * \param nick_name nick name
291  */
292  void setNickname(const std::string& nick_name)
293  {
294  this->nick_name = nick_name;
295  }
296 
297 
298  /**
299  * Check request.
300  *
301  * \return true if request can be honoured; else false
302  */
303  bool checkRequest() const
304  {
305  return requestAll || requestCounter != 0;
306  }
307 
308 
309  /**
310  * Increment request by one.
311  */
313  {
314  ++requestCounter;
315  }
316 
317 
318  /**
319  * Decrement request by one.
320  */
322  {
323  --requestCounter;
324  }
325 
326 
327  /**
328  * Set no request.
329  */
331  {
332  requestAll = true;
333  }
334 
335 
336  /**
337  * Set subcription.
338  *
339  * \param subscription subscription
340  * \return true of OK; else false
341  */
342  bool setSubscription(const std::string& subscription)
343  {
344  using namespace std;
345 
346  subscriptionAll.clear();
347  subscriptionAny.clear();
348 
349  try {
350 
351  char c;
352  JTag tag;
353 
354  for (istringstream is(subscription); is >> c >> tag; ) {
355  if (c == SUBSCRIBE_ALL) subscriptionAll.insert(tag);
356  else if (c == SUBSCRIBE_ANY) subscriptionAny.insert(tag);
357  //else if (c == SUBSCRIBE_SHARED_MEMORY) subscriptionAny.insert(tag);
358  }
359  }
360  catch(const JControlHostException& error) {
361  return false;
362  }
363 
364  return true;
365  }
366 
367 
368  /**
369  * Check subscription for given prefix.
370  *
371  * \param prefix prefix
372  * \return true if subscription valid; else false
373  */
374  bool checkSubscriptionAll(const JPrefix_t& prefix) const
375  {
376  return subscriptionAll.find(prefix) != subscriptionAll.end();
377  }
378 
379 
380  /**
381  * Check subscription for given prefix.
382  *
383  * \param prefix prefix
384  * \return true if subscription valid; else false
385  */
386  bool checkSubscriptionAny(const JPrefix_t& prefix) const
387  {
388  return subscriptionAny.find(prefix) != subscriptionAny.end() && checkRequest() && queue.size() < QUEUE_LIMIT;
389  }
390 
391 
392  /**
393  * Check subscription for given prefix.
394  *
395  * \param prefix prefix
396  * \return true if subscription valid; else false
397  */
398  bool checkSubscription(const JPrefix_t& prefix) const
399  {
400  return checkSubscriptionAll(prefix) || checkSubscriptionAny(prefix);
401  }
402 
403 
404  /**
405  * Add message to client queues depending on subscription of each client.
406  * Note that adding a message may result in dropping (other) messages.
407  *
408  * \param message message
409  */
410  void add(const JDispatch& message)
411  {
412  if (checkSubscription(message)) {
413 
414  queue.push_back(message);
415 
416  if (queue.size() > QUEUE_LIMIT) {
417  drop();
418  }
419  }
420  }
421 
422 
423  /**
424  * Drop all messages for which the client has not the 'all' subscription.
425  */
426  void drop()
427  {
428  for (std::deque<JDispatch>::iterator i = queue.begin(); i != queue.end(); ) {
429  if (!checkSubscriptionAll(*i) && (i != queue.begin() || !out.isBusy()))
430  i = queue.erase(i);
431  else
432  ++i;
433  }
434  }
435 
436 
437  JSocketInputChannel_t in; //!< reader for incoming messages
438  JSocketNonblockingWriter out; //!< writer for outgoing messages
439  std::deque<JDispatch> queue; //!< queue for outgoing messages
440 
441  protected:
444  std::string nick_name;
447  };
448 
449 
450  /**
451  * List of ControlHost client managers.
452  */
453  class JClientList :
454  public std::vector<JClient>
455  {
456  public:
457  /**
458  * Default constructor.
459  */
461  std::vector<JClient>()
462  {}
463 
464 
465  /**
466  * Add message to client queues depending on subscription of each client.
467  *
468  * \param message message
469  */
470  void add(const JDispatch& message)
471  {
472  for (iterator i = this->begin(); i != this->end(); ++i) {
473  i->add(message);
474  }
475  }
476 
477 
478  /**
479  * Drop all messages from client queues for which the client has not the 'all' subscription.
480  */
481  void drop()
482  {
483  for (iterator i = this->begin(); i != this->end(); ++i) {
484  i->drop();
485  }
486  }
487  };
488 
489 
490  /**
491  * Print message.
492  *
493  * \param out output stream
494  * \param message message
495  * \return output stream
496  */
497  inline std::ostream& operator<<(std::ostream& out, const JDispatch& message)
498  {
499  return out << "(" << message.getTag() << "," << message.size() << ")";
500  }
501 
502 
503  /**
504  * Print socket.
505  *
506  * \param out output stream
507  * \param socket socket
508  * \return output stream
509  */
510  inline std::ostream& operator<<(std::ostream& out, const JSocket& socket)
511  {
512  return out << "[" << socket.getFileDescriptor() << "]";
513  }
514 
515 
516  /**
517  * Print socket status.
518  *
519  * \param out output stream
520  * \param status socket status
521  * \return output stream
522  */
523  inline std::ostream& operator<<(std::ostream& out, const JSocketStatus& status)
524  {
525  return out << "(" << status.isReady() << "," << status.getCounter() << ")";
526  }
527 
528 
529  /**
530  * Print socket input buffer.
531  *
532  * \param out output stream
533  * \param buffer socket buffer
534  * \return output stream
535  */
536  inline std::ostream& operator<<(std::ostream& out, const JSocketInputBuffer& buffer)
537  {
538  return out << "(" << buffer.isReady() << "," << buffer.getCounter() << "," << buffer.getSize() << ")";
539  }
540 
541 
542  long long int JDispatch::MEMORY_TOTAL = 0; //!< Total memory allocation.
543  long long int JDispatch::MEMORY_LIMIT; //!< Limit memory allocation.
544 
545  unsigned int JClient::QUEUE_LIMIT; //!< queue size limit
546 }
547 
548 
549 /**
550  * \file
551  *
552  * ControlHost server.
553  * \author mdejong
554  */
555 int main(int argc, char* argv[])
556 {
557  using namespace std;
558  using namespace JPP;
559 
560  int port;
561  int backlog;
562  int timeout_us;
563  int buffer_size;
564  int debug;
565 
566  try {
567 
568  JParser<> zap("ControlHost server.");
569 
570  zap['P'] = make_field(port) = DISPATCH_PORT;
571  zap['q'] = make_field(backlog) = 1024;
572  zap['T'] = make_field(timeout_us) = 1;
573  zap['s'] = make_field(buffer_size) = 262144;
574  zap['Q'] = make_field(JClient::QUEUE_LIMIT) = 100;
575  zap['M'] = make_field(JDispatch::MEMORY_LIMIT) = (JSYSTEM::getRAM() >> 1);
576  zap['d'] = make_field(debug) = 0;
577 
578  zap(argc, argv);
579  }
580  catch(const exception &error) {
581  FATAL(error.what() << endl);
582  }
583 
584 
585  JServerSocket server(port, backlog);
586  JSelect select;
587  JClientList clientList;
588 
589 
590  DEBUG("Port " << setw(10) << port << endl);
591  DEBUG("Memory limit " << setw(10) << JDispatch::MEMORY_LIMIT << endl);
592  DEBUG("Queue limit " << setw(10) << JClient::QUEUE_LIMIT << endl);
593 
594 
595  for ( ; ; ) {
596 
597  select.reset();
598  select.setReaderMask(server);
599 
600  for (JClientList::iterator client = clientList.begin(); client != clientList.end(); ++client) {
601 
602  if (!client->in.isReady()) {
603  select.setReaderMask(*client);
604  }
605 
606  if (client->out.isReset()) {
607 
608  if (!client->queue.empty()) {
609 
610  DEBUG("Client" << *client << ".set" << client->queue.front() << endl);
611 
612  client->out.set(client->queue.front());
613  client->decrementRequest();
614 
615  select.setWriterMask(*client);
616  }
617 
618  } else if (client->out.isBusy()) {
619 
620  select.setWriterMask(*client);
621  }
622  }
623 
624  if (select(timeout_us) > 0) {
625 
626  for (JClientList::iterator client = clientList.begin(); client != clientList.end(); ) {
627 
628  try {
629 
630  if (select.hasReaderMask(*client)) {
631 
632  try {
633  client->in.read();
634  }
635  catch(const exception& error) {
636 
637  ERROR("Remove (3) client" << *client << "<" << client->getNickname() << ">: " << error.what() << endl);
638 
639  if (client->getNickname() != "") {
640  clientList.add(JDispatch(DISPTAG_Died, client->getNickname()));
641  }
642 
643  client->shutdown();
644 
645  client = clientList.erase(client);
646 
647  continue;
648  }
649 
650  DEBUG("Client" << *client << ".read" << static_cast<const JSocketInputBuffer&>(client->in) << endl);
651  }
652 
653  if (client->in.isReady()) {
654 
655  DEBUG("Message" << *client << ' ' << client->in.prefix.c_str() << ' ' << client->in.size() << endl);
656 
657  bool special = JControlHost::maybe_special(client->in.prefix);
658 
659  if (special) {
660 
661  client->in.seekg(sizeof(JPrefix_t)); // skip prefix
662 
663  if (client->in.prefix.getTag() == DISPTAG_Subscribe) {
664 
665  client->setSubscription(string(client->in.getRemainingData(), client->in.getRemainingSize()));
666 
667  } else if (client->in.prefix.getTag() == DISPTAG_MyId) {
668 
669  client->setNickname(string(client->in.getRemainingData(), client->in.getRemainingSize()));
670 
671  clientList.add(JDispatch(DISPTAG_Born, client->getNickname()));
672 
673  } else if (client->in.prefix.getTag() == DISPTAG_Gime) {
674 
675  client->incrementRequest();
676 
677  } else if (client->in.prefix.getTag() == DISPTAG_Always) {
678 
679  client->setRequestAll();
680 
681  } else if (client->in.prefix.getTag() == DISPTAG_WhereIs) {
682 
683  string nick_name(client->in.getRemainingData(), client->in.getRemainingSize());
684  string buffer;
685 
686  for (JClientList::iterator i = clientList.begin(); i != clientList.end(); ++i) {
687  if (i->getNickname() == nick_name) {
688  buffer += " " + i->getHostname();
689  }
690  }
691 
692  JControlHost socket(*client);
693 
694  socket.PutFullString(DISPTAG_WhereIs, buffer.substr(buffer.empty() ? 0 : 1));
695 
696  DEBUG("Remove (1) client" << *client << endl);
697 
698  client->shutdown();
699 
700  client = clientList.erase(client);
701 
702  continue; // skip any action
703 
704  } else {
705 
706  special = false; // not a reserved tag.
707  }
708  }
709 
710  if (!special) {
711 
712  clientList.add(JDispatch(client->in.prefix, client->in.data()));
713 
715 
716  WARNING("Memory " << setw(10) << JDispatch::MEMORY_TOTAL << " > " << setw(10) << JDispatch::MEMORY_LIMIT << endl);
717 
718  clientList.drop();
719  }
720  }
721 
722  client->in.reset();
723  }
724 
725  if (select.hasWriterMask(*client)) {
726 
727  client->out.write();
728 
729  DEBUG("Client" << *client << ".write" << static_cast<const JSocketStatus&>(client->out) << endl);
730 
731  if (client->out.isReady()) {
732  client->out.reset();
733  client->queue.pop_front();
734  }
735  }
736 
737  ++client;
738  }
739  catch(const exception& error) {
740 
741  DEBUG("Remove (2) client" << *client << "<" << client->getNickname() << ">: " << error.what() << endl);
742 
743  if (client->getNickname() != "") {
744  clientList.add(JDispatch(DISPTAG_Died, client->getNickname()));
745  }
746 
747  client->shutdown();
748 
749  client = clientList.erase(client);
750  }
751  }
752 
753  if (select.hasReaderMask(server)) {
754 
755  JSocket socket;
756 
757  socket.accept(server.getFileDescriptor());
758 
759  socket.setSendBufferSize (buffer_size);
760  socket.setReceiveBufferSize(buffer_size);
761 
762  socket.setKeepAlive (true);
763  socket.setNonBlocking(true);
764 
765  DEBUG("New client" << socket << endl);
766 
767  clientList.push_back(JClient(socket));
768  }
769  }
770  }
771 }
JSocketNonblockingWriter out
writer for outgoing messages
Definition: JLigier.cc:438
ControlHost prefix.
Definition: JPrefix.hh:31
bool setSubscription(const std::string &subscription)
Set subcription.
Definition: JLigier.cc:342
Utility class to parse command line options.
Definition: JParser.hh:1410
static const JTag DISPTAG_Subscribe("_Subscri")
Special ControlHost tags.
std::set< JTag > subscriptionAll
Definition: JLigier.cc:442
int getSizeOfPacket(const KM3NETDAQ::JDAQAbstractPreamble &preamble)
Definition: JDataFilter.cc:81
static const int DISPATCH_PORT
Default ControlHost port.
Definition: JHostname.hh:24
#define WARNING(A)
Definition: JMessage.hh:63
JDispatch & operator=(const JDispatch &message)
Assignment operator.
Definition: JLigier.cc:153
static const JTag DISPTAG_Died("Died")
Shared counter.
Data structure of a ControlHost message.
Definition: JLigier.cc:63
JSocketBuffer< const char > JSocketOutputBuffer
void setRequestAll()
Set no request.
Definition: JLigier.cc:330
ControlHost client manager.
Definition: JLigier.cc:241
std::deque< JDispatch > queue
queue for outgoing messages
Definition: JLigier.cc:439
void release()
Release memory.
Definition: JLigier.cc:227
Auxiliary class for non-blocking socket I/O.
List of ControlHost client managers.
Definition: JLigier.cc:453
std::set< JTag > subscriptionAny
Definition: JLigier.cc:443
static const JTag DISPTAG_MyId("_MyId")
void attach(const JSharedCounter &object)
Attach this counter to given shared counter object.
bool requestAll
Definition: JLigier.cc:445
void setNickname(const std::string &nick_name)
Set nick name.
Definition: JLigier.cc:292
static const JTag DISPTAG_WhereIs("_WhereIs")
bool checkRequest() const
Check request.
Definition: JLigier.cc:303
bool checkSubscription(const JPrefix_t &prefix) const
Check subscription for given prefix.
Definition: JLigier.cc:398
Socket class.
Definition: JSocket.hh:42
void setSize(const int length)
Set size.
Definition: JPrefix.hh:74
JClient()
Default constructor.
Definition: JLigier.cc:253
const std::string & getNickname() const
Get nick name.
Definition: JLigier.cc:281
char tag[TAGSIZE]
Definition: JTag.hh:247
void decrementRequest()
Decrement request by one.
Definition: JLigier.cc:321
bool checkSubscriptionAll(const JPrefix_t &prefix) const
Check subscription for given prefix.
Definition: JLigier.cc:374
static long long int MEMORY_LIMIT
Limit size of data [Bytes].
Definition: JLigier.cc:72
int getSize() const
Get size.
Definition: JPrefix.hh:63
JClientList()
Default constructor.
Definition: JLigier.cc:460
JPrefix JPrefix_t
Definition: JLigier.cc:28
static void release(T *p)
Release memory.
Definition: JMemory.hh:112
bool checkSubscriptionAny(const JPrefix_t &prefix) const
Check subscription for given prefix.
Definition: JLigier.cc:386
int requestCounter
Definition: JLigier.cc:446
int getFileDescriptor() const
Get file descriptor.
JSocketInputChannel_t in
reader for incoming messages
Definition: JLigier.cc:437
int getCounter() const
Get number of I/O attempts.
std::ostream & operator<<(std::ostream &out, const JDispatch &message)
Print message.
Definition: JLigier.cc:497
Non-blocking socket writer.
void incrementRequest()
Increment request by one.
Definition: JLigier.cc:312
#define make_field(A,...)
macro to convert parameter to JParserTemplateElement object
Definition: JParser.hh:1836
bool isReady() const
Check ready status.
void add(const JDispatch &message)
Add message to client queues depending on subscription of each client.
Definition: JLigier.cc:470
Memory management class for create/release policy based on malloc()/free().
Definition: JMemory.hh:82
Exception for failure of memory allocation.
Definition: JException.hh:270
static const size_t buffer_size
JDispatch()
Default constructor.
Definition: JLigier.cc:77
#define ERROR(A)
Definition: JMessage.hh:64
void drop()
Drop all messages for which the client has not the &#39;all&#39; subscription.
Definition: JLigier.cc:426
JDispatch(const JPrefix_t &prefix, const char *data)
Constructor.
Definition: JLigier.cc:91
JDispatch(const JDispatch &message)
Copy constructor.
Definition: JLigier.cc:127
void create()
Allocate memory.
Definition: JLigier.cc:207
void drop()
Drop all messages from client queues for which the client has not the &#39;all&#39; subscription.
Definition: JLigier.cc:481
Exception for ControlHost.
Definition: JException.hh:432
int debug
debug level
Definition: JSirene.cc:59
JClient(const JSocket &socket)
Constructor.
Definition: JLigier.cc:267
Socket input channel.
const JTag & getTag() const
Get tag.
Definition: JTag.hh:82
static bool maybe_special(const JTag &tag)
Check special ControlHost tags.
General purpose messaging.
void initialise()
Initialise counter.
#define FATAL(A)
Definition: JMessage.hh:65
static const JTag DISPTAG_Born("Born")
static long long int MEMORY_TOTAL
Total size of data [Bytes].
Definition: JLigier.cc:71
JMalloc< char > JMemory_t
Definition: JLigier.cc:69
static unsigned int QUEUE_LIMIT
Maximum number of messages in queue.
Definition: JLigier.cc:247
const char * data() const
Get data.
Definition: JLigier.cc:197
Utility class to parse command line options.
void add(const JDispatch &message)
Add message to client queues depending on subscription of each client.
Definition: JLigier.cc:410
unsigned long long int getRAM()
Get RAM of this CPU.
static const JTag DISPTAG_Gime("_Gime")
int getSize() const
Get size of pending data.
System calls via shell interactions.
~JDispatch()
Destructor.
Definition: JLigier.cc:140
char * buffer
Definition: JLigier.cc:234
JSocketInputChannel< JPrefix_t > JSocketInputChannel_t
Definition: JLigier.cc:29
void setSizeOfPacket(const int size, JPrefix_t &prefix)
Set total size of internet packet.
Definition: JLigier.cc:50
bool detach()
Detach.
ControlHost tag.
Definition: JTag.hh:35
static const JTag DISPTAG_Always("_Always")
Base class for memory management.
Auxiliary class for non-blocking socket I/O.
static T * create()
Create object in memory.
Definition: JMemory.hh:89
bool isBusy() const
Check busy status.
std::string nick_name
Definition: JLigier.cc:444
int size() const
Get size.
Definition: JLigier.cc:186
JDispatch(const JTag &tag, const std::string &message)
Constructor.
Definition: JLigier.cc:109
#define DEBUG(A)
Message macros.
Definition: JMessage.hh:60
int main(int argc, char *argv[])
Definition: Main.cpp:15