Logo Search packages:      
Sourcecode: kmess version File versions

directconnectionbase.cpp

/***************************************************************************
                          directconnectionbase.cpp -  description
                             -------------------
    begin                : Tue 12 27 2005
    copyright            : (C) 2005 by Diederik van der Boor
    email                : vdboor --at-- codingdomain.com
 ***************************************************************************/

/***************************************************************************
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 2 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 ***************************************************************************/

#include "directconnectionbase.h"

#include "../../utils/kmessconfig.h"
#include "../../kmessdebug.h"

#include <QHostAddress>
#include <QTcpServer>
#include <QTcpSocket>
#include <QBuffer>

#include <KConfigGroup>


#ifdef KMESSDEBUG_DIRECTCONNECTION
  #define KMESSDEBUG_DIRECTCONNECTION_GENERAL
#endif


quint16 DirectConnectionBase::nextServerPort_( 0 );



// The constructor
DirectConnectionBase::DirectConnectionBase( QObject *parent )
  : QObject(parent)
  , authorized_(false)
  , isServer_(false)
  , lastWriteFailed_(false)
  , server_(0)
  , serverPort_(0)
  , socket_(0)
  , timeout_(false)
  , writeHandlerCount_(0)
{
  connect( &connectionTimer_, SIGNAL(               timeout() ),
           this,              SLOT  ( slotConnectionTimeout() ));

  // Read the interval of ports which we'll use to try establishing a connection
  KConfigGroup group = KMessConfig::instance()->getGlobalConfig( "General" );
  lowestServerPortLimit_  = (quint16)group.readEntry( "lowestTransferPort", 6891 );
  highestServerPortLimit_ = (quint16)group.readEntry( "highestTransferPort",  6900 );
#ifdef KMESSDEBUG_DIRECTCONNECTION_GENERAL
  kDebug() << "Using port interval" << lowestServerPortLimit_ << "-" << highestServerPortLimit_;
#endif

  connectionTimer_.setSingleShot( true );
}



// The destructor
DirectConnectionBase::~DirectConnectionBase()
{
  // Close and clean up
  if(socket_ != 0 || server_ != 0)
  {
    closeConnection();
  }

#ifdef KMESSDEBUG_DIRECTCONNECTION_GENERAL
  kDebug() << "DESTROYED. [connectTo=" << connectingTo_ << " wasConnected=" << isConnected() << " wasServer=" << isServer_ << " wasAuthorized=" <<  authorized_ << "]";
#endif
}



// Close the connection
void DirectConnectionBase::closeConnection()
{
#ifdef KMESSDEBUG_DIRECTCONNECTION_GENERAL
  kDebug() << "closing sockets";
#endif

  // Reset globals
  // Also see slotSocketDisconnected()
  isServer_   = false;
  authorized_ = false;
  connectionTimer_.stop();

  // Check whether we should emit a signal:
  bool closed = closeClientSocket() || closeServerSocket();

  // Signal (only once) that the connection was closed
  if(closed)
  {
    emit connectionClosed();
  }
}



// Close and delete the client socket
// if the socket_ is already deleted, this method does nothing
bool DirectConnectionBase::closeClientSocket()
{
  connectionTimer_.stop();

  // Notify the socket was already closed.
  if( socket_ == 0 )
  {
    return false;
  }

#ifdef KMESSDEBUG_DIRECTCONNECTION_GENERAL
  kDebug() << "destroying client socket.";
#endif

#ifdef KMESS_NETWORK_WINDOW
  KMESS_NET_CLOSE(this);
#endif

  // Close the socket and delete it.
  // Use deleteLater since it could be called from slotSocketError()
  // block signals before disconnecting - otherwise we might find ourselves crashing.
  socket_->blockSignals( true );
  socket_->disconnectFromHost();
  socket_->deleteLater();
  socket_ = 0;
  writeHandlerCount_ = 0;

#ifdef KMESSDEBUG_DIRECTCONNECTION_GENERAL
  kDebug() << "destroyed client socket.";
#endif

  return true;
}



// Close and delete the server socket
bool DirectConnectionBase::closeServerSocket()
{
#ifdef KMESSTEST
  KMESS_ASSERT( socket_ == 0 || server_ != 0 );  // nextPendingConnection() turns socket_ into a child of server_ 
#endif

  connectionTimer_.stop();

  // Notify the socket was already closed.
  if( KMESS_NULL( server_ ) ) return false;

#ifdef KMESSDEBUG_DIRECTCONNECTION_GENERAL
  if( socket_ == 0 )
  {
    kDebug() << "destroying server socket.";
  }
  else
  {
    kDebug() << "closing server socket. Object will not be destroyed yet because a socket is connected.";
  }
#endif

  // Close the server
  server_->close();

  // Can't delete the object if there is still a socket_
  // because socket_ becomes a qobject child of server_.
  if( socket_ == 0 )
  {
    // Use deleteLater since it could be called from slotSocketError()
    server_->deleteLater();
    server_ = 0;
  }

  return true;
}



// Register a slot which will be called each time the write buffer is empty (for autonomous/asynchonous writing).
void DirectConnectionBase::connectWriteHandler(QObject *receiver, const char *slot)
{
  if( KMESS_NULL(socket_) ) return;

  // Enable the readyWrite signal.
  // This is invoked each time data is written off the write-buffer.
  // meaning the derived class can send data fast in a autonomous way.

  // When you don't need to write commands very often,
  // this will generate a lot of of CPU overhead.
  // Since our socket is buffered, writeBlock() can be called directly too.

  // Just before KMess 1.5, we've added an internal slot here.
  // If a block can't be written properly,
  // it's remaining part will be buffered here to be sent ASAP.
  // That why there is a slotSocketReadyWrite() between the receiver and the socket now.

  if( writeHandlerCount_++ <= 0 )
  {
    connect( socket_, SIGNAL(bytesWritten(qint64)), this, SLOT(slotSocketReadyWrite(qint64)) );
  }

  connect( this, SIGNAL(writeHandlerReady()), receiver, slot );

  // Starting with Qt 4, there is no real "readyWrite()" signal anymore,
  // only a bytesWritten(). We use that signal to fake the existance of a readyWrite().
  // Start the first round now.
  emit writeHandlerReady();
}



// Unregister the slot which will be called each time the write buffer is empty.
void DirectConnectionBase::disconnectWriteHandler(QObject *receiver, const char *slot)
{
  if( KMESS_NULL(socket_) ) return;

  // If all listeners are unset, disable the signal again.
  if( --writeHandlerCount_ <= 0 )
  {
    disconnect( socket_, SIGNAL(bytesWritten(qint64)), this, SLOT(slotSocketReadyWrite(qint64)) );
  }

  disconnect( this, SIGNAL(writeHandlerReady()), receiver, slot );
}



// Return the number of bytes which are already received in the local buffer
qint64 DirectConnectionBase::getAvailableBytes() const
{
  if(KMESS_NULL(socket_)) return -2;  // same as KExtendedSocket API, means "invalid state"
  return socket_->bytesAvailable();
}



// Get the server port that will be used
quint16 DirectConnectionBase::getLocalServerPort()
{
  // Port not chosen yet
  if( serverPort_ == 0 )
  {
    // It's possible to use a random port nowadays,
    // old clients with the "only port 6891" are no longer in use.
    //
    // KMess continues to use ports between 6891-6900 so it's easier
    // to firewall these.
    //
    // TODO: verify whether we can use the port by calling openServerPort() earlier.

    if( nextServerPort_ == 0 || nextServerPort_ > highestServerPortLimit_ )
    {
      nextServerPort_ = lowestServerPortLimit_;
    }

    serverPort_ = nextServerPort_;
    nextServerPort_++;

#ifdef KMESSDEBUG_DIRECTCONNECTION_GENERAL
    kDebug() << "Reserved port " << serverPort_ << " for server socket.";
#endif
  }

  return serverPort_;
}



// Get the remote ip the socket is connected with.
QString DirectConnectionBase::getRemoteIp() const
{
  if(socket_ == 0)
  {
    return QString::null;
  }

  QHostAddress address = socket_->peerAddress();
  return address.toString();
}



// Get the remote port the socket is connected with.
quint16 DirectConnectionBase::getRemotePort() const
{
  if(socket_ == 0)
  {
    return 0;
  }
  else
  {
    return socket_->peerPort();
  }
}



// Return the error description
QString DirectConnectionBase::getSocketError() const
{
  if( timeout_ )
  {
    return "connection timeout event";
  }
  else if(socket_ == 0)
  {
    return QString::null;
  }
  else
  {
    return socket_->errorString();
  }
}



// Return true if the last write action failed.
bool DirectConnectionBase::hasLastWriteFailed() const
{
  return lastWriteFailed_;
}



// Return true when the write buffer is full
bool DirectConnectionBase::hasTemporaryWriteError() const
{
  return ! additionalWriteBuffer_.isEmpty();
}



// Find out if the connection has been inactive since 15 minutes
bool DirectConnectionBase::hasTimedOut() const
{
  // The int returned by QTime::elapsed() is in milliseconds, so it's 900 seconds or 15 min
  return ( lastActivity_.elapsed() > 900000 );
}



// Initialize the connection, return true when this was succesful
bool DirectConnectionBase::initialize()
{
  // The base class doesn't need any initialisation, MsnDirectConnection does.
  return true;
}



// Return whether the connection login was successful.
bool DirectConnectionBase::isAuthorized() const
{
  return authorized_;
}



// Return true if a connection is active
bool DirectConnectionBase::isConnected() const
{
  return ( socket_ != 0 && socket_->isValid() && socket_->state() == QAbstractSocket::ConnectedState );
}



// Return true if this class acts as server, false if it acts as client.
bool DirectConnectionBase::isServer() const
{
  return isServer_;
}



// Return true when a write handler is connnected.
bool DirectConnectionBase::isWriteHandlerConnected() const
{
  return ( writeHandlerCount_ > 0 );
}



// Connect to a host
bool DirectConnectionBase::openConnection( const QString &ipAddress, const quint16 port )
{
#ifdef KMESSDEBUG_DIRECTCONNECTION_GENERAL
  kDebug() << "Connecting to " << ipAddress << " port " << port << ".";
#endif
#ifdef KMESSTEST
  KMESS_ASSERT( socket_ == 0 );
#endif
  connectingTo_ = ipAddress + ":" + QString::number(port);

  // Configure the socket for asynchronous operation
  // Note that a buffered socket only emits the readyRead() signal when data is really received.
  // The socket is made non-blocking, so the connectionSuccess() signal tells us when the connection is made.
  // Otherwise KMess would freeze until a connection is made or fails.
  socket_ = new QTcpSocket( this );
   //KExtendedSocket(ipAddress, port, KExtendedSocket::bufferedSocket );
  connect( socket_, SIGNAL(              connected() ),
           this,    SLOT  (    slotSocketConnected() ));
  connect( socket_, SIGNAL(           disconnected() ),
           this,    SLOT  ( slotSocketDisconnected() ));
  connect( socket_, SIGNAL(              readyRead() ),
           this,    SLOT  ( slotSocketDataReceived() ));
  connect( socket_, SIGNAL(           error(QAbstractSocket::SocketError) ),
           this,    SLOT  ( slotSocketError(QAbstractSocket::SocketError) ));

  // Indicate we'll become a direct-connection client.
  isServer_ = false;
  timeout_  = false;

  // Start timer, as the asyncConnect doesn't seam to honor the timeout value.
  // Needs to be started before connectToHost() to avoid a potential race condition if the call failed immediately.
  connectionTimer_.start( 10000 );

  // Connect the socket
  socket_->connectToHost( ipAddress, port );

  // The code continues with slotSocketConnected() or slotSocketError()
  return true;
}



// Wait for an incoming connection
bool DirectConnectionBase::openServerPort()
{
#ifdef KMESSDEBUG_DIRECTCONNECTION_GENERAL
  kDebug() << "Creating server socket.";
#endif
#ifdef KMESSTEST
  KMESS_ASSERT( server_ == 0 );
#endif

  quint16 port;

  // Get the port
  port = getLocalServerPort();
  connectingTo_ = "0.0.0.0:" + QString::number(port);

  // Create the server socket to the given port.
  server_ = new QTcpServer( this );
  server_->setMaxPendingConnections( 1 );
  connect( server_, SIGNAL( newConnection() ), this, SLOT( slotAcceptConnection() ));

  // Start timer, but allow a longer timeout.
  // Start before listen() so event's will stop the timer agian.
  connectionTimer_.start( 30000 );

  // Indicate we'll become a direct-connection server.
  isServer_ = true;
  timeout_  = false;

  // Put the socket in listen mode.
  bool listenSuccess = server_->listen( QHostAddress::Any, port );
  if( ! listenSuccess )
  {
#ifdef KMESSDEBUG_DIRECTCONNECTION_GENERAL
    kDebug() << "Could not listen at port " << port << ", freeing for next one.";
#endif
    serverPort_ = 0;  // reset, so getLocalServerPort() picks the next port with another openServerPort() call.
    delete server_;
    server_ = 0;
    return false;
  }

  // The code continues with slotAcceptConnection()
  return true;
}



// Verify how many bytes the read buffer has. Note this actually reads the data to test it.
qint64 DirectConnectionBase::peekBlock( const qint64 size )
{
  // Avoid crashes
  if(KMESS_NULL(socket_)) return -1;

  // Read the data and see how much got read.
  // This is the only reliable method, but expensive in terms of performance.
  QByteArray buffer;
  buffer.resize( (int)size );
#ifdef KMESSTEST
  KMESS_ASSERT( size > 0 );
  KMESS_ASSERT( buffer.size() == size );
#endif
  return socket_->peek( buffer.data(), size );
}



// Read data from the socket
qint64 DirectConnectionBase::readBlock( char *buffer, qint64 size )
{
  // Avoid crashes
  if(KMESS_NULL(socket_)) return -1;
  if(KMESS_NULL(buffer))  return -1;

  // Fill the buffer
  qint64 noBytesRead = socket_->read( buffer, size );
  if( noBytesRead < 0 )
  {
    kWarning() << "Error while reading " << size << " bytes from socket, return code: " << noBytesRead << ", system error: " << getSocketError() << " remote address: " << getRemoteIp() << ":" << getRemotePort() << "!";
    return -1;
  }

#ifdef KMESSDEBUG_DIRECTCONNECTION_GENERAL
  if( noBytesRead < size )
  {
    // TODO: Even if the code detects it's not connected anymore, we don't signal this yet.
    //       It opens a new can of worms to handle various event flows which are covered now.
    kDebug() << "only " << noBytesRead
              << " of " << size << " bytes could be read (still connected=" << isConnected() << ").";
  }
#endif

  // Return the amount of bytes actually read
  return noBytesRead;
}



// Read data from the socket (uses the QByteArray size() as block size)
qint64 DirectConnectionBase::readBlock( QByteArray &buffer, const qint64 maxSize, const qint64 bufferOffset )
{
  // Avoid crashes
  if(KMESS_NULL(socket_)) return -1;

  // Make final connected check
  if( ! isConnected() )
  {
    kWarning() << "Attempting to read data from a disconnected socket.";
    return false;
  }

  // Determine the maximum number of bytes we can write to the buffer
  qint64 bufferSpace = buffer.size() - bufferOffset;

  // API usage check.
  // Warn if certain sizes get a unusual value
  if(buffer.size() == 0)
  {
    kWarning() << "Buffer is not initialized!";
    return -1;
  }
  else if(bufferSpace < 0)
  {
    kWarning() << "Offset of " << bufferOffset << " bytes"
                << " exceeds the buffer size of " << buffer.size() << " bytes!";
  }
  else if( bufferSpace < maxSize)
  {
    kWarning() << "Maximum allowed read size"
                << " exceeds the buffer size of " << buffer.size() << " bytes!";
  }

  // Fill the buffer from the give offset,
  // size argument is limited to what the buffer can/may sustain.
  char *bufferStart = buffer.data() + bufferOffset;
  qint64 size = qMin( bufferSpace, maxSize );
  return readBlock( bufferStart, size );
}



// Read data from the socket (stored it in a QBuffer)
qint64 DirectConnectionBase::readBlock( QBuffer &buffer, const qint64 maxSize )
{
  char charBuffer[1024];
  qint64 readSize = qMin( maxSize, (qint64) 1024 );
  qint64 noBytesRead = readBlock( charBuffer, readSize );

  if( noBytesRead > 0 )
  {
    buffer.write( charBuffer, noBytesRead );
  }

  return noBytesRead;
}



// Mark the remote host as authorized (usually after the handshake was successful)
void DirectConnectionBase::setAuthorized(bool authorized)
{
#ifdef KMESSTEST
  KMESS_ASSERT( isConnected() );
#endif

  // notify when it wasn't authorized before, but will be authorized now.
  bool notify = (! authorized_ && authorized);

  authorized_ = authorized;

  if( notify )
  {
    emit connectionAuthorized();
  }
  else
  {
    kWarning() << "connection is already authorized, not notifying applications again.";
  }
}



// Accept incoming connections on the socket.
void DirectConnectionBase::slotAcceptConnection()
{
#ifdef KMESSTEST
  KMESS_ASSERT( socket_ == 0 );
  KMESS_ASSERT( server_ != 0 );
#endif
#ifdef KMESSDEBUG_DIRECTCONNECTION_GENERAL
  kDebug() << "Accept a socket";
#endif

  if(socket_ != 0)
  {
    // Already accepted a connection, close server
#ifdef KMESSDEBUG_DIRECTCONNECTION_GENERAL
    kDebug() << "Already got a connection, rejecting this request";
#endif

    return;
  }

  // Wait until a connection was established
  socket_ = server_->nextPendingConnection();
  if( socket_ == 0 )
  {
#ifdef KMESSDEBUG_DIRECTCONNECTION_GENERAL
    kWarning() << "Failed to accept incoming connection.";
#endif
    // TODO: signal failure?
    return;
  }

  // Accept success
#ifdef KMESSDEBUG_DIRECTCONNECTION_GENERAL
  kDebug() << "Accept success";
#endif

  // Close the server, we don't need it anymore (no connectionClosed events()).
  // Can't delete the object here, because socket_ becomes a qobject child of server_.
  server_->close();
  connectionTimer_.stop();

  // Connect the socket signals
  connect( socket_, SIGNAL(           disconnected() ),
           this,    SLOT  ( slotSocketDisconnected() ));
  connect( socket_, SIGNAL(              readyRead() ),
           this,    SLOT  ( slotSocketDataReceived() ));
  connect( socket_, SIGNAL(           error(QAbstractSocket::SocketError) ),
           this,    SLOT  ( slotSocketError(QAbstractSocket::SocketError) ));

  // Inform there is a connection
  emit connectionEstablished();
  slotConnectionEstablished(); // call manually, no need to connect
}



// This is called when the connection is established
void DirectConnectionBase::slotConnectionEstablished()
{
  // This method is added for derived classes to overload.
  // It pretends we're also listening to our own signals.

  // This is both called when a outgoing connection was made or
  // an incoming connection was accepted.
  // By default, it's empty,
}



// This is called when there's an error in the socket.
void DirectConnectionBase::slotConnectionFailed()
{
  // This method is added for derived classes to overload.
  // It pretends we're also listening to our own signals.

  QString sysError( getSocketError() );  // avoids poking sys_errlist[] manually

  if( ! sysError.isNull() )
  {
    kWarning() << "Failed to connect with " << connectingTo_ << ", system error: " << sysError << ".";
  }
  else
  {
    kWarning() << "Failed to connect with " << connectingTo_ << "!";
  }

  // No events here, this method is called from slotSocketError()
}



// A timeout occured to connect a socket
void DirectConnectionBase::slotConnectionTimeout()
{
  if( isConnected() )
  {
    // Somehow the code execution seams to get here when:
    // - a connection is established
    // - the other contact didn't send any data yet.
    // This could be happening before because the timer.start() was placed after the startAsyncConnect() calls.
    // This bug is fixed, but keep the check nevertheless.

#ifdef KMESSDEBUG_DIRECTCONNECTION_GENERAL
    kDebug() << "Timer fired but connection "
                 "with " << connectingTo_ << " is already established, ignore.";
#endif
    return;
  }

#ifdef KMESSDEBUG_DIRECTCONNECTION_GENERAL
  kDebug() << "Timer fired before connection "
               "with " << connectingTo_ << " was established, "
               "closing sockets and reporting failure.";
#endif

  // Delete sockets, no connectionClosed events.
  closeClientSocket();
  closeServerSocket();

  // Set state for getSocketError();
  timeout_ = true;
  connectionTimer_.stop();

  // Inform derived class and other listeners
  // This pretends we're connected to our signals but without generating the overhead.
  slotConnectionFailed();
  emit connectionFailed();
}



// Signal that the connection was established
void DirectConnectionBase::slotSocketConnected()
{
  // This is called when the socket in openConnection() was connected.
#ifdef KMESSTEST
  KMESS_ASSERT( ! isServer_ );
#endif
#ifdef KMESSDEBUG_DIRECTCONNECTION_GENERAL
  kDebug() << "Connected with host " << connectingTo_ << ".";
#endif

  connectionTimer_.stop();

  // Inform derived class and other listeners
  // This pretends we're connected to our signals but without generating the overhead.
  slotConnectionEstablished();
  emit connectionEstablished();
}



// Signal that the connection was closed
void DirectConnectionBase::slotSocketDisconnected()
{
#ifdef KMESSTEST
  KMESS_ASSERT( socket_ != 0 );
#endif
#ifdef KMESSDEBUG_DIRECTCONNECTION_GENERAL
  kDebug() << "Socket got closed, state=" << socket_->state() << ".";
#endif

  // Reset the globals.
  isServer_   = false;
  authorized_ = false;
  connectionTimer_.stop();

  // Make sure the objects are cleaned up too.
  closeClientSocket();
  closeServerSocket();
}



// Signal that the connection could not be made.
void DirectConnectionBase::slotSocketError( QAbstractSocket::SocketError error )
{
  // This is called when the socket in openConnection() failed,
  // or when the socket closed.
#ifdef KMESSDEBUG_DIRECTCONNECTION_GENERAL
  kDebug() << "Got error with host " << connectingTo_ << " "
              "(code="   << error <<
              " state="  << socket_->state() <<
              " reason=" << socket_->errorString() << ").";
#endif

  connectionTimer_.stop();

  // Make sure the sockets are removed.
  // When the class is reused, create new sockets.
  closeClientSocket();
  closeServerSocket();

  // Filter errors that happen when you've connected already.
  switch( error )
  {
    case QAbstractSocket::RemoteHostClosedError:
      emit connectionClosed();
      return;

    case QAbstractSocket::DatagramTooLargeError:
    case QAbstractSocket::NetworkError:
    case QAbstractSocket::UnknownSocketError:
#ifdef KMESSDEBUG_DIRECTCONNECTION_GENERAL
      kDebug() << "No signals emitted for this error code.";
#endif
      break;

    default:
      // Inform derived class and other listeners
      // This pretends we're connected to our signals but without generating the overhead.
      slotConnectionFailed();
      emit connectionFailed();
  }
}



// Slot called when the socket is ready to write data.
void DirectConnectionBase::slotSocketReadyWrite( qint64 bytesWritten )
{
  // First see if the additional write buffer can be cleared.
  if( ! additionalWriteBuffer_.isEmpty() )
  {
#ifdef KMESSDEBUG_DIRECTCONNECTION_GENERAL
    kDebug() << "flushing buffer with unsent data first. "
                "(bytesWritten=" << bytesWritten <<
                " bytesToWrite=" << socket_->bytesToWrite() <<
                " additional_bytesToWrite=" << additionalWriteBuffer_.size() << ").";
#else
    Q_UNUSED( bytesWritten );
#endif

    // TODO: some notice in the network window that we'll be sending crap now (remaining parts of a p2p packet).

    // Simply re-use the function.
    // Not the most efficient method, but the most reliable.
    QByteArray newBuffer = additionalWriteBuffer_;
    additionalWriteBuffer_.truncate(0);

    // See what get's sent, and what's added to additionalWriteBuffer_ again.
    bool success = writeBlock( newBuffer );
    if( ! success )
    {
      if( additionalWriteBuffer_.isEmpty() )
      {
        kWarning() << "the remaining " << newBuffer.size() <<
                      " unsent bytes could still not be sent!";
      }
      else
      {
        // else ok, remaining is in additional buffer again.
#ifdef KMESSDEBUG_DIRECTCONNECTION_GENERAL
        kDebug() << "buffer not completely flushed yet, "
                    "still " << additionalWriteBuffer_.size() << " bytes to send.";
#endif
      }

      // Since the data is not completely sent yet, don't signal our listeners yet.
      return;
    }

#ifdef KMESSDEBUG_DIRECTCONNECTION_GENERAL
    kDebug() << "buffer flushed, see if the write handler can be called for more data.";
#endif
  }


  // Then make sure we ask for more data.
  if( socket_->bytesToWrite() > 2000 )
  {
#ifdef KMESSDEBUG_DIRECTCONNECTION_GENERAL
    kDebug() << "still got " << socket_->bytesToWrite() << " bytes to write, delaying next write handler signal.";
#endif
  }
  else
  {
#ifdef KMESSDEBUG_DIRECTCONNECTION_GENERAL
    kDebug() << "firing next write handler signal. "
                "(bytesWritten=" << bytesWritten <<
                " bytesToWrite=" << socket_->bytesToWrite() << ").";
#endif

    // Notify the real senders
    emit writeHandlerReady();
  }
}



/**
 * @brief Write data to the socket
 *
 * This function writes the packet to the socket.
 * If there is a write error, false is returned.
 * If the error is considered to be temporary, isSentBuffer
 *
 * @param  block  Pointer to the data block to write.
 * @param  size   Size of the block to read.
 * @returns  Whether the has been a write error.
 */
00918 bool DirectConnectionBase::writeBlock( const char *block, const qint64 size )
{
  lastWriteFailed_ = true;
  if(KMESS_NULL(socket_)) return false;

  // Make final connected check
  if( ! isConnected() )
  {
    kWarning() << "Attempting to write data to a disconnected socket.";
    return false;
  }

  // If the additional buffer is still not written, don't write next block
  // This would send messages in the wrong order.
  if( ! additionalWriteBuffer_.isEmpty() )
  {
#ifdef KMESSTEST
    KMESS_ASSERT( isWriteHandlerConnected() );
#endif

    // Warn!
    if( additionalWriteBuffer_.size() > 1000 )
    {
      // only warn with release for larger additions. avoid false-positives for size preamble field.
      kWarning() << "received another mesage of " << size << " bytes "
                     "while the write buffer is full!";
    }
    else
    {
      // else only warn in debug mode.
#ifdef KMESSDEBUG_DIRECTCONNECTION_GENERAL
      kWarning() << "received another mesage of " << size << " bytes "
                     "while the write buffer is full!";
#endif
    }

    additionalWriteBuffer_.append( QByteArray::fromRawData( block, (int)size ) );

    // Protect against consuming all system memory
    // 50 kB is more then enough if you're writing messages of 1300 bytes!
    if( additionalWriteBuffer_.size() > 50000 )
    {
      kWarning() << "write buffers are flooding, "
                     "dropping connection immediately!";
      additionalWriteBuffer_.truncate(0);
      closeConnection();
      return false;
    }
  }

  // Write the data
  qint64 noBytesWritten = socket_->write( block, size );
  if( noBytesWritten < 0 )
  {
    kWarning() << "Error while writing " << size << " bytes to socket, return code: " << noBytesWritten << ", system error: " << getSocketError() << " remote address: " << getRemoteIp() << ":" << getRemotePort() << "!";
    return false;
  }

  // Log in network window.
  // Use actual size of written block.
#ifdef KMESS_NETWORK_WINDOW
  QByteArray wrapper = QByteArray::fromRawData( block, (int)noBytesWritten );
  KMESS_NET_SENT(this, wrapper);
#endif

  // Warn if number of written bytes is off,
  // this corrupts file transfers if the data is not sent later on.
  if( noBytesWritten < size )
  {
    kWarning() << "only " << noBytesWritten
               << " of " << size << " bytes could be written!";

    additionalWriteBuffer_.append( QByteArray::fromRawData( block + noBytesWritten, (int)(size - noBytesWritten) ) );
  }
  else
  {
    // Finally set to positive.
    lastWriteFailed_ = false;
  }

  // Return true if the data was indeed written.
  return ( noBytesWritten == size );
}



// Write data to the socket (convenience function)
bool DirectConnectionBase::writeBlock( const QByteArray &block )
{
  return writeBlock( block.data(), block.size() );
}


#include "directconnectionbase.moc"

Generated by  Doxygen 1.6.0   Back to index