From 75a9bd2a4f637fb8e8e3dc4609a7045547119e80 Mon Sep 17 00:00:00 2001 From: Alex Trotsenko Date: Wed, 24 Sep 2014 17:31:33 +0300 Subject: [PATCH] Introduce SCTP sockets support Add protocol-specific code and the QSctpServer, QSctpSocket classes. Change-Id: Ie9a1d87bd1fda866a2405043d1c15c12ded5a96e Reviewed-by: Thiago Macieira --- config.tests/unix/sctp/sctp.cpp | 60 ++ config.tests/unix/sctp/sctp.pro | 4 + config_help.txt | 3 + configure.json | 13 + .../multistreamclient/chatconsumer.cpp | 83 +++ .../network/multistreamclient/chatconsumer.h | 69 +++ examples/network/multistreamclient/client.cpp | 207 +++++++ examples/network/multistreamclient/client.h | 90 +++ examples/network/multistreamclient/consumer.h | 65 +++ examples/network/multistreamclient/main.cpp | 50 ++ .../multistreamclient/movieconsumer.cpp | 73 +++ .../network/multistreamclient/movieconsumer.h | 64 +++ .../multistreamclient/multistreamclient.pro | 16 + .../multistreamclient/timeconsumer.cpp | 84 +++ .../network/multistreamclient/timeconsumer.h | 69 +++ .../network/multistreamserver/animation.gif | Bin 0 -> 42629 bytes .../multistreamserver/chatprovider.cpp | 67 +++ .../network/multistreamserver/chatprovider.h | 57 ++ examples/network/multistreamserver/main.cpp | 51 ++ .../multistreamserver/movieprovider.cpp | 63 ++ .../network/multistreamserver/movieprovider.h | 63 ++ .../multistreamserver/multistreamserver.pro | 24 + examples/network/multistreamserver/provider.h | 66 +++ examples/network/multistreamserver/server.cpp | 159 +++++ examples/network/multistreamserver/server.h | 89 +++ .../multistreamserver/timeprovider.cpp | 67 +++ .../network/multistreamserver/timeprovider.h | 55 ++ examples/network/network.pro | 1 + src/corelib/io/qiodevice.cpp | 12 + src/corelib/io/qiodevice_p.h | 2 + .../code/src_network_socket_qsctpsocket.cpp | 53 ++ src/network/kernel/qnetworkdatagram.h | 1 + src/network/kernel/qnetworkdatagram_p.h | 10 +- src/network/kernel/qnetworkproxy.cpp | 37 +- src/network/kernel/qnetworkproxy.h | 8 +- src/network/kernel/qnetworkproxy_win.cpp | 10 +- src/network/network.pro | 1 + src/network/socket/qabstractsocket.cpp | 73 ++- src/network/socket/qabstractsocket.h | 1 + src/network/socket/qabstractsocket_p.h | 7 +- src/network/socket/qabstractsocketengine_p.h | 11 +- src/network/socket/qhttpsocketengine.cpp | 26 +- src/network/socket/qhttpsocketengine_p.h | 6 +- src/network/socket/qnativesocketengine.cpp | 40 +- src/network/socket/qnativesocketengine_p.h | 6 +- .../socket/qnativesocketengine_unix.cpp | 182 +++++- .../socket/qnativesocketengine_win.cpp | 11 + .../socket/qnativesocketengine_winrt.cpp | 16 + src/network/socket/qsctpserver.cpp | 250 ++++++++ src/network/socket/qsctpserver.h | 77 +++ src/network/socket/qsctpserver_p.h | 76 +++ src/network/socket/qsctpsocket.cpp | 543 ++++++++++++++++++ src/network/socket/qsctpsocket.h | 82 +++ src/network/socket/qsctpsocket_p.h | 90 +++ src/network/socket/qsocks5socketengine.cpp | 58 +- src/network/socket/qsocks5socketengine_p.h | 6 +- src/network/socket/qtcpserver.cpp | 34 +- src/network/socket/qtcpserver.h | 3 +- src/network/socket/qtcpserver_p.h | 2 + src/network/socket/qudpsocket.cpp | 15 +- src/network/socket/socket.pri | 12 + .../tst_platformsocketengine.cpp | 4 +- .../socket/qsctpsocket/qsctpsocket.pro | 6 + .../socket/qsctpsocket/tst_qsctpsocket.cpp | 489 ++++++++++++++++ tests/auto/network/socket/socket.pro | 4 + tools/configure/configureapp.cpp | 20 + 66 files changed, 3875 insertions(+), 121 deletions(-) create mode 100644 config.tests/unix/sctp/sctp.cpp create mode 100644 config.tests/unix/sctp/sctp.pro create mode 100644 examples/network/multistreamclient/chatconsumer.cpp create mode 100644 examples/network/multistreamclient/chatconsumer.h create mode 100644 examples/network/multistreamclient/client.cpp create mode 100644 examples/network/multistreamclient/client.h create mode 100644 examples/network/multistreamclient/consumer.h create mode 100644 examples/network/multistreamclient/main.cpp create mode 100644 examples/network/multistreamclient/movieconsumer.cpp create mode 100644 examples/network/multistreamclient/movieconsumer.h create mode 100644 examples/network/multistreamclient/multistreamclient.pro create mode 100644 examples/network/multistreamclient/timeconsumer.cpp create mode 100644 examples/network/multistreamclient/timeconsumer.h create mode 100644 examples/network/multistreamserver/animation.gif create mode 100644 examples/network/multistreamserver/chatprovider.cpp create mode 100644 examples/network/multistreamserver/chatprovider.h create mode 100644 examples/network/multistreamserver/main.cpp create mode 100644 examples/network/multistreamserver/movieprovider.cpp create mode 100644 examples/network/multistreamserver/movieprovider.h create mode 100644 examples/network/multistreamserver/multistreamserver.pro create mode 100644 examples/network/multistreamserver/provider.h create mode 100644 examples/network/multistreamserver/server.cpp create mode 100644 examples/network/multistreamserver/server.h create mode 100644 examples/network/multistreamserver/timeprovider.cpp create mode 100644 examples/network/multistreamserver/timeprovider.h create mode 100644 src/network/doc/snippets/code/src_network_socket_qsctpsocket.cpp create mode 100644 src/network/socket/qsctpserver.cpp create mode 100644 src/network/socket/qsctpserver.h create mode 100644 src/network/socket/qsctpserver_p.h create mode 100644 src/network/socket/qsctpsocket.cpp create mode 100644 src/network/socket/qsctpsocket.h create mode 100644 src/network/socket/qsctpsocket_p.h create mode 100644 tests/auto/network/socket/qsctpsocket/qsctpsocket.pro create mode 100644 tests/auto/network/socket/qsctpsocket/tst_qsctpsocket.cpp diff --git a/config.tests/unix/sctp/sctp.cpp b/config.tests/unix/sctp/sctp.cpp new file mode 100644 index 00000000000..40d7bb1bcbb --- /dev/null +++ b/config.tests/unix/sctp/sctp.cpp @@ -0,0 +1,60 @@ +/**************************************************************************** +** +** Copyright (C) 2016 Alex Trotsenko +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the config.tests of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** 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 Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** 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-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +/* Sample program for configure to test for SCTP sockets support +on target platforms. */ + +#include +#include +#include +#include + +int main() +{ + int fd; + sctp_initmsg sctpInitMsg; + socklen_t sctpInitMsgSize = sizeof(sctpInitMsg); + + fd = socket(PF_INET, SOCK_STREAM, IPPROTO_SCTP); + if (fd == -1 || getsockopt(fd, SOL_SCTP, SCTP_INITMSG, &sctpInitMsg, + &sctpInitMsgSize) != 0) + return 1; + + return 0; +} diff --git a/config.tests/unix/sctp/sctp.pro b/config.tests/unix/sctp/sctp.pro new file mode 100644 index 00000000000..edcc0a444a8 --- /dev/null +++ b/config.tests/unix/sctp/sctp.pro @@ -0,0 +1,4 @@ +SOURCES = sctp.cpp +CONFIG -= qt +QT = +LIBS += $$QMAKE_LIBS_NETWORK diff --git a/config_help.txt b/config_help.txt index b13dbe3c278..2ea2a812e42 100644 --- a/config_help.txt +++ b/config_help.txt @@ -365,6 +365,9 @@ Additional options: -no-system-proxies ... Do not use system network proxies by default. * -system-proxies ...... Use system network proxies by default. + * -no-sctp ............. Do not compile SCTP network protocol support. + -sctp ................ Compile SCTP support. + -no-warnings-are-errors Make warnings be treated normally -warnings-are-errors Make warnings be treated as errors (enabled if -developer-build is active) diff --git a/configure.json b/configure.json index a062477f538..3013a84ccf3 100644 --- a/configure.json +++ b/configure.json @@ -130,6 +130,7 @@ "release": { "type": "enum", "name": "debug", "values": { "yes": "no", "no": "yes" } }, "rpath": "boolean", "sanitize": "sanitize", + "sctp": "boolean", "sdk": "string", "securetransport": "boolean", "separate-debug-info": { "type": "boolean", "name": "separate_debug_info" }, @@ -596,6 +597,11 @@ "type": "openssl", "libs": "-lssl -lcrypto" }, + "sctp": { + "description": "SCTP support", + "type": "compile", + "test": "unix/sctp" + }, "icu": { "description": "ICU", "type": "compile", @@ -1625,6 +1631,12 @@ "condition": "features.securetransport || features.openssl", "output": [ "feature" ] }, + "sctp": { + "description": "SCTP", + "autoDetect": false, + "condition": "tests.sctp", + "output": [ "feature" ] + }, "accessibility": { "description": "Accessibility", "output": [ "feature" ] @@ -2462,6 +2474,7 @@ Please apply the patch corresponding to your Standard Library vendor, found in }, "openssl", "openssl-linked", + "sctp", "system-proxies" ] }, diff --git a/examples/network/multistreamclient/chatconsumer.cpp b/examples/network/multistreamclient/chatconsumer.cpp new file mode 100644 index 00000000000..65840accae8 --- /dev/null +++ b/examples/network/multistreamclient/chatconsumer.cpp @@ -0,0 +1,83 @@ +/**************************************************************************** +** +** Copyright (C) 2016 Alex Trotsenko +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the examples of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** You may use this file under the terms of the BSD license as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of The Qt Company Ltd nor the names of its +** contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "chatconsumer.h" +#include +#include +#include +#include +#include + +ChatConsumer::ChatConsumer(QObject *parent) + : Consumer(parent) +{ + frameWidget = new QWidget; + frameWidget->setFocusPolicy(Qt::TabFocus); + + textEdit = new QTextEdit; + textEdit->setFocusPolicy(Qt::NoFocus); + textEdit->setReadOnly(true); + + lineEdit = new QLineEdit; + frameWidget->setFocusProxy(lineEdit); + + connect(lineEdit, &QLineEdit::returnPressed, this, &ChatConsumer::returnPressed); + + QVBoxLayout *layout = new QVBoxLayout(frameWidget); + layout->setContentsMargins( 0, 0, 0, 0); + layout->addWidget(textEdit); + layout->addWidget(lineEdit); +} + +QWidget *ChatConsumer::widget() +{ + return frameWidget; +} + +void ChatConsumer::readDatagram(const QByteArray &ba) +{ + textEdit->append(QString::fromUtf8(ba)); +} + +void ChatConsumer::returnPressed() +{ + emit writeDatagram(lineEdit->text().toUtf8()); + lineEdit->clear(); +} diff --git a/examples/network/multistreamclient/chatconsumer.h b/examples/network/multistreamclient/chatconsumer.h new file mode 100644 index 00000000000..cac846757f1 --- /dev/null +++ b/examples/network/multistreamclient/chatconsumer.h @@ -0,0 +1,69 @@ +/**************************************************************************** +** +** Copyright (C) 2016 Alex Trotsenko +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the examples of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** You may use this file under the terms of the BSD license as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of The Qt Company Ltd nor the names of its +** contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef CHATCONSUMER_H +#define CHATCONSUMER_H + +#include "consumer.h" + +QT_BEGIN_NAMESPACE +class QTextEdit; +class QLineEdit; +QT_END_NAMESPACE + +class ChatConsumer : public Consumer +{ + Q_OBJECT +public: + explicit ChatConsumer(QObject *parent = nullptr); + + QWidget *widget() Q_DECL_OVERRIDE; + void readDatagram(const QByteArray &ba) Q_DECL_OVERRIDE; + +private slots: + void returnPressed(); + +private: + QWidget *frameWidget; + QTextEdit *textEdit; + QLineEdit *lineEdit; +}; + +#endif diff --git a/examples/network/multistreamclient/client.cpp b/examples/network/multistreamclient/client.cpp new file mode 100644 index 00000000000..fbe138162e3 --- /dev/null +++ b/examples/network/multistreamclient/client.cpp @@ -0,0 +1,207 @@ +/**************************************************************************** +** +** Copyright (C) 2016 Alex Trotsenko +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the examples of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** You may use this file under the terms of the BSD license as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of The Qt Company Ltd nor the names of its +** contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include +#include + +#include "client.h" +#include "movieconsumer.h" +#include "timeconsumer.h" +#include "chatconsumer.h" + +Client::Client(QWidget *parent) + : QDialog(parent) + , consumers(NumberOfChannels) +{ + setWindowTitle(tr("Multi-stream Client")); + + sctpSocket = new QSctpSocket(this); + + QLabel *hostLabel = new QLabel(tr("&Server name:")); + QLabel *portLabel = new QLabel(tr("S&erver port:")); + + hostCombo = new QComboBox; + hostCombo->setEditable(true); + // find out name of this machine + QString name = QHostInfo::localHostName(); + if (!name.isEmpty()) { + hostCombo->addItem(name); + QString domain = QHostInfo::localDomainName(); + if (!domain.isEmpty()) + hostCombo->addItem(name + QChar('.') + domain); + } + if (name != QString("localhost")) + hostCombo->addItem(QString("localhost")); + // find out IP addresses of this machine + QList ipAddressesList = QNetworkInterface::allAddresses(); + // add non-localhost addresses + for (int i = 0; i < ipAddressesList.size(); ++i) { + if (!ipAddressesList.at(i).isLoopback()) + hostCombo->addItem(ipAddressesList.at(i).toString()); + } + // add localhost addresses + for (int i = 0; i < ipAddressesList.size(); ++i) { + if (ipAddressesList.at(i).isLoopback()) + hostCombo->addItem(ipAddressesList.at(i).toString()); + } + + portLineEdit = new QLineEdit; + portLineEdit->setValidator(new QIntValidator(1, 65535, this)); + + hostLabel->setBuddy(hostCombo); + portLabel->setBuddy(portLineEdit); + + connectButton = new QPushButton(tr("Connect")); + connectButton->setDefault(true); + connectButton->setEnabled(false); + + QPushButton *quitButton = new QPushButton(tr("Quit")); + quitButton->setAutoDefault(false); + + QDialogButtonBox *buttonBox = new QDialogButtonBox; + buttonBox->addButton(connectButton, QDialogButtonBox::ActionRole); + buttonBox->addButton(quitButton, QDialogButtonBox::AcceptRole); + + QLabel *movieLabel = new QLabel(tr("Movie stream:")); + consumers[Movie] = new MovieConsumer(this); + QLabel *timeLabel = new QLabel(tr("Time stream:")); + consumers[Time] = new TimeConsumer(this); + QLabel *chatLabel = new QLabel(tr("&Chat:")); + consumers[Chat] = new ChatConsumer(this); + chatLabel->setBuddy(consumers[Chat]->widget()); + + connect(hostCombo, &QComboBox::editTextChanged, this, &Client::enableConnectButton); + connect(portLineEdit, &QLineEdit::textChanged, this, &Client::enableConnectButton); + connect(connectButton, &QPushButton::clicked, this, &Client::requestConnect); + connect(buttonBox, &QDialogButtonBox::accepted, this, &Client::accept); + connect(sctpSocket, &QSctpSocket::connected, this, &Client::connected); + connect(sctpSocket, &QSctpSocket::disconnected, this, &Client::disconnected); + connect(sctpSocket, &QSctpSocket::channelReadyRead, this, &Client::readDatagram); + connect(sctpSocket, SIGNAL(error(QAbstractSocket::SocketError)), + this, SLOT(displayError(QAbstractSocket::SocketError))); + connect(consumers[Time], &Consumer::writeDatagram, this, &Client::writeDatagram); + connect(consumers[Chat], &Consumer::writeDatagram, this, &Client::writeDatagram); + + QGridLayout *mainLayout = new QGridLayout; + mainLayout->addWidget(hostLabel, 0, 0); + mainLayout->addWidget(hostCombo, 0, 1); + mainLayout->addWidget(portLabel, 1, 0); + mainLayout->addWidget(portLineEdit, 1, 1); + mainLayout->addWidget(buttonBox, 2, 0, 1, 2); + mainLayout->addWidget(movieLabel, 3, 0); + mainLayout->addWidget(timeLabel, 3, 1); + mainLayout->addWidget(consumers[Movie]->widget(), 4, 0); + mainLayout->addWidget(consumers[Time]->widget(), 4, 1); + mainLayout->addWidget(chatLabel, 5, 0); + mainLayout->addWidget(consumers[Chat]->widget(), 6, 0, 1, 2); + setLayout(mainLayout); + + portLineEdit->setFocus(); +} + +Client::~Client() +{ + delete sctpSocket; +} + +void Client::connected() +{ + consumers[Chat]->widget()->setFocus(); +} + +void Client::disconnected() +{ + for (Consumer *consumer : consumers) + consumer->serverDisconnected(); + + sctpSocket->close(); +} + +void Client::requestConnect() +{ + connectButton->setEnabled(false); + sctpSocket->abort(); + sctpSocket->connectToHost(hostCombo->currentText(), + portLineEdit->text().toInt()); +} + +void Client::readDatagram(int channel) +{ + sctpSocket->setCurrentReadChannel(channel); + consumers[channel]->readDatagram(sctpSocket->readDatagram().data()); +} + +void Client::displayError(QAbstractSocket::SocketError socketError) +{ + switch (socketError) { + case QAbstractSocket::HostNotFoundError: + QMessageBox::information(this, tr("Multi-stream Client"), + tr("The host was not found. Please check the " + "host name and port settings.")); + break; + case QAbstractSocket::ConnectionRefusedError: + QMessageBox::information(this, tr("Multi-stream Client"), + tr("The connection was refused by the peer. " + "Make sure the multi-stream server is running, " + "and check that the host name and port " + "settings are correct.")); + break; + default: + QMessageBox::information(this, tr("Multi-stream Client"), + tr("The following error occurred: %1.") + .arg(sctpSocket->errorString())); + } + + connectButton->setEnabled(true); +} + +void Client::enableConnectButton() +{ + connectButton->setEnabled(!hostCombo->currentText().isEmpty() && + !portLineEdit->text().isEmpty()); +} + +void Client::writeDatagram(const QByteArray &ba) +{ + if (sctpSocket->isValid() && sctpSocket->state() == QAbstractSocket::ConnectedState) { + sctpSocket->setCurrentWriteChannel(consumers.indexOf(static_cast(sender()))); + sctpSocket->writeDatagram(ba); + } +} diff --git a/examples/network/multistreamclient/client.h b/examples/network/multistreamclient/client.h new file mode 100644 index 00000000000..6ec804e9be6 --- /dev/null +++ b/examples/network/multistreamclient/client.h @@ -0,0 +1,90 @@ +/**************************************************************************** +** +** Copyright (C) 2016 Alex Trotsenko +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the examples of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** You may use this file under the terms of the BSD license as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of The Qt Company Ltd nor the names of its +** contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef CLIENT_H +#define CLIENT_H + +#include +#include +#include + +QT_BEGIN_NAMESPACE +class QComboBox; +class QLineEdit; +class QPushButton; +class QByteArray; +QT_END_NAMESPACE + +class Consumer; + +class Client : public QDialog +{ + Q_OBJECT +public: + explicit Client(QWidget *parent = nullptr); + virtual ~Client(); + +private slots: + void connected(); + void disconnected(); + void requestConnect(); + void readDatagram(int channel); + void displayError(QAbstractSocket::SocketError socketError); + void enableConnectButton(); + void writeDatagram(const QByteArray &ba); + +private: + enum ChannelNumber { + Movie = 0, + Time = 1, + Chat = 2, + + NumberOfChannels = 3 + }; + + QVector consumers; + QSctpSocket *sctpSocket; + + QComboBox *hostCombo; + QLineEdit *portLineEdit; + QPushButton *connectButton; +}; + +#endif diff --git a/examples/network/multistreamclient/consumer.h b/examples/network/multistreamclient/consumer.h new file mode 100644 index 00000000000..0c7c1bbfb6f --- /dev/null +++ b/examples/network/multistreamclient/consumer.h @@ -0,0 +1,65 @@ +/**************************************************************************** +** +** Copyright (C) 2016 Alex Trotsenko +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the examples of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** You may use this file under the terms of the BSD license as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of The Qt Company Ltd nor the names of its +** contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef CONSUMER_H +#define CONSUMER_H + +#include +#include + +QT_BEGIN_NAMESPACE +class QWidget; +QT_END_NAMESPACE + +class Consumer : public QObject +{ + Q_OBJECT +public: + explicit inline Consumer(QObject *parent = nullptr) : QObject(parent) { } + + virtual QWidget *widget() = 0; + virtual void readDatagram(const QByteArray &ba) = 0; + virtual void serverDisconnected() { } + +signals: + void writeDatagram(const QByteArray &ba); +}; + +#endif diff --git a/examples/network/multistreamclient/main.cpp b/examples/network/multistreamclient/main.cpp new file mode 100644 index 00000000000..c9e0c38d9a1 --- /dev/null +++ b/examples/network/multistreamclient/main.cpp @@ -0,0 +1,50 @@ +/**************************************************************************** +** +** Copyright (C) 2016 Alex Trotsenko +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the examples of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** You may use this file under the terms of the BSD license as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of The Qt Company Ltd nor the names of its +** contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include +#include "client.h" + +int main(int argc, char *argv[]) +{ + QApplication app(argc, argv); + Client client; + + return (client.exec() == QDialog::Accepted) ? 0 : -1; +} diff --git a/examples/network/multistreamclient/movieconsumer.cpp b/examples/network/multistreamclient/movieconsumer.cpp new file mode 100644 index 00000000000..68c7b4a2294 --- /dev/null +++ b/examples/network/multistreamclient/movieconsumer.cpp @@ -0,0 +1,73 @@ +/**************************************************************************** +** +** Copyright (C) 2016 Alex Trotsenko +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the examples of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** You may use this file under the terms of the BSD license as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of The Qt Company Ltd nor the names of its +** contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "movieconsumer.h" +#include +#include +#include +#include + +MovieConsumer::MovieConsumer(QObject *parent) + : Consumer(parent) +{ + label = new QLabel; + label->setFrameStyle(QFrame::Box | QFrame::Raised); + label->setFixedSize(128 + label->frameWidth() * 2, + 64 + label->frameWidth() * 2); +} + +QWidget *MovieConsumer::widget() +{ + return label; +} + +void MovieConsumer::readDatagram(const QByteArray &ba) +{ + QDataStream ds(ba); + QImage image; + + ds >> image; + label->setPixmap(QPixmap::fromImage(image)); +} + +void MovieConsumer::serverDisconnected() +{ + label->setPixmap(QPixmap()); +} diff --git a/examples/network/multistreamclient/movieconsumer.h b/examples/network/multistreamclient/movieconsumer.h new file mode 100644 index 00000000000..0f4fd7af421 --- /dev/null +++ b/examples/network/multistreamclient/movieconsumer.h @@ -0,0 +1,64 @@ +/**************************************************************************** +** +** Copyright (C) 2016 Alex Trotsenko +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the examples of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** You may use this file under the terms of the BSD license as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of The Qt Company Ltd nor the names of its +** contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef MOVIECONSUMER_H +#define MOVIECONSUMER_H + +#include "consumer.h" + +QT_BEGIN_NAMESPACE +class QLabel; +QT_END_NAMESPACE + +class MovieConsumer : public Consumer +{ + Q_OBJECT +public: + explicit MovieConsumer(QObject *parent = nullptr); + + QWidget *widget() Q_DECL_OVERRIDE; + void readDatagram(const QByteArray &ba) Q_DECL_OVERRIDE; + void serverDisconnected() Q_DECL_OVERRIDE; + +private: + QLabel *label; +}; + +#endif diff --git a/examples/network/multistreamclient/multistreamclient.pro b/examples/network/multistreamclient/multistreamclient.pro new file mode 100644 index 00000000000..ed887ab53e1 --- /dev/null +++ b/examples/network/multistreamclient/multistreamclient.pro @@ -0,0 +1,16 @@ +QT += network widgets + +HEADERS = client.h \ + consumer.h \ + movieconsumer.h \ + timeconsumer.h \ + chatconsumer.h +SOURCES = client.cpp \ + movieconsumer.cpp \ + timeconsumer.cpp \ + chatconsumer.cpp \ + main.cpp + +# install +target.path = $$[QT_INSTALL_EXAMPLES]/network/multistreamclient +INSTALLS += target diff --git a/examples/network/multistreamclient/timeconsumer.cpp b/examples/network/multistreamclient/timeconsumer.cpp new file mode 100644 index 00000000000..3ad73a87120 --- /dev/null +++ b/examples/network/multistreamclient/timeconsumer.cpp @@ -0,0 +1,84 @@ +/**************************************************************************** +** +** Copyright (C) 2016 Alex Trotsenko +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the examples of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** You may use this file under the terms of the BSD license as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of The Qt Company Ltd nor the names of its +** contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "timeconsumer.h" +#include +#include +#include +#include + +TimeConsumer::TimeConsumer(QObject *parent) + : Consumer(parent) +{ + lcdNumber = new QLCDNumber(8); + + QTimer *timer = new QTimer(this); + connect(timer, &QTimer::timeout, this, &TimeConsumer::timerTick); + timer->start(100); + + serverDisconnected(); +} + +QWidget *TimeConsumer::widget() +{ + return lcdNumber; +} + +void TimeConsumer::readDatagram(const QByteArray &ba) +{ + QDataStream ds(ba); + + ds >> lastTime; + lcdNumber->display(lastTime.toString("hh:mm:ss")); +} + +void TimeConsumer::timerTick() +{ + QByteArray buf; + QDataStream ds(&buf, QIODevice::WriteOnly); + + ds << lastTime; + emit writeDatagram(buf); +} + +void TimeConsumer::serverDisconnected() +{ + lcdNumber->display(QLatin1String("--:--:--")); +} diff --git a/examples/network/multistreamclient/timeconsumer.h b/examples/network/multistreamclient/timeconsumer.h new file mode 100644 index 00000000000..d382e99d866 --- /dev/null +++ b/examples/network/multistreamclient/timeconsumer.h @@ -0,0 +1,69 @@ +/**************************************************************************** +** +** Copyright (C) 2016 Alex Trotsenko +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the examples of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** You may use this file under the terms of the BSD license as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of The Qt Company Ltd nor the names of its +** contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef TIMECONSUMER_H +#define TIMECONSUMER_H + +#include "consumer.h" +#include + +QT_BEGIN_NAMESPACE +class QLCDNumber; +QT_END_NAMESPACE + +class TimeConsumer : public Consumer +{ + Q_OBJECT +public: + explicit TimeConsumer(QObject *parent = nullptr); + + QWidget *widget() Q_DECL_OVERRIDE; + void readDatagram(const QByteArray &ba) Q_DECL_OVERRIDE; + void serverDisconnected() Q_DECL_OVERRIDE; + +private slots: + void timerTick(); + +private: + QTime lastTime; + QLCDNumber *lcdNumber; +}; + +#endif diff --git a/examples/network/multistreamserver/animation.gif b/examples/network/multistreamserver/animation.gif new file mode 100644 index 0000000000000000000000000000000000000000..f674369efc40d0523cc426756857d794dd0bcf0c GIT binary patch literal 42629 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 AI{*Lx literal 0 HcmV?d00001 diff --git a/examples/network/multistreamserver/chatprovider.cpp b/examples/network/multistreamserver/chatprovider.cpp new file mode 100644 index 00000000000..9a88d491abc --- /dev/null +++ b/examples/network/multistreamserver/chatprovider.cpp @@ -0,0 +1,67 @@ +/**************************************************************************** +** +** Copyright (C) 2016 Alex Trotsenko +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the examples of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** You may use this file under the terms of the BSD license as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of The Qt Company Ltd nor the names of its +** contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "chatprovider.h" +#include +#include +#include + +ChatProvider::ChatProvider(QObject *parent) + : Provider(parent) +{ +} + +void ChatProvider::readDatagram(QSctpSocket &from, const QByteArray &ba) +{ + emit writeDatagram(0, QString(QLatin1String("<%1:%2> %3")) + .arg(from.peerAddress().toString()) + .arg(QString::number(from.peerPort())) + .arg(QString::fromUtf8(ba)).toUtf8()); +} + +void ChatProvider::newConnection(QSctpSocket &client) +{ + readDatagram(client, QString(tr("has joined")).toUtf8()); +} + +void ChatProvider::clientDisconnected(QSctpSocket &client) +{ + readDatagram(client, QString(tr("has left")).toUtf8()); +} diff --git a/examples/network/multistreamserver/chatprovider.h b/examples/network/multistreamserver/chatprovider.h new file mode 100644 index 00000000000..912101ec346 --- /dev/null +++ b/examples/network/multistreamserver/chatprovider.h @@ -0,0 +1,57 @@ +/**************************************************************************** +** +** Copyright (C) 2016 Alex Trotsenko +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the examples of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** You may use this file under the terms of the BSD license as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of The Qt Company Ltd nor the names of its +** contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef CHATPROVIDER_H +#define CHATPROVIDER_H + +#include "provider.h" + +class ChatProvider : public Provider +{ + Q_OBJECT +public: + explicit ChatProvider(QObject *parent = nullptr); + + void readDatagram(QSctpSocket &from, const QByteArray &ba) Q_DECL_OVERRIDE; + void newConnection(QSctpSocket &client) Q_DECL_OVERRIDE; + void clientDisconnected(QSctpSocket &client) Q_DECL_OVERRIDE; +}; + +#endif diff --git a/examples/network/multistreamserver/main.cpp b/examples/network/multistreamserver/main.cpp new file mode 100644 index 00000000000..04350cd13dc --- /dev/null +++ b/examples/network/multistreamserver/main.cpp @@ -0,0 +1,51 @@ +/**************************************************************************** +** +** Copyright (C) 2016 Alex Trotsenko +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the examples of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** You may use this file under the terms of the BSD license as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of The Qt Company Ltd nor the names of its +** contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include + +#include "server.h" + +int main(int argc, char *argv[]) +{ + QApplication app(argc, argv); + Server server; + + return (server.exec() == QDialog::Accepted) ? 0 : -1; +} diff --git a/examples/network/multistreamserver/movieprovider.cpp b/examples/network/multistreamserver/movieprovider.cpp new file mode 100644 index 00000000000..cc534e6f405 --- /dev/null +++ b/examples/network/multistreamserver/movieprovider.cpp @@ -0,0 +1,63 @@ +/**************************************************************************** +** +** Copyright (C) 2016 Alex Trotsenko +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the examples of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** You may use this file under the terms of the BSD license as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of The Qt Company Ltd nor the names of its +** contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "movieprovider.h" +#include +#include +#include + +MovieProvider::MovieProvider(QObject *parent) + : Provider(parent) +{ + movie = new QMovie(this); + movie->setCacheMode(QMovie::CacheAll); + movie->setFileName(QLatin1String("animation.gif")); + connect(movie, &QMovie::frameChanged, this, &MovieProvider::frameChanged); + movie->start(); +} + +void MovieProvider::frameChanged() +{ + QByteArray buf; + QDataStream ds(&buf, QIODevice::WriteOnly); + + ds << movie->currentImage(); + emit writeDatagram(0, buf); +} diff --git a/examples/network/multistreamserver/movieprovider.h b/examples/network/multistreamserver/movieprovider.h new file mode 100644 index 00000000000..d6d8fb74496 --- /dev/null +++ b/examples/network/multistreamserver/movieprovider.h @@ -0,0 +1,63 @@ +/**************************************************************************** +** +** Copyright (C) 2016 Alex Trotsenko +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the examples of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** You may use this file under the terms of the BSD license as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of The Qt Company Ltd nor the names of its +** contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef MOVIEPROVIDER_H +#define MOVIEPROVIDER_H + +#include "provider.h" + +QT_BEGIN_NAMESPACE +class QMovie; +QT_END_NAMESPACE + +class MovieProvider : public Provider +{ + Q_OBJECT +public: + explicit MovieProvider(QObject *parent = nullptr); + +private slots: + void frameChanged(); + +private: + QMovie *movie; +}; + +#endif diff --git a/examples/network/multistreamserver/multistreamserver.pro b/examples/network/multistreamserver/multistreamserver.pro new file mode 100644 index 00000000000..75a7e6bbecc --- /dev/null +++ b/examples/network/multistreamserver/multistreamserver.pro @@ -0,0 +1,24 @@ +QT += network widgets + +HEADERS = server.h \ + provider.h \ + movieprovider.h \ + timeprovider.h \ + chatprovider.h +SOURCES = server.cpp \ + movieprovider.cpp \ + timeprovider.cpp \ + chatprovider.cpp \ + main.cpp + +EXAMPLE_FILES = animation.gif + +# install +target.path = $$[QT_INSTALL_EXAMPLES]/network/multistreamserver +INSTALLS += target + +wince*: { + addFiles.files += *.gif + addFiles.path = . + DEPLOYMENT += addFiles +} diff --git a/examples/network/multistreamserver/provider.h b/examples/network/multistreamserver/provider.h new file mode 100644 index 00000000000..c6ec6122755 --- /dev/null +++ b/examples/network/multistreamserver/provider.h @@ -0,0 +1,66 @@ +/**************************************************************************** +** +** Copyright (C) 2016 Alex Trotsenko +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the examples of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** You may use this file under the terms of the BSD license as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of The Qt Company Ltd nor the names of its +** contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef PROVIDER_H +#define PROVIDER_H + +#include + +QT_BEGIN_NAMESPACE +class QSctpSocket; +class QByteArray; +QT_END_NAMESPACE + +class Provider : public QObject +{ + Q_OBJECT + +public: + explicit inline Provider(QObject *parent = nullptr) : QObject(parent) { } + + virtual void readDatagram(QSctpSocket &, const QByteArray &) { } + virtual void newConnection(QSctpSocket &) { } + virtual void clientDisconnected(QSctpSocket &) { } + +signals: + void writeDatagram(QSctpSocket *to, const QByteArray &ba); +}; + +#endif diff --git a/examples/network/multistreamserver/server.cpp b/examples/network/multistreamserver/server.cpp new file mode 100644 index 00000000000..1fb18e80b96 --- /dev/null +++ b/examples/network/multistreamserver/server.cpp @@ -0,0 +1,159 @@ +/**************************************************************************** +** +** Copyright (C) 2015 Alex Trotsenko +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the examples of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** You may use this file under the terms of the BSD license as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of The Qt Company Ltd nor the names of its +** contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include +#include +#include + +#include "server.h" +#include "movieprovider.h" +#include "timeprovider.h" +#include "chatprovider.h" + +Server::Server(QWidget *parent) + : QDialog(parent) + , providers(NumberOfChannels) +{ + setWindowTitle(tr("Multi-stream Server")); + + sctpServer = new QSctpServer(this); + sctpServer->setMaxChannelCount(NumberOfChannels); + + statusLabel = new QLabel; + QPushButton *quitButton = new QPushButton(tr("Quit")); + + providers[Movie] = new MovieProvider(this); + providers[Time] = new TimeProvider(this); + providers[Chat] = new ChatProvider(this); + + connect(sctpServer, &QSctpServer::newConnection, this, &Server::newConnection); + connect(quitButton, &QPushButton::clicked, this, &Server::accept); + connect(providers[Movie], &Provider::writeDatagram, this, &Server::writeDatagram); + connect(providers[Time], &Provider::writeDatagram, this, &Server::writeDatagram); + connect(providers[Chat], &Provider::writeDatagram, this, &Server::writeDatagram); + + QVBoxLayout *mainLayout = new QVBoxLayout; + mainLayout->addWidget(statusLabel); + mainLayout->addWidget(quitButton); + setLayout(mainLayout); +} + +Server::~Server() +{ + qDeleteAll(connections.begin(), connections.end()); +} + +int Server::exec() +{ + if (!sctpServer->listen()) { + QMessageBox::critical(this, windowTitle(), + tr("Unable to start the server: %1.") + .arg(sctpServer->errorString())); + return QDialog::Rejected; + } + + QString ipAddress; + QList ipAddressesList = QNetworkInterface::allAddresses(); + // use the first non-localhost IPv4 address + for (int i = 0; i < ipAddressesList.size(); ++i) { + if (ipAddressesList.at(i) != QHostAddress::LocalHost && + ipAddressesList.at(i).toIPv4Address()) { + ipAddress = ipAddressesList.at(i).toString(); + break; + } + } + // if we did not find one, use IPv4 localhost + if (ipAddress.isEmpty()) + ipAddress = QHostAddress(QHostAddress::LocalHost).toString(); + statusLabel->setText(tr("The server is running on\n\nIP: %1\nport: %2\n\n" + "Run the Multi-stream Client example now.") + .arg(ipAddress).arg(sctpServer->serverPort())); + + return QDialog::exec(); +} + +void Server::newConnection() +{ + QSctpSocket *connection = sctpServer->nextPendingDatagramConnection(); + + connections.append(connection); + connect(connection, &QSctpSocket::channelReadyRead, this, &Server::readDatagram); + connect(connection, &QSctpSocket::disconnected, this, &Server::clientDisconnected); + + for (Provider *provider : providers) + provider->newConnection(*connection); +} + +void Server::clientDisconnected() +{ + QSctpSocket *connection = static_cast(sender()); + + connections.removeOne(connection); + connection->disconnect(); + + for (Provider *provider : providers) + provider->clientDisconnected(*connection); + + connection->deleteLater(); +} + +void Server::readDatagram(int channel) +{ + QSctpSocket *connection = static_cast(sender()); + + connection->setCurrentReadChannel(channel); + providers[channel]->readDatagram(*connection, connection->readDatagram().data()); +} + +void Server::writeDatagram(QSctpSocket *to, const QByteArray &ba) +{ + int channel = providers.indexOf(static_cast(sender())); + + if (to) { + to->setCurrentWriteChannel(channel); + to->writeDatagram(ba); + return; + } + + for (QSctpSocket *connection : connections) { + connection->setCurrentWriteChannel(channel); + connection->writeDatagram(ba); + } +} diff --git a/examples/network/multistreamserver/server.h b/examples/network/multistreamserver/server.h new file mode 100644 index 00000000000..6d01f7a2383 --- /dev/null +++ b/examples/network/multistreamserver/server.h @@ -0,0 +1,89 @@ +/**************************************************************************** +** +** Copyright (C) 2016 Alex Trotsenko +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the examples of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** You may use this file under the terms of the BSD license as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of The Qt Company Ltd nor the names of its +** contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef SERVER_H +#define SERVER_H + +#include +#include +#include + +QT_BEGIN_NAMESPACE +class QSctpServer; +class QSctpSocket; +class QLabel; +class QByteArray; +QT_END_NAMESPACE + +class Provider; + +class Server : public QDialog +{ + Q_OBJECT +public: + explicit Server(QWidget *parent = nullptr); + virtual ~Server(); + +public slots: + int exec() Q_DECL_OVERRIDE; + +private slots: + void newConnection(); + void clientDisconnected(); + void readDatagram(int channel); + void writeDatagram(QSctpSocket *to, const QByteArray &ba); + +private: + enum ChannelNumber { + Movie = 0, + Time = 1, + Chat = 2, + + NumberOfChannels = 3 + }; + + QVector providers; + QSctpServer *sctpServer; + QList connections; + + QLabel *statusLabel; +}; + +#endif diff --git a/examples/network/multistreamserver/timeprovider.cpp b/examples/network/multistreamserver/timeprovider.cpp new file mode 100644 index 00000000000..043810cdf36 --- /dev/null +++ b/examples/network/multistreamserver/timeprovider.cpp @@ -0,0 +1,67 @@ +/**************************************************************************** +** +** Copyright (C) 2016 Alex Trotsenko +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the examples of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** You may use this file under the terms of the BSD license as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of The Qt Company Ltd nor the names of its +** contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "timeprovider.h" +#include +#include +#include + +TimeProvider::TimeProvider(QObject *parent) + : Provider(parent) +{ +} + +void TimeProvider::readDatagram(QSctpSocket &from, const QByteArray &ba) +{ + QDataStream in_ds(ba); + QTime curTime = QTime::currentTime(); + QTime clientTime; + + in_ds >> clientTime; + if (!clientTime.isValid() || curTime.second() != clientTime.second() + || curTime.minute() != clientTime.minute() + || curTime.hour() != clientTime.hour()) { + QByteArray buf; + QDataStream out_ds(&buf, QIODevice::WriteOnly); + + out_ds << curTime; + emit writeDatagram(&from, buf); + } +} diff --git a/examples/network/multistreamserver/timeprovider.h b/examples/network/multistreamserver/timeprovider.h new file mode 100644 index 00000000000..b57c066c248 --- /dev/null +++ b/examples/network/multistreamserver/timeprovider.h @@ -0,0 +1,55 @@ +/**************************************************************************** +** +** Copyright (C) 2016 Alex Trotsenko +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the examples of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** You may use this file under the terms of the BSD license as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of The Qt Company Ltd nor the names of its +** contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef TIMEPROVIDER_H +#define TIMEPROVIDER_H + +#include "provider.h" + +class TimeProvider : public Provider +{ + Q_OBJECT +public: + explicit TimeProvider(QObject *parent = nullptr); + + void readDatagram(QSctpSocket &from, const QByteArray &ba) Q_DECL_OVERRIDE; +}; + +#endif diff --git a/examples/network/network.pro b/examples/network/network.pro index 86bb08d22a9..ff7fbcf4f22 100644 --- a/examples/network/network.pro +++ b/examples/network/network.pro @@ -32,4 +32,5 @@ qtHaveModule(widgets) { contains(QT_CONFIG, openssl):SUBDIRS += securesocketclient contains(QT_CONFIG, openssl-linked):SUBDIRS += securesocketclient + contains(QT_CONFIG, sctp):SUBDIRS += multistreamserver multistreamclient } diff --git a/src/corelib/io/qiodevice.cpp b/src/corelib/io/qiodevice.cpp index 94fb68450c6..fbee1a223fc 100644 --- a/src/corelib/io/qiodevice.cpp +++ b/src/corelib/io/qiodevice.cpp @@ -738,6 +738,18 @@ void QIODevicePrivate::setWriteChannelCount(int count) setCurrentWriteChannel(currentWriteChannel); } +/*! + \internal +*/ +bool QIODevicePrivate::allWriteBuffersEmpty() const +{ + for (const QRingBuffer &ringBuffer : writeBuffers) { + if (!ringBuffer.isEmpty()) + return false; + } + return true; +} + /*! Opens the device and sets its OpenMode to \a mode. Returns \c true if successful; otherwise returns \c false. This function should be called from any diff --git a/src/corelib/io/qiodevice_p.h b/src/corelib/io/qiodevice_p.h index eed98a8fe36..76bec89ef27 100644 --- a/src/corelib/io/qiodevice_p.h +++ b/src/corelib/io/qiodevice_p.h @@ -154,6 +154,8 @@ public: return buffer.isEmpty() || (transactionStarted && isSequential() && transactionPos == buffer.size()); } + bool allWriteBuffersEmpty() const; + void seekBuffer(qint64 newPos); inline void setCurrentReadChannel(int channel) diff --git a/src/network/doc/snippets/code/src_network_socket_qsctpsocket.cpp b/src/network/doc/snippets/code/src_network_socket_qsctpsocket.cpp new file mode 100644 index 00000000000..3783a6f9390 --- /dev/null +++ b/src/network/doc/snippets/code/src_network_socket_qsctpsocket.cpp @@ -0,0 +1,53 @@ +/**************************************************************************** +** +** Copyright (C) 2016 Alex Trotsenko +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the documentation of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** You may use this file under the terms of the BSD license as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of The Qt Company Ltd nor the names of its +** contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +//! [0] +QSctpSocket *socket = new QSctpSocket(this); + +socket->setMaxChannelCount(16); +socket->connectToHost(QHostAddress::LocalHost, 1973); + +if (socket->waitForConnected(1000)) { + int inputChannels = socket->readChannelCount(); + int outputChannels = socket->writeChannelCount(); + + .... +} +//! [0] diff --git a/src/network/kernel/qnetworkdatagram.h b/src/network/kernel/qnetworkdatagram.h index 6cbea13afc4..a20d69185a4 100644 --- a/src/network/kernel/qnetworkdatagram.h +++ b/src/network/kernel/qnetworkdatagram.h @@ -104,6 +104,7 @@ public: private: QNetworkDatagramPrivate *d; friend class QUdpSocket; + friend class QSctpSocket; explicit QNetworkDatagram(QNetworkDatagramPrivate &dd); QNetworkDatagram makeReply_helper(const QByteArray &data) const; diff --git a/src/network/kernel/qnetworkdatagram_p.h b/src/network/kernel/qnetworkdatagram_p.h index e28be095085..e55651a78be 100644 --- a/src/network/kernel/qnetworkdatagram_p.h +++ b/src/network/kernel/qnetworkdatagram_p.h @@ -54,7 +54,8 @@ class QIpPacketHeader { public: QIpPacketHeader(const QHostAddress &dstAddr = QHostAddress(), quint16 port = 0) - : destinationAddress(dstAddr), ifindex(0), hopLimit(-1), destinationPort(port) + : destinationAddress(dstAddr), ifindex(0), hopLimit(-1), streamNumber(-1), + destinationPort(port), endOfRecord(false) {} void clear() @@ -63,6 +64,8 @@ public: destinationAddress.clear(); ifindex = 0; hopLimit = -1; + streamNumber = -1; + endOfRecord = false; } QHostAddress senderAddress; @@ -70,8 +73,10 @@ public: uint ifindex; int hopLimit; + int streamNumber; quint16 senderPort; quint16 destinationPort; + bool endOfRecord; }; class QNetworkDatagramPrivate @@ -81,6 +86,9 @@ public: const QHostAddress &dstAddr = QHostAddress(), quint16 port = 0) : data(data), header(dstAddr, port) {} + QNetworkDatagramPrivate(const QByteArray &data, const QIpPacketHeader &header) + : data(data), header(header) + {} QByteArray data; QIpPacketHeader header; diff --git a/src/network/kernel/qnetworkproxy.cpp b/src/network/kernel/qnetworkproxy.cpp index ad78c48fd8d..fc3185bd5e7 100644 --- a/src/network/kernel/qnetworkproxy.cpp +++ b/src/network/kernel/qnetworkproxy.cpp @@ -212,6 +212,12 @@ lookup on a remote host name and connect to it, as opposed to requiring the application to perform the name lookup and request connection to IP addresses only. + + \value SctpTunnelingCapability Ability to open transparent, tunneled + SCTP connections to a remote host. + + \value SctpListeningCapability Ability to create a listening socket + and wait for an incoming SCTP connection from a remote host. */ #include "qnetworkproxy.h" @@ -369,7 +375,9 @@ static QNetworkProxy::Capabilities defaultCapabilitiesForType(QNetworkProxy::Pro /* [QNetworkProxy::DefaultProxy] = */ (int(QNetworkProxy::ListeningCapability) | int(QNetworkProxy::TunnelingCapability) | - int(QNetworkProxy::UdpTunnelingCapability)), + int(QNetworkProxy::UdpTunnelingCapability) | + int(QNetworkProxy::SctpTunnelingCapability) | + int(QNetworkProxy::SctpListeningCapability)), /* [QNetworkProxy::Socks5Proxy] = */ (int(QNetworkProxy::TunnelingCapability) | int(QNetworkProxy::ListeningCapability) | @@ -379,7 +387,9 @@ static QNetworkProxy::Capabilities defaultCapabilitiesForType(QNetworkProxy::Pro /* [QNetworkProxy::NoProxy] = */ (int(QNetworkProxy::ListeningCapability) | int(QNetworkProxy::TunnelingCapability) | - int(QNetworkProxy::UdpTunnelingCapability)), + int(QNetworkProxy::UdpTunnelingCapability) | + int(QNetworkProxy::SctpTunnelingCapability) | + int(QNetworkProxy::SctpListeningCapability)), /* [QNetworkProxy::HttpProxy] = */ (int(QNetworkProxy::TunnelingCapability) | int(QNetworkProxy::CachingCapability) | @@ -965,6 +975,14 @@ template<> void QSharedDataPointer::detach() can all be used or be left unused, depending on the characteristics of the socket. The URL component is not used. + \row + \li SctpSocket + \li Message-oriented sockets requesting a connection to a remote + server. The peer hostname and peer port match the values passed + to QSctpSocket::connectToHost(). The local port is usually -1, + indicating the socket has no preference in which port should be + used. The URL component is not used. + \row \li TcpServer \li Passive server sockets that listen on a port and await @@ -981,6 +999,14 @@ template<> void QSharedDataPointer::detach() indicate that more detailed information is present in the URL component. For ease of implementation, the URL's host and port are set as the destination address. + + \row + \li SctpServer + \li Passive server sockets that listen on a SCTP port and await + incoming connections from the network. Normally, only the + local port is used, but the remote address could be used in + specific circumstances, for example to indicate which remote + host a connection is expected from. The URL component is not used. \endtable It should be noted that any of the criteria may be missing or @@ -1001,10 +1027,13 @@ template<> void QSharedDataPointer::detach() \value TcpSocket a normal, outgoing TCP socket \value UdpSocket a datagram-based UDP socket, which could send to multiple destinations + \value SctpSocket a message-oriented, outgoing SCTP socket \value TcpServer a TCP server that listens for incoming connections from the network \value UrlRequest a more complex request which involves loading of a URL + \value SctpServer a SCTP server that listens for incoming + connections from the network \sa queryType(), setQueryType() */ @@ -1614,6 +1643,10 @@ QDebug operator<<(QDebug debug, const QNetworkProxy &proxy) scaps << QStringLiteral("Caching"); if (caps & QNetworkProxy::HostNameLookupCapability) scaps << QStringLiteral("NameLookup"); + if (caps & QNetworkProxy::SctpTunnelingCapability) + scaps << QStringLiteral("SctpTunnel"); + if (caps & QNetworkProxy::SctpListeningCapability) + scaps << QStringLiteral("SctpListen"); debug << '[' << scaps.join(QLatin1Char(' ')) << ']'; return debug; } diff --git a/src/network/kernel/qnetworkproxy.h b/src/network/kernel/qnetworkproxy.h index c9f43725963..fc919a24a65 100644 --- a/src/network/kernel/qnetworkproxy.h +++ b/src/network/kernel/qnetworkproxy.h @@ -60,8 +60,10 @@ public: enum QueryType { TcpSocket, UdpSocket, + SctpSocket, TcpServer = 100, - UrlRequest + UrlRequest, + SctpServer }; QNetworkProxyQuery(); @@ -141,7 +143,9 @@ public: ListeningCapability = 0x0002, UdpTunnelingCapability = 0x0004, CachingCapability = 0x0008, - HostNameLookupCapability = 0x0010 + HostNameLookupCapability = 0x0010, + SctpTunnelingCapability = 0x00020, + SctpListeningCapability = 0x00040 }; Q_DECLARE_FLAGS(Capabilities, Capability) diff --git a/src/network/kernel/qnetworkproxy_win.cpp b/src/network/kernel/qnetworkproxy_win.cpp index 2727bd92579..c022c718cf2 100644 --- a/src/network/kernel/qnetworkproxy_win.cpp +++ b/src/network/kernel/qnetworkproxy_win.cpp @@ -232,9 +232,15 @@ static QList filterProxyListByCapabilities(const QList parseServerList(const QNetworkProxyQuery &query, con QList result; QHash taggedProxies; const QString requiredTag = query.protocolTag(); - bool checkTags = !requiredTag.isEmpty() && query.queryType() != QNetworkProxyQuery::TcpServer; //windows tags are only for clients + // windows tags are only for clients + bool checkTags = !requiredTag.isEmpty() && query.queryType() != QNetworkProxyQuery::TcpServer + && query.queryType() != QNetworkProxyQuery::SctpServer; for (const QString &entry : proxyList) { int server = 0; diff --git a/src/network/network.pro b/src/network/network.pro index 256d718df6f..75105bd681c 100644 --- a/src/network/network.pro +++ b/src/network/network.pro @@ -9,6 +9,7 @@ DEFINES += QT_NO_USING_NAMESPACE QT_NO_FOREACH #DEFINES += QABSTRACTSOCKET_DEBUG QNATIVESOCKETENGINE_DEBUG #DEFINES += QTCPSOCKETENGINE_DEBUG QTCPSOCKET_DEBUG QTCPSERVER_DEBUG QSSLSOCKET_DEBUG #DEFINES += QUDPSOCKET_DEBUG QUDPSERVER_DEBUG +#DEFINES += QSCTPSOCKET_DEBUG QSCTPSERVER_DEBUG win32-msvc*|win32-icc:QMAKE_LFLAGS += /BASE:0x64000000 QMAKE_DOCS = $$PWD/doc/qtnetwork.qdocconf diff --git a/src/network/socket/qabstractsocket.cpp b/src/network/socket/qabstractsocket.cpp index cbae297278f..0615cd1cb30 100644 --- a/src/network/socket/qabstractsocket.cpp +++ b/src/network/socket/qabstractsocket.cpp @@ -267,7 +267,8 @@ \value TcpSocket TCP \value UdpSocket UDP - \value UnknownSocketType Other than TCP and UDP + \value SctpSocket SCTP + \value UnknownSocketType Other than TCP, UDP and SCTP \sa QAbstractSocket::socketType() */ @@ -626,6 +627,7 @@ bool QAbstractSocketPrivate::initSocketLayer(QAbstractSocket::NetworkLayerProtoc QString typeStr; if (q->socketType() == QAbstractSocket::TcpSocket) typeStr = QLatin1String("TcpSocket"); else if (q->socketType() == QAbstractSocket::UdpSocket) typeStr = QLatin1String("UdpSocket"); + else if (q->socketType() == QAbstractSocket::SctpSocket) typeStr = QLatin1String("SctpSocket"); else typeStr = QLatin1String("UnknownSocketType"); QString protocolStr; if (protocol == QAbstractSocket::IPv4Protocol) protocolStr = QLatin1String("IPv4Protocol"); @@ -670,6 +672,12 @@ bool QAbstractSocketPrivate::initSocketLayer(QAbstractSocket::NetworkLayerProtoc */ void QAbstractSocketPrivate::configureCreatedSocket() { +#ifndef QT_NO_SCTP + Q_Q(QAbstractSocket); + // Set single stream mode for unbuffered SCTP socket + if (socketEngine && q->socketType() == QAbstractSocket::SctpSocket) + socketEngine->setOption(QAbstractSocketEngine::MaxStreamsSocketOption, 1); +#endif } /*! \internal @@ -771,7 +779,8 @@ void QAbstractSocketPrivate::canCloseNotification() QMetaObject::invokeMethod(socketEngine, "closeNotification", Qt::QueuedConnection); } - } else if (socketType == QAbstractSocket::TcpSocket && socketEngine) { + } else if ((socketType == QAbstractSocket::TcpSocket || + socketType == QAbstractSocket::SctpSocket) && socketEngine) { emitReadyRead(); } } @@ -862,13 +871,9 @@ bool QAbstractSocketPrivate::writeToSocket() if (written > 0) { // Remove what we wrote so far. writeBuffer.free(written); - // Don't emit bytesWritten() recursively. - if (!emittedBytesWritten) { - QScopedValueRollback r(emittedBytesWritten); - emittedBytesWritten = true; - emit q->bytesWritten(written); - } - emit q->channelBytesWritten(0, written); + + // Emit notifications. + emitBytesWritten(written); } if (writeBuffer.isEmpty() && socketEngine && !socketEngine->bytesToWrite()) @@ -889,7 +894,7 @@ bool QAbstractSocketPrivate::flush() { bool dataWasWritten = false; - while (!writeBuffer.isEmpty() && writeToSocket()) + while (!allWriteBuffersEmpty() && writeToSocket()) dataWasWritten = true; return dataWasWritten; @@ -912,6 +917,8 @@ void QAbstractSocketPrivate::resolveProxy(const QString &hostname, quint16 port) QNetworkProxyQuery query(hostname, port, QString(), socketType == QAbstractSocket::TcpSocket ? QNetworkProxyQuery::TcpSocket : + socketType == QAbstractSocket::SctpSocket ? + QNetworkProxyQuery::SctpSocket : QNetworkProxyQuery::UdpSocket); proxies = QNetworkProxyFactory::proxyForQuery(query); } @@ -926,6 +933,10 @@ void QAbstractSocketPrivate::resolveProxy(const QString &hostname, quint16 port) (p.capabilities() & QNetworkProxy::TunnelingCapability) == 0) continue; + if (socketType == QAbstractSocket::SctpSocket && + (p.capabilities() & QNetworkProxy::SctpTunnelingCapability) == 0) + continue; + proxyInUse = p; return; } @@ -1280,16 +1291,34 @@ bool QAbstractSocketPrivate::readFromSocket() Prevents from the recursive readyRead() emission. */ -void QAbstractSocketPrivate::emitReadyRead() +void QAbstractSocketPrivate::emitReadyRead(int channel) { Q_Q(QAbstractSocket); // Only emit readyRead() when not recursing. - if (!emittedReadyRead) { + if (!emittedReadyRead && channel == currentReadChannel) { QScopedValueRollback r(emittedReadyRead); emittedReadyRead = true; emit q->readyRead(); } - emit q->channelReadyRead(0); + // channelReadyRead() can be emitted recursively - even for the same channel. + emit q->channelReadyRead(channel); +} + +/*! \internal + + Prevents from the recursive bytesWritten() emission. +*/ +void QAbstractSocketPrivate::emitBytesWritten(qint64 bytes, int channel) +{ + Q_Q(QAbstractSocket); + // Only emit bytesWritten() when not recursing. + if (!emittedBytesWritten && channel == currentWriteChannel) { + QScopedValueRollback r(emittedBytesWritten); + emittedBytesWritten = true; + emit q->bytesWritten(bytes); + } + // channelBytesWritten() can be emitted recursively - even for the same channel. + emit q->channelBytesWritten(channel, bytes); } /*! \internal @@ -1400,8 +1429,8 @@ QAbstractSocket::QAbstractSocket(SocketType socketType, Q_D(QAbstractSocket); #if defined(QABSTRACTSOCKET_DEBUG) qDebug("QAbstractSocket::QAbstractSocket(%sSocket, QAbstractSocketPrivate == %p, parent == %p)", - socketType == TcpSocket ? "Tcp" : socketType == UdpSocket - ? "Udp" : "Unknown", &dd, parent); + socketType == TcpSocket ? "Tcp" : socketType == UdpSocket ? "Udp" + : socketType == SctpSocket ? "Sctp" : "Unknown", &dd, parent); #endif d->socketType = socketType; } @@ -1665,9 +1694,9 @@ void QAbstractSocket::connectToHost(const QString &hostName, quint16 port, #endif if (openMode & QIODevice::Unbuffered) - d->isBuffered = false; // Unbuffered QTcpSocket + d->isBuffered = false; else if (!d_func()->isBuffered) - openMode |= QAbstractSocket::Unbuffered; // QUdpSocket + openMode |= QAbstractSocket::Unbuffered; QIODevice::open(openMode); d->readChannelCount = d->writeChannelCount = 0; @@ -2503,10 +2532,8 @@ qint64 QAbstractSocket::writeData(const char *data, qint64 size) qt_prettyDebug(data, qMin((int)size, 32), size).data(), size, written); #endif - if (written >= 0) { - emit bytesWritten(written); - emit channelBytesWritten(0, written); - } + if (written >= 0) + d->emitBytesWritten(written); return written; } @@ -2714,14 +2741,14 @@ void QAbstractSocket::disconnectFromHost() } // Wait for pending data to be written. - if (d->socketEngine && d->socketEngine->isValid() && (d->writeBuffer.size() > 0 + if (d->socketEngine && d->socketEngine->isValid() && (!d->allWriteBuffersEmpty() || d->socketEngine->bytesToWrite() > 0)) { // hack: when we are waiting for the socket engine to write bytes (only // possible when using Socks5 or HTTP socket engine), then close // anyway after 2 seconds. This is to prevent a timeout on Mac, where we // sometimes just did not get the write notifier from the underlying // CFSocket and no progress was made. - if (d->writeBuffer.size() == 0 && d->socketEngine->bytesToWrite() > 0) { + if (d->allWriteBuffersEmpty() && d->socketEngine->bytesToWrite() > 0) { if (!d->disconnectTimer) { d->disconnectTimer = new QTimer(this); connect(d->disconnectTimer, SIGNAL(timeout()), this, diff --git a/src/network/socket/qabstractsocket.h b/src/network/socket/qabstractsocket.h index 2c32fa046fa..73a8f115379 100644 --- a/src/network/socket/qabstractsocket.h +++ b/src/network/socket/qabstractsocket.h @@ -64,6 +64,7 @@ public: enum SocketType { TcpSocket, UdpSocket, + SctpSocket, UnknownSocketType = -1 }; Q_ENUM(SocketType) diff --git a/src/network/socket/qabstractsocket_p.h b/src/network/socket/qabstractsocket_p.h index 41a8cf1c6bf..1578d7bb35b 100644 --- a/src/network/socket/qabstractsocket_p.h +++ b/src/network/socket/qabstractsocket_p.h @@ -86,7 +86,7 @@ public: virtual bool bind(const QHostAddress &address, quint16 port, QAbstractSocket::BindMode mode); - bool canReadNotification(); + virtual bool canReadNotification(); bool canWriteNotification(); void canCloseNotification(); @@ -136,8 +136,9 @@ public: void startConnectingByName(const QString &host); void fetchConnectionParameters(); bool readFromSocket(); - bool writeToSocket(); - void emitReadyRead(); + virtual bool writeToSocket(); + void emitReadyRead(int channel = 0); + void emitBytesWritten(qint64 bytes, int channel = 0); void setError(QAbstractSocket::SocketError errorCode, const QString &errorString); void setErrorAndEmit(QAbstractSocket::SocketError errorCode, const QString &errorString); diff --git a/src/network/socket/qabstractsocketengine_p.h b/src/network/socket/qabstractsocketengine_p.h index 4b835ce6efd..0cb519ce90a 100644 --- a/src/network/socket/qabstractsocketengine_p.h +++ b/src/network/socket/qabstractsocketengine_p.h @@ -104,7 +104,8 @@ public: MulticastLoopbackOption, TypeOfServiceOption, ReceivePacketInformation, - ReceiveHopLimit + ReceiveHopLimit, + MaxStreamsSocketOption }; enum PacketHeaderOption { @@ -112,6 +113,8 @@ public: WantDatagramSender = 0x01, WantDatagramDestination = 0x02, WantDatagramHopLimit = 0x04, + WantStreamNumber = 0x08, + WantEndOfRecord = 0x10, WantAll = 0xff }; @@ -147,13 +150,13 @@ public: virtual bool setMulticastInterface(const QNetworkInterface &iface) = 0; #endif // QT_NO_NETWORKINTERFACE - virtual qint64 readDatagram(char *data, qint64 maxlen, QIpPacketHeader *header = 0, - PacketHeaderOptions = WantNone) = 0; - virtual qint64 writeDatagram(const char *data, qint64 len, const QIpPacketHeader &header) = 0; virtual bool hasPendingDatagrams() const = 0; virtual qint64 pendingDatagramSize() const = 0; #endif // QT_NO_UDPSOCKET + virtual qint64 readDatagram(char *data, qint64 maxlen, QIpPacketHeader *header = 0, + PacketHeaderOptions = WantNone) = 0; + virtual qint64 writeDatagram(const char *data, qint64 len, const QIpPacketHeader &header) = 0; virtual qint64 bytesToWrite() const = 0; virtual int option(SocketOption option) const = 0; diff --git a/src/network/socket/qhttpsocketengine.cpp b/src/network/socket/qhttpsocketengine.cpp index f9ff9585259..899c02fba60 100644 --- a/src/network/socket/qhttpsocketengine.cpp +++ b/src/network/socket/qhttpsocketengine.cpp @@ -289,6 +289,19 @@ bool QHttpSocketEngine::setMulticastInterface(const QNetworkInterface &) } #endif // QT_NO_NETWORKINTERFACE +bool QHttpSocketEngine::hasPendingDatagrams() const +{ + qWarning("Operation is not supported"); + return false; +} + +qint64 QHttpSocketEngine::pendingDatagramSize() const +{ + qWarning("Operation is not supported"); + return -1; +} +#endif // QT_NO_UDPSOCKET + qint64 QHttpSocketEngine::readDatagram(char *, qint64, QIpPacketHeader *, PacketHeaderOptions) { qWarning("Operation is not supported"); @@ -305,19 +318,6 @@ qint64 QHttpSocketEngine::writeDatagram(const char *, qint64, const QIpPacketHea return -1; } -bool QHttpSocketEngine::hasPendingDatagrams() const -{ - qWarning("Operation is not supported"); - return false; -} - -qint64 QHttpSocketEngine::pendingDatagramSize() const -{ - qWarning("Operation is not supported"); - return -1; -} -#endif // QT_NO_UDPSOCKET - qint64 QHttpSocketEngine::bytesToWrite() const { Q_D(const QHttpSocketEngine); diff --git a/src/network/socket/qhttpsocketengine_p.h b/src/network/socket/qhttpsocketengine_p.h index 87400812a72..07815a7e51c 100644 --- a/src/network/socket/qhttpsocketengine_p.h +++ b/src/network/socket/qhttpsocketengine_p.h @@ -112,13 +112,13 @@ public: bool setMulticastInterface(const QNetworkInterface &iface) Q_DECL_OVERRIDE; #endif // QT_NO_NETWORKINTERFACE - qint64 readDatagram(char *data, qint64 maxlen, QIpPacketHeader *, - PacketHeaderOptions) Q_DECL_OVERRIDE; - qint64 writeDatagram(const char *data, qint64 len, const QIpPacketHeader &) Q_DECL_OVERRIDE; bool hasPendingDatagrams() const Q_DECL_OVERRIDE; qint64 pendingDatagramSize() const Q_DECL_OVERRIDE; #endif // QT_NO_UDPSOCKET + qint64 readDatagram(char *data, qint64 maxlen, QIpPacketHeader *, + PacketHeaderOptions) Q_DECL_OVERRIDE; + qint64 writeDatagram(const char *data, qint64 len, const QIpPacketHeader &) Q_DECL_OVERRIDE; qint64 bytesToWrite() const Q_DECL_OVERRIDE; int option(SocketOption option) const Q_DECL_OVERRIDE; diff --git a/src/network/socket/qnativesocketengine.cpp b/src/network/socket/qnativesocketengine.cpp index 7c70d664e77..f2bc3cec94c 100644 --- a/src/network/socket/qnativesocketengine.cpp +++ b/src/network/socket/qnativesocketengine.cpp @@ -174,6 +174,12 @@ QT_BEGIN_NAMESPACE " socket other than "#type""); \ return (returnValue); \ } } while (0) +#define Q_CHECK_TYPES(function, type1, type2, returnValue) do { \ + if (d->socketType != (type1) && d->socketType != (type2)) { \ + qWarning(#function" was called by a" \ + " socket other than "#type1" or "#type2); \ + return (returnValue); \ + } } while (0) #define Q_TR(a) QT_TRANSLATE_NOOP(QNativeSocketEngine, a) /*! \internal @@ -417,6 +423,7 @@ bool QNativeSocketEngine::initialize(QAbstractSocket::SocketType socketType, QAb QString typeStr = QLatin1String("UnknownSocketType"); if (socketType == QAbstractSocket::TcpSocket) typeStr = QLatin1String("TcpSocket"); else if (socketType == QAbstractSocket::UdpSocket) typeStr = QLatin1String("UdpSocket"); + else if (socketType == QAbstractSocket::SctpSocket) typeStr = QLatin1String("SctpSocket"); QString protocolStr = QLatin1String("UnknownProtocol"); if (protocol == QAbstractSocket::IPv4Protocol) protocolStr = QLatin1String("IPv4Protocol"); else if (protocol == QAbstractSocket::IPv6Protocol) protocolStr = QLatin1String("IPv6Protocol"); @@ -659,7 +666,12 @@ bool QNativeSocketEngine::listen() Q_D(QNativeSocketEngine); Q_CHECK_VALID_SOCKETLAYER(QNativeSocketEngine::listen(), false); Q_CHECK_STATE(QNativeSocketEngine::listen(), QAbstractSocket::BoundState, false); +#ifndef QT_NO_SCTP + Q_CHECK_TYPES(QNativeSocketEngine::listen(), QAbstractSocket::TcpSocket, + QAbstractSocket::SctpSocket, false); +#else Q_CHECK_TYPE(QNativeSocketEngine::listen(), QAbstractSocket::TcpSocket, false); +#endif // We're using a backlog of 50. Most modern kernels support TCP // syncookies by default, and if they do, the backlog is ignored. @@ -680,7 +692,12 @@ int QNativeSocketEngine::accept() Q_D(QNativeSocketEngine); Q_CHECK_VALID_SOCKETLAYER(QNativeSocketEngine::accept(), -1); Q_CHECK_STATE(QNativeSocketEngine::accept(), QAbstractSocket::ListeningState, -1); +#ifndef QT_NO_SCTP + Q_CHECK_TYPES(QNativeSocketEngine::accept(), QAbstractSocket::TcpSocket, + QAbstractSocket::SctpSocket, -1); +#else Q_CHECK_TYPE(QNativeSocketEngine::accept(), QAbstractSocket::TcpSocket, -1); +#endif return d->nativeAccept(); } @@ -793,6 +810,7 @@ qint64 QNativeSocketEngine::pendingDatagramSize() const return d->nativePendingDatagramSize(); } +#endif // QT_NO_UDPSOCKET /*! Reads up to \a maxSize bytes of a datagram from the socket, @@ -800,9 +818,10 @@ qint64 QNativeSocketEngine::pendingDatagramSize() const address, port, and other IP header fields are stored in \a header according to the request in \a options. - To avoid unnecessarily loss of data, call pendingDatagramSize() to - determine the size of the pending message before reading it. If \a - maxSize is too small, the rest of the datagram will be lost. + For UDP sockets, to avoid unnecessarily loss of data, call + pendingDatagramSize() to determine the size of the pending message + before reading it. If \a maxSize is too small, the rest of the + datagram will be lost. Returns -1 if an error occurred. @@ -813,13 +832,14 @@ qint64 QNativeSocketEngine::readDatagram(char *data, qint64 maxSize, QIpPacketHe { Q_D(QNativeSocketEngine); Q_CHECK_VALID_SOCKETLAYER(QNativeSocketEngine::readDatagram(), -1); - Q_CHECK_TYPE(QNativeSocketEngine::readDatagram(), QAbstractSocket::UdpSocket, -1); + Q_CHECK_STATES(QNativeSocketEngine::readDatagram(), QAbstractSocket::BoundState, + QAbstractSocket::ConnectedState, -1); return d->nativeReceiveDatagram(data, maxSize, header, options); } /*! - Writes a UDP datagram of size \a size bytes to the socket from + Writes a datagram of size \a size bytes to the socket from \a data to the destination contained in \a header, and returns the number of bytes written, or -1 if an error occurred. If \a header contains other settings like hop limit or source address, this function @@ -844,11 +864,11 @@ qint64 QNativeSocketEngine::writeDatagram(const char *data, qint64 size, const Q { Q_D(QNativeSocketEngine); Q_CHECK_VALID_SOCKETLAYER(QNativeSocketEngine::writeDatagram(), -1); - Q_CHECK_TYPE(QNativeSocketEngine::writeDatagram(), QAbstractSocket::UdpSocket, -1); + Q_CHECK_STATES(QNativeSocketEngine::writeDatagram(), QAbstractSocket::BoundState, + QAbstractSocket::ConnectedState, -1); return d->nativeSendDatagram(data, size, header); } -#endif // QT_NO_UDPSOCKET /*! Writes a block of \a size bytes from \a data to the socket. @@ -881,7 +901,11 @@ qint64 QNativeSocketEngine::read(char *data, qint64 maxSize) qint64 readBytes = d->nativeRead(data, maxSize); // Handle remote close - if (readBytes == 0 && d->socketType == QAbstractSocket::TcpSocket) { + if (readBytes == 0 && (d->socketType == QAbstractSocket::TcpSocket +#ifndef QT_NO_SCTP + || d->socketType == QAbstractSocket::SctpSocket +#endif + )) { d->setError(QAbstractSocket::RemoteHostClosedError, QNativeSocketEnginePrivate::RemoteHostClosedErrorString); close(); diff --git a/src/network/socket/qnativesocketengine_p.h b/src/network/socket/qnativesocketengine_p.h index 8b1a2720068..1ca0fa02130 100644 --- a/src/network/socket/qnativesocketengine_p.h +++ b/src/network/socket/qnativesocketengine_p.h @@ -157,13 +157,13 @@ public: bool setMulticastInterface(const QNetworkInterface &iface) Q_DECL_OVERRIDE; #endif - qint64 readDatagram(char *data, qint64 maxlen, QIpPacketHeader * = 0, - PacketHeaderOptions = WantNone) Q_DECL_OVERRIDE; - qint64 writeDatagram(const char *data, qint64 len, const QIpPacketHeader &) Q_DECL_OVERRIDE; bool hasPendingDatagrams() const Q_DECL_OVERRIDE; qint64 pendingDatagramSize() const Q_DECL_OVERRIDE; #endif // QT_NO_UDPSOCKET + qint64 readDatagram(char *data, qint64 maxlen, QIpPacketHeader * = 0, + PacketHeaderOptions = WantNone) Q_DECL_OVERRIDE; + qint64 writeDatagram(const char *data, qint64 len, const QIpPacketHeader &) Q_DECL_OVERRIDE; qint64 bytesToWrite() const Q_DECL_OVERRIDE; qint64 receiveBufferSize() const; diff --git a/src/network/socket/qnativesocketengine_unix.cpp b/src/network/socket/qnativesocketengine_unix.cpp index fcb8a60c8fe..2a9d6006306 100644 --- a/src/network/socket/qnativesocketengine_unix.cpp +++ b/src/network/socket/qnativesocketengine_unix.cpp @@ -68,6 +68,11 @@ #endif #include +#ifndef QT_NO_SCTP +#include +#include +#include +#endif QT_BEGIN_NAMESPACE @@ -142,6 +147,7 @@ static void convertToLevelAndOption(QNativeSocketEngine::SocketOption opt, switch (opt) { case QNativeSocketEngine::NonBlockingSocketOption: // fcntl, not setsockopt case QNativeSocketEngine::BindExclusively: // not handled on Unix + case QNativeSocketEngine::MaxStreamsSocketOption: Q_UNREACHABLE(); case QNativeSocketEngine::BroadcastSocketOption: @@ -229,13 +235,28 @@ static void convertToLevelAndOption(QNativeSocketEngine::SocketOption opt, bool QNativeSocketEnginePrivate::createNewSocket(QAbstractSocket::SocketType socketType, QAbstractSocket::NetworkLayerProtocol &socketProtocol) { - int protocol = (socketProtocol == QAbstractSocket::IPv6Protocol || socketProtocol == QAbstractSocket::AnyIPProtocol) ? AF_INET6 : AF_INET; +#ifndef QT_NO_SCTP + int protocol = (socketType == QAbstractSocket::SctpSocket) ? IPPROTO_SCTP : 0; +#else + if (socketType == QAbstractSocket::SctpSocket) { + setError(QAbstractSocket::UnsupportedSocketOperationError, + ProtocolUnsupportedErrorString); +#if defined (QNATIVESOCKETENGINE_DEBUG) + qDebug("QNativeSocketEnginePrivate::createNewSocket(%d, %d): unsupported protocol", + socketType, socketProtocol); +#endif + return false; + } + int protocol = 0; +#endif // QT_NO_SCTP + int domain = (socketProtocol == QAbstractSocket::IPv6Protocol + || socketProtocol == QAbstractSocket::AnyIPProtocol) ? AF_INET6 : AF_INET; int type = (socketType == QAbstractSocket::UdpSocket) ? SOCK_DGRAM : SOCK_STREAM; - int socket = qt_safe_socket(protocol, type, 0, O_NONBLOCK); + int socket = qt_safe_socket(domain, type, protocol, O_NONBLOCK); if (socket < 0 && socketProtocol == QAbstractSocket::AnyIPProtocol && errno == EAFNOSUPPORT) { - protocol = AF_INET; - socket = qt_safe_socket(protocol, type, 0, O_NONBLOCK); + domain = AF_INET; + socket = qt_safe_socket(domain, type, protocol, O_NONBLOCK); socketProtocol = QAbstractSocket::IPv4Protocol; } @@ -291,10 +312,26 @@ int QNativeSocketEnginePrivate::option(QNativeSocketEngine::SocketOption opt) co if (!q->isValid()) return -1; - // handle non-getsockopt cases first - if (opt == QNativeSocketEngine::BindExclusively || opt == QNativeSocketEngine::NonBlockingSocketOption - || opt == QNativeSocketEngine::BroadcastSocketOption) + // handle non-getsockopt and specific cases first + switch (opt) { + case QNativeSocketEngine::BindExclusively: + case QNativeSocketEngine::NonBlockingSocketOption: + case QNativeSocketEngine::BroadcastSocketOption: return true; + case QNativeSocketEngine::MaxStreamsSocketOption: { +#ifndef QT_NO_SCTP + sctp_initmsg sctpInitMsg; + QT_SOCKOPTLEN_T sctpInitMsgSize = sizeof(sctpInitMsg); + if (::getsockopt(socketDescriptor, SOL_SCTP, SCTP_INITMSG, &sctpInitMsg, + &sctpInitMsgSize) == 0) + return int(qMin(sctpInitMsg.sinit_num_ostreams, sctpInitMsg.sinit_max_instreams)); +#endif + return -1; + } + + default: + break; + } int n, level; int v = -1; @@ -317,7 +354,7 @@ bool QNativeSocketEnginePrivate::setOption(QNativeSocketEngine::SocketOption opt if (!q->isValid()) return false; - // handle non-setsockopt cases first + // handle non-setsockopt and specific cases first switch (opt) { case QNativeSocketEngine::NonBlockingSocketOption: { // Make the socket nonblocking. @@ -351,6 +388,20 @@ bool QNativeSocketEnginePrivate::setOption(QNativeSocketEngine::SocketOption opt case QNativeSocketEngine::BindExclusively: return true; + case QNativeSocketEngine::MaxStreamsSocketOption: { +#ifndef QT_NO_SCTP + sctp_initmsg sctpInitMsg; + QT_SOCKOPTLEN_T sctpInitMsgSize = sizeof(sctpInitMsg); + if (::getsockopt(socketDescriptor, SOL_SCTP, SCTP_INITMSG, &sctpInitMsg, + &sctpInitMsgSize) == 0) { + sctpInitMsg.sinit_num_ostreams = sctpInitMsg.sinit_max_instreams = uint16_t(v); + return ::setsockopt(socketDescriptor, SOL_SCTP, SCTP_INITMSG, &sctpInitMsg, + sctpInitMsgSize) == 0; + } +#endif + return false; + } + default: break; } @@ -829,6 +880,9 @@ qint64 QNativeSocketEnginePrivate::nativeReceiveDatagram(char *data, qint64 maxS quintptr cbuf[(CMSG_SPACE(sizeof(struct in6_pktinfo)) + CMSG_SPACE(sizeof(int)) #if !defined(IP_PKTINFO) && defined(IP_RECVIF) && defined(Q_OS_BSD4) + CMSG_SPACE(sizeof(sockaddr_dl)) +#endif +#ifndef QT_NO_SCTP + + CMSG_SPACE(sizeof(struct sctp_sndrcvinfo)) #endif + sizeof(quintptr) - 1) / sizeof(quintptr)]; @@ -848,7 +902,8 @@ qint64 QNativeSocketEnginePrivate::nativeReceiveDatagram(char *data, qint64 maxS msg.msg_name = &aa; msg.msg_namelen = sizeof(aa); } - if (options & (QAbstractSocketEngine::WantDatagramHopLimit | QAbstractSocketEngine::WantDatagramDestination)) { + if (options & (QAbstractSocketEngine::WantDatagramHopLimit | QAbstractSocketEngine::WantDatagramDestination + | QAbstractSocketEngine::WantStreamNumber)) { msg.msg_control = cbuf; msg.msg_controllen = sizeof(cbuf); } @@ -859,13 +914,27 @@ qint64 QNativeSocketEnginePrivate::nativeReceiveDatagram(char *data, qint64 maxS } while (recvResult == -1 && errno == EINTR); if (recvResult == -1) { - setError(QAbstractSocket::NetworkError, ReceiveDatagramErrorString); + switch (errno) { +#if EWOULDBLOCK-0 && EWOULDBLOCK != EAGAIN + case EWOULDBLOCK: +#endif + case EAGAIN: + // No datagram was available for reading + recvResult = -2; + break; + case ECONNREFUSED: + setError(QAbstractSocket::ConnectionRefusedError, ConnectionRefusedErrorString); + break; + default: + setError(QAbstractSocket::NetworkError, ReceiveDatagramErrorString); + } if (header) header->clear(); } else if (options != QAbstractSocketEngine::WantNone) { Q_ASSERT(header); qt_socket_getPortAndAddress(&aa, &header->senderPort, &header->senderAddress); header->destinationPort = localPort; + header->endOfRecord = (msg.msg_flags & MSG_EOR) != 0; // parse the ancillary data struct cmsghdr *cmsgptr; @@ -912,6 +981,15 @@ qint64 QNativeSocketEnginePrivate::nativeReceiveDatagram(char *data, qint64 maxS || (cmsgptr->cmsg_level == IPPROTO_IP && cmsgptr->cmsg_type == IP_TTL))) { header->hopLimit = *reinterpret_cast(CMSG_DATA(cmsgptr)); } + +#ifndef QT_NO_SCTP + if (cmsgptr->cmsg_level == IPPROTO_SCTP && cmsgptr->cmsg_type == SCTP_SNDRCV + && cmsgptr->cmsg_len >= CMSG_LEN(sizeof(sctp_sndrcvinfo))) { + sctp_sndrcvinfo *rcvInfo = reinterpret_cast(CMSG_DATA(cmsgptr)); + + header->streamNumber = int(rcvInfo->sinfo_stream); + } +#endif } } @@ -924,13 +1002,17 @@ qint64 QNativeSocketEnginePrivate::nativeReceiveDatagram(char *data, qint64 maxS ? header->senderPort : 0, (qint64) recvResult); #endif - return qint64(maxSize ? recvResult : recvResult == -1 ? -1 : 0); + return qint64((maxSize || recvResult < 0) ? recvResult : Q_INT64_C(0)); } qint64 QNativeSocketEnginePrivate::nativeSendDatagram(const char *data, qint64 len, const QIpPacketHeader &header) { // we use quintptr to force the alignment - quintptr cbuf[(CMSG_SPACE(sizeof(struct in6_pktinfo)) + CMSG_SPACE(sizeof(int)) + sizeof(quintptr) - 1) / sizeof(quintptr)]; + quintptr cbuf[(CMSG_SPACE(sizeof(struct in6_pktinfo)) + CMSG_SPACE(sizeof(int)) +#ifndef QT_NO_SCTP + + CMSG_SPACE(sizeof(struct sctp_sndrcvinfo)) +#endif + + sizeof(quintptr) - 1) / sizeof(quintptr)]; struct cmsghdr *cmsgptr = reinterpret_cast(cbuf); struct msghdr msg; @@ -943,10 +1025,13 @@ qint64 QNativeSocketEnginePrivate::nativeSendDatagram(const char *data, qint64 l vec.iov_len = len; msg.msg_iov = &vec; msg.msg_iovlen = 1; - msg.msg_name = &aa.a; msg.msg_control = &cbuf; - setPortAndAddress(header.destinationPort, header.destinationAddress, &aa, &msg.msg_namelen); + if (header.destinationPort != 0) { + msg.msg_name = &aa.a; + setPortAndAddress(header.destinationPort, header.destinationAddress, + &aa, &msg.msg_namelen); + } if (msg.msg_namelen == sizeof(aa.a6)) { if (header.hopLimit != -1) { @@ -1001,15 +1086,37 @@ qint64 QNativeSocketEnginePrivate::nativeSendDatagram(const char *data, qint64 l #endif } +#ifndef QT_NO_SCTP + if (header.streamNumber != -1) { + struct sctp_sndrcvinfo *data = reinterpret_cast(CMSG_DATA(cmsgptr)); + memset(data, 0, sizeof(*data)); + msg.msg_controllen += CMSG_SPACE(sizeof(sctp_sndrcvinfo)); + cmsgptr->cmsg_len = CMSG_LEN(sizeof(sctp_sndrcvinfo)); + cmsgptr->cmsg_level = IPPROTO_SCTP; + cmsgptr->cmsg_type = SCTP_SNDRCV; + data->sinfo_stream = uint16_t(header.streamNumber); + cmsgptr = reinterpret_cast(reinterpret_cast(cmsgptr) + CMSG_SPACE(sizeof(*data))); + } +#endif + if (msg.msg_controllen == 0) msg.msg_control = 0; ssize_t sentBytes = qt_safe_sendmsg(socketDescriptor, &msg, 0); if (sentBytes < 0) { switch (errno) { +#if EWOULDBLOCK-0 && EWOULDBLOCK != EAGAIN + case EWOULDBLOCK: +#endif + case EAGAIN: + sentBytes = -2; + break; case EMSGSIZE: setError(QAbstractSocket::DatagramTooLargeError, DatagramTooLargeErrorString); break; + case ECONNRESET: + setError(QAbstractSocket::RemoteHostClosedError, RemoteHostClosedErrorString); + break; default: setError(QAbstractSocket::NetworkError, SendDatagramErrorString); } @@ -1082,21 +1189,51 @@ bool QNativeSocketEnginePrivate::fetchConnectionParameters() #endif // Determine the remote address - if (!::getpeername(socketDescriptor, &sa.a, &sockAddrSize)) { + bool connected = ::getpeername(socketDescriptor, &sa.a, &sockAddrSize) == 0; + if (connected) { qt_socket_getPortAndAddress(&sa, &peerPort, &peerAddress); inboundStreamCount = outboundStreamCount = 1; } - // Determine the socket type (UDP/TCP) + // Determine the socket type (UDP/TCP/SCTP) int value = 0; QT_SOCKOPTLEN_T valueSize = sizeof(int); if (::getsockopt(socketDescriptor, SOL_SOCKET, SO_TYPE, &value, &valueSize) == 0) { - if (value == SOCK_STREAM) - socketType = QAbstractSocket::TcpSocket; - else if (value == SOCK_DGRAM) - socketType = QAbstractSocket::UdpSocket; - else - socketType = QAbstractSocket::UnknownSocketType; + if (value == SOCK_STREAM) { +#ifndef QT_NO_SCTP + if (option(QNativeSocketEngine::MaxStreamsSocketOption) != -1) { + socketType = QAbstractSocket::SctpSocket; + if (connected) { + sctp_status sctpStatus; + QT_SOCKOPTLEN_T sctpStatusSize = sizeof(sctpStatus); + sctp_event_subscribe sctpEvents; + + memset(&sctpEvents, 0, sizeof(sctpEvents)); + sctpEvents.sctp_data_io_event = 1; + if (::getsockopt(socketDescriptor, SOL_SCTP, SCTP_STATUS, &sctpStatus, + &sctpStatusSize) == 0 && + ::setsockopt(socketDescriptor, SOL_SCTP, SCTP_EVENTS, &sctpEvents, + sizeof(sctpEvents)) == 0) { + inboundStreamCount = int(sctpStatus.sstat_instrms); + outboundStreamCount = int(sctpStatus.sstat_outstrms); + } else { + setError(QAbstractSocket::UnsupportedSocketOperationError, + InvalidSocketErrorString); + return false; + } + } + } else { + socketType = QAbstractSocket::TcpSocket; + } +#else + socketType = QAbstractSocket::TcpSocket; +#endif + } else { + if (value == SOCK_DGRAM) + socketType = QAbstractSocket::UdpSocket; + else + socketType = QAbstractSocket::UnknownSocketType; + } } #if defined (QNATIVESOCKETENGINE_DEBUG) QString socketProtocolStr = QStringLiteral("UnknownProtocol"); @@ -1106,6 +1243,7 @@ bool QNativeSocketEnginePrivate::fetchConnectionParameters() QString socketTypeStr = QStringLiteral("UnknownSocketType"); if (socketType == QAbstractSocket::TcpSocket) socketTypeStr = QStringLiteral("TcpSocket"); else if (socketType == QAbstractSocket::UdpSocket) socketTypeStr = QStringLiteral("UdpSocket"); + else if (socketType == QAbstractSocket::SctpSocket) socketTypeStr = QStringLiteral("SctpSocket"); qDebug("QNativeSocketEnginePrivate::fetchConnectionParameters() local == %s:%i," " peer == %s:%i, socket == %s - %s, inboundStreamCount == %i, outboundStreamCount == %i", diff --git a/src/network/socket/qnativesocketengine_win.cpp b/src/network/socket/qnativesocketengine_win.cpp index 0c5b8d9264e..9ae2d8ba8f8 100644 --- a/src/network/socket/qnativesocketengine_win.cpp +++ b/src/network/socket/qnativesocketengine_win.cpp @@ -214,6 +214,7 @@ static void convertToLevelAndOption(QNativeSocketEngine::SocketOption opt, switch (opt) { case QNativeSocketEngine::NonBlockingSocketOption: // WSAIoctl case QNativeSocketEngine::TypeOfServiceOption: // not supported + case QNativeSocketEngine::MaxStreamsSocketOption: Q_UNREACHABLE(); case QNativeSocketEngine::ReceiveBufferSocketOption: @@ -325,6 +326,14 @@ bool QNativeSocketEnginePrivate::createNewSocket(QAbstractSocket::SocketType soc return -1; } */ + + //### SCTP not implemented + if (socketType == QAbstractSocket::SctpSocket) { + setError(QAbstractSocket::UnsupportedSocketOperationError, + ProtocolUnsupportedErrorString); + return false; + } + QSysInfo::WinVersion osver = QSysInfo::windowsVersion(); //Windows XP and 2003 support IPv6 but not dual stack sockets @@ -451,6 +460,7 @@ int QNativeSocketEnginePrivate::option(QNativeSocketEngine::SocketOption opt) co break; } case QNativeSocketEngine::TypeOfServiceOption: + case QNativeSocketEngine::MaxStreamsSocketOption: return -1; default: @@ -501,6 +511,7 @@ bool QNativeSocketEnginePrivate::setOption(QNativeSocketEngine::SocketOption opt break; } case QNativeSocketEngine::TypeOfServiceOption: + case QNativeSocketEngine::MaxStreamsSocketOption: return false; default: diff --git a/src/network/socket/qnativesocketengine_winrt.cpp b/src/network/socket/qnativesocketengine_winrt.cpp index 9817d4c57d1..641863b4fdf 100644 --- a/src/network/socket/qnativesocketengine_winrt.cpp +++ b/src/network/socket/qnativesocketengine_winrt.cpp @@ -632,6 +632,7 @@ qint64 QNativeSocketEngine::write(const char *data, qint64 len) qint64 QNativeSocketEngine::readDatagram(char *data, qint64 maxlen, QIpPacketHeader *header, PacketHeaderOptions) { +#ifndef QT_NO_UDPSOCKET Q_D(QNativeSocketEngine); if (d->socketType != QAbstractSocket::UdpSocket || d->pendingDatagrams.isEmpty()) { if (header) @@ -654,10 +655,17 @@ qint64 QNativeSocketEngine::readDatagram(char *data, qint64 maxlen, QIpPacketHea } memcpy(data, readOrigin, qMin(maxlen, qint64(datagram.data.length()))); return readOrigin.length(); +#else + Q_UNUSED(data) + Q_UNUSED(maxlen) + Q_UNUSED(header) + return -1; +#endif // QT_NO_UDPSOCKET } qint64 QNativeSocketEngine::writeDatagram(const char *data, qint64 len, const QIpPacketHeader &header) { +#ifndef QT_NO_UDPSOCKET Q_D(QNativeSocketEngine); if (d->socketType != QAbstractSocket::UdpSocket) return -1; @@ -684,6 +692,12 @@ qint64 QNativeSocketEngine::writeDatagram(const char *data, qint64 len, const QI Q_ASSERT_SUCCEEDED(hr); return writeIOStream(stream, data, len); +#else + Q_UNUSED(data) + Q_UNUSED(len) + Q_UNUSED(header) + return -1; +#endif // QT_NO_UDPSOCKET } bool QNativeSocketEngine::hasPendingDatagrams() const @@ -1088,6 +1102,7 @@ int QNativeSocketEnginePrivate::option(QAbstractSocketEngine::SocketOption opt) case QAbstractSocketEngine::MulticastTtlOption: case QAbstractSocketEngine::MulticastLoopbackOption: case QAbstractSocketEngine::TypeOfServiceOption: + case QAbstractSocketEngine::MaxStreamsSocketOption: default: return -1; } @@ -1146,6 +1161,7 @@ bool QNativeSocketEnginePrivate::setOption(QAbstractSocketEngine::SocketOption o case QAbstractSocketEngine::MulticastTtlOption: case QAbstractSocketEngine::MulticastLoopbackOption: case QAbstractSocketEngine::TypeOfServiceOption: + case QAbstractSocketEngine::MaxStreamsSocketOption: default: return false; } diff --git a/src/network/socket/qsctpserver.cpp b/src/network/socket/qsctpserver.cpp new file mode 100644 index 00000000000..a2dc824a093 --- /dev/null +++ b/src/network/socket/qsctpserver.cpp @@ -0,0 +1,250 @@ +/**************************************************************************** +** +** Copyright (C) 2016 Alex Trotsenko +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtNetwork module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** 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 Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** 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-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +//#define QSCTPSERVER_DEBUG + +/*! + \class QSctpServer + \since 5.8 + + \brief The QSctpServer class provides an SCTP-based server. + + \ingroup network + \inmodule QtNetwork + + SCTP (Stream Control Transmission Protocol) is a transport layer + protocol serving in a similar role as the popular protocols TCP + and UDP. Like UDP, SCTP is message-oriented, but it ensures reliable, + in-sequence transport of messages with congestion control like + TCP. See the QSctpSocket documentation for more protocol details. + + QSctpServer is a convenience subclass of QTcpServer that allows + you to accept incoming STCP socket connections either in TCP + emulation or in datagram mode. + + The most common way to use QSctpServer is to construct an object + and set the maximum number of channels that the server is + prepared to support, by calling setMaxChannelCount(). You can set + the TCP emulation mode by passing a negative argument in this + call. Also, a special value of 0 (the default) indicates to use + the peer's value as the actual number of channels. The new incoming + connection inherits this number from the server socket descriptor + and adjusts it according to the remote endpoint settings. + + In TCP emulation mode, accepted clients use a single continuous + byte stream for data transmission, and QSctpServer acts like a + plain QTcpServer. Call nextPendingConnection() to accept the + pending connection as a connected QTcpSocket. The function returns + a pointer to a QTcpSocket in QAbstractSocket::ConnectedState that + you can use for communicating with the client. This mode gives + access only to basic SCTP protocol features. The socket transmits SCTP + packets over IP at system level and interacts through the + QTcpSocket interface with the application. + + In contrast, datagram mode is message-oriented and provides a + complete simultaneous transmission of multiple data streams + between endpoints. Call nextPendingDatagramConnection() to accept + the pending datagram-mode connection as a connected QSctpSocket. + + \note This feature is not supported on the Windows platform. + + \sa QTcpServer, QSctpSocket, QAbstractSocket +*/ + +#include "qsctpserver.h" +#include "qsctpserver_p.h" + +#include "qsctpsocket.h" +#include "qabstractsocketengine_p.h" + +#ifdef QSCTPSERVER_DEBUG +#include +#endif + +QT_BEGIN_NAMESPACE + +/*! \internal +*/ +QSctpServerPrivate::QSctpServerPrivate() + : maxChannelCount(0) +{ +} + +/*! \internal +*/ +QSctpServerPrivate::~QSctpServerPrivate() +{ +} + +/*! \internal +*/ +void QSctpServerPrivate::configureCreatedSocket() +{ + QTcpServerPrivate::configureCreatedSocket(); + if (socketEngine) + socketEngine->setOption(QAbstractSocketEngine::MaxStreamsSocketOption, + maxChannelCount == -1 ? 1 : maxChannelCount); +} + +/*! + Constructs a QSctpServer object. + + Sets the datagram operation mode. The \a parent argument is passed + to QObject's constructor. + + \sa setMaxChannelCount(), listen(), setSocketDescriptor() +*/ +QSctpServer::QSctpServer(QObject *parent) + : QTcpServer(QAbstractSocket::SctpSocket, *new QSctpServerPrivate, parent) +{ +#if defined(QSCTPSERVER_DEBUG) + qDebug("QSctpServer::QSctpServer()"); +#endif +} + +/*! + Destroys the QSctpServer object. If the server is listening for + connections, the socket is automatically closed. + + \sa close() +*/ +QSctpServer::~QSctpServer() +{ +#if defined(QSCTPSERVER_DEBUG) + qDebug("QSctpServer::~QSctpServer()"); +#endif +} + +/*! + Sets the maximum number of channels that the server is prepared to + support in datagram mode, to \a count. If \a count is 0, endpoint + maximum number of channels value would be used. Negative \a count + sets a TCP emulation mode. + + Call this member only when QSctpServer is in UnconnectedState. + + \sa maxChannelCount(), QSctpSocket +*/ +void QSctpServer::setMaxChannelCount(int count) +{ + Q_D(QSctpServer); + if (d->state != QAbstractSocket::UnconnectedState) { + qWarning("QSctpServer::setMaxChannelCount() is only allowed in UnconnectedState"); + return; + } +#if defined(QSCTPSERVER_DEBUG) + qDebug("QSctpServer::setMaxChannelCount(%i)", count); +#endif + d->maxChannelCount = count; +} + +/*! + Returns the maximum number of channels that the accepted sockets are + able to support. + + A value of 0 (the default) means that the number of connection + channels would be set by the remote endpoint. + + Returns -1, if QSctpServer running in TCP emulation mode. + + \sa setMaxChannelCount() +*/ +int QSctpServer::maxChannelCount() const +{ + return d_func()->maxChannelCount; +} + +/*! \reimp +*/ +void QSctpServer::incomingConnection(qintptr socketDescriptor) +{ +#if defined (QSCTPSERVER_DEBUG) + qDebug("QSctpServer::incomingConnection(%i)", socketDescriptor); +#endif + + QSctpSocket *socket = new QSctpSocket(this); + socket->setMaxChannelCount(d_func()->maxChannelCount); + socket->setSocketDescriptor(socketDescriptor); + addPendingConnection(socket); +} + +/*! + Returns the next pending datagram-mode connection as a connected + QSctpSocket object. + + Datagram-mode connection provides a message-oriented, multi-stream + communication. + + The socket is created as a child of the server, which means that + it is automatically deleted when the QSctpServer object is + destroyed. It is still a good idea to delete the object + explicitly when you are done with it, to avoid wasting memory. + + This function returns null if there are no pending datagram-mode + connections. + + \note The returned QSctpSocket object cannot be used from another + thread. If you want to use an incoming connection from another + thread, you need to override incomingConnection(). + + \sa hasPendingConnections(), nextPendingConnection(), QSctpSocket +*/ +QSctpSocket *QSctpServer::nextPendingDatagramConnection() +{ + Q_D(QSctpServer); + + QMutableListIterator i(d->pendingConnections); + while (i.hasNext()) { + QSctpSocket *socket = qobject_cast(i.next()); + Q_ASSERT(socket); + + if (socket->inDatagramMode()) { + i.remove(); + Q_ASSERT(d->socketEngine); + d->socketEngine->setReadNotificationEnabled(true); + return socket; + } + } + + return nullptr; +} + +QT_END_NAMESPACE + +#include "moc_qsctpserver.cpp" diff --git a/src/network/socket/qsctpserver.h b/src/network/socket/qsctpserver.h new file mode 100644 index 00000000000..fb017a924df --- /dev/null +++ b/src/network/socket/qsctpserver.h @@ -0,0 +1,77 @@ +/**************************************************************************** +** +** Copyright (C) 2016 Alex Trotsenko +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtNetwork module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** 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 Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** 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-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QSCTPSERVER_H +#define QSCTPSERVER_H + +#include + +QT_BEGIN_NAMESPACE + + +#ifndef QT_NO_SCTP + +class QSctpServerPrivate; +class QSctpSocket; + +class Q_NETWORK_EXPORT QSctpServer : public QTcpServer +{ + Q_OBJECT +public: + explicit QSctpServer(QObject *parent = nullptr); + virtual ~QSctpServer(); + + void setMaxChannelCount(int count); + int maxChannelCount() const; + + QSctpSocket *nextPendingDatagramConnection(); + +protected: + void incomingConnection(qintptr handle) Q_DECL_OVERRIDE; + +private: + Q_DISABLE_COPY(QSctpServer) + Q_DECLARE_PRIVATE(QSctpServer) +}; + +#endif // QT_NO_SCTP + +QT_END_NAMESPACE + +#endif // QSCTPSERVER_H diff --git a/src/network/socket/qsctpserver_p.h b/src/network/socket/qsctpserver_p.h new file mode 100644 index 00000000000..32760caffe9 --- /dev/null +++ b/src/network/socket/qsctpserver_p.h @@ -0,0 +1,76 @@ +/**************************************************************************** +** +** Copyright (C) 2016 Alex Trotsenko +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtNetwork module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** 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 Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** 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-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QSCTPSERVER_P_H +#define QSCTPSERVER_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of the QLibrary class. This header file may change from +// version to version without notice, or even be removed. +// +// We mean it. +// + +#include "private/qtcpserver_p.h" + +QT_BEGIN_NAMESPACE + +#ifndef QT_NO_SCTP + +class QSctpServerPrivate : public QTcpServerPrivate +{ + Q_DECLARE_PUBLIC(QSctpServer) +public: + QSctpServerPrivate(); + virtual ~QSctpServerPrivate(); + + int maxChannelCount; + + void configureCreatedSocket() Q_DECL_OVERRIDE; +}; + +#endif // QT_NO_SCTP + +QT_END_NAMESPACE + +#endif // QSCTPSERVER_P_H diff --git a/src/network/socket/qsctpsocket.cpp b/src/network/socket/qsctpsocket.cpp new file mode 100644 index 00000000000..dcfd6806a4c --- /dev/null +++ b/src/network/socket/qsctpsocket.cpp @@ -0,0 +1,543 @@ +/**************************************************************************** +** +** Copyright (C) 2016 Alex Trotsenko +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtNetwork module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** 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 Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** 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-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +//#define QSCTPSOCKET_DEBUG + +/*! + \class QSctpSocket + \since 5.8 + + \brief The QSctpSocket class provides an SCTP socket. + + \ingroup network + \inmodule QtNetwork + + SCTP (Stream Control Transmission Protocol) is a transport layer + protocol serving in a similar role as the popular protocols TCP + and UDP. Like UDP, SCTP is message-oriented, but it ensures reliable, + in-sequence transport of messages with congestion control like + TCP. + + SCTP is connection-oriented protocol, which provides the complete + simultaneous transmission of multiple data streams between + endpoints. This multi-streaming allows data to be delivered by + independent channels, so that if there is data loss in one stream, + delivery will not be affected for the other streams. + + As message-oriented, SCTP transports a sequence of messages, + rather than transporting an unbroken stream of bytes as does TCP. + Like in UDP, in SCTP a sender sends a message in one operation, + and that exact message is passed to the receiving application + process in one operation. But unlike UDP, the delivery is + guaranteed. + + It also supports multi-homing, meaning that a connected endpoint + can have alternate IP addresses associated with it in order to + route around network failure or changing conditions. + + QSctpSocket is a convenience subclass of QTcpSocket that allows + you to emulate TCP data stream over SCTP or establish an SCTP + connection for reliable datagram service. + + QSctpSocket can operate in one of two possible modes: + + \list + \li Continuous byte stream (TCP emulation). + \li Multi-streamed datagram mode. + \endlist + + To set a continuous byte stream mode, instantiate QSctpSocket and + call setMaxChannelCount() with the negative number of channels. This + gives the ability to use QSctpSocket as a regular buffered + QTcpSocket. You can call connectToHost() to initiate connection + with endpoint, write() to transmit and read() to receive data from + the peer, but you cannot distinguish message boundaries. + + By default, QSctpSocket operates in datagram mode. Before + connecting, call setMaxChannelCount() to set the maximum number of + channels that the application is prepared to support. This number + is a negotiated parameter with remote endpoint and its value can + be bounded by the operating system. The default value of 0 + indicates to use the peer's value. If both endpoints have default + values, then number of connection channels is system-dependent. + After establishing a connection, you can fetch the actual number + of channels by calling readChannelCount() and writeChannelCount(). + + \snippet code/src_network_socket_qsctpsocket.cpp 0 + + In datagram mode, QSctpSocket performs the buffering of datagrams + independently for each channel. You can queue a datagram to the + buffer of the current channel by calling writeDatagram() and read + a pending datagram by calling readDatagram() respectively. + + Using the standard QIODevice functions read(), readLine(), write(), + etc. is allowed in datagram mode with the same limitations as in + continuous byte stream mode. + + \note This feature is not supported on the Windows platform. + + \sa QSctpServer, QTcpSocket, QAbstractSocket +*/ + +#include "qsctpsocket.h" +#include "qsctpsocket_p.h" + +#include "qabstractsocketengine_p.h" +#include "private/qbytearray_p.h" + +#ifdef QSCTPSOCKET_DEBUG +#include +#endif + +QT_BEGIN_NAMESPACE + +/*! \internal +*/ +QSctpSocketPrivate::QSctpSocketPrivate() + : maxChannelCount(0) +{ +} + +/*! \internal +*/ +QSctpSocketPrivate::~QSctpSocketPrivate() +{ +} + +/*! \internal +*/ +bool QSctpSocketPrivate::canReadNotification() +{ + Q_Q(QSctpSocket); +#if defined (QSCTPSOCKET_DEBUG) + qDebug("QSctpSocketPrivate::canReadNotification()"); +#endif + + // Handle TCP emulation mode in the base implementation. + if (!q->inDatagramMode()) + return QTcpSocketPrivate::canReadNotification(); + + const int savedCurrentChannel = currentReadChannel; + bool currentChannelRead = false; + do { + int datagramSize = incomingDatagram.size(); + QIpPacketHeader header; + + do { + // Determine the size of the pending datagram. + qint64 bytesToRead = socketEngine->bytesAvailable(); + if (bytesToRead == 0) + bytesToRead = 4096; + + Q_ASSERT((datagramSize + int(bytesToRead)) < MaxByteArraySize); + incomingDatagram.resize(datagramSize + int(bytesToRead)); + +#if defined (QSCTPSOCKET_DEBUG) + qDebug("QSctpSocketPrivate::canReadNotification() about to read %lli bytes", + bytesToRead); +#endif + qint64 readBytes = socketEngine->readDatagram( + incomingDatagram.data() + datagramSize, bytesToRead, &header, + QAbstractSocketEngine::WantAll); + if (readBytes <= 0) { + if (readBytes == -2) { // no data available for reading + incomingDatagram.resize(datagramSize); + return currentChannelRead; + } + + socketEngine->close(); + if (readBytes == 0) { + setErrorAndEmit(QAbstractSocket::RemoteHostClosedError, + QSctpSocket::tr("The remote host closed the connection")); + } else { +#if defined (QSCTPSOCKET_DEBUG) + qDebug("QSctpSocketPrivate::canReadNotification() read failed: %s", + socketEngine->errorString().toLatin1().constData()); +#endif + setErrorAndEmit(socketEngine->error(), socketEngine->errorString()); + } + +#if defined (QSCTPSOCKET_DEBUG) + qDebug("QSctpSocketPrivate::canReadNotification() disconnecting socket"); +#endif + q->disconnectFromHost(); + return currentChannelRead; + } + datagramSize += int(readBytes); // update datagram size + } while (!header.endOfRecord); + +#if defined (QSCTPSOCKET_DEBUG) + qDebug("QSctpSocketPrivate::canReadNotification() got datagram from channel %i, size = %i", + header.streamNumber, datagramSize); +#endif + + // Drop the datagram, if opened only for writing + if (!q->isReadable()) { + incomingDatagram.clear(); + continue; + } + + // Store datagram in the channel buffer + Q_ASSERT(header.streamNumber < readBuffers.size()); + incomingDatagram.resize(datagramSize); + readBuffers[header.streamNumber].setChunkSize(0); // set packet mode on channel buffer + readBuffers[header.streamNumber].append(incomingDatagram); + incomingDatagram = QByteArray(); + + if (readHeaders.size() != readBuffers.size()) + readHeaders.resize(readBuffers.size()); + readHeaders[header.streamNumber].push_back(header); + + // Emit notifications. + if (header.streamNumber == savedCurrentChannel) + currentChannelRead = true; + emitReadyRead(header.streamNumber); + + } while (state == QAbstractSocket::ConnectedState); + + return currentChannelRead; +} + +/*! \internal +*/ +bool QSctpSocketPrivate::writeToSocket() +{ + Q_Q(QSctpSocket); +#if defined (QSCTPSOCKET_DEBUG) + qDebug("QSctpSocketPrivate::writeToSocket()"); +#endif + + // Handle TCP emulation mode in the base implementation. + if (!q->inDatagramMode()) + return QTcpSocketPrivate::writeToSocket(); + + if (!socketEngine) + return false; + + QIpPacketHeader defaultHeader; + const int savedCurrentChannel = currentWriteChannel; + bool currentChannelWritten = false; + bool transmitting; + do { + transmitting = false; + + for (int channel = 0; channel < writeBuffers.size(); ++channel) { + QRingBuffer &channelBuffer = writeBuffers[channel]; + + if (channelBuffer.isEmpty()) + continue; + + const bool hasHeader = (channel < writeHeaders.size()) + && !writeHeaders[channel].empty(); + QIpPacketHeader &header = hasHeader ? writeHeaders[channel].front() : defaultHeader; + header.streamNumber = channel; + qint64 sent = socketEngine->writeDatagram(channelBuffer.readPointer(), + channelBuffer.nextDataBlockSize(), + header); + if (sent < 0) { + if (sent == -2) // temporary error in writeDatagram + return currentChannelWritten; + + socketEngine->close(); +#if defined (QSCTPSOCKET_DEBUG) + qDebug("QSctpSocketPrivate::writeToSocket() write error, aborting. %s", + socketEngine->errorString().toLatin1().constData()); +#endif + setErrorAndEmit(socketEngine->error(), socketEngine->errorString()); + // An unexpected error so close the socket. + q->disconnectFromHost(); + return currentChannelWritten; + } + Q_ASSERT(sent == channelBuffer.nextDataBlockSize()); +#if defined (QSCTPSOCKET_DEBUG) + qDebug("QSctpSocketPrivate::writeToSocket() sent datagram of size %lli to channel %i", + sent, channel); +#endif + transmitting = true; + + // Remove datagram from the buffer + channelBuffer.read(); + if (hasHeader) + writeHeaders[channel].pop_front(); + + // Emit notifications. + if (channel == savedCurrentChannel) + currentChannelWritten = true; + emitBytesWritten(sent, channel); + + // If we were closed as a result of the bytesWritten() signal, return. + if (state == QAbstractSocket::UnconnectedState) { +#if defined (QSCTPSOCKET_DEBUG) + qDebug("QSctpSocketPrivate::writeToSocket() socket is closing - returning"); +#endif + return currentChannelWritten; + } + } + } while (transmitting); + + // At this point socket is either in Connected or Closing state, + // write buffers are empty. + if (state == QAbstractSocket::ClosingState) + q->disconnectFromHost(); + else + socketEngine->setWriteNotificationEnabled(false); + + return currentChannelWritten; +} + +/*! \internal +*/ +void QSctpSocketPrivate::configureCreatedSocket() +{ + if (socketEngine) + socketEngine->setOption(QAbstractSocketEngine::MaxStreamsSocketOption, + maxChannelCount < 0 ? 1 : maxChannelCount); +} + +/*! + Creates a QSctpSocket object in state \c UnconnectedState. + + Sets the datagram operation mode. The \a parent argument is passed + to QObject's constructor. + + \sa socketType(), setMaxChannelCount() +*/ +QSctpSocket::QSctpSocket(QObject *parent) + : QTcpSocket(SctpSocket, *new QSctpSocketPrivate, parent) +{ +#if defined(QSCTPSOCKET_DEBUG) + qDebug("QSctpSocket::QSctpSocket()"); +#endif + d_func()->isBuffered = true; +} + +/*! + Destroys the socket, closing the connection if necessary. + + \sa close() +*/ +QSctpSocket::~QSctpSocket() +{ +#if defined(QSCTPSOCKET_DEBUG) + qDebug("QSctpSocket::~QSctpSocket()"); +#endif +} + +/*! \reimp +*/ +qint64 QSctpSocket::readData(char *data, qint64 maxSize) +{ + Q_D(QSctpSocket); + + // Cleanup headers, if the user calls the standard QIODevice functions + if (d->currentReadChannel < d->readHeaders.size()) + d->readHeaders[d->currentReadChannel].clear(); + + return QTcpSocket::readData(data, maxSize); +} + +/*! \reimp +*/ +qint64 QSctpSocket::readLineData(char *data, qint64 maxlen) +{ + Q_D(QSctpSocket); + + // Cleanup headers, if the user calls the standard QIODevice functions + if (d->currentReadChannel < d->readHeaders.size()) + d->readHeaders[d->currentReadChannel].clear(); + + return QTcpSocket::readLineData(data, maxlen); +} + +/*! \reimp +*/ +void QSctpSocket::close() +{ + QTcpSocket::close(); + d_func()->readHeaders.clear(); +} + +/*! \reimp +*/ +void QSctpSocket::disconnectFromHost() +{ + Q_D(QSctpSocket); + + QTcpSocket::disconnectFromHost(); + if (d->state == QAbstractSocket::UnconnectedState) { + d->incomingDatagram.clear(); + d->writeHeaders.clear(); + } +} + +/*! + Sets the maximum number of channels that the application is + prepared to support in datagram mode, to \a count. If \a count + is 0, endpoint's value for maximum number of channels is used. + Negative \a count sets a continuous byte stream mode. + + Call this member only when QSctpSocket is in UnconnectedState. + + \sa maxChannelCount(), readChannelCount(), writeChannelCount() +*/ +void QSctpSocket::setMaxChannelCount(int count) +{ + Q_D(QSctpSocket); + if (d->state != QAbstractSocket::UnconnectedState) { + qWarning("QSctpSocket::setMaxChannelCount() is only allowed in UnconnectedState"); + return; + } +#if defined(QSCTPSOCKET_DEBUG) + qDebug("QSctpSocket::setMaxChannelCount(%i)", count); +#endif + d->maxChannelCount = qMax(count, -1); +} + +/*! + Returns the maximum number of channels that QSctpSocket is able to + support. + + A value of 0 (the default) means that the number of connection + channels would be set by the remote endpoint. + + Returns -1 if QSctpSocket is running in continuous byte stream + mode. + + \sa setMaxChannelCount(), readChannelCount(), writeChannelCount() +*/ +int QSctpSocket::maxChannelCount() const +{ + return d_func()->maxChannelCount; +} + +/*! + Returns \c true if the socket is running in datagram mode. + + \sa setMaxChannelCount() +*/ +bool QSctpSocket::inDatagramMode() const +{ + Q_D(const QSctpSocket); + return d->maxChannelCount != -1 && d->isBuffered; +} + +/*! + Reads a datagram from the buffer of the current read channel, and + returns it as a QNetworkDatagram object, along with the sender's + host address and port. If possible, this function will also try to + determine the datagram's destination address, port, and the number + of hop counts at reception time. + + On failure, returns a QNetworkDatagram that reports \l + {QNetworkDatagram::isValid()}{not valid}. + + \sa writeDatagram(), inDatagramMode(), currentReadChannel() +*/ +QNetworkDatagram QSctpSocket::readDatagram() +{ + Q_D(QSctpSocket); + + if (!isReadable() || !inDatagramMode()) { + qWarning("QSctpSocket::readDatagram(): operation is not permitted"); + return QNetworkDatagram(); + } + + if (d->currentReadChannel >= d->readHeaders.size() + || (d->readHeaders[d->currentReadChannel].size() == 0)) { + Q_ASSERT(d->buffer.isEmpty()); + return QNetworkDatagram(); + } + + QNetworkDatagram result(*new QNetworkDatagramPrivate(d->buffer.read(), + d->readHeaders[d->currentReadChannel].front())); + d->readHeaders[d->currentReadChannel].pop_front(); + +#if defined (QSCTPSOCKET_DEBUG) + qDebug("QSctpSocket::readDatagram() returning datagram (%p, %i, \"%s\", %i)", + result.d->data.constData(), + result.d->data.size(), + result.senderAddress().toString().toLatin1().constData(), + result.senderPort()); +#endif + + return result; +} + +/*! + Writes a datagram to the buffer of the current write channel. + Returns true on success; otherwise returns false. + + \sa readDatagram(), inDatagramMode(), currentWriteChannel() +*/ +bool QSctpSocket::writeDatagram(const QNetworkDatagram &datagram) +{ + Q_D(QSctpSocket); + + if (!isWritable() || d->state != QAbstractSocket::ConnectedState || !d->socketEngine + || !d->socketEngine->isValid() || !inDatagramMode()) { + qWarning("QSctpSocket::writeDatagram(): operation is not permitted"); + return false; + } + + if (datagram.d->data.isEmpty()) { + qWarning("QSctpSocket::writeDatagram() is called with empty datagram"); + return false; + } + + +#if defined QSCTPSOCKET_DEBUG + qDebug("QSctpSocket::writeDatagram(%p, %i, \"%s\", %i)", + datagram.d->data.constData(), + datagram.d->data.size(), + datagram.destinationAddress().toString().toLatin1().constData(), + datagram.destinationPort()); +#endif + + if (d->writeHeaders.size() != d->writeBuffers.size()) + d->writeHeaders.resize(d->writeBuffers.size()); + Q_ASSERT(d->currentWriteChannel < d->writeHeaders.size()); + d->writeHeaders[d->currentWriteChannel].push_back(datagram.d->header); + d->writeBuffer.setChunkSize(0); // set packet mode on channel buffer + d->writeBuffer.append(datagram.d->data); + + d->socketEngine->setWriteNotificationEnabled(true); + return true; +} + +QT_END_NAMESPACE diff --git a/src/network/socket/qsctpsocket.h b/src/network/socket/qsctpsocket.h new file mode 100644 index 00000000000..aaa4e1ecca7 --- /dev/null +++ b/src/network/socket/qsctpsocket.h @@ -0,0 +1,82 @@ +/**************************************************************************** +** +** Copyright (C) 2016 Alex Trotsenko +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtNetwork module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** 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 Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** 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-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QSCTPSOCKET_H +#define QSCTPSOCKET_H + +#include +#include + +QT_BEGIN_NAMESPACE + +#ifndef QT_NO_SCTP + +class QSctpSocketPrivate; + +class Q_NETWORK_EXPORT QSctpSocket : public QTcpSocket +{ + Q_OBJECT +public: + explicit QSctpSocket(QObject *parent = nullptr); + virtual ~QSctpSocket(); + + void close() Q_DECL_OVERRIDE; + void disconnectFromHost() Q_DECL_OVERRIDE; + + void setMaxChannelCount(int count); + int maxChannelCount() const; + bool inDatagramMode() const; + + QNetworkDatagram readDatagram(); + bool writeDatagram(const QNetworkDatagram &datagram); + +protected: + qint64 readData(char *data, qint64 maxlen) Q_DECL_OVERRIDE; + qint64 readLineData(char *data, qint64 maxlen) Q_DECL_OVERRIDE; + +private: + Q_DISABLE_COPY(QSctpSocket) + Q_DECLARE_PRIVATE(QSctpSocket) +}; + +#endif // QT_NO_SCTP + +QT_END_NAMESPACE + +#endif // QSCTPSOCKET_H diff --git a/src/network/socket/qsctpsocket_p.h b/src/network/socket/qsctpsocket_p.h new file mode 100644 index 00000000000..f38095330f6 --- /dev/null +++ b/src/network/socket/qsctpsocket_p.h @@ -0,0 +1,90 @@ +/**************************************************************************** +** +** Copyright (C) 2016 Alex Trotsenko +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtNetwork module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** 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 Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** 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-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QSCTPSOCKET_P_H +#define QSCTPSOCKET_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of the QLibrary class. This header file may change from +// version to version without notice, or even be removed. +// +// We mean it. +// + +#include +#include +#include +#include +#include + +#include + +QT_BEGIN_NAMESPACE + +#ifndef QT_NO_SCTP + +class QSctpSocketPrivate : public QTcpSocketPrivate +{ + Q_DECLARE_PUBLIC(QSctpSocket) +public: + QSctpSocketPrivate(); + virtual ~QSctpSocketPrivate(); + + bool canReadNotification() Q_DECL_OVERRIDE; + bool writeToSocket() Q_DECL_OVERRIDE; + + QByteArray incomingDatagram; + int maxChannelCount; + + typedef std::deque IpHeaderList; + QVector readHeaders; + QVector writeHeaders; + + void configureCreatedSocket() Q_DECL_OVERRIDE; +}; + +#endif // QT_NO_SCTP + +QT_END_NAMESPACE + +#endif // QSCTPSOCKET_P_H diff --git a/src/network/socket/qsocks5socketengine.cpp b/src/network/socket/qsocks5socketengine.cpp index ee3e0d9f0e0..518ec21f904 100644 --- a/src/network/socket/qsocks5socketengine.cpp +++ b/src/network/socket/qsocks5socketengine.cpp @@ -1605,8 +1605,31 @@ bool QSocks5SocketEngine::setMulticastInterface(const QNetworkInterface &) } #endif // QT_NO_NETWORKINTERFACE +bool QSocks5SocketEngine::hasPendingDatagrams() const +{ + Q_D(const QSocks5SocketEngine); + Q_INIT_CHECK(false); + + d->checkForDatagrams(); + + return !d->udpData->pendingDatagrams.isEmpty(); +} + +qint64 QSocks5SocketEngine::pendingDatagramSize() const +{ + Q_D(const QSocks5SocketEngine); + + d->checkForDatagrams(); + + if (!d->udpData->pendingDatagrams.isEmpty()) + return d->udpData->pendingDatagrams.head().data.size(); + return 0; +} +#endif // QT_NO_UDPSOCKET + qint64 QSocks5SocketEngine::readDatagram(char *data, qint64 maxlen, QIpPacketHeader *header, PacketHeaderOptions) { +#ifndef QT_NO_UDPSOCKET Q_D(QSocks5SocketEngine); d->checkForDatagrams(); @@ -1620,10 +1643,17 @@ qint64 QSocks5SocketEngine::readDatagram(char *data, qint64 maxlen, QIpPacketHea header->senderAddress = datagram.address; header->senderPort = datagram.port; return copyLen; +#else + Q_UNUSED(data) + Q_UNUSED(maxlen) + Q_UNUSED(header) + return -1; +#endif // QT_NO_UDPSOCKET } qint64 QSocks5SocketEngine::writeDatagram(const char *data, qint64 len, const QIpPacketHeader &header) { +#ifndef QT_NO_UDPSOCKET Q_D(QSocks5SocketEngine); // it is possible to send with out first binding with udp, but socks5 requires a bind. @@ -1660,29 +1690,13 @@ qint64 QSocks5SocketEngine::writeDatagram(const char *data, qint64 len, const QI } return len; -} - -bool QSocks5SocketEngine::hasPendingDatagrams() const -{ - Q_D(const QSocks5SocketEngine); - Q_INIT_CHECK(false); - - d->checkForDatagrams(); - - return !d->udpData->pendingDatagrams.isEmpty(); -} - -qint64 QSocks5SocketEngine::pendingDatagramSize() const -{ - Q_D(const QSocks5SocketEngine); - - d->checkForDatagrams(); - - if (!d->udpData->pendingDatagrams.isEmpty()) - return d->udpData->pendingDatagrams.head().data.size(); - return 0; -} +#else + Q_UNUSED(data) + Q_UNUSED(len) + Q_UNUSED(header) + return -1; #endif // QT_NO_UDPSOCKET +} qint64 QSocks5SocketEngine::bytesToWrite() const { diff --git a/src/network/socket/qsocks5socketengine_p.h b/src/network/socket/qsocks5socketengine_p.h index bff7e9ecaca..864b1634892 100644 --- a/src/network/socket/qsocks5socketengine_p.h +++ b/src/network/socket/qsocks5socketengine_p.h @@ -100,13 +100,13 @@ public: bool setMulticastInterface(const QNetworkInterface &iface) Q_DECL_OVERRIDE; #endif // QT_NO_NETWORKINTERFACE - qint64 readDatagram(char *data, qint64 maxlen, QIpPacketHeader * = 0, - PacketHeaderOptions = WantNone) Q_DECL_OVERRIDE; - qint64 writeDatagram(const char *data, qint64 len, const QIpPacketHeader &) Q_DECL_OVERRIDE; bool hasPendingDatagrams() const Q_DECL_OVERRIDE; qint64 pendingDatagramSize() const Q_DECL_OVERRIDE; #endif // QT_NO_UDPSOCKET + qint64 readDatagram(char *data, qint64 maxlen, QIpPacketHeader * = 0, + PacketHeaderOptions = WantNone) Q_DECL_OVERRIDE; + qint64 writeDatagram(const char *data, qint64 len, const QIpPacketHeader &) Q_DECL_OVERRIDE; qint64 bytesToWrite() const Q_DECL_OVERRIDE; int option(SocketOption option) const Q_DECL_OVERRIDE; diff --git a/src/network/socket/qtcpserver.cpp b/src/network/socket/qtcpserver.cpp index d9ffdbd2148..eddf7899215 100644 --- a/src/network/socket/qtcpserver.cpp +++ b/src/network/socket/qtcpserver.cpp @@ -119,6 +119,7 @@ QT_BEGIN_NAMESPACE */ QTcpServerPrivate::QTcpServerPrivate() : port(0) + , socketType(QAbstractSocket::UnknownSocketType) , state(QAbstractSocket::UnconnectedState) , socketEngine(0) , serverSocketError(QAbstractSocket::UnknownSocketError) @@ -148,13 +149,21 @@ QNetworkProxy QTcpServerPrivate::resolveProxy(const QHostAddress &address, quint proxies << proxy; } else { // try the application settings instead - QNetworkProxyQuery query(port, QString(), QNetworkProxyQuery::TcpServer); + QNetworkProxyQuery query(port, QString(), + socketType == QAbstractSocket::SctpSocket ? + QNetworkProxyQuery::SctpServer : + QNetworkProxyQuery::TcpServer); proxies = QNetworkProxyFactory::proxyForQuery(query); } // return the first that we can use for (const QNetworkProxy &p : qAsConst(proxies)) { - if (p.capabilities() & QNetworkProxy::ListeningCapability) + if (socketType == QAbstractSocket::TcpSocket && + (p.capabilities() & QNetworkProxy::ListeningCapability) != 0) + return p; + + if (socketType == QAbstractSocket::SctpSocket && + (p.capabilities() & QNetworkProxy::SctpListeningCapability) != 0) return p; } @@ -228,9 +237,11 @@ void QTcpServerPrivate::readNotification() QTcpServer::QTcpServer(QObject *parent) : QObject(*new QTcpServerPrivate, parent) { + Q_D(QTcpServer); #if defined(QTCPSERVER_DEBUG) qDebug("QTcpServer::QTcpServer(%p)", parent); #endif + d->socketType = QAbstractSocket::TcpSocket; } /*! @@ -251,13 +262,22 @@ QTcpServer::~QTcpServer() } /*! \internal + + Constructs a new server object with socket of type \a socketType. The \a + parent argument is passed to QObject's constructor. */ -QTcpServer::QTcpServer(QTcpServerPrivate &dd, QObject *parent) - : QObject(dd, parent) +QTcpServer::QTcpServer(QAbstractSocket::SocketType socketType, QTcpServerPrivate &dd, + QObject *parent) : QObject(dd, parent) { + Q_D(QTcpServer); #if defined(QTCPSERVER_DEBUG) - qDebug("QTcpServer::QTcpServer(QTcpServerPrivate == %p, parent == %p)", &dd, parent); + qDebug("QTcpServer::QTcpServer(%sSocket, QTcpServerPrivate == %p, parent == %p)", + socketType == QAbstractSocket::TcpSocket ? "Tcp" + : socketType == QAbstractSocket::UdpSocket ? "Udp" + : socketType == QAbstractSocket::SctpSocket ? "Sctp" + : "Unknown", &dd, parent); #endif + d->socketType = socketType; } /*! @@ -288,7 +308,7 @@ bool QTcpServer::listen(const QHostAddress &address, quint16 port) #endif delete d->socketEngine; - d->socketEngine = QAbstractSocketEngine::createSocketEngine(QAbstractSocket::TcpSocket, proxy, this); + d->socketEngine = QAbstractSocketEngine::createSocketEngine(d->socketType, proxy, this); if (!d->socketEngine) { d->serverSocketError = QAbstractSocket::UnsupportedSocketOperationError; d->serverSocketErrorString = tr("Operation on socket is not supported"); @@ -298,7 +318,7 @@ bool QTcpServer::listen(const QHostAddress &address, quint16 port) //copy network session down to the socket engine (if it has been set) d->socketEngine->setProperty("_q_networksession", property("_q_networksession")); #endif - if (!d->socketEngine->initialize(QAbstractSocket::TcpSocket, proto)) { + if (!d->socketEngine->initialize(d->socketType, proto)) { d->serverSocketError = d->socketEngine->error(); d->serverSocketErrorString = d->socketEngine->errorString(); return false; diff --git a/src/network/socket/qtcpserver.h b/src/network/socket/qtcpserver.h index 34cf9ea9d16..192cbce54c8 100644 --- a/src/network/socket/qtcpserver.h +++ b/src/network/socket/qtcpserver.h @@ -94,7 +94,8 @@ protected: virtual void incomingConnection(qintptr handle); void addPendingConnection(QTcpSocket* socket); - QTcpServer(QTcpServerPrivate &dd, QObject *parent = Q_NULLPTR); + QTcpServer(QAbstractSocket::SocketType socketType, QTcpServerPrivate &dd, + QObject *parent = Q_NULLPTR); Q_SIGNALS: void newConnection(); diff --git a/src/network/socket/qtcpserver_p.h b/src/network/socket/qtcpserver_p.h index 47f45a8404c..b11dd93718b 100644 --- a/src/network/socket/qtcpserver_p.h +++ b/src/network/socket/qtcpserver_p.h @@ -1,6 +1,7 @@ /**************************************************************************** ** ** Copyright (C) 2016 The Qt Company Ltd. +** Copyright (C) 2016 Alex Trotsenko ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the QtNetwork module of the Qt Toolkit. @@ -74,6 +75,7 @@ public: quint16 port; QHostAddress address; + QAbstractSocket::SocketType socketType; QAbstractSocket::SocketState state; QAbstractSocketEngine *socketEngine; diff --git a/src/network/socket/qudpsocket.cpp b/src/network/socket/qudpsocket.cpp index d78379fb39d..37b385dfb54 100644 --- a/src/network/socket/qudpsocket.cpp +++ b/src/network/socket/qudpsocket.cpp @@ -347,6 +347,12 @@ qint64 QUdpSocket::writeDatagram(const char *data, qint64 size, const QHostAddre if (sent >= 0) { emit bytesWritten(sent); } else { + if (sent == -2) { + // Socket engine reports EAGAIN. Treat as a temporary error. + d->setErrorAndEmit(QAbstractSocket::TemporaryError, + tr("Unable to send a datagram")); + return -1; + } d->setErrorAndEmit(d->socketEngine->error(), d->socketEngine->errorString()); } return sent; @@ -495,8 +501,15 @@ qint64 QUdpSocket::readDatagram(char *data, qint64 maxSize, QHostAddress *addres d->hasPendingData = false; d->socketEngine->setReadNotificationEnabled(true); - if (readBytes < 0) + if (readBytes < 0) { + if (readBytes == -2) { + // No pending datagram. Treat as a temporary error. + d->setErrorAndEmit(QAbstractSocket::TemporaryError, + tr("No datagram available for reading")); + return -1; + } d->setErrorAndEmit(d->socketEngine->error(), d->socketEngine->errorString()); + } return readBytes; } diff --git a/src/network/socket/socket.pri b/src/network/socket/socket.pri index 2e3e26d37db..b1c0b6bd6e4 100644 --- a/src/network/socket/socket.pri +++ b/src/network/socket/socket.pri @@ -25,6 +25,18 @@ SOURCES += socket/qabstractsocketengine.cpp \ socket/qlocalsocket.cpp \ socket/qlocalserver.cpp +# SCTP support. + +contains(QT_CONFIG, sctp) { + HEADERS += socket/qsctpserver.h \ + socket/qsctpserver_p.h \ + socket/qsctpsocket.h \ + socket/qsctpsocket_p.h + + SOURCES += socket/qsctpserver.cpp \ + socket/qsctpsocket.cpp +} + !winrt { SOURCES += socket/qnativesocketengine.cpp HEADERS += socket/qnativesocketengine_p.h diff --git a/tests/auto/network/socket/platformsocketengine/tst_platformsocketengine.cpp b/tests/auto/network/socket/platformsocketengine/tst_platformsocketengine.cpp index d159d6d6832..bc9d3cc9bf4 100644 --- a/tests/auto/network/socket/platformsocketengine/tst_platformsocketengine.cpp +++ b/tests/auto/network/socket/platformsocketengine/tst_platformsocketengine.cpp @@ -603,8 +603,8 @@ void tst_PlatformSocketEngine::invalidSend() PLATFORMSOCKETENGINE socket; QVERIFY(socket.initialize(QAbstractSocket::TcpSocket)); - QTest::ignoreMessage(QtWarningMsg, PLATFORMSOCKETENGINESTRING "::writeDatagram() was" - " called by a socket other than QAbstractSocket::UdpSocket"); + QTest::ignoreMessage(QtWarningMsg, PLATFORMSOCKETENGINESTRING "::writeDatagram() was called" + " not in QAbstractSocket::BoundState or QAbstractSocket::ConnectedState"); QCOMPARE(socket.writeDatagram("hei", 3, QIpPacketHeader(QHostAddress::LocalHost, 143)), (qlonglong) -1); } diff --git a/tests/auto/network/socket/qsctpsocket/qsctpsocket.pro b/tests/auto/network/socket/qsctpsocket/qsctpsocket.pro new file mode 100644 index 00000000000..49a40ce9b54 --- /dev/null +++ b/tests/auto/network/socket/qsctpsocket/qsctpsocket.pro @@ -0,0 +1,6 @@ +CONFIG += testcase +TARGET = tst_qsctpsocket +QT = core network testlib + +SOURCES += tst_qsctpsocket.cpp + diff --git a/tests/auto/network/socket/qsctpsocket/tst_qsctpsocket.cpp b/tests/auto/network/socket/qsctpsocket/tst_qsctpsocket.cpp new file mode 100644 index 00000000000..70297f8cc41 --- /dev/null +++ b/tests/auto/network/socket/qsctpsocket/tst_qsctpsocket.cpp @@ -0,0 +1,489 @@ +/**************************************************************************** +** +** Copyright (C) 2016 Alex Trotsenko +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL21$ +** 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 http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 or version 3 as published by the Free +** Software Foundation and appearing in the file LICENSE.LGPLv21 and +** LICENSE.LGPLv3 included in the packaging of this file. Please review the +** following information to ensure the GNU Lesser General Public License +** requirements will be met: https://www.gnu.org/licenses/lgpl.html and +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** As a special exception, The Qt Company gives you certain additional +** rights. These rights are described in The Qt Company LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include + +#define SOCKET int +#define INVALID_SOCKET -1 + +class tst_QSctpSocket : public QObject +{ + Q_OBJECT + +public: + static void enterLoop(int secs) + { + ++loopLevel; + QTestEventLoop::instance().enterLoop(secs); + --loopLevel; + } + static void exitLoop() + { + // Safe exit - if we aren't in an event loop, don't + // exit one. + if (loopLevel > 0) + QTestEventLoop::instance().exitLoop(); + } + static bool timeout() + { + return QTestEventLoop::instance().timeout(); + } + +private slots: + void constructing(); + void bind_data(); + void bind(); + void setInvalidSocketDescriptor(); + void setSocketDescriptor(); + void socketDescriptor(); + void hostNotFound(); + void connecting(); + void readAndWrite(); + void loop_data(); + void loop(); + void loopInTCPMode_data(); + void loopInTCPMode(); + void readDatagramAfterClose(); + void clientSendDataOnDelayedDisconnect(); + +protected slots: + void exitLoopSlot(); + +private: + static int loopLevel; +}; + +int tst_QSctpSocket::loopLevel = 0; + +//---------------------------------------------------------------------------------- +void tst_QSctpSocket::constructing() +{ + QSctpSocket socket; + + // Check the initial state of the QSctpSocket. + QCOMPARE(socket.state(), QAbstractSocket::UnconnectedState); + QVERIFY(socket.isSequential()); + QVERIFY(!socket.isOpen()); + QVERIFY(!socket.isValid()); + QCOMPARE(socket.socketType(), QAbstractSocket::SctpSocket); + QCOMPARE(socket.maxChannelCount(), 0); + QCOMPARE(socket.readChannelCount(), 0); + QCOMPARE(socket.writeChannelCount(), 0); + + char c; + QCOMPARE(socket.getChar(&c), false); + QCOMPARE((int) socket.bytesAvailable(), 0); + QCOMPARE(socket.canReadLine(), false); + QCOMPARE(socket.readLine(), QByteArray()); + QCOMPARE(socket.socketDescriptor(), (qintptr)-1); + QCOMPARE((int) socket.localPort(), 0); + QVERIFY(socket.localAddress() == QHostAddress()); + QCOMPARE((int) socket.peerPort(), 0); + QVERIFY(socket.peerAddress() == QHostAddress()); + QCOMPARE(socket.error(), QAbstractSocket::UnknownSocketError); + QCOMPARE(socket.errorString(), QString("Unknown error")); +} + +//---------------------------------------------------------------------------------- +void tst_QSctpSocket::bind_data() +{ + QTest::addColumn("stringAddr"); + QTest::addColumn("successExpected"); + QTest::addColumn("stringExpectedLocalAddress"); + + // iterate all interfaces, add all addresses on them as test data + QList 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")) + || entry.ip().isInSubnet(QHostAddress::parseSubnet("169.254/16"))) + 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 guaranteed 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(); + } +} + +// Testing bind function +void tst_QSctpSocket::bind() +{ + QFETCH(QString, stringAddr); + QFETCH(bool, successExpected); + QFETCH(QString, stringExpectedLocalAddress); + + QHostAddress addr(stringAddr); + QHostAddress expectedLocalAddress(stringExpectedLocalAddress); + + QSctpSocket socket; + qDebug() << "Binding " << addr; + + if (successExpected) { + QVERIFY2(socket.bind(addr), qPrintable(socket.errorString())); + } else { + QVERIFY(!socket.bind(addr)); + } + + QCOMPARE(socket.localAddress(), expectedLocalAddress); +} + +//---------------------------------------------------------------------------------- +void tst_QSctpSocket::setInvalidSocketDescriptor() +{ + QSctpSocket socket; + QCOMPARE(socket.socketDescriptor(), (qintptr)INVALID_SOCKET); + QVERIFY(!socket.setSocketDescriptor(-5, QAbstractSocket::UnconnectedState)); + QCOMPARE(socket.socketDescriptor(), (qintptr)INVALID_SOCKET); + + QCOMPARE(socket.error(), QAbstractSocket::UnsupportedSocketOperationError); +} + +//---------------------------------------------------------------------------------- +void tst_QSctpSocket::setSocketDescriptor() +{ + QSctpServer server; + + server.setMaxChannelCount(16); + QVERIFY(server.listen()); + + SOCKET sock = ::socket(AF_INET, SOCK_STREAM, IPPROTO_SCTP); + + QVERIFY(sock != INVALID_SOCKET); + QSctpSocket socket; + QVERIFY(socket.setSocketDescriptor(sock, QAbstractSocket::UnconnectedState)); + QCOMPARE(socket.socketDescriptor(), (qintptr)sock); + QCOMPARE(socket.readChannelCount(), 0); + QCOMPARE(socket.writeChannelCount(), 0); + + socket.connectToHost(QHostAddress::LocalHost, server.serverPort()); + QVERIFY(socket.waitForConnected(3000)); + QVERIFY(server.waitForNewConnection(3000)); + + QCOMPARE(socket.readChannelCount(), server.maxChannelCount()); + QVERIFY(socket.writeChannelCount() <= server.maxChannelCount()); + + QSctpSocket *acceptedSocket = server.nextPendingDatagramConnection(); + QVERIFY(acceptedSocket); + QCOMPARE(acceptedSocket->readChannelCount(), socket.writeChannelCount()); + QCOMPARE(acceptedSocket->writeChannelCount(), socket.readChannelCount()); +} + +//---------------------------------------------------------------------------------- +void tst_QSctpSocket::socketDescriptor() +{ + QSctpSocket socket; + + QSctpServer server; + + QVERIFY(server.listen()); + + QCOMPARE(socket.socketDescriptor(), (qintptr)INVALID_SOCKET); + socket.connectToHost(QHostAddress::LocalHost, server.serverPort()); + QVERIFY(server.waitForNewConnection(3000)); + if (socket.state() != QAbstractSocket::ConnectedState) { + QVERIFY((socket.state() == QAbstractSocket::HostLookupState + && socket.socketDescriptor() == INVALID_SOCKET) + || socket.state() == QAbstractSocket::ConnectingState); + QVERIFY(socket.waitForConnected(3000)); + QCOMPARE(socket.state(), QAbstractSocket::ConnectedState); + } + QVERIFY(socket.socketDescriptor() != INVALID_SOCKET); +} + +//---------------------------------------------------------------------------------- +void tst_QSctpSocket::hostNotFound() +{ + QSctpSocket socket; + + socket.connectToHost("nosuchserver.qt-project.org", 80); + QVERIFY(!socket.waitForConnected(3000)); + QCOMPARE(socket.state(), QTcpSocket::UnconnectedState); + QCOMPARE(socket.error(), QAbstractSocket::HostNotFoundError); +} + +// Testing connect function +void tst_QSctpSocket::connecting() +{ + QSctpServer server; + + QVERIFY(server.listen()); + + QSctpSocket socket; + socket.connectToHost(QHostAddress::LocalHost, server.serverPort()); + QVERIFY(socket.waitForConnected(3000)); + + QVERIFY(server.waitForNewConnection(3000)); + QSctpSocket *acceptedSocket = server.nextPendingDatagramConnection(); + QVERIFY(acceptedSocket); + + QCOMPARE(socket.state(), QAbstractSocket::ConnectedState); + QCOMPARE(acceptedSocket->state(), QAbstractSocket::ConnectedState); + QCOMPARE(socket.readChannelCount(), acceptedSocket->readChannelCount()); + QCOMPARE(socket.writeChannelCount(),acceptedSocket->writeChannelCount()); +} + +// Testing read/write functions +void tst_QSctpSocket::readAndWrite() +{ + QSctpServer server; + + QVERIFY(server.listen()); + + QSctpSocket socket; + socket.connectToHost(QHostAddress::LocalHost, server.serverPort()); + QVERIFY(socket.waitForConnected(3000)); + + QVERIFY(server.waitForNewConnection(3000)); + QSctpSocket *acceptedSocket = server.nextPendingDatagramConnection(); + QVERIFY(acceptedSocket); + + QByteArray ba(1000, 1); + QVERIFY(acceptedSocket->writeDatagram(ba)); + QVERIFY(acceptedSocket->waitForBytesWritten(3000)); + + QVERIFY(socket.waitForReadyRead(3000)); + QNetworkDatagram datagram = socket.readDatagram(); + QVERIFY(datagram.isValid()); + QCOMPARE(datagram.data(), ba); + + QCOMPARE(socket.state(), QAbstractSocket::ConnectedState); + QCOMPARE(socket.error(), QAbstractSocket::UnknownSocketError); + QCOMPARE(socket.errorString(), QString("Unknown error")); + QCOMPARE(acceptedSocket->state(), QAbstractSocket::ConnectedState); + QCOMPARE(acceptedSocket->error(), QAbstractSocket::UnknownSocketError); + QCOMPARE(acceptedSocket->errorString(), QString("Unknown error")); +} + +//---------------------------------------------------------------------------------- +void tst_QSctpSocket::loop_data() +{ + QTest::addColumn("peterDatagram"); + QTest::addColumn("paulDatagram"); + QTest::addColumn("peterChannel"); + QTest::addColumn("paulChannel"); + + QTest::newRow("\"Almond!\" | \"Joy!\"") << QByteArray("Almond!") << QByteArray("Joy!") << 0 << 0; + QTest::newRow("\"A\" | \"B\"") << QByteArray("A") << QByteArray("B") << 1 << 1; + QTest::newRow("\"AB\" | \"B\"") << QByteArray("AB") << QByteArray("B") << 0 << 1; + QTest::newRow("\"AB\" | \"BB\"") << QByteArray("AB") << QByteArray("BB") << 1 << 0; + QTest::newRow("\"A\\0B\" | \"B\\0B\"") << QByteArray::fromRawData("A\0B", 3) << QByteArray::fromRawData("B\0B", 3) << 0 << 1; + QTest::newRow("BigDatagram") << QByteArray(600, '@') << QByteArray(600, '@') << 1 << 0; +} + +void tst_QSctpSocket::loop() +{ + QFETCH(QByteArray, peterDatagram); + QFETCH(QByteArray, paulDatagram); + QFETCH(int, peterChannel); + QFETCH(int, paulChannel); + + QSctpServer server; + + server.setMaxChannelCount(10); + QVERIFY(server.listen()); + + QSctpSocket peter; + peter.setMaxChannelCount(10); + peter.connectToHost(QHostAddress::LocalHost, server.serverPort()); + QVERIFY(peter.waitForConnected(3000)); + + QVERIFY(server.waitForNewConnection(3000)); + QSctpSocket *paul = server.nextPendingDatagramConnection(); + QVERIFY(paul); + + peter.setCurrentWriteChannel(peterChannel); + QVERIFY(peter.writeDatagram(peterDatagram)); + paul->setCurrentWriteChannel(paulChannel); + QVERIFY(paul->writeDatagram(paulDatagram)); + QVERIFY(peter.flush()); + QVERIFY(paul->flush()); + + peter.setCurrentReadChannel(paulChannel); + QVERIFY(peter.waitForReadyRead(3000)); + QCOMPARE(peter.bytesAvailable(), paulDatagram.size()); + QCOMPARE(peter.readDatagram().data(), paulDatagram); + + paul->setCurrentReadChannel(peterChannel); + QVERIFY(paul->waitForReadyRead(3000)); + QCOMPARE(paul->bytesAvailable(), peterDatagram.size()); + QCOMPARE(paul->readDatagram().data(), peterDatagram); +} + +//---------------------------------------------------------------------------------- +void tst_QSctpSocket::loopInTCPMode_data() +{ + QTest::addColumn("peterDatagram"); + QTest::addColumn("paulDatagram"); + + QTest::newRow("\"Almond!\" | \"Joy!\"") << QByteArray("Almond!") << QByteArray("Joy!"); + QTest::newRow("\"A\" | \"B\"") << QByteArray("A") << QByteArray("B"); + QTest::newRow("\"AB\" | \"B\"") << QByteArray("AB") << QByteArray("B"); + QTest::newRow("\"AB\" | \"BB\"") << QByteArray("AB") << QByteArray("BB"); + QTest::newRow("\"A\\0B\" | \"B\\0B\"") << QByteArray::fromRawData("A\0B", 3) << QByteArray::fromRawData("B\0B", 3); + QTest::newRow("BigDatagram") << QByteArray(600, '@') << QByteArray(600, '@'); +} + +void tst_QSctpSocket::loopInTCPMode() +{ + QFETCH(QByteArray, peterDatagram); + QFETCH(QByteArray, paulDatagram); + + QSctpServer server; + + server.setMaxChannelCount(-1); + QVERIFY(server.listen()); + + QSctpSocket peter; + peter.setMaxChannelCount(-1); + peter.connectToHost(QHostAddress::LocalHost, server.serverPort()); + QVERIFY(peter.waitForConnected(3000)); + QVERIFY(server.waitForNewConnection(3000)); + + QTcpSocket *paul = server.nextPendingConnection(); + QVERIFY(paul); + + QCOMPARE(peter.write(peterDatagram), qint64(peterDatagram.size())); + QCOMPARE(paul->write(paulDatagram), qint64(paulDatagram.size())); + QVERIFY(peter.flush()); + QVERIFY(paul->flush()); + + QVERIFY(peter.waitForReadyRead(3000)); + QVERIFY(paul->waitForReadyRead(3000)); + + QCOMPARE(peter.bytesAvailable(), paulDatagram.size()); + QByteArray peterBuffer = peter.readAll(); + + QCOMPARE(paul->bytesAvailable(), peterDatagram.size()); + QByteArray paulBuffer = paul->readAll(); + + QCOMPARE(peterBuffer, paulDatagram); + QCOMPARE(paulBuffer, peterDatagram); +} + +//---------------------------------------------------------------------------------- +void tst_QSctpSocket::exitLoopSlot() +{ + exitLoop(); +} + +//---------------------------------------------------------------------------------- +void tst_QSctpSocket::readDatagramAfterClose() +{ + QSctpServer server; + + QVERIFY(server.listen()); + + QSctpSocket socket; + socket.connectToHost(QHostAddress::LocalHost, server.serverPort()); + QVERIFY(socket.waitForConnected(3000)); + QVERIFY(server.waitForNewConnection(3000)); + + QSctpSocket *acceptedSocket = server.nextPendingDatagramConnection(); + QVERIFY(acceptedSocket); + + connect(&socket, &QIODevice::readyRead, this, &tst_QSctpSocket::exitLoopSlot); + + QByteArray ba(1000, 1); + QVERIFY(acceptedSocket->writeDatagram(ba)); + + enterLoop(10); + if (timeout()) + QFAIL("Network operation timed out"); + + QCOMPARE(socket.bytesAvailable(), ba.size()); + socket.close(); + QVERIFY(!socket.readDatagram().isValid()); +} + +// Test buffered socket properly send data on delayed disconnect +void tst_QSctpSocket::clientSendDataOnDelayedDisconnect() +{ + QSctpServer server; + + QVERIFY(server.listen()); + + // Connect to server, write data and close socket + QSctpSocket socket; + socket.connectToHost(QHostAddress::LocalHost, server.serverPort()); + QVERIFY(socket.waitForConnected(3000)); + + QByteArray sendData("GET /\r\n"); + sendData = sendData.repeated(1000); + QVERIFY(socket.writeDatagram(sendData)); + socket.close(); + QCOMPARE(socket.state(), QAbstractSocket::ClosingState); + QVERIFY(socket.waitForDisconnected(3000)); + + QVERIFY(server.waitForNewConnection(3000)); + QSctpSocket *acceptedSocket = server.nextPendingDatagramConnection(); + QVERIFY(acceptedSocket); + + QVERIFY(acceptedSocket->waitForReadyRead(3000)); + QNetworkDatagram datagram = acceptedSocket->readDatagram(); + QVERIFY(datagram.isValid()); + QCOMPARE(datagram.data(), sendData); +} + +QTEST_MAIN(tst_QSctpSocket) + +#include "tst_qsctpsocket.moc" diff --git a/tests/auto/network/socket/socket.pro b/tests/auto/network/socket/socket.pro index 436ebe5c7fb..75f9e3f3a0a 100644 --- a/tests/auto/network/socket/socket.pro +++ b/tests/auto/network/socket/socket.pro @@ -8,6 +8,7 @@ SUBDIRS=\ qsocks5socketengine \ qabstractsocket \ platformsocketengine \ + qsctpsocket \ !contains(QT_CONFIG, private_tests): SUBDIRS -= \ platformsocketengine \ @@ -15,6 +16,9 @@ SUBDIRS=\ qhttpsocketengine \ qsocks5socketengine \ +!contains(QT_CONFIG, sctp): SUBDIRS -= \ + qsctpsocket \ + winrt: SUBDIRS -= \ qhttpsocketengine \ qsocks5socketengine \ diff --git a/tools/configure/configureapp.cpp b/tools/configure/configureapp.cpp index 312316aba30..fa282ea6e69 100644 --- a/tools/configure/configureapp.cpp +++ b/tools/configure/configureapp.cpp @@ -180,6 +180,7 @@ Configure::Configure(int& argc, char** argv) : verbose(0) dictionary[ "PPS" ] = "no"; dictionary[ "LGMON" ] = "no"; dictionary[ "SYSTEM_PROXIES" ] = "yes"; + dictionary[ "SCTP" ] = "no"; dictionary[ "WERROR" ] = "auto"; dictionary[ "QREAL" ] = "double"; dictionary[ "ATOMIC64" ] = "auto"; @@ -829,6 +830,10 @@ void Configure::parseCmdLine() dictionary[ "SYSTEM_PROXIES" ] = "no"; } else if (configCmdLine.at(i) == "-system-proxies") { dictionary[ "SYSTEM_PROXIES" ] = "yes"; + } else if (configCmdLine.at(i) == "-no-sctp") { + dictionary[ "SCTP" ] = "no"; + } else if (configCmdLine.at(i) == "-sctp") { + dictionary[ "SCTP" ] = "yes"; } else if (configCmdLine.at(i) == "-warnings-are-errors" || configCmdLine.at(i) == "-Werror") { dictionary[ "WERROR" ] = "yes"; @@ -1698,6 +1703,9 @@ bool Configure::displayHelp() desc("SYSTEM_PROXIES", "yes", "-system-proxies", "Use system network proxies by default."); desc("SYSTEM_PROXIES", "no", "-no-system-proxies", "Do not use system network proxies by default.\n"); + desc("SCTP", "yes", "-sctp", "Compile SCTP support."); + desc("SCTP", "no", "-no-sctp", "Do not compile SCTP network protocol support.\n"); + desc("WERROR", "yes", "-warnings-are-errors", "Make warnings be treated as errors."); desc("WERROR", "no", "-no-warnings-are-errors","Make warnings be treated normally."); @@ -2103,6 +2111,8 @@ bool Configure::checkAvailability(const QString &part) available = (platform() == QNX) && tryCompileProject("unix/pps"); } else if (part == "LGMON") { available = (platform() == QNX) && tryCompileProject("unix/lgmon"); + } else if (part == "SCTP") { + available = tryCompileProject("unix/sctp"); } else if (part == "NEON") { available = dictionary["QT_CPU_FEATURES"].contains("neon"); } else if (part == "FONT_CONFIG") { @@ -2301,6 +2311,10 @@ void Configure::autoDetection() dictionary["LGMON"] = checkAvailability("LGMON") ? "yes" : "no"; } + if (dictionary["SCTP"] == "auto") { + dictionary["SCTP"] = checkAvailability("SCTP") ? "yes" : "no"; + } + if (dictionary["QT_EVENTFD"] == "auto") dictionary["QT_EVENTFD"] = checkAvailability("QT_EVENTFD") ? "yes" : "no"; @@ -2744,6 +2758,9 @@ void Configure::generateOutputVars() if (dictionary[ "SYSTEM_PROXIES" ] == "yes") qtConfig += "system-proxies"; + if (dictionary[ "SCTP" ] == "yes") + qtConfig += "sctp"; + if (dictionary.contains("XQMAKESPEC") && (dictionary["QMAKESPEC"] != dictionary["XQMAKESPEC"])) { qmakeConfig += "cross_compile"; dictionary["CROSS_COMPILE"] = "yes"; @@ -3387,6 +3404,8 @@ void Configure::generateConfigfiles() else qconfigList += "QT_NO_NIS"; + if (dictionary["SCTP"] == "no") qconfigList += "QT_NO_SCTP"; + if (dictionary["LARGE_FILE"] == "yes") qconfigList += "QT_LARGEFILE_SUPPORT=64"; if (dictionary["QT_CUPS"] == "no") qconfigList += "QT_NO_CUPS"; if (dictionary["QT_ICONV"] == "no") qconfigList += "QT_NO_ICONV"; @@ -3520,6 +3539,7 @@ void Configure::displayConfig() sout << "DirectWrite support........." << dictionary[ "DIRECTWRITE" ] << endl; sout << "DirectWrite 2 support......." << dictionary[ "DIRECTWRITE2" ] << endl; sout << "Use system proxies.........." << dictionary[ "SYSTEM_PROXIES" ] << endl; + sout << "SCTP support................" << dictionary[ "SCTP" ] << endl; sout << endl; sout << "QPA Backends:" << endl;