/**************************************************************************** ** ** Copyright (C) 2016 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the test suite of the Qt Toolkit. ** ** $QT_BEGIN_LICENSE:GPL-EXCEPT$ ** Commercial License Usage ** Licensees holding valid commercial Qt licenses may use this file in ** accordance with the commercial license agreement provided with the ** Software or, alternatively, in accordance with the terms contained in ** a written agreement between you and The Qt Company. For licensing terms ** and conditions see https://www.qt.io/terms-conditions. For further ** information use the contact form at https://www.qt.io/contact-us. ** ** GNU General Public License Usage ** Alternatively, this file may be used under the terms of the GNU ** General Public License version 3 as published by the Free Software ** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT ** included in the packaging of this file. Please review the following ** information to ensure the GNU General Public License requirements will ** be met: https://www.gnu.org/licenses/gpl-3.0.html. ** ** $QT_END_LICENSE$ ** ****************************************************************************/ #include #include #include #include #include #include #include #include #include #ifdef QT_BUILD_INTERNAL # include #endif #include "minihttpserver.h" #include "../../auto/network-settings.h" #include #ifdef Q_OS_UNIX # include # include # include # include # include # include # include # include # include typedef int SOCKET; # define INVALID_SOCKET -1 # define SOCKET_ERROR -1 #elif defined(Q_OS_WIN) # include #endif class tst_NetworkStressTest : public QObject { Q_OBJECT public: enum { AttemptCount = 100 }; tst_NetworkStressTest(); MiniHttpServer server; qint64 byteCounter; QNetworkAccessManager manager; bool intermediateDebug; private: void clearManager(); public slots: void initTestCase_data(); void initTestCase(); void init(); void slotReadAll() { byteCounter += static_cast(sender())->readAll().size(); } private Q_SLOTS: void nativeBlockingConnectDisconnect(); void nativeNonBlockingConnectDisconnect(); void blockingConnectDisconnect(); void blockingPipelined(); void blockingMultipleRequests(); void connectDisconnect(); void parallelConnectDisconnect_data(); void parallelConnectDisconnect(); void namGet_data(); void namGet(); }; tst_NetworkStressTest::tst_NetworkStressTest() : intermediateDebug(qgetenv("STRESSDEBUG").toInt() > 0) { #ifdef Q_OS_WIN WSAData wsadata; // IPv6 requires Winsock v2.0 or better. WSAStartup(MAKEWORD(2,0), &wsadata); #elif defined(Q_OS_UNIX) ::signal(SIGALRM, SIG_IGN); #endif } void tst_NetworkStressTest::initTestCase_data() { QTest::addColumn("isLocalhost"); QTest::addColumn("hostname"); QTest::addColumn("port"); QTest::newRow("localhost") << true << "localhost" << server.port(); QTest::newRow("remote") << false << QtNetworkSettings::serverName() << 80; } void tst_NetworkStressTest::initTestCase() { QVERIFY(QtNetworkSettings::verifyTestNetworkSettings()); } void tst_NetworkStressTest::init() { // clear the internal cache #ifndef QT_BUILD_INTERNAL if (strncmp(QTest::currentTestFunction(), "nam", 3) == 0) QSKIP("QNetworkAccessManager tests disabled"); #endif } void tst_NetworkStressTest::clearManager() { #ifdef QT_BUILD_INTERNAL QNetworkAccessManagerPrivate::clearAuthenticationCache(&manager); QNetworkAccessManagerPrivate::clearConnectionCache(&manager); manager.setProxy(QNetworkProxy()); manager.setCache(0); #endif } bool nativeLookup(const char *hostname, int port, QByteArray &buf) { #if 0 addrinfo *res = 0; struct addrinfo hints; memset(&hints, 0, sizeof(hints)); hints.ai_family = PF_UNSPEC; int result = getaddrinfo(QUrl::toAce(hostname).constData(), QByteArray::number(port).constData(), &hints, &res); if (!result) return false; for (addrinfo *node = res; node; node = node->ai_next) { if (node->ai_family == AF_INET) { buf = QByteArray((char *)node->ai_addr, node->ai_addrlen); break; } } freeaddrinfo(res); #else hostent *result = gethostbyname(hostname); if (!result || result->h_addrtype != AF_INET) return false; struct sockaddr_in s; s.sin_family = AF_INET; s.sin_port = htons(port); s.sin_addr = *(struct in_addr *) result->h_addr_list[0]; buf = QByteArray((char *)&s, sizeof s); #endif return !buf.isEmpty(); } bool nativeSelect(int fd, int timeout, bool selectForWrite) { if (timeout < 0) return false; // wait for connected fd_set fds, fde; FD_ZERO(&fds); FD_ZERO(&fde); FD_SET(fd, &fds); FD_SET(fd, &fde); int ret; do { struct timeval tv; tv.tv_sec = timeout / 1000; tv.tv_usec = timeout % 1000; if (selectForWrite) ret = ::select(fd + 1, 0, &fds, &fde, &tv); else ret = ::select(fd + 1, &fds, 0, &fde, &tv); } while (ret == -1 && errno == EINTR); return ret != 0; } void tst_NetworkStressTest::nativeBlockingConnectDisconnect() { QFETCH_GLOBAL(QString, hostname); QFETCH_GLOBAL(int, port); qint64 totalBytes = 0; QElapsedTimer outerTimer; outerTimer.start(); for (int i = 0; i < AttemptCount; ++i) { QElapsedTimer timeout; byteCounter = 0; timeout.start(); #if defined(Q_OS_UNIX) alarm(10); #endif // look up the host QByteArray addr; if (!nativeLookup(QUrl::toAce(hostname).constData(), port, addr)) QFAIL("Lookup failed"); // connect SOCKET fd = ::socket(AF_INET, SOCK_STREAM, 0); QVERIFY(fd != INVALID_SOCKET); QVERIFY(::connect(fd, (sockaddr *)addr.data(), addr.size()) != SOCKET_ERROR); // send request { QByteArray request = "GET /qtest/bigfile HTTP/1.1\r\n" "Connection: close\r\n" "User-Agent: tst_QTcpSocket_stresstest/1.0\r\n" "Host: " + hostname.toLatin1() + "\r\n" "\r\n"; qint64 bytesWritten = 0; while (bytesWritten < request.size()) { qint64 ret = ::send(fd, request.constData() + bytesWritten, request.size() - bytesWritten, 0); if (ret == -1) { ::close(fd); QFAIL("Timeout"); } bytesWritten += ret; } } // receive reply char buf[16384]; while (true) { qint64 ret = ::recv(fd, buf, sizeof buf, 0); if (ret == -1) { ::close(fd); QFAIL("Timeout"); } else if (ret == 0) { break; // EOF } byteCounter += ret; } ::close(fd); totalBytes += byteCounter; if (intermediateDebug) { double rate = (byteCounter * 1.0 / timeout.elapsed()); qDebug() << i << byteCounter << "bytes in" << timeout.elapsed() << "ms:" << (rate / 1024.0 / 1024 * 1000) << "MB/s"; } } qDebug() << "Average transfer rate was" << (totalBytes / 1024.0 / 1024 * 1000 / outerTimer.elapsed()) << "MB/s"; #if defined(Q_OS_UNIX) alarm(0); #endif } void tst_NetworkStressTest::nativeNonBlockingConnectDisconnect() { QFETCH_GLOBAL(QString, hostname); QFETCH_GLOBAL(int, port); qint64 totalBytes = 0; QElapsedTimer outerTimer; outerTimer.start(); for (int i = 0; i < AttemptCount; ++i) { QElapsedTimer timeout; byteCounter = 0; timeout.start(); // look up the host QByteArray addr; if (!nativeLookup(QUrl::toAce(hostname).constData(), port, addr)) QFAIL("Lookup failed"); SOCKET fd; { #if defined(Q_OS_UNIX) fd = ::socket(AF_INET, SOCK_STREAM, 0); QVERIFY(fd != INVALID_SOCKET); // set the socket to non-blocking and start connecting # if !defined(Q_OS_VXWORKS) int flags = ::fcntl(fd, F_GETFL, 0); QVERIFY(flags != -1); QVERIFY(::fcntl(fd, F_SETFL, flags | O_NONBLOCK) != -1); # else // Q_OS_VXWORKS int onoff = 1; QVERIFY(::ioctl(socketDescriptor, FIONBIO, &onoff) >= 0); # endif // Q_OS_VXWORKS while (true) { if (::connect(fd, (sockaddr *)addr.data(), addr.size()) == -1) { QVERIFY2(errno == EINPROGRESS, QByteArray("Error connecting: ").append(strerror(errno)).constData()); QVERIFY2(nativeSelect(fd, 10000 - timeout.elapsed(), true), "Timeout"); } else { break; // connected } } #elif defined(Q_OS_WIN) fd = ::WSASocket(AF_INET, SOCK_STREAM, 0, NULL, 0, WSA_FLAG_OVERLAPPED); QVERIFY(fd != INVALID_SOCKET); // set the socket to non-blocking and start connecting unsigned long buf = 0; unsigned long outBuf; DWORD sizeWritten = 0; QVERIFY(::WSAIoctl(fd, FIONBIO, &buf, sizeof(unsigned long), &outBuf, sizeof(unsigned long), &sizeWritten, 0,0) != SOCKET_ERROR); while (true) { int connectResult = ::WSAConnect(fd, (sockaddr *)addr.data(), addr.size(), 0,0,0,0); if (connectResult == 0 || WSAGetLastError() == WSAEISCONN) { break; // connected } else { QVERIFY2(WSAGetLastError() == WSAEINPROGRESS, "Unexpected error"); QVERIFY2(nativeSelect(fd, 10000 - timeout.elapsed(), true), "Timeout"); } } #endif } // send request { QByteArray request = "GET /qtest/bigfile HTTP/1.1\r\n" "Connection: close\r\n" "User-Agent: tst_QTcpSocket_stresstest/1.0\r\n" "Host: " + hostname.toLatin1() + "\r\n" "\r\n"; qint64 bytesWritten = 0; while (bytesWritten < request.size()) { qint64 ret = ::send(fd, request.constData() + bytesWritten, request.size() - bytesWritten, 0); if (ret == -1 && errno != EWOULDBLOCK) { ::close(fd); QFAIL(QByteArray("Error writing: ").append(strerror(errno)).constData()); } else if (ret == -1) { // wait for writing QVERIFY2(nativeSelect(fd, 10000 - timeout.elapsed(), true), "Timeout"); continue; } bytesWritten += ret; } } // receive reply char buf[16384]; while (true) { qint64 ret = ::recv(fd, buf, sizeof buf, 0); if (ret == -1 && errno != EWOULDBLOCK) { ::close(fd); QFAIL("Timeout"); } else if (ret == -1) { // wait for reading QVERIFY2(nativeSelect(fd, 10000 - timeout.elapsed(), false), "Timeout"); } else if (ret == 0) { break; // EOF } byteCounter += ret; } ::close(fd); totalBytes += byteCounter; if (intermediateDebug) { double rate = (byteCounter * 1.0 / timeout.elapsed()); qDebug() << i << byteCounter << "bytes in" << timeout.elapsed() << "ms:" << (rate / 1024.0 / 1024 * 1000) << "MB/s"; } } qDebug() << "Average transfer rate was" << (totalBytes / 1024.0 / 1024 * 1000 / outerTimer.elapsed()) << "MB/s"; } void tst_NetworkStressTest::blockingConnectDisconnect() { QFETCH_GLOBAL(QString, hostname); QFETCH_GLOBAL(int, port); qint64 totalBytes = 0; QElapsedTimer outerTimer; outerTimer.start(); for (int i = 0; i < AttemptCount; ++i) { QElapsedTimer timeout; byteCounter = 0; timeout.start(); QTcpSocket socket; socket.connectToHost(hostname, port); QVERIFY2(socket.waitForConnected(), "Timeout"); socket.write("GET /qtest/bigfile HTTP/1.1\r\n" "Connection: close\r\n" "User-Agent: tst_QTcpSocket_stresstest/1.0\r\n" "Host: " + hostname.toLatin1() + "\r\n" "\r\n"); while (socket.bytesToWrite()) QVERIFY2(socket.waitForBytesWritten(), "Timeout"); while (socket.state() == QAbstractSocket::ConnectedState && !timeout.hasExpired(10000)) { socket.waitForReadyRead(); byteCounter += socket.readAll().size(); // discard } totalBytes += byteCounter; if (intermediateDebug) { double rate = (byteCounter * 1.0 / timeout.elapsed()); qDebug() << i << byteCounter << "bytes in" << timeout.elapsed() << "ms:" << (rate / 1024.0 / 1024 * 1000) << "MB/s"; } } qDebug() << "Average transfer rate was" << (totalBytes / 1024.0 / 1024 * 1000 / outerTimer.elapsed()) << "MB/s"; } void tst_NetworkStressTest::blockingPipelined() { QFETCH_GLOBAL(QString, hostname); QFETCH_GLOBAL(int, port); qint64 totalBytes = 0; QElapsedTimer outerTimer; outerTimer.start(); for (int i = 0; i < AttemptCount / 2; ++i) { QElapsedTimer timeout; byteCounter = 0; timeout.start(); QTcpSocket socket; socket.connectToHost(hostname, port); QVERIFY2(socket.waitForConnected(), "Timeout"); for (int j = 0 ; j < 3; ++j) { if (intermediateDebug) qDebug("Attempt %d%c", i, 'a' + j); socket.write("GET /qtest/bigfile HTTP/1.1\r\n" "Connection: " + QByteArray(j == 2 ? "close" : "keep-alive") + "\r\n" "User-Agent: tst_QTcpSocket_stresstest/1.0\r\n" "Host: " + hostname.toLatin1() + "\r\n" "\r\n"); while (socket.bytesToWrite()) QVERIFY2(socket.waitForBytesWritten(), "Timeout"); } while (socket.state() == QAbstractSocket::ConnectedState && !timeout.hasExpired(10000)) { socket.waitForReadyRead(); byteCounter += socket.readAll().size(); // discard } totalBytes += byteCounter; if (intermediateDebug) { double rate = (byteCounter * 1.0 / timeout.elapsed()); qDebug() << i << byteCounter << "bytes in" << timeout.elapsed() << "ms:" << (rate / 1024.0 / 1024 * 1000) << "MB/s"; } } qDebug() << "Average transfer rate was" << (totalBytes / 1024.0 / 1024 * 1000 / outerTimer.elapsed()) << "MB/s"; } void tst_NetworkStressTest::blockingMultipleRequests() { QFETCH_GLOBAL(QString, hostname); QFETCH_GLOBAL(int, port); qint64 totalBytes = 0; QElapsedTimer outerTimer; outerTimer.start(); for (int i = 0; i < AttemptCount / 5; ++i) { QTcpSocket socket; socket.connectToHost(hostname, port); QVERIFY2(socket.waitForConnected(), "Timeout"); for (int j = 0 ; j < 5; ++j) { QElapsedTimer timeout; byteCounter = 0; timeout.start(); socket.write("GET /qtest/bigfile HTTP/1.1\r\n" "Connection: keep-alive\r\n" "User-Agent: tst_QTcpSocket_stresstest/1.0\r\n" "Host: " + hostname.toLatin1() + "\r\n" "\r\n"); while (socket.bytesToWrite()) QVERIFY2(socket.waitForBytesWritten(), "Timeout"); qint64 bytesRead = 0; qint64 bytesExpected = -1; QByteArray buffer; while (socket.state() == QAbstractSocket::ConnectedState && !timeout.hasExpired(10000)) { socket.waitForReadyRead(); buffer += socket.readAll(); byteCounter += buffer.size(); int pos = buffer.indexOf("\r\n\r\n"); if (pos == -1) continue; bytesRead = buffer.length() - pos - 4; buffer.truncate(pos + 2); buffer = buffer.toLower(); pos = buffer.indexOf("\r\ncontent-length: "); if (pos == -1) { qWarning() << "no content-length:" << QString(buffer); break; } pos += int(strlen("\r\ncontent-length: ")); int eol = buffer.indexOf("\r\n", pos + 2); if (eol == -1) { qWarning() << "invalid header"; break; } bytesExpected = buffer.mid(pos, eol - pos).toLongLong(); break; } QVERIFY(bytesExpected > 0); QVERIFY2(!timeout.hasExpired(10000), "Timeout"); while (socket.state() == QAbstractSocket::ConnectedState && !timeout.hasExpired(10000) && bytesExpected > bytesRead) { socket.waitForReadyRead(); int blocklen = socket.read(bytesExpected - bytesRead).length(); // discard bytesRead += blocklen; byteCounter += blocklen; } QVERIFY2(!timeout.hasExpired(10000), "Timeout"); QCOMPARE(bytesRead, bytesExpected); totalBytes += byteCounter; if (intermediateDebug) { double rate = (byteCounter * 1.0 / timeout.elapsed()); qDebug() << i << byteCounter << "bytes in" << timeout.elapsed() << "ms:" << (rate / 1024.0 / 1024 * 1000) << "MB/s"; } } socket.disconnectFromHost(); QVERIFY(socket.state() == QAbstractSocket::UnconnectedState); } qDebug() << "Average transfer rate was" << (totalBytes / 1024.0 / 1024 * 1000 / outerTimer.elapsed()) << "MB/s"; } void tst_NetworkStressTest::connectDisconnect() { QFETCH_GLOBAL(QString, hostname); QFETCH_GLOBAL(int, port); qint64 totalBytes = 0; QElapsedTimer outerTimer; outerTimer.start(); for (int i = 0; i < AttemptCount; ++i) { QElapsedTimer timeout; byteCounter = 0; timeout.start(); QTcpSocket socket; socket.connectToHost(hostname, port); socket.write("GET /qtest/bigfile HTTP/1.1\r\n" "Connection: close\r\n" "User-Agent: tst_QTcpSocket_stresstest/1.0\r\n" "Host: " + hostname.toLatin1() + "\r\n" "\r\n"); connect(&socket, SIGNAL(readyRead()), SLOT(slotReadAll())); QTestEventLoop::instance().connect(&socket, SIGNAL(disconnected()), SLOT(exitLoop())); QTestEventLoop::instance().enterLoop(30); QVERIFY2(!QTestEventLoop::instance().timeout(), "Timeout"); totalBytes += byteCounter; if (intermediateDebug) { double rate = (byteCounter * 1.0 / timeout.elapsed()); qDebug() << i << byteCounter << "bytes in" << timeout.elapsed() << "ms:" << (rate / 1024.0 / 1024 * 1000) << "MB/s"; } } qDebug() << "Average transfer rate was" << (totalBytes / 1024.0 / 1024 * 1000 / outerTimer.elapsed()) << "MB/s"; } void tst_NetworkStressTest::parallelConnectDisconnect_data() { QTest::addColumn("parallelAttempts"); QTest::newRow("1") << 1; QTest::newRow("2") << 2; QTest::newRow("4") << 4; QTest::newRow("5") << 5; QTest::newRow("6") << 6; QTest::newRow("8") << 8; QTest::newRow("10") << 10; QTest::newRow("25") << 25; QTest::newRow("100") << 100; QTest::newRow("500") << 500; } void tst_NetworkStressTest::parallelConnectDisconnect() { QFETCH_GLOBAL(QString, hostname); QFETCH_GLOBAL(int, port); QFETCH(int, parallelAttempts); if (parallelAttempts > 100) { QFETCH_GLOBAL(bool, isLocalhost); if (!isLocalhost) QSKIP("Localhost-only test"); } qint64 totalBytes = 0; QElapsedTimer outerTimer; outerTimer.start(); for (int i = 0; i < qMax(2, AttemptCount/qMax(2, parallelAttempts/4)); ++i) { QElapsedTimer timeout; byteCounter = 0; timeout.start(); QTcpSocket *socket = new QTcpSocket[parallelAttempts]; for (int j = 0; j < parallelAttempts; ++j) { socket[j].connectToHost(hostname, port); socket[j].write("GET /qtest/bigfile HTTP/1.1\r\n" "Connection: close\r\n" "User-Agent: tst_QTcpSocket_stresstest/1.0\r\n" "Host: " + hostname.toLatin1() + "\r\n" "\r\n"); connect(&socket[j], SIGNAL(readyRead()), SLOT(slotReadAll())); QTestEventLoop::instance().connect(&socket[j], SIGNAL(disconnected()), SLOT(exitLoop())); } while (!timeout.hasExpired(30000)) { QTestEventLoop::instance().enterLoop(10); int done = 0; for (int j = 0; j < parallelAttempts; ++j) done += socket[j].state() == QAbstractSocket::UnconnectedState ? 1 : 0; if (done == parallelAttempts) break; } delete[] socket; QVERIFY2(!timeout.hasExpired(30000), "Timeout"); totalBytes += byteCounter; if (intermediateDebug) { double rate = (byteCounter * 1.0 / timeout.elapsed()); qDebug() << i << byteCounter << "bytes in" << timeout.elapsed() << "ms:" << (rate / 1024.0 / 1024 * 1000) << "MB/s"; } } qDebug() << "Average transfer rate was" << (totalBytes / 1024.0 / 1024 * 1000 / outerTimer.elapsed()) << "MB/s"; } void tst_NetworkStressTest::namGet_data() { QTest::addColumn("parallelAttempts"); QTest::addColumn("pipelineAllowed"); QTest::newRow("1") << 1 << false; QTest::newRow("1p") << 1 << true; QTest::newRow("2") << 2 << false; QTest::newRow("2p") << 2 << true; QTest::newRow("4") << 4 << false; QTest::newRow("4p") << 4 << true; QTest::newRow("5") << 5 << false; QTest::newRow("5p") << 5 << true; QTest::newRow("6") << 6 << false; QTest::newRow("6p") << 6 << true; QTest::newRow("8") << 8 << false; QTest::newRow("8p") << 8 << true; QTest::newRow("10") << 10 << false; QTest::newRow("10p") << 10 << true; QTest::newRow("25") << 25 << false; QTest::newRow("25p") << 25 << true; QTest::newRow("100") << 100 << false; QTest::newRow("100p") << 100 << true; QTest::newRow("500") << 500 << false; QTest::newRow("500p") << 500 << true; } void tst_NetworkStressTest::namGet() { QFETCH_GLOBAL(QString, hostname); QFETCH_GLOBAL(int, port); QFETCH(int, parallelAttempts); QFETCH(bool, pipelineAllowed); if (parallelAttempts > 100) { QFETCH_GLOBAL(bool, isLocalhost); if (!isLocalhost) QSKIP("Localhost-only test"); } qint64 totalBytes = 0; QElapsedTimer outerTimer; outerTimer.start(); for (int i = 0; i < qMax(2, AttemptCount/qMax(2, parallelAttempts/4)); ++i) { QElapsedTimer timeout; byteCounter = 0; timeout.start(); QUrl url; url.setScheme("http"); url.setHost(hostname); url.setPort(port); url.setEncodedPath("/qtest/bigfile"); QNetworkRequest req(url); req.setAttribute(QNetworkRequest::HttpPipeliningAllowedAttribute, pipelineAllowed); QVector > replies; replies.resize(parallelAttempts); for (int j = 0; j < parallelAttempts; ++j) { QNetworkReply *r = manager.get(req); connect(r, SIGNAL(readyRead()), SLOT(slotReadAll())); QTestEventLoop::instance().connect(r, SIGNAL(finished()), SLOT(exitLoop())); replies[j] = QSharedPointer(r); } while (!timeout.hasExpired(30000)) { QTestEventLoop::instance().enterLoop(10); int done = 0; for (int j = 0; j < parallelAttempts; ++j) done += replies[j]->isFinished() ? 1 : 0; if (done == parallelAttempts) break; } replies.clear(); QVERIFY2(!timeout.hasExpired(30000), "Timeout"); totalBytes += byteCounter; if (intermediateDebug) { double rate = (byteCounter * 1.0 / timeout.elapsed()); qDebug() << i << byteCounter << "bytes in" << timeout.elapsed() << "ms:" << (rate / 1024.0 / 1024 * 1000) << "MB/s"; } } qDebug() << "Average transfer rate was" << (totalBytes / 1024.0 / 1024 * 1000 / outerTimer.elapsed()) << "MB/s"; } QTEST_MAIN(tst_NetworkStressTest); #include "tst_network_stresstest.moc"