[atlantik-cvs] CVS: kdegames/atlantik/libatlantikclient klatencytimer.h,NONE,1.1 klatencytimer.cpp,NONE,1.1

[email protected] Thu, 24 Jul 2003 20:12:30 +0200 (CEST)
Newsgroups gmane.comp.kde.devel.atlantik
Message-ID <[email protected]>
Update of /home/kde/kdegames/atlantik/libatlantikclient
In directory office:/tmp/cvs-serv32530

Added Files:
	klatencytimer.h klatencytimer.cpp 
Log Message:
Initial revision of the KLatencyTimer class: simple UDP-based latency measurer


--- NEW FILE: klatencytimer.h ---
/*
 *  Copyright (C) 2003 Thiago Macieira <[email protected]>
 *
 *  This library is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU Library General Public
 *  License as published by the Free Software Foundation; either
 *  version 2 of the License, or (at your option) any later version.
 *
 *  This library is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 *  Library General Public License for more details.
 *
 *  You should have received a copy of the GNU Library General Public License
 *  along with this library; see the file COPYING.LIB.  If not, write to
 *  the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
 *  Boston, MA 02111-1307, USA.
 */
#ifndef KLATENCYTIMER_H
#define KLATENCYTIMER_H

#include <qobject.h>
#include <qstring.h>

/////////////
// forward declarations

// POSIX structs
struct in_addr;
struct in6_addr;

// from standard Qt
class QHostAddress;

// from libqt-addon
class QIpAddress;
class QSocketAddress;

// from KDE
class KSocketAddress;

class KLatencyTimerPrivate;
/**
 * Simple measurement of latency, similar to ping.
 *
 * This class does a simple measurement of the latency to reach
 * the remote host. It is similar in behaviour to the system's
 * ping command, but it's not a ping and it also does not execute
 * any external programs.
 *
 * The method used consists of sending a dummy UDP packet to a
 * given or random port on the target host. When we receive the
 * reply telling us that the port is unreachable, we calculate
 * the time it took.
 *
 * @warning The method will time-out if we end up hitting an open
 *          port or if it's blocked by firewall. Also note that
 *          if we hit an open port, the other side may not like
 *          the dummy packet we sent.
 *
 * @author Thiago Macieira
 * @version 0.9
 * $Id: klatencytimer.h,v 1.1 2003/07/24 18:12:27 thiago Exp $
 */
class KLatencyTimer: public QObject
{
  Q_OBJECT

public:
  /**
   * Default constructor.
   *
   * @param port	the port to send the packet to
   *			or -1 to choose one randomly
   */
  explicit KLatencyTimer(int port = -1, QObject* parent = 0L, const char *name = 0L);

  /**
   * Destructor.
   */
  virtual ~KLatencyTimer();

  /**
   * Retrieves the stored port number
   */
  int port() const
  { return m_port; }

  /**
   * Sets the port to the given value. Use value -1 to
   * make the class select a port number.
   *
   * @param port	the port to send the packet to
   *			or -1 to choose one randomly
   */
  void setPort(int port);

  /**
   * Sets the target host. The @p host parameter must be
   * the string representation of a numeric IP address
   * (v4 or v6). Host names must be resolved by other
   * lookup means.
   *
   * @param node	the target host
   */
  void setHost(const QString& node);

  /**
   * @overload
   *
   * Sets the target host.
   *
   * @param node	the target host
   */
  void setHost(const in_addr& node);

  /**
   * @overload
   *
   * Sets the target host.
   *
   * @param node	the target host
   */
  void setHost(const in6_addr& node);

  /**
   * @overload
   *
   * Sets the target host.
   *
   * @param node	the target host
   */
  void setHost(const QHostAddress& node);

  /**
   * @overload
   *
   * Sets the target host.
   *
   * @param node	the target host
   */
  void setHost(const QIpAddress& node);

  /**
   * @overload
   *
   * Sets the target host from the host component
   * of the socket address.
   *
   * @param node	the target host
   */
  void setHost(const QSocketAddress& node);

  /**
   * @overload
   *
   * Sets the target host from the host component
   * of the socket address.
   *
   * @param node	the target host
   */
  void setHost(const KSocketAddress* node);

  /**
   * Starts the measurement.
   */
  void start();

  /**
   * Returns true if there has been a reply from the
   * target host.
   */
  bool finished() const
  { return !m_running; }

  /**
   * Returns true if there was an error.
   */
  bool error() const
  { return m_error; }

  /**
   * Returns the elapsed time since start. If the process
   * is not @ref finished, subsequent calls will return
   * different values. If it is finished, the value
   * won't change.
   *
   * @return the time in milliseconds
   */
  int elapsed() const;

public slots:
  /**
   * Cancels a running measurement.
   *
   * This function is provided as a slot so that you can make
   * constructs like following to create a time-out mechanism:
   *
   * \code
   *    // time out after 2 seconds
   *	QTimer::singleShot(2000, latencytimer, SLOT(cancel()));
   * \endcode
   */
  void cancel();

signals:
  /**
   * Signals the receipt of an answer.
   *
   * @param msec	the time in milliseconds that was given as result
   */
  void answer(int msec);

private slots:
  void activity();

private:
  int m_port;
  bool m_running;
  bool m_error;
  KLatencyTimerPrivate *d;
};

#endif


--- NEW FILE: klatencytimer.cpp ---
/*
 *  Copyright (C) 2003 Thiago Macieira <[email protected]>
 *
 *  This library is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU Library General Public
 *  License as published by the Free Software Foundation; either
 *  version 2 of the License, or (at your option) any later version.
 *
 *  This library is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 *  Library General Public License for more details.
 *
 *  You should have received a copy of the GNU Library General Public License
 *  along with this library; see the file COPYING.LIB.  If not, write to
 *  the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
 *  Boston, MA 02111-1307, USA.
 */

#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <errno.h>
#include <fcntl.h>
#include <stdlib.h>
#include <unistd.h>

#include <qobject.h>
#include <qstring.h>
#include <qdatetime.h>
#include <qhostaddress.h>
#include <qsocketnotifier.h>

#include <ksockaddr.h>

#ifdef USE_QT_ADDON
# include <qsocketaddress.h>
#endif

#include "klatencytimer.h"

/*
 * temporary class exposing the needed functionality
 */
#ifdef USE_QT_ADDON
class SocketAddress : public QInetSocketAddress
{
public:
  void setHost(const QString& host)
  { QInetSocketAddress::setHost(QIpAddress(host)); }

  void setHost(const in_addr& addr)
  { QInetSocketAddress::setHost(QIpAddress(&addr, 4)); }

  void setHost(const in6_addr& addr)
  { QInetSocketAddress::setHost(QIpAddress(&addr, 6)); }

  //using QInetAddress::setHost(const QIpAddress&);
  void setHost(const QIpAddress& addr)
  { QInetSocketAddress::setHost(addr); }

  unsigned size() const
  { return length(); }
};
#else
class SocketAddress : public KInetSocketAddress
{
};
#endif

class KLatencyTimerPrivate
{
public:
  SocketAddress target;
  QTime time;
  QSocketNotifier* notifier;
  int sock;
  int elapsed;

  KLatencyTimerPrivate()
    : notifier(0L)
  {
  }
};

KLatencyTimer::KLatencyTimer(int port, QObject* parent, const char* name)
  : QObject(parent, name), m_port(port), m_running(false), m_error(false),
    d(new KLatencyTimerPrivate)
{
}

KLatencyTimer::~KLatencyTimer()
{
  cancel();
  delete d;
}

void KLatencyTimer::setPort(int port)
{
  if (m_running)
    return;

  if (port < 0 || port > 0xffff)
    m_port = -1;
  else
    m_port = port;
}

void KLatencyTimer::setHost(const QString& node)
{
  d->target.setHost(node);
}

void KLatencyTimer::setHost(const QHostAddress& node)
{
  if (!node.isIp4Addr())
    return;
  in_addr addr;
  addr.s_addr = node.ip4Addr();
  d->target.setHost(addr);
}

#ifdef USE_QT_ADDON
void KLatencyTimer::setHost(const QIpAddress& node)
{
  d->target.setHost(node);
}

void KLatencyTimer::setHost(const QSocketAddress& node)
{
  if (node.inet().ipVersion() != 0)
    d->target.setHost(node.inet().ipAddress());
}
#endif

void KLatencyTimer::setHost(const KSocketAddress* node)
{
  const KInetSocketAddress *nodeinet = dynamic_cast<const KInetSocketAddress*>(node);
  if (nodeinet == 0L)
    return;
  if (nodeinet->size() == sizeof(sockaddr_in))
    d->target.setHost(nodeinet->hostV4());
  else
    d->target.setHost(nodeinet->hostV6());
}

void KLatencyTimer::start()
{
  if (m_running)
    return;			// already running

  d->sock = ::socket(d->target.family(), SOCK_DGRAM, IPPROTO_UDP);
  if (d->sock == -1)
    {
      m_error = true;
      return;			// error
    }

  // set non-blocking mode
  int fdflags = fcntl(d->sock, F_GETFL, 0);
  if (fdflags == -1)
    {
      m_error = true;
      return;			// error
    }
  fdflags |= O_NONBLOCK;

  if (fcntl(d->sock, F_SETFL, fdflags) == -1)
    {
      m_error = true;
      return;			// error
    }

  d->notifier = new QSocketNotifier(d->sock, QSocketNotifier::Read);
  d->notifier->setEnabled(true);
  connect(d->notifier, SIGNAL(activated(int)), this, SLOT(activity()));

  if (m_port != -1)
    d->target.setPort(m_port);
  else
    d->target.setPort(rand());

  ::connect(d->sock, d->target.address(), d->target.size());

  // now send a dummy packet to the destination
  static const char dummydata[] = "PING";
  m_running = true;
  m_error = false;
  d->time.start();
  ::write(d->sock, dummydata, sizeof dummydata - 1);
}

int KLatencyTimer::elapsed() const
{
  if (m_running)
    return d->time.elapsed();
  else
    return d->elapsed;
}

void KLatencyTimer::cancel()
{
  if (!m_running)
    return;

  delete d->notifier;
  ::close(d->sock);
  m_running = false;
  m_error = false;
}

void KLatencyTimer::activity()
{
  if (!m_running)
    return;			// error!

  // retrieve the socket status
  int errcode;
  socklen_t len = sizeof(errcode);
  if (getsockopt(d->sock, SOL_SOCKET, SO_ERROR, (char*)&errcode, &len) == -1)
    {
      m_error = true;
      return; 			// error!
    }

  if (errcode != ECONNREFUSED)
    m_error = true;
  else
    m_error = false;

  d->elapsed = d->time.elapsed();
  delete d->notifier;
  close(d->sock);
  m_running = false;

  emit answer(d->elapsed);
}

#include "klatencytimer.moc"