summaryrefslogtreecommitdiffstats
path: root/src/network/qsocket.cpp
diff options
context:
space:
mode:
authorTimothy Pearson <kb9vqf@pearsoncomputing.net>2011-07-10 15:24:15 -0500
committerTimothy Pearson <kb9vqf@pearsoncomputing.net>2011-07-10 15:24:15 -0500
commitbd0f3345a938b35ce6a12f6150373b0955b8dd12 (patch)
tree7a520322212d48ebcb9fbe1087e7fca28b76185c /src/network/qsocket.cpp
downloadqt3-bd0f3345a938b35ce6a12f6150373b0955b8dd12.tar.gz
qt3-bd0f3345a938b35ce6a12f6150373b0955b8dd12.zip
Add Qt3 development HEAD version
Diffstat (limited to 'src/network/qsocket.cpp')
-rw-r--r--src/network/qsocket.cpp1546
1 files changed, 1546 insertions, 0 deletions
diff --git a/src/network/qsocket.cpp b/src/network/qsocket.cpp
new file mode 100644
index 0000000..492bc72
--- /dev/null
+++ b/src/network/qsocket.cpp
@@ -0,0 +1,1546 @@
+/****************************************************************************
+**
+** Implementation of QSocket class.
+**
+** Created : 970521
+**
+** Copyright (C) 1992-2008 Trolltech ASA. All rights reserved.
+**
+** This file is part of the network module of the Qt GUI Toolkit.
+**
+** This file may be used under the terms of the GNU General
+** Public License versions 2.0 or 3.0 as published by the Free
+** Software Foundation and appearing in the files LICENSE.GPL2
+** and LICENSE.GPL3 included in the packaging of this file.
+** Alternatively you may (at your option) use any later version
+** of the GNU General Public License if such license has been
+** publicly approved by Trolltech ASA (or its successors, if any)
+** and the KDE Free Qt Foundation.
+**
+** Please review the following information to ensure GNU General
+** Public Licensing requirements will be met:
+** http://trolltech.com/products/qt/licenses/licensing/opensource/.
+** If you are unsure which license is appropriate for your use, please
+** review the following information:
+** http://trolltech.com/products/qt/licenses/licensing/licensingoverview
+** or contact the sales department at sales@trolltech.com.
+**
+** This file may be used under the terms of the Q Public License as
+** defined by Trolltech ASA and appearing in the file LICENSE.QPL
+** included in the packaging of this file. Licensees holding valid Qt
+** Commercial licenses may use this file in accordance with the Qt
+** Commercial License Agreement provided with the Software.
+**
+** This file is provided "AS IS" with NO WARRANTY OF ANY KIND,
+** INCLUDING THE WARRANTIES OF DESIGN, MERCHANTABILITY AND FITNESS FOR
+** A PARTICULAR PURPOSE. Trolltech reserves all rights not granted
+** herein.
+**
+**********************************************************************/
+
+#include "qsocket.h"
+#ifndef QT_NO_NETWORK
+#include "qptrlist.h"
+#include "qtimer.h"
+#include "qsocketdevice.h"
+#include "qdns.h"
+#include "private/qinternal_p.h"
+
+#include <string.h>
+#ifndef NO_ERRNO_H
+#include <errno.h>
+#endif
+
+//#define QSOCKET_DEBUG
+
+/*
+ Perhaps this private functionality needs to be refactored.
+
+ Comment from Robert D Gatlin (Intel):
+
+ It would be nice to have the functionality inherent in QSocket available
+ as a separate class as a standard part of the Qt library, something along
+ the line of:
+
+ class QByteBuffer : public QIODevice { ... }
+
+ The same class could/would be used within QSocket for the Read/Write
+ buffers.
+
+ The above class could be used in the following way(s):
+
+ buffer.open( IO_WriteOnly | IO_Append );
+ buffer.writeBlock( a ); // a = QByteArray
+ buffer.close();
+
+ QByteArray b;
+ b.resize( buffer.size() );
+ buffer.open( IO_ReadOnly );
+ buffer.readBlock( b.data(), b.size() );
+ buffer.close();
+
+ But would also be useable with QDataStream (via QIODevice) with:
+
+ buffer.open( IO_WriteOnly | IO_Append );
+ QDataStream is( &buffer );
+ is << 100;
+ buffer.close();
+
+ buffer.open( IO_ReadOnly );
+ QDataStream os( &buffer );
+ Q_UINT32 x;
+ os >> x;
+ buffer.close();
+
+ The real usefulness is with any situations where data (QByteArray) arrives
+ incrementally (as in QSocket and filter case above).
+
+ I tried using QBuffer, but QBuffer does not trim bytes from the front of
+ the buffer in cases like:
+
+ QBuffer buf;
+ buf.open( IO_ReadOnly );
+ QDataStream ds( &buf );
+ Q_INT32 x;
+ ds >> x;
+ buf.close();
+
+ In the above case, buf.size() will be identical before and after the
+ operation with QDataStream. Based on the implementation of QBuffer, it
+ does not appear well suited for this kind of operation.
+*/
+
+// Private class for QSocket
+
+class QSocketPrivate {
+public:
+ QSocketPrivate();
+ ~QSocketPrivate();
+ void closeSocket();
+ void close();
+ void connectionClosed();
+ void setSocketDevice( QSocket *q, QSocketDevice *device );
+
+ QSocket::State state; // connection state
+ QString host; // host name
+ Q_UINT16 port; // host port
+ QSocketDevice *socket; // connection socket
+ QSocketNotifier *rsn, *wsn; // socket notifiers
+ QMembuf rba; // read buffer
+ Q_ULONG readBufferSize; // limit for the read buffer size
+ QPtrList<QByteArray> wba; // list of write bufs
+ QHostAddress addr; // connection address
+ QValueList<QHostAddress> addresses; // alternatives looked up
+ QIODevice::Offset wsize; // write total buf size
+ QIODevice::Offset windex; // write index
+#ifndef QT_NO_DNS
+ QDns *dns4;
+ QDns *dns6;
+#endif
+ static QPtrList<QSocket> sn_read_alreadyCalled; // used to avoid unwanted recursion
+ QValueList<QHostAddress> l4;
+ QValueList<QHostAddress> l6;
+};
+
+QPtrList<QSocket> QSocketPrivate::sn_read_alreadyCalled;
+
+QSocketPrivate::QSocketPrivate()
+ : state(QSocket::Idle), host(QString::fromLatin1("")), port(0),
+ socket(0), rsn(0), wsn(0), readBufferSize(0), wsize(0), windex(0)
+{
+#ifndef QT_NO_DNS
+ dns4 = 0;
+ dns6 = 0;
+#endif
+ wba.setAutoDelete( TRUE );
+}
+
+QSocketPrivate::~QSocketPrivate()
+{
+ close();
+ delete socket;
+#ifndef QT_NO_DNS
+ delete dns4;
+ delete dns6;
+#endif
+}
+
+void QSocketPrivate::closeSocket()
+{
+ // Order is important here - the socket notifiers must go away
+ // before the socket does, otherwise libc or the kernel will
+ // become unhappy.
+ delete rsn;
+ rsn = 0;
+ delete wsn;
+ wsn = 0;
+ if ( socket )
+ socket->close();
+}
+
+void QSocketPrivate::close()
+{
+ closeSocket();
+ wsize = 0;
+ rba.clear(); wba.clear();
+ windex = 0;
+}
+
+void QSocketPrivate::connectionClosed()
+{
+ // We keep the open state in case there's unread incoming data
+ state = QSocket::Idle;
+ closeSocket();
+ wba.clear();
+ windex = wsize = 0;
+}
+
+void QSocketPrivate::setSocketDevice( QSocket *q, QSocketDevice *device )
+{
+ delete socket;
+ delete rsn;
+ delete wsn;
+
+ if ( device ) {
+ socket = device;
+ } else {
+ socket = new QSocketDevice( QSocketDevice::Stream,
+ ( addr.isIPv4Address() ?
+ QSocketDevice::IPv4 :
+ QSocketDevice::IPv6 ), 0 );
+ socket->setBlocking( FALSE );
+ socket->setAddressReusable( TRUE );
+ }
+
+ rsn = new QSocketNotifier( socket->socket(),
+ QSocketNotifier::Read, q, "read" );
+ wsn = new QSocketNotifier( socket->socket(),
+ QSocketNotifier::Write, q, "write" );
+
+ QObject::connect( rsn, SIGNAL(activated(int)), q, SLOT(sn_read()) );
+ rsn->setEnabled( FALSE );
+ QObject::connect( wsn, SIGNAL(activated(int)), q, SLOT(sn_write()) );
+ wsn->setEnabled( FALSE );
+}
+
+/*!
+ \class QSocket qsocket.h
+ \brief The QSocket class provides a buffered TCP connection.
+\if defined(commercial)
+ It is part of the <a href="commercialeditions.html">Qt Enterprise Edition</a>.
+\endif
+
+ \ingroup io
+ \module network
+
+ It provides a totally non-blocking QIODevice, and modifies and
+ extends the API of QIODevice with socket-specific code.
+
+ Note that a QApplication must have been constructed before this
+ class can be used.
+
+ The functions you're likely to call most are connectToHost(),
+ bytesAvailable(), canReadLine() and the ones it inherits from
+ QIODevice.
+
+ connectToHost() is the most-used function. As its name implies,
+ it opens a connection to a named host.
+
+ Most network protocols are either packet-oriented or
+ line-oriented. canReadLine() indicates whether a connection
+ contains an entire unread line or not, and bytesAvailable()
+ returns the number of bytes available for reading.
+
+ The signals error(), connected(), readyRead() and
+ connectionClosed() inform you of the progress of the connection.
+ There are also some less commonly used signals. hostFound() is
+ emitted when connectToHost() has finished its DNS lookup and is
+ starting its TCP connection. delayedCloseFinished() is emitted
+ when close() succeeds. bytesWritten() is emitted when QSocket
+ moves data from its "to be written" queue into the TCP
+ implementation.
+
+ There are several access functions for the socket: state() returns
+ whether the object is idle, is doing a DNS lookup, is connecting,
+ has an operational connection, etc. address() and port() return
+ the IP address and port used for the connection. The peerAddress()
+ and peerPort() functions return the IP address and port used by
+ the peer, and peerName() returns the name of the peer (normally
+ the name that was passed to connectToHost()). socketDevice()
+ returns a pointer to the QSocketDevice used for this socket.
+
+ QSocket inherits QIODevice, and reimplements some functions. In
+ general, you can treat it as a QIODevice for writing, and mostly
+ also for reading. The match isn't perfect, since the QIODevice
+ API is designed for devices that are controlled by the same
+ machine, and an asynchronous peer-to-peer network connection isn't
+ quite like that. For example, there is nothing that matches
+ QIODevice::size() exactly. The documentation for open(), close(),
+ flush(), size(), at(), atEnd(), readBlock(), writeBlock(),
+ getch(), putch(), ungetch() and readLine() describes the
+ differences in detail.
+
+ \warning QSocket is not suitable for use in threads. If you need
+ to uses sockets in threads use the lower-level QSocketDevice class.
+
+ \warning Because Qt doesn't use the native socketstream
+ implementation on Mac OS X, QSocket has an implicit transfer
+ latency of 100ms. You can achieve lower latency on Mac OS X by
+ using QSocketDevice instead.
+
+ \sa QSocketDevice, QHostAddress, QSocketNotifier
+*/
+
+
+/*!
+ Creates a QSocket object in \c QSocket::Idle state.
+
+ The \a parent and \a name arguments are passed on to the QObject
+ constructor.
+
+ Note that a QApplication must have been constructed before sockets
+ can be used.
+*/
+
+QSocket::QSocket( QObject *parent, const char *name )
+ : QObject( parent, name )
+{
+ d = new QSocketPrivate;
+ setSocketDevice( 0 );
+ setFlags( IO_Direct );
+ resetStatus();
+}
+
+
+/*!
+ Destroys the socket. Closes the connection if necessary.
+
+ \sa close()
+*/
+
+QSocket::~QSocket()
+{
+#if defined(QSOCKET_DEBUG)
+ qDebug( "QSocket (%s): Destroy", name() );
+#endif
+ if ( state() != Idle )
+ close();
+ Q_ASSERT( d != 0 );
+ delete d;
+}
+
+
+/*!
+ Returns a pointer to the internal socket device.
+
+ There is normally no need to manipulate the socket device directly
+ since this class does the necessary setup for most applications.
+*/
+
+QSocketDevice *QSocket::socketDevice()
+{
+ return d->socket;
+}
+
+/*!
+ Sets the internal socket device to \a device. Passing a \a device
+ of 0 will cause the internal socket device to be used. Any
+ existing connection will be disconnected before using the new \a
+ device.
+
+ The new device should not be connected before being associated
+ with a QSocket; after setting the socket call connectToHost() to
+ make the connection.
+
+ This function is useful if you need to subclass QSocketDevice and
+ want to use the QSocket API, for example, to implement Unix domain
+ sockets.
+*/
+
+void QSocket::setSocketDevice( QSocketDevice *device )
+{
+ if ( state() != Idle )
+ close();
+ d->setSocketDevice( this, device );
+}
+
+/*!
+ \enum QSocket::State
+
+ This enum defines the connection states:
+
+ \value Idle if there is no connection
+ \value HostLookup during a DNS lookup
+ \value Connecting during TCP connection establishment
+ \value Connected when there is an operational connection
+ \value Closing if the socket is closing down, but is not yet closed.
+*/
+
+/*!
+ Returns the current state of the socket connection.
+
+ \sa QSocket::State
+*/
+
+QSocket::State QSocket::state() const
+{
+ return d->state;
+}
+
+
+#ifndef QT_NO_DNS
+
+/*!
+ Attempts to make a connection to \a host on the specified \a port
+ and return immediately.
+
+ Any connection or pending connection is closed immediately, and
+ QSocket goes into the \c HostLookup state. When the lookup
+ succeeds, it emits hostFound(), starts a TCP connection and goes
+ into the \c Connecting state. Finally, when the connection
+ succeeds, it emits connected() and goes into the \c Connected
+ state. If there is an error at any point, it emits error().
+
+ \a host may be an IP address in string form, or it may be a DNS
+ name. QSocket will do a normal DNS lookup if required. Note that
+ \a port is in native byte order, unlike some other libraries.
+
+ \sa state()
+*/
+
+void QSocket::connectToHost( const QString &host, Q_UINT16 port )
+{
+#if defined(QSOCKET_DEBUG)
+ qDebug( "QSocket (%s)::connectToHost: host %s, port %d",
+ name(), host.ascii(), port );
+#endif
+ setSocketIntern( -1 );
+ d->state = HostLookup;
+ d->host = host;
+ d->port = port;
+ d->dns4 = new QDns( host, QDns::A );
+ d->dns6 = new QDns( host, QDns::Aaaa );
+
+ // try if the address is already available (for faster connecting...)
+ tryConnecting();
+ if ( d->state == HostLookup ) {
+ connect( d->dns4, SIGNAL(resultsReady()),
+ this, SLOT(tryConnecting()) );
+ connect( d->dns6, SIGNAL(resultsReady()),
+ this, SLOT(tryConnecting()) );
+ }
+}
+
+#endif
+
+
+/*!
+ This private slots continues the connection process where
+ connectToHost() leaves off.
+*/
+
+void QSocket::tryConnecting()
+{
+#if defined(QSOCKET_DEBUG)
+ qDebug( "QSocket (%s)::tryConnecting()", name() );
+#endif
+ // ### this ifdef isn't correct - addresses() also does /etc/hosts and
+ // numeric-address-as-string handling.
+#ifndef QT_NO_DNS
+
+ if ( d->dns4 ) {
+ d->l4 = d->dns4->addresses();
+ if ( !d->l4.isEmpty() || !d->dns4->isWorking() ) {
+#if defined(QSOCKET_DEBUG)
+ qDebug( "QSocket (%s)::tryConnecting: host %s, port %d: "
+ "%d IPv4 addresses",
+ name(), d->host.ascii(), d->port, d->l4.count() );
+#endif
+ delete d->dns4;
+ d->dns4 = 0;
+ }
+ }
+
+ if ( d->dns6 ) {
+ d->l6 = d->dns6->addresses();
+ if ( !d->l6.isEmpty() || !d->dns6->isWorking() ) {
+#if defined(QSOCKET_DEBUG)
+ qDebug( "QSocket (%s)::tryConnecting: host %s, port %d: "
+ "%d IPv6 addresses",
+ name(), d->host.ascii(), d->port, d->l6.count() );
+#endif
+ delete d->dns6;
+ d->dns6 = 0;
+ }
+ }
+
+ if ( d->state == HostLookup ) {
+ if ( d->l4.isEmpty() && d->l6.isEmpty() &&
+ !d->dns4 && !d->dns6 ) {
+ // no results and we're not still looking: give up
+ d->state = Idle;
+ emit error( ErrHostNotFound );
+ return;
+ }
+ if ( d->l4.isEmpty() && d->l6.isEmpty() ) {
+ // no results (yet): try again later
+ return;
+ }
+
+ // we've found something. press on with that. if we later find
+ // more, fine.
+ emit hostFound();
+ d->state = Connecting;
+ }
+
+ if ( d->state == Connecting ) {
+ d->addresses += d->l4;
+ d->addresses += d->l6;
+ d->l4.clear();
+ d->l6.clear();
+
+ // try one address at a time, falling back to the next one if
+ // there is a connection failure. (should also support a timeout,
+ // or do multiple TCP-level connects at a time, with staggered
+ // starts to avoid bandwidth waste and cause fewer
+ // "connect-and-abort" errors. but that later.)
+ bool stuck = TRUE;
+ while( stuck ) {
+ stuck = FALSE;
+ if ( d->socket &&
+ d->socket->connect( d->addr, d->port ) == FALSE ) {
+ if ( d->socket->error() == QSocketDevice::NoError ) {
+ if ( d->wsn )
+ d->wsn->setEnabled( TRUE );
+ return; // not serious, try again later
+ }
+
+#if defined(QSOCKET_DEBUG)
+ qDebug( "QSocket (%s)::tryConnecting: "
+ "Gave up on IP address %s",
+ name(), d->socket->peerAddress().toString().ascii() );
+#endif
+ delete d->wsn;
+ d->wsn = 0;
+ delete d->rsn;
+ d->rsn = 0;
+ delete d->socket;
+ d->socket = 0;
+
+ if(d->addresses.isEmpty()) {
+ emit error( ErrConnectionRefused );
+ return;
+ }
+ }
+ // if the host has more addresses, try another some.
+ if ( d->socket == 0 && !d->addresses.isEmpty() ) {
+ d->addr = *d->addresses.begin();
+ d->addresses.remove( d->addresses.begin() );
+ d->setSocketDevice( this, 0 );
+ stuck = TRUE;
+#if defined(QSOCKET_DEBUG)
+ qDebug( "QSocket (%s)::tryConnecting: Trying IP address %s",
+ name(), d->addr.toString().ascii() );
+#endif
+ }
+ };
+
+ // The socket write notifier will fire when the connection succeeds
+ if ( d->wsn )
+ d->wsn->setEnabled( TRUE );
+ }
+#endif
+}
+
+/*!
+ \enum QSocket::Error
+
+ This enum specifies the possible errors:
+ \value ErrConnectionRefused if the connection was refused
+ \value ErrHostNotFound if the host was not found
+ \value ErrSocketRead if a read from the socket failed
+*/
+
+/*!
+ \fn void QSocket::error( int )
+
+ This signal is emitted after an error occurred. The parameter is
+ the \l Error value.
+*/
+
+/*!
+ \fn void QSocket::hostFound()
+
+ This signal is emitted after connectToHost() has been called and
+ the host lookup has succeeded.
+
+ \sa connected()
+*/
+
+
+/*!
+ \fn void QSocket::connected()
+
+ This signal is emitted after connectToHost() has been called and a
+ connection has been successfully established.
+
+ \sa connectToHost(), connectionClosed()
+*/
+
+
+/*!
+ \fn void QSocket::connectionClosed()
+
+ This signal is emitted when the other end has closed the
+ connection. The read buffers may contain buffered input data which
+ you can read after the connection was closed.
+
+ \sa connectToHost(), close()
+*/
+
+
+/*!
+ \fn void QSocket::delayedCloseFinished()
+
+ This signal is emitted when a delayed close is finished.
+
+ If you call close() and there is buffered output data to be
+ written, QSocket goes into the \c QSocket::Closing state and
+ returns immediately. It will then keep writing to the socket until
+ all the data has been written. Then, the delayedCloseFinished()
+ signal is emitted.
+
+ \sa close()
+*/
+
+
+/*!
+ \fn void QSocket::readyRead()
+
+ This signal is emitted every time there is new incoming data.
+
+ Bear in mind that new incoming data is only reported once; if you do not
+ read all the data, this class buffers the data and you can read it later,
+ but no signal is emitted unless new data arrives. A good practice is to
+ read all data in the slot connected to this signal unless you are sure that
+ you need to receive more data to be able to process it.
+
+ \sa readBlock(), readLine(), bytesAvailable()
+*/
+
+
+/*!
+ \fn void QSocket::bytesWritten( int nbytes )
+
+ This signal is emitted when data has been written to the network.
+ The \a nbytes parameter specifies how many bytes were written.
+
+ The bytesToWrite() function is often used in the same context; it
+ indicates how many buffered bytes there are left to write.
+
+ \sa writeBlock(), bytesToWrite()
+*/
+
+
+/*!
+ Opens the socket using the specified QIODevice file mode \a m.
+ This function is called automatically when needed and you should
+ not call it yourself.
+
+ \sa close()
+*/
+
+bool QSocket::open( int m )
+{
+ if ( isOpen() ) {
+#if defined(QT_CHECK_STATE)
+ qWarning( "QSocket::open: Already open" );
+#endif
+ return FALSE;
+ }
+ QIODevice::setMode( m & IO_ReadWrite );
+ setState( IO_Open );
+ return TRUE;
+}
+
+
+/*!
+ Closes the socket.
+
+ The read buffer is cleared.
+
+ If the output buffer is empty, the state is set to \c
+ QSocket::Idle and the connection is terminated immediately. If the
+ output buffer still contains data to be written, QSocket goes into
+ the \c QSocket::Closing state and the rest of the data will be
+ written. When all of the outgoing data have been written, the
+ state is set to \c QSocket::Idle and the connection is terminated.
+ At this point, the delayedCloseFinished() signal is emitted.
+
+ If you don't want that the data of the output buffer is written, call
+ clearPendingData() before you call close().
+
+ \sa state(), bytesToWrite() clearPendingData()
+*/
+
+void QSocket::close()
+{
+ if ( !isOpen() || d->state == Idle ) // already closed
+ return;
+ if ( d->state == Closing )
+ return;
+ if ( !d->rsn || !d->wsn )
+ return;
+#if defined(QSOCKET_DEBUG)
+ qDebug( "QSocket (%s): close socket", name() );
+#endif
+ if ( d->socket && d->wsize ) { // there's data to be written
+ d->state = Closing;
+ if ( d->rsn )
+ d->rsn->setEnabled( FALSE );
+ if ( d->wsn )
+ d->wsn->setEnabled( TRUE );
+ d->rba.clear(); // clear incoming data
+ return;
+ }
+ setFlags( IO_Sequential );
+ resetStatus();
+ setState( 0 );
+ d->close();
+ d->state = Idle;
+}
+
+
+/*!
+ This function consumes \a nbytes bytes of data from the write
+ buffer.
+*/
+
+bool QSocket::consumeWriteBuf( Q_ULONG nbytes )
+{
+ if ( nbytes <= 0 || nbytes > d->wsize )
+ return FALSE;
+#if defined(QSOCKET_DEBUG)
+ qDebug( "QSocket (%s): skipWriteBuf %d bytes", name(), (int)nbytes );
+#endif
+ d->wsize -= nbytes;
+ for ( ;; ) {
+ QByteArray *a = d->wba.first();
+ if ( d->windex + nbytes >= a->size() ) {
+ nbytes -= a->size() - d->windex;
+ d->wba.remove();
+ d->windex = 0;
+ if ( nbytes == 0 )
+ break;
+ } else {
+ d->windex += nbytes;
+ break;
+ }
+ }
+ return TRUE;
+}
+
+
+
+/*!
+ Implementation of the abstract virtual QIODevice::flush() function.
+*/
+
+void QSocket::flush()
+{
+ if ( !d->socket )
+ return;
+ bool osBufferFull = FALSE;
+ int consumed = 0;
+ while ( !osBufferFull && d->state >= Connecting && d->wsize > 0 ) {
+#if defined(QSOCKET_DEBUG)
+ qDebug( "QSocket (%s): flush: Write data to the socket", name() );
+#endif
+ QByteArray *a = d->wba.first();
+ int nwritten;
+ int i = 0;
+ if ( (int)a->size() - d->windex < 1460 ) {
+ // Concatenate many smaller blocks. the first may be
+ // partial, but each subsequent block is copied entirely
+ // or not at all. the sizes here are picked so that we
+ // generally won't trigger nagle's algorithm in the tcp
+ // implementation: we concatenate if we'd otherwise send
+ // less than PMTU bytes (we assume PMTU is 1460 bytes),
+ // and concatenate up to the largest payload TCP/IP can
+ // carry. with these precautions, nagle's algorithm
+ // should apply only when really appropriate.
+ QByteArray out( 65536 );
+ int j = d->windex;
+ int s = a->size() - j;
+ while ( a && i+s < (int)out.size() ) {
+ memcpy( out.data()+i, a->data()+j, s );
+ j = 0;
+ i += s;
+ a = d->wba.next();
+ s = a ? a->size() : 0;
+ }
+ nwritten = d->socket->writeBlock( out.data(), i );
+ if ( d->wsn )
+ d->wsn->setEnabled( FALSE ); // the QSocketNotifier documentation says so
+ } else {
+ // Big block, write it immediately
+ i = a->size() - d->windex;
+ nwritten = d->socket->writeBlock( a->data() + d->windex, i );
+ if ( d->wsn )
+ d->wsn->setEnabled( FALSE ); // the QSocketNotifier documentation says so
+ }
+ if ( nwritten > 0 ) {
+ if ( consumeWriteBuf( nwritten ) )
+ consumed += nwritten;
+ }
+ if ( nwritten < i )
+ osBufferFull = TRUE;
+ }
+ if ( consumed > 0 ) {
+#if defined(QSOCKET_DEBUG)
+ qDebug( "QSocket (%s): flush: wrote %d bytes, %d left",
+ name(), consumed, (int)d->wsize );
+#endif
+ emit bytesWritten( consumed );
+ }
+ if ( d->state == Closing && d->wsize == 0 ) {
+#if defined(QSOCKET_DEBUG)
+ qDebug( "QSocket (%s): flush: Delayed close done. Terminating.",
+ name() );
+#endif
+ setFlags( IO_Sequential );
+ resetStatus();
+ setState( 0 );
+ d->close();
+ d->state = Idle;
+ emit delayedCloseFinished();
+ return;
+ }
+ if ( !d->socket->isOpen() ) {
+ d->connectionClosed();
+ emit connectionClosed();
+ return;
+ }
+ if ( d->wsn )
+ d->wsn->setEnabled( d->wsize > 0 ); // write if there's data
+}
+
+
+/*!
+ Returns the number of incoming bytes that can be read right now
+ (like bytesAvailable()).
+*/
+
+QIODevice::Offset QSocket::size() const
+{
+ return (Offset)bytesAvailable();
+}
+
+
+/*!
+ Returns the current read index. Since QSocket is a sequential
+ device, the current read index is always zero.
+*/
+
+QIODevice::Offset QSocket::at() const
+{
+ return 0;
+}
+
+
+/*!
+ \overload
+
+ Moves the read index forward to \a index and returns TRUE if the
+ operation was successful; otherwise returns FALSE. Moving the
+ index forward means skipping incoming data.
+*/
+
+bool QSocket::at( Offset index )
+{
+ if ( index > d->rba.size() )
+ return FALSE;
+ d->rba.consumeBytes( (Q_ULONG)index, 0 ); // throw away data 0..index-1
+ // After we read data from our internal buffer, if we use the
+ // setReadBufferSize() to limit our buffer, we might now be able to
+ // read more data in our buffer. So enable the read socket notifier,
+ // but do this only if we are not in a slot connected to the
+ // readyRead() signal since this might cause a bad recursive behavior.
+ // We can test for this condition by looking at the
+ // sn_read_alreadyCalled flag.
+ if ( d->rsn && QSocketPrivate::sn_read_alreadyCalled.findRef(this) == -1 )
+ d->rsn->setEnabled( TRUE );
+ return TRUE;
+}
+
+
+/*!
+ Returns TRUE if there is no more data to read; otherwise returns FALSE.
+*/
+
+bool QSocket::atEnd() const
+{
+ if ( d->socket == 0 )
+ return TRUE;
+ QSocket * that = (QSocket *)this;
+ if ( that->d->socket->bytesAvailable() ) // a little slow, perhaps...
+ that->sn_read();
+ return that->d->rba.size() == 0;
+}
+
+
+/*!
+ Returns the number of incoming bytes that can be read, i.e. the
+ size of the input buffer. Equivalent to size().
+
+ This function can trigger the readyRead() signal, if more data has
+ arrived on the socket.
+
+ \sa bytesToWrite()
+*/
+
+Q_ULONG QSocket::bytesAvailable() const
+{
+ if ( d->socket == 0 )
+ return 0;
+ QSocket * that = (QSocket *)this;
+ if ( that->d->socket->bytesAvailable() ) // a little slow, perhaps...
+ (void)that->sn_read();
+ return that->d->rba.size();
+}
+
+
+/*!
+ Wait up to \a msecs milliseconds for more data to be available.
+
+ If \a msecs is -1 the call will block indefinitely.
+
+ Returns the number of bytes available.
+
+ If \a timeout is non-null and no error occurred (i.e. it does not
+ return -1): this function sets \a *timeout to TRUE, if the reason
+ for returning was that the timeout was reached; otherwise it sets
+ \a *timeout to FALSE. This is useful to find out if the peer
+ closed the connection.
+
+ \warning This is a blocking call and should be avoided in event
+ driven applications.
+
+ \sa bytesAvailable()
+*/
+
+Q_ULONG QSocket::waitForMore( int msecs, bool *timeout ) const
+{
+ if ( d->socket == 0 )
+ return 0;
+ QSocket * that = (QSocket *)this;
+ if ( that->d->socket->waitForMore( msecs, timeout ) > 0 )
+ (void)that->sn_read( TRUE );
+ return that->d->rba.size();
+}
+
+/*! \overload
+*/
+
+Q_ULONG QSocket::waitForMore( int msecs ) const
+{
+ return waitForMore( msecs, 0 );
+}
+
+/*!
+ Returns the number of bytes that are waiting to be written, i.e.
+ the size of the output buffer.
+
+ \sa bytesAvailable() clearPendingData()
+*/
+
+Q_ULONG QSocket::bytesToWrite() const
+{
+ return d->wsize;
+}
+
+/*!
+ Deletes the data that is waiting to be written. This is useful if you want
+ to close the socket without waiting for all the data to be written.
+
+ \sa bytesToWrite() close() delayedCloseFinished()
+*/
+
+void QSocket::clearPendingData()
+{
+ d->wba.clear();
+ d->windex = d->wsize = 0;
+}
+
+/*!
+ Reads \a maxlen bytes from the socket into \a data and returns the
+ number of bytes read. Returns -1 if an error occurred.
+*/
+
+Q_LONG QSocket::readBlock( char *data, Q_ULONG maxlen )
+{
+ if ( data == 0 && maxlen != 0 ) {
+#if defined(QT_CHECK_NULL)
+ qWarning( "QSocket::readBlock: Null pointer error" );
+#endif
+ return -1;
+ }
+ if ( !isOpen() ) {
+#if defined(QT_CHECK_STATE)
+ qWarning( "QSocket::readBlock: Socket is not open" );
+#endif
+ return -1;
+ }
+ if ( maxlen >= d->rba.size() )
+ maxlen = d->rba.size();
+#if defined(QSOCKET_DEBUG)
+ qDebug( "QSocket (%s): readBlock %d bytes", name(), (int)maxlen );
+#endif
+ d->rba.consumeBytes( maxlen, data );
+ // After we read data from our internal buffer, if we use the
+ // setReadBufferSize() to limit our buffer, we might now be able to
+ // read more data in our buffer. So enable the read socket notifier,
+ // but do this only if we are not in a slot connected to the
+ // readyRead() signal since this might cause a bad recursive behavior.
+ // We can test for this condition by looking at the
+ // sn_read_alreadyCalled flag.
+ if ( d->rsn && QSocketPrivate::sn_read_alreadyCalled.findRef(this) == -1 )
+ d->rsn->setEnabled( TRUE );
+ return maxlen;
+}
+
+
+/*!
+ Writes \a len bytes to the socket from \a data and returns the
+ number of bytes written. Returns -1 if an error occurred.
+*/
+
+Q_LONG QSocket::writeBlock( const char *data, Q_ULONG len )
+{
+#if defined(QT_CHECK_NULL)
+ if ( data == 0 && len != 0 ) {
+ qWarning( "QSocket::writeBlock: Null pointer error" );
+ }
+#endif
+#if defined(QT_CHECK_STATE)
+ if ( !isOpen() ) {
+ qWarning( "QSocket::writeBlock: Socket is not open" );
+ return -1;
+ }
+#endif
+#if defined(QT_CHECK_STATE)
+ if ( d->state == Closing ) {
+ qWarning( "QSocket::writeBlock: Cannot write, socket is closing" );
+ }
+#endif
+ if ( len == 0 || d->state == Closing || d->state == Idle )
+ return 0;
+ QByteArray *a = d->wba.last();
+
+ // next bit is sensitive. if we're writing really small chunks,
+ // try to buffer up since system calls are expensive, and nagle's
+ // algorithm is even more expensive. but if anything even
+ // remotely large is being written, try to issue a write at once.
+
+ bool writeNow = ( d->wsize + len >= 1400 || len > 512 );
+
+ if ( a && a->size() + len < 128 ) {
+ // small buffer, resize
+ int i = a->size();
+ a->resize( i+len );
+ memcpy( a->data()+i, data, len );
+ } else {
+ // append new buffer
+ a = new QByteArray( len );
+ memcpy( a->data(), data, len );
+ d->wba.append( a );
+ }
+ d->wsize += len;
+ if ( writeNow )
+ flush();
+ else if ( d->wsn )
+ d->wsn->setEnabled( TRUE );
+#if defined(QSOCKET_DEBUG)
+ qDebug( "QSocket (%s): writeBlock %d bytes", name(), (int)len );
+#endif
+ return len;
+}
+
+
+/*!
+ Reads a single byte/character from the internal read buffer.
+ Returns the byte/character read, or -1 if there is nothing to be
+ read.
+
+ \sa bytesAvailable(), putch()
+*/
+
+int QSocket::getch()
+{
+ if ( isOpen() && d->rba.size() > 0 ) {
+ uchar c;
+ d->rba.consumeBytes( 1, (char*)&c );
+ // After we read data from our internal buffer, if we use the
+ // setReadBufferSize() to limit our buffer, we might now be able to
+ // read more data in our buffer. So enable the read socket notifier,
+ // but do this only if we are not in a slot connected to the
+ // readyRead() signal since this might cause a bad recursive behavior.
+ // We can test for this condition by looking at the
+ // sn_read_alreadyCalled flag.
+ if ( d->rsn && QSocketPrivate::sn_read_alreadyCalled.findRef(this) == -1 )
+ d->rsn->setEnabled( TRUE );
+ return c;
+ }
+ return -1;
+}
+
+
+/*!
+ Writes the character \a ch to the output buffer.
+
+ Returns \a ch, or -1 if an error occurred.
+
+ \sa getch()
+*/
+
+int QSocket::putch( int ch )
+{
+ char buf[2];
+ buf[0] = ch;
+ return writeBlock(buf, 1) == 1 ? ch : -1;
+}
+
+
+/*!
+ This implementation of the virtual function QIODevice::ungetch()
+ prepends the character \a ch to the read buffer so that the next
+ read returns this character as the first character of the output.
+*/
+
+int QSocket::ungetch( int ch )
+{
+#if defined(QT_CHECK_STATE)
+ if ( !isOpen() ) {
+ qWarning( "QSocket::ungetch: Socket not open" );
+ return -1;
+ }
+#endif
+ return d->rba.ungetch( ch );
+}
+
+
+/*!
+ Returns TRUE if it's possible to read an entire line of text from
+ this socket at this time; otherwise returns FALSE.
+
+ Note that if the peer closes the connection unexpectedly, this
+ function returns FALSE. This means that loops such as this won't
+ work:
+
+ \code
+ while( !socket->canReadLine() ) // WRONG
+ ;
+ \endcode
+
+ \sa readLine()
+*/
+
+bool QSocket::canReadLine() const
+{
+ if ( ((QSocket*)this)->d->rba.scanNewline( 0 ) )
+ return TRUE;
+ return ( bytesAvailable() > 0 &&
+ ((QSocket*)this)->d->rba.scanNewline( 0 ) );
+}
+
+/*!
+ \reimp
+ \internal
+ So that it's not hidden by our other readLine().
+*/
+Q_LONG QSocket::readLine( char *data, Q_ULONG maxlen )
+{
+ return QIODevice::readLine(data,maxlen);
+}
+
+/*!
+ Returns a line of text including a terminating newline character
+ (\n). Returns "" if canReadLine() returns FALSE.
+
+ \sa canReadLine()
+*/
+
+QString QSocket::readLine()
+{
+ QByteArray a(256);
+ bool nl = d->rba.scanNewline( &a );
+ QString s;
+ if ( nl ) {
+ at( a.size() ); // skips the data read
+ s = QString( a );
+ }
+ return s;
+}
+
+/*!
+ \internal
+ Internal slot for handling socket read notifications.
+
+ This function has can usually only be entered once (i.e. no
+ recursive calls). If the argument \a force is TRUE, the function
+ is executed, but no readyRead() signals are emitted. This
+ behaviour is useful for the waitForMore() function, so that it is
+ possible to call waitForMore() in a slot connected to the
+ readyRead() signal.
+*/
+
+void QSocket::sn_read( bool force )
+{
+ Q_LONG maxToRead = 0;
+ if ( d->readBufferSize > 0 ) {
+ maxToRead = d->readBufferSize - d->rba.size();
+ if ( maxToRead <= 0 ) {
+ if ( d->rsn )
+ d->rsn->setEnabled( FALSE );
+ return;
+ }
+ }
+
+ // Use QSocketPrivate::sn_read_alreadyCalled to avoid recursive calls of
+ // sn_read() (and as a result avoid emitting the readyRead() signal in a
+ // slot for readyRead(), if you use bytesAvailable()).
+ if ( !force && QSocketPrivate::sn_read_alreadyCalled.findRef(this) != -1 )
+ return;
+ QSocketPrivate::sn_read_alreadyCalled.append( this );
+
+ char buf[4096];
+ Q_LONG nbytes = d->socket->bytesAvailable();
+ Q_LONG nread;
+ QByteArray *a = 0;
+
+ if ( state() == Connecting ) {
+ if ( nbytes > 0 ) {
+ tryConnection();
+ } else {
+ // nothing to do, nothing to care about
+ QSocketPrivate::sn_read_alreadyCalled.removeRef( this );
+ return;
+ }
+ }
+ if ( state() == Idle ) {
+ QSocketPrivate::sn_read_alreadyCalled.removeRef( this );
+ return;
+ }
+
+ if ( nbytes <= 0 ) { // connection closed?
+ // On Windows this may happen when the connection is still open.
+ // This happens when the system is heavily loaded and we have
+ // read all the data on the socket before a new WSAAsyncSelect
+ // event is processed. A new read operation would then block.
+ // This code is also useful when QSocket is used without an
+ // event loop.
+ nread = d->socket->readBlock( buf, maxToRead ? QMIN((Q_LONG)sizeof(buf),maxToRead) : sizeof(buf) );
+ if ( nread == 0 ) { // really closed
+ if ( !d->socket->isOpen() ) {
+#if defined(QSOCKET_DEBUG)
+ qDebug( "QSocket (%s): sn_read: Connection closed", name() );
+#endif
+ d->connectionClosed();
+ emit connectionClosed();
+ }
+ QSocketPrivate::sn_read_alreadyCalled.removeRef( this );
+ return;
+ } else {
+ if ( nread < 0 ) {
+ if ( d->socket->error() == QSocketDevice::NoError ) {
+ // all is fine
+ QSocketPrivate::sn_read_alreadyCalled.removeRef( this );
+ return;
+ }
+#if defined(QSOCKET_DEBUG)
+ qWarning( "QSocket::sn_read (%s): Close error", name() );
+#endif
+ if ( d->rsn )
+ d->rsn->setEnabled( FALSE );
+ emit error( ErrSocketRead );
+ QSocketPrivate::sn_read_alreadyCalled.removeRef( this );
+ return;
+ }
+ a = new QByteArray( nread );
+ memcpy( a->data(), buf, nread );
+ }
+
+ } else { // data to be read
+#if defined(QSOCKET_DEBUG)
+ qDebug( "QSocket (%s): sn_read: %ld incoming bytes", name(), nbytes );
+#endif
+ if ( nbytes > (int)sizeof(buf) ) {
+ // big
+ a = new QByteArray( nbytes );
+ nread = d->socket->readBlock( a->data(), maxToRead ? QMIN(nbytes,maxToRead) : nbytes );
+ } else {
+ a = 0;
+ nread = d->socket->readBlock( buf, maxToRead ? QMIN((Q_LONG)sizeof(buf),maxToRead) : sizeof(buf) );
+ if ( nread > 0 ) {
+ // ##### could setRawData
+ a = new QByteArray( nread );
+ memcpy( a->data(), buf, nread );
+ }
+ }
+ if ( nread == 0 ) {
+#if defined(QSOCKET_DEBUG)
+ qDebug( "QSocket (%s): sn_read: Connection closed", name() );
+#endif
+ // ### we should rather ask the socket device if it is closed
+ d->connectionClosed();
+ emit connectionClosed();
+ QSocketPrivate::sn_read_alreadyCalled.removeRef( this );
+ delete a;
+ return;
+ } else if ( nread < 0 ) {
+ delete a;
+
+ if ( d->socket->error() == QSocketDevice::NoError ) {
+ // all is fine
+ QSocketPrivate::sn_read_alreadyCalled.removeRef( this );
+ return;
+ }
+#if defined(QT_CHECK_RANGE)
+ qWarning( "QSocket::sn_read: Read error" );
+#endif
+ if ( d->rsn )
+ d->rsn->setEnabled( FALSE );
+ emit error( ErrSocketRead );
+ QSocketPrivate::sn_read_alreadyCalled.removeRef( this );
+ return;
+ }
+ if ( nread != (int)a->size() ) { // unexpected
+#if defined(CHECK_RANGE) && !defined(Q_OS_WIN32)
+ qWarning( "QSocket::sn_read: Unexpected short read" );
+#endif
+ a->resize( nread );
+ }
+ }
+ d->rba.append( a );
+ if ( !force ) {
+ if ( d->rsn )
+ d->rsn->setEnabled( FALSE );
+ emit readyRead();
+ if ( d->rsn )
+ d->rsn->setEnabled( TRUE );
+ }
+
+ QSocketPrivate::sn_read_alreadyCalled.removeRef( this );
+}
+
+
+/*!
+ \internal
+ Internal slot for handling socket write notifications.
+*/
+
+void QSocket::sn_write()
+{
+ if ( d->state == Connecting ) // connection established?
+ tryConnection();
+ flush();
+}
+
+void QSocket::emitErrorConnectionRefused()
+{
+ emit error( ErrConnectionRefused );
+}
+
+void QSocket::tryConnection()
+{
+ if ( d->socket->connect( d->addr, d->port ) ) {
+ d->state = Connected;
+#if defined(QSOCKET_DEBUG)
+ qDebug( "QSocket (%s): sn_write: Got connection to %s",
+ name(), peerName().ascii() );
+#endif
+ if ( d->rsn )
+ d->rsn->setEnabled( TRUE );
+ emit connected();
+ } else {
+ d->state = Idle;
+ QTimer::singleShot( 0, this, SLOT(emitErrorConnectionRefused()) );
+ return;
+ }
+}
+
+
+/*!
+ Returns the socket number, or -1 if there is no socket at the moment.
+*/
+
+int QSocket::socket() const
+{
+ if ( d->socket == 0 )
+ return -1;
+ return d->socket->socket();
+}
+
+/*!
+ Sets the socket to use \a socket and the state() to \c Connected.
+ The socket must already be connected.
+
+ This allows us to use the QSocket class as a wrapper for other
+ socket types (e.g. Unix Domain Sockets).
+*/
+
+void QSocket::setSocket( int socket )
+{
+ setSocketIntern( socket );
+ d->state = Connection;
+ d->rsn->setEnabled( TRUE );
+}
+
+
+/*!
+ Sets the socket to \a socket. This is used by both setSocket() and
+ connectToHost() and can also be used on unconnected sockets.
+*/
+
+void QSocket::setSocketIntern( int socket )
+{
+ if ( state() != Idle ) {
+ clearPendingData();
+ close();
+ }
+ Q_ULONG oldBufferSize = d ? d->readBufferSize : 0;
+ delete d;
+
+ d = new QSocketPrivate;
+ if (oldBufferSize)
+ d->readBufferSize = oldBufferSize;
+ if ( socket >= 0 ) {
+ QSocketDevice *sd = new QSocketDevice( socket, QSocketDevice::Stream );
+ sd->setBlocking( FALSE );
+ sd->setAddressReusable( TRUE );
+ d->setSocketDevice( this, sd );
+ }
+ d->state = Idle;
+
+ // Initialize the IO device flags
+ setFlags( IO_Direct );
+ resetStatus();
+ open( IO_ReadWrite );
+
+ // hm... this is not very nice.
+ d->host = QString::null;
+ d->port = 0;
+#ifndef QT_NO_DNS
+ delete d->dns4;
+ d->dns4 = 0;
+ delete d->dns6;
+ d->dns6 = 0;
+#endif
+}
+
+
+/*!
+ Returns the host port number of this socket, in native byte order.
+*/
+
+Q_UINT16 QSocket::port() const
+{
+ if ( d->socket == 0 )
+ return 0;
+ return d->socket->port();
+}
+
+
+/*!
+ Returns the peer's host port number, normally as specified to the
+ connectToHost() function. If none has been set, this function
+ returns 0.
+
+ Note that Qt always uses native byte order, i.e. 67 is 67 in Qt;
+ there is no need to call htons().
+*/
+
+Q_UINT16 QSocket::peerPort() const
+{
+ if ( d->socket == 0 )
+ return 0;
+ return d->socket->peerPort();
+}
+
+
+/*!
+ Returns the host address of this socket. (This is normally the
+ main IP address of the host, but can be e.g. 127.0.0.1 for
+ connections to localhost.)
+*/
+
+QHostAddress QSocket::address() const
+{
+ if ( d->socket == 0 ) {
+ QHostAddress tmp;
+ return tmp;
+ }
+ return d->socket->address();
+}
+
+
+/*!
+ Returns the address of the connected peer if the socket is in
+ Connected state; otherwise an empty QHostAddress is returned.
+*/
+
+QHostAddress QSocket::peerAddress() const
+{
+ if ( d->socket == 0 ) {
+ QHostAddress tmp;
+ return tmp;
+ }
+ return d->socket->peerAddress();
+}
+
+
+/*!
+ Returns the host name as specified to the connectToHost()
+ function. An empty string is returned if none has been set.
+*/
+
+QString QSocket::peerName() const
+{
+ return d->host;
+}
+
+/*!
+ Sets the size of the QSocket's internal read buffer to \a bufSize.
+
+ Usually QSocket reads all data that is available from the operating
+ system's socket. If the buffer size is limited to a certain size, this
+ means that the QSocket class doesn't buffer more than this size of data.
+
+ If the size of the read buffer is 0, the read buffer is unlimited and all
+ incoming data is buffered. This is the default.
+
+ If you read the data in the readyRead() signal, you shouldn't use this
+ option since it might slow down your program unnecessary. This option is
+ useful if you only need to read the data at certain points in time, like in
+ a realtime streaming application.
+
+ \sa readBufferSize()
+*/
+
+void QSocket::setReadBufferSize( Q_ULONG bufSize )
+{
+ d->readBufferSize = bufSize;
+}
+
+/*!
+ Returns the size of the read buffer.
+
+ \sa setReadBufferSize()
+*/
+
+Q_ULONG QSocket::readBufferSize() const
+{
+ return d->readBufferSize;
+}
+
+#endif //QT_NO_NETWORK