Move support for socket binding from QUdpSocket upstream to QAbstractSocket.

This should be API-compatible with Qt 4, but is not ABI-compatible, due to
removing the enum from QUdpSocket.

Task-number: QTBUG-121
Change-Id: I967968c6cb6f96d3ab1d6300eadd5bde6154b300
Reviewed-by: Thiago Macieira <thiago.macieira@intel.com>
This commit is contained in:
Robin Burchell 2011-10-23 20:04:52 +02:00 committed by Sergio Ahumada
parent 3aa81c55e2
commit 03f852cb47
5 changed files with 212 additions and 171 deletions

View File

@ -291,10 +291,10 @@
bytes). bytes).
\value NetworkError An error occurred with the network (e.g., the \value NetworkError An error occurred with the network (e.g., the
network cable was accidentally plugged out). network cable was accidentally plugged out).
\value AddressInUseError The address specified to QUdpSocket::bind() is \value AddressInUseError The address specified to QAbstractSocket::bind() is
already in use and was set to be exclusive. already in use and was set to be exclusive.
\value SocketAddressNotAvailableError The address specified to \value SocketAddressNotAvailableError The address specified to
QUdpSocket::bind() does not belong to the host. QAbstractSocket::bind() does not belong to the host.
\value UnsupportedSocketOperationError The requested socket operation is \value UnsupportedSocketOperationError The requested socket operation is
not supported by the local operating system (e.g., lack of not supported by the local operating system (e.g., lack of
IPv6 support). IPv6 support).
@ -329,7 +329,7 @@
\value HostLookupState The socket is performing a host name lookup. \value HostLookupState The socket is performing a host name lookup.
\value ConnectingState The socket has started establishing a connection. \value ConnectingState The socket has started establishing a connection.
\value ConnectedState A connection is established. \value ConnectedState A connection is established.
\value BoundState The socket is bound to an address and port (for servers). \value BoundState The socket is bound to an address and port.
\value ClosingState The socket is about to close (data may still \value ClosingState The socket is about to close (data may still
be waiting to be written). be waiting to be written).
\value ListeningState For internal use only. \value ListeningState For internal use only.
@ -363,6 +363,51 @@
\sa QAbstractSocket::setSocketOption(), QAbstractSocket::socketOption() \sa QAbstractSocket::setSocketOption(), QAbstractSocket::socketOption()
*/ */
/*! \enum QAbstractSocket::BindFlag
\since 5.0
This enum describes the different flags you can pass to modify the
behavior of QAbstractSocket::bind().
\note On Symbian OS bind flags behaviour depends on process capabilties.
If process has NetworkControl capability, the bind attempt with
ReuseAddressHint will always succeed even if the address and port is already
bound by another socket with any flags. If process does not have
NetworkControl capability, the bind attempt to address and port already
bound by another socket will always fail.
\value ShareAddress Allow other services to bind to the same address
and port. This is useful when multiple processes share
the load of a single service by listening to the same address and port
(e.g., a web server with several pre-forked listeners can greatly
improve response time). However, because any service is allowed to
rebind, this option is subject to certain security considerations.
Note that by combining this option with ReuseAddressHint, you will
also allow your service to rebind an existing shared address. On
Unix, this is equivalent to the SO_REUSEADDR socket option. On Windows,
this option is ignored.
\value DontShareAddress Bind the address and port exclusively, so that
no other services are allowed to rebind. By passing this option to
QAbstractSocket::bind(), you are guaranteed that on successs, your service
is the only one that listens to the address and port. No services are
allowed to rebind, even if they pass ReuseAddressHint. This option
provides more security than ShareAddress, but on certain operating
systems, it requires you to run the server with administrator privileges.
On Unix and Mac OS X, not sharing is the default behavior for binding
an address and port, so this option is ignored. On Windows, this
option uses the SO_EXCLUSIVEADDRUSE socket option.
\value ReuseAddressHint Provides a hint to QAbstractSocket that it should try
to rebind the service even if the address and port are already bound by
another socket. On Windows, this is equivalent to the SO_REUSEADDR
socket option. On Unix, this option is ignored.
\value DefaultForPlatform The default option for the current platform.
On Unix and Mac OS X, this is equivalent to (DontShareAddress
+ ReuseAddressHint), and on Windows, its equivalent to ShareAddress.
*/
#include "qabstractsocket.h" #include "qabstractsocket.h"
#include "qabstractsocket_p.h" #include "qabstractsocket_p.h"
@ -1293,6 +1338,94 @@ QAbstractSocket::~QAbstractSocket()
abort(); abort();
} }
/*!
\since 5.0
Binds to \a address on port \a port, using the BindMode \a mode.
Binds this socket to the address \a address and the port \a port.
For UDP sockets, after binding, the signal QUdpSocket::readyRead() is emitted
whenever a UDP datagram arrives on the specified address and port.
Thus, This function is useful to write UDP servers.
For TCP sockets, this function may be used to specify which interface to use
for an outgoing connection, which is useful in case of multiple network
interfaces.
By default, the socket is bound using the DefaultForPlatform BindMode.
If a port is not specified, a random port is chosen.
On success, the functions returns true and the socket enters
BoundState; otherwise it returns false.
*/
bool QAbstractSocket::bind(const QHostAddress &address, quint16 port, BindMode mode)
{
Q_D(QAbstractSocket);
// now check if the socket engine is initialized and to the right type
if (!d->socketEngine || !d->socketEngine->isValid()) {
QHostAddress nullAddress;
d->resolveProxy(nullAddress.toString(), port);
QAbstractSocket::NetworkLayerProtocol protocol = address.protocol();
if (protocol == QAbstractSocket::UnknownNetworkLayerProtocol)
protocol = nullAddress.protocol();
if (!d->initSocketLayer(protocol))
return false;
}
#ifdef Q_OS_UNIX
if ((mode & ShareAddress) || (mode & ReuseAddressHint))
d->socketEngine->setOption(QAbstractSocketEngine::AddressReusable, 1);
else
d->socketEngine->setOption(QAbstractSocketEngine::AddressReusable, 0);
#endif
#ifdef Q_OS_WIN
if (mode & ReuseAddressHint)
d->socketEngine->setOption(QAbstractSocketEngine::AddressReusable, 1);
else
d->socketEngine->setOption(QAbstractSocketEngine::AddressReusable, 0);
if (mode & DontShareAddress)
d->socketEngine->setOption(QAbstractSocketEngine::BindExclusively, 1);
else
d->socketEngine->setOption(QAbstractSocketEngine::BindExclusively, 0);
#endif
bool result = d->socketEngine->bind(address, port);
d->cachedSocketDescriptor = d->socketEngine->socketDescriptor();
if (!result) {
d->socketError = d->socketEngine->error();
setErrorString(d->socketEngine->errorString());
emit error(d->socketError);
return false;
}
d->state = BoundState;
d->localAddress = d->socketEngine->localAddress();
d->localPort = d->socketEngine->localPort();
emit stateChanged(d->state);
d->socketEngine->setReadNotificationEnabled(true);
return true;
}
/*!
\since 5.0
\overload
Binds to QHostAddress:Any on port \a port, using the BindMode \a mode.
By default, the socket is bound using the DefaultForPlatform BindMode.
If a port is not specified, a random port is chosen.
*/
bool QAbstractSocket::bind(quint16 port, BindMode mode)
{
return bind(QHostAddress::Any, port, mode);
}
/*! /*!
Returns true if the socket is valid and ready for use; otherwise Returns true if the socket is valid and ready for use; otherwise
returns false. returns false.
@ -2345,7 +2478,7 @@ qint64 QAbstractSocket::writeData(const char *data, qint64 size)
proxy connections for virtual connection settings. proxy connections for virtual connection settings.
Note that this function does not bind the local port of the socket Note that this function does not bind the local port of the socket
prior to a connection (e.g., QUdpSocket::bind()). prior to a connection (e.g., QAbstractSocket::bind()).
\sa localAddress(), setLocalAddress(), setPeerPort() \sa localAddress(), setLocalAddress(), setPeerPort()
*/ */
@ -2367,7 +2500,7 @@ void QAbstractSocket::setLocalPort(quint16 port)
proxy connections for virtual connection settings. proxy connections for virtual connection settings.
Note that this function does not bind the local address of the socket Note that this function does not bind the local address of the socket
prior to a connection (e.g., QUdpSocket::bind()). prior to a connection (e.g., QAbstractSocket::bind()).
\sa localAddress(), setLocalPort(), setPeerAddress() \sa localAddress(), setLocalPort(), setPeerAddress()
*/ */

View File

@ -115,10 +115,20 @@ public:
MulticastTtlOption, // IP_MULTICAST_TTL MulticastTtlOption, // IP_MULTICAST_TTL
MulticastLoopbackOption // IP_MULTICAST_LOOPBACK MulticastLoopbackOption // IP_MULTICAST_LOOPBACK
}; };
enum BindFlag {
DefaultForPlatform = 0x0,
ShareAddress = 0x1,
DontShareAddress = 0x2,
ReuseAddressHint = 0x4
};
Q_DECLARE_FLAGS(BindMode, BindFlag)
QAbstractSocket(SocketType socketType, QObject *parent); QAbstractSocket(SocketType socketType, QObject *parent);
virtual ~QAbstractSocket(); virtual ~QAbstractSocket();
bool bind(const QHostAddress &address, quint16 port = 0, BindMode mode = DefaultForPlatform);
bool bind(quint16 port = 0, BindMode mode = DefaultForPlatform);
// ### Qt 5: Make connectToHost() and disconnectFromHost() virtual. // ### Qt 5: Make connectToHost() and disconnectFromHost() virtual.
void connectToHost(const QString &hostName, quint16 port, OpenMode mode = ReadWrite, NetworkLayerProtocol protocol = AnyIPProtocol); void connectToHost(const QString &hostName, quint16 port, OpenMode mode = ReadWrite, NetworkLayerProtocol protocol = AnyIPProtocol);
void connectToHost(const QHostAddress &address, quint16 port, OpenMode mode = ReadWrite); void connectToHost(const QHostAddress &address, quint16 port, OpenMode mode = ReadWrite);
@ -214,6 +224,9 @@ private:
Q_PRIVATE_SLOT(d_func(), void _q_forceDisconnect()) Q_PRIVATE_SLOT(d_func(), void _q_forceDisconnect())
}; };
Q_DECLARE_OPERATORS_FOR_FLAGS(QAbstractSocket::BindMode)
#ifndef QT_NO_DEBUG_STREAM #ifndef QT_NO_DEBUG_STREAM
Q_NETWORK_EXPORT QDebug operator<<(QDebug, QAbstractSocket::SocketError); Q_NETWORK_EXPORT QDebug operator<<(QDebug, QAbstractSocket::SocketError);
Q_NETWORK_EXPORT QDebug operator<<(QDebug, QAbstractSocket::SocketState); Q_NETWORK_EXPORT QDebug operator<<(QDebug, QAbstractSocket::SocketState);

View File

@ -108,51 +108,6 @@
\sa QTcpSocket \sa QTcpSocket
*/ */
/*! \enum QUdpSocket::BindFlag
\since 4.1
This enum describes the different flags you can pass to modify the
behavior of QUdpSocket::bind().
\note On Symbian OS bind flags behaviour depends on process capabilties.
If process has NetworkControl capability, the bind attempt with
ReuseAddressHint will always succeed even if the address and port is already
bound by another socket with any flags. If process does not have
NetworkControl capability, the bind attempt to address and port already
bound by another socket will always fail.
\value ShareAddress Allow other services to bind to the same address
and port. This is useful when multiple processes share
the load of a single service by listening to the same address and port
(e.g., a web server with several pre-forked listeners can greatly
improve response time). However, because any service is allowed to
rebind, this option is subject to certain security considerations.
Note that by combining this option with ReuseAddressHint, you will
also allow your service to rebind an existing shared address. On
Unix, this is equivalent to the SO_REUSEADDR socket option. On Windows,
this option is ignored.
\value DontShareAddress Bind the address and port exclusively, so that
no other services are allowed to rebind. By passing this option to
QUdpSocket::bind(), you are guaranteed that on successs, your service
is the only one that listens to the address and port. No services are
allowed to rebind, even if they pass ReuseAddressHint. This option
provides more security than ShareAddress, but on certain operating
systems, it requires you to run the server with administrator privileges.
On Unix and Mac OS X, not sharing is the default behavior for binding
an address and port, so this option is ignored. On Windows, this
option uses the SO_EXCLUSIVEADDRUSE socket option.
\value ReuseAddressHint Provides a hint to QUdpSocket that it should try
to rebind the service even if the address and port are already bound by
another socket. On Windows, this is equivalent to the SO_REUSEADDR
socket option. On Unix, this option is ignored.
\value DefaultForPlatform The default option for the current platform.
On Unix and Mac OS X, this is equivalent to (DontShareAddress
+ ReuseAddressHint), and on Windows, its equivalent to ShareAddress.
*/
#include "qhostaddress.h" #include "qhostaddress.h"
#include "qnetworkinterface.h" #include "qnetworkinterface.h"
#include "qabstractsocket_p.h" #include "qabstractsocket_p.h"
@ -224,111 +179,6 @@ QUdpSocket::~QUdpSocket()
{ {
} }
/*!
Binds this socket to the address \a address and the port \a port.
When bound, the signal readyRead() is emitted whenever a UDP
datagram arrives on the specified address and port. This function
is useful to write UDP servers.
On success, the functions returns true and the socket enters
BoundState; otherwise it returns false.
The socket is bound using the DefaultForPlatform BindMode.
\sa readDatagram()
*/
bool QUdpSocket::bind(const QHostAddress &address, quint16 port)
{
Q_D(QUdpSocket);
if (!d->ensureInitialized(address, port))
return false;
bool result = d_func()->socketEngine->bind(address, port);
d->cachedSocketDescriptor = d->socketEngine->socketDescriptor();
if (!result) {
d->socketError = d_func()->socketEngine->error();
setErrorString(d_func()->socketEngine->errorString());
emit error(d_func()->socketError);
return false;
}
d->state = BoundState;
d->localAddress = d->socketEngine->localAddress();
d->localPort = d->socketEngine->localPort();
emit stateChanged(d_func()->state);
d_func()->socketEngine->setReadNotificationEnabled(true);
return true;
}
/*!
\since 4.1
\overload
Binds to \a address on port \a port, using the BindMode \a mode.
*/
bool QUdpSocket::bind(const QHostAddress &address, quint16 port, BindMode mode)
{
Q_D(QUdpSocket);
if (!d->ensureInitialized(address, port))
return false;
#ifdef Q_OS_UNIX
if ((mode & ShareAddress) || (mode & ReuseAddressHint))
d->socketEngine->setOption(QAbstractSocketEngine::AddressReusable, 1);
else
d->socketEngine->setOption(QAbstractSocketEngine::AddressReusable, 0);
#endif
#ifdef Q_OS_WIN
if (mode & ReuseAddressHint)
d->socketEngine->setOption(QAbstractSocketEngine::AddressReusable, 1);
else
d->socketEngine->setOption(QAbstractSocketEngine::AddressReusable, 0);
if (mode & DontShareAddress)
d->socketEngine->setOption(QAbstractSocketEngine::BindExclusively, 1);
else
d->socketEngine->setOption(QAbstractSocketEngine::BindExclusively, 0);
#endif
bool result = d_func()->socketEngine->bind(address, port);
d->cachedSocketDescriptor = d->socketEngine->socketDescriptor();
if (!result) {
d->socketError = d_func()->socketEngine->error();
setErrorString(d_func()->socketEngine->errorString());
emit error(d_func()->socketError);
return false;
}
d->state = BoundState;
d->localAddress = d->socketEngine->localAddress();
d->localPort = d->socketEngine->localPort();
emit stateChanged(d_func()->state);
d_func()->socketEngine->setReadNotificationEnabled(true);
return true;
}
/*! \overload
Binds to QHostAddress:Any on port \a port.
*/
bool QUdpSocket::bind(quint16 port)
{
return bind(QHostAddress::Any, port);
}
/*!
\since 4.1
\overload
Binds to QHostAddress:Any on port \a port, using the BindMode \a mode.
*/
bool QUdpSocket::bind(quint16 port, BindMode mode)
{
return bind(QHostAddress::Any, port, mode);
}
#ifndef QT_NO_NETWORKINTERFACE #ifndef QT_NO_NETWORKINTERFACE
/*! /*!

View File

@ -60,23 +60,9 @@ class Q_NETWORK_EXPORT QUdpSocket : public QAbstractSocket
{ {
Q_OBJECT Q_OBJECT
public: public:
enum BindFlag {
DefaultForPlatform = 0x0,
ShareAddress = 0x1,
DontShareAddress = 0x2,
ReuseAddressHint = 0x4
};
Q_DECLARE_FLAGS(BindMode, BindFlag)
explicit QUdpSocket(QObject *parent = 0); explicit QUdpSocket(QObject *parent = 0);
virtual ~QUdpSocket(); virtual ~QUdpSocket();
bool bind(const QHostAddress &address, quint16 port);
bool bind(quint16 port = 0);
bool bind(const QHostAddress &address, quint16 port, BindMode mode);
bool bind(quint16 port, BindMode mode);
// ### Qt 5: Merge the bind functions
#ifndef QT_NO_NETWORKINTERFACE #ifndef QT_NO_NETWORKINTERFACE
bool joinMulticastGroup(const QHostAddress &groupAddress); bool joinMulticastGroup(const QHostAddress &groupAddress);
bool joinMulticastGroup(const QHostAddress &groupAddress, bool joinMulticastGroup(const QHostAddress &groupAddress,
@ -101,8 +87,6 @@ private:
Q_DECLARE_PRIVATE(QUdpSocket) Q_DECLARE_PRIVATE(QUdpSocket)
}; };
Q_DECLARE_OPERATORS_FOR_FLAGS(QUdpSocket::BindMode)
#endif // QT_NO_UDPSOCKET #endif // QT_NO_UDPSOCKET
QT_END_NAMESPACE QT_END_NAMESPACE

View File

@ -138,6 +138,8 @@ public slots:
private slots: private slots:
void socketsConstructedBeforeEventLoop(); void socketsConstructedBeforeEventLoop();
void constructing(); void constructing();
void bind_data();
void bind();
void setInvalidSocketDescriptor(); void setInvalidSocketDescriptor();
void setSocketDescriptor(); void setSocketDescriptor();
void socketDescriptor(); void socketDescriptor();
@ -479,6 +481,65 @@ void tst_QTcpSocket::constructing()
//---------------------------------------------------------------------------------- //----------------------------------------------------------------------------------
void tst_QTcpSocket::bind_data()
{
QTest::addColumn<QString>("stringAddr");
QTest::addColumn<bool>("successExpected");
QTest::addColumn<QString>("stringExpectedLocalAddress");
// iterate all interfaces, add all addresses on them as test data
QList<QNetworkInterface> interfaces = QNetworkInterface::allInterfaces();
foreach (const QNetworkInterface &interface, interfaces) {
if (!interface.isValid())
continue;
foreach (const QNetworkAddressEntry &entry, interface.addressEntries()) {
if (entry.ip().isInSubnet(QHostAddress::parseSubnet("fe80::/10")))
continue; // link-local bind will fail, at least on Linux, so skip it.
QString ip(entry.ip().toString());
QTest::newRow(ip.toLatin1().constData()) << ip << true << ip;
}
}
// additionally, try bind to known-bad addresses, and make sure this doesn't work
// these ranges are guarenteed to be reserved for 'documentation purposes',
// and thus, should be unused in the real world. Not that I'm assuming the
// world is full of competent administrators, or anything.
QStringList knownBad;
knownBad << "198.51.100.1";
knownBad << "2001:0DB8::1";
foreach (const QString &badAddress, knownBad) {
QTest::newRow(badAddress.toLatin1().constData()) << badAddress << false << QString();
}
}
void tst_QTcpSocket::bind()
{
QFETCH(QString, stringAddr);
QFETCH(bool, successExpected);
QFETCH(QString, stringExpectedLocalAddress);
QHostAddress addr(stringAddr);
QHostAddress expectedLocalAddress(stringExpectedLocalAddress);
QTcpSocket *socket = newSocket();
qDebug() << "Binding " << addr;
if (successExpected) {
QVERIFY2(socket->bind(addr), qPrintable(socket->errorString()));
} else {
QVERIFY(!socket->bind(addr));
}
QCOMPARE(socket->localAddress(), expectedLocalAddress);
delete socket;
}
//----------------------------------------------------------------------------------
void tst_QTcpSocket::setInvalidSocketDescriptor() void tst_QTcpSocket::setInvalidSocketDescriptor()
{ {
QTcpSocket *socket = newSocket(); QTcpSocket *socket = newSocket();