You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
tdelibs/kdecore/network/kstreamsocket.cpp

369 lines
8.7 KiB

/* -*- C++ -*-
* Copyright (C) 2003 Thiago Macieira <thiago.macieira@kdemail.net>
*
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be included
* in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#include <config.h>
#include <tqsocketnotifier.h>
#include <tqdatetime.h>
#include <tqtimer.h>
#include <tqguardedptr.h>
#include "ksocketaddress.h"
#include "kresolver.h"
#include "ksocketdevice.h"
#include "kstreamsocket.h"
using namespace KNetwork;
class KNetwork::KStreamSocketPrivate
{
public:
KResolverResults::ConstIterator local, peer;
TQTime startTime;
TQTimer timer;
int timeout;
inline KStreamSocketPrivate()
: timeout(0)
{ }
};
KStreamSocket::KStreamSocket(const TQString& node, const TQString& service,
TQObject* parent, const char *name)
: KClientSocketBase(parent, name), d(new KStreamSocketPrivate)
{
peerResolver().setNodeName(node);
peerResolver().setServiceName(service);
peerResolver().setFamily(KResolver::KnownFamily);
localResolver().setFamily(KResolver::KnownFamily);
setSocketOptions(socketOptions() & ~Blocking);
TQObject::connect(&d->timer, TQT_SIGNAL(timeout()), this, TQT_SLOT(timeoutSlot()));
}
KStreamSocket::~KStreamSocket()
{
delete d;
// KClientSocketBase's destructor closes the socket
}
int KStreamSocket::timeout() const
{
return d->timeout;
}
int KStreamSocket::remainingTimeout() const
{
if (state() != Connecting)
return timeout();
if (timeout() <= 0)
return 0;
return timeout() - d->startTime.elapsed();
}
void KStreamSocket::setTimeout(int msecs)
{
d->timeout = msecs;
if (state() == Connecting)
d->timer.changeInterval(msecs);
}
bool KStreamSocket::bind(const TQString& node, const TQString& service)
{
if (state() != Idle)
return false;
if (!node.isNull())
localResolver().setNodeName(node);
if (!service.isNull())
localResolver().setServiceName(service);
return true;
}
bool KStreamSocket::connect(const TQString& node, const TQString& service)
{
if (state() == Connected)
return true; // already connected
if (state() > Connected)
return false; // can't do much here
if (!node.isNull())
peerResolver().setNodeName(node);
if (!service.isNull())
peerResolver().setServiceName(service);
if (state() == Connecting && !blocking())
{
setError(IO_ConnectError, InProgress);
emit gotError(InProgress);
return true; // we're already connecting
}
if (state() < HostFound)
{
// connection hasn't started yet
if (!blocking())
{
QObject::connect(this, TQT_SIGNAL(hostFound()), TQT_SLOT(hostFoundSlot()));
return lookup();
}
// blocking mode
if (!lookup())
return false; // lookup failure
}
/*
* lookup results are available here
*/
if (timeout() > 0)
{
if (!blocking() && !d->timer.isActive())
d->timer.start(timeout(), true);
else
{
// blocking connection with timeout
// this must be handled as a special case because it requires a
// non-blocking socket
d->timer.stop(); // no need for a timer here
socketDevice()->setBlocking(false);
while (true)
{
connectionEvent();
if (state() < Connecting)
return false; // error connecting
if (state() == Connected)
return true; // connected!
if (remainingTimeout() <= 0)
{
// we've timed out
timeoutSlot();
return false;
}
if (socketDevice()->error() == InProgress)
{
bool timedout;
socketDevice()->poll(remainingTimeout(), &timedout);
if (timedout)
{
timeoutSlot();
return false;
}
}
}
}
}
connectionEvent();
return error() == NoError;
}
bool KStreamSocket::connect(const KResolverEntry& entry)
{
return KClientSocketBase::connect(entry);
}
void KStreamSocket::hostFoundSlot()
{
QObject::disconnect(this, TQT_SLOT(hostFoundSlot()));
if (timeout() > 0)
d->timer.start(timeout(), true);
TQTimer::singleShot(0, this, TQT_SLOT(connectionEvent()));
}
void KStreamSocket::connectionEvent()
{
if (state() != HostFound && state() != Connecting)
return; // nothing to do
const KResolverResults& peer = peerResults();
if (state() == HostFound)
{
d->startTime.start();
setState(Connecting);
emit stateChanged(Connecting);
d->peer = peer.begin();
d->local = localResults().begin(); // just to be on the safe side
}
while (d->peer != peer.end())
{
const KResolverEntry &r = *d->peer;
if (socketDevice()->socket() != -1)
{
// we have an existing file descriptor
// this means that we've got activity in it (connection result)
if (socketDevice()->connect(r) && socketDevice()->error() == NoError)
{
// yes, it did connect!
connectionSucceeded(r);
return;
}
else if (socketDevice()->error() == InProgress)
// nope, still in progress
return;
// no, the socket failed to connect
copyError();
socketDevice()->close();
++d->peer;
continue;
}
// try to bind
if (!bindLocallyFor(r))
{
// could not find a matching family
++d->peer;
continue;
}
{
bool skip = false;
emit aboutToConnect(r, skip);
if (skip)
{
++d->peer;
continue;
}
}
if (socketDevice()->connect(r) || socketDevice()->error() == InProgress)
{
// socket is attempting to connect
if (socketDevice()->error() == InProgress)
{
TQSocketNotifier *n = socketDevice()->readNotifier();
TQObject::connect(n, TQT_SIGNAL(activated(int)),
this, TQT_SLOT(connectionEvent()));
n->setEnabled(true);
n = socketDevice()->writeNotifier();
TQObject::connect(n, TQT_SIGNAL(activated(int)),
this, TQT_SLOT(connectionEvent()));
n->setEnabled(true);
return; // wait for activity
}
// socket has connected
connectionSucceeded(r);
return;
}
// connection failed
// try next
copyError();
socketDevice()->close();
++d->peer;
}
// that was the last item
socketDevice()->setSocketOptions(socketOptions());
setState(Idle);
emit stateChanged(Idle);
emit gotError(error());
return;
}
void KStreamSocket::timeoutSlot()
{
if (state() != Connecting)
return;
// halt the connections
socketDevice()->close(); // this also kills the notifiers
setError(IO_TimeOutError, Timeout);
setState(HostFound);
emit stateChanged(HostFound);
TQGuardedPtr<KStreamSocket> that = this;
emit gotError(Timeout);
if (!that.isNull())
emit timedOut();
}
bool KStreamSocket::bindLocallyFor(const KResolverEntry& peer)
{
const KResolverResults& local = localResults();
if (local.isEmpty())
// user doesn't want to bind to any specific local address
return true;
bool foundone = false;
// scan the local resolution for a matching family
for (d->local = local.begin(); d->local != local.end(); ++d->local)
if ((*d->local).family() == peer.family())
{
// found a suitable address!
foundone = true;
if (socketDevice()->bind(*d->local))
return true;
}
if (!foundone)
{
// found nothing
setError(IO_BindError, NotSupported);
emit gotError(NotSupported);
}
else
copyError();
return false;
}
void KStreamSocket::connectionSucceeded(const KResolverEntry& peer)
{
TQObject::disconnect(socketDevice()->readNotifier(), 0, this, TQT_SLOT(connectionEvent()));
TQObject::disconnect(socketDevice()->writeNotifier(), 0, this, TQT_SLOT(connectionEvent()));
resetError();
setFlags(IO_Sequential | IO_Raw | IO_ReadWrite | IO_Open | IO_Async);
setState(Connected);
socketDevice()->setSocketOptions(socketOptions());
d->timer.stop();
emit stateChanged(Connected);
if (!localResults().isEmpty())
emit bound(*d->local);
emit connected(peer);
}
#include "kstreamsocket.moc"