From 75a9bd2a4f637fb8e8e3dc4609a7045547119e80 Mon Sep 17 00:00:00 2001 From: Alex Trotsenko Date: Wed, 24 Sep 2014 17:31:33 +0300 Subject: 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 + .../network/multistreamclient/chatconsumer.cpp | 83 ++++ examples/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 ++ .../network/multistreamclient/movieconsumer.cpp | 73 +++ examples/network/multistreamclient/movieconsumer.h | 64 +++ .../multistreamclient/multistreamclient.pro | 16 + .../network/multistreamclient/timeconsumer.cpp | 84 ++++ examples/network/multistreamclient/timeconsumer.h | 69 +++ examples/network/multistreamserver/animation.gif | Bin 0 -> 42629 bytes .../network/multistreamserver/chatprovider.cpp | 67 +++ examples/network/multistreamserver/chatprovider.h | 57 +++ examples/network/multistreamserver/main.cpp | 51 ++ .../network/multistreamserver/movieprovider.cpp | 63 +++ examples/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 ++++ .../network/multistreamserver/timeprovider.cpp | 67 +++ examples/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 | 22 +- src/network/socket/qhttpsocketengine_p.h | 6 +- src/network/socket/qnativesocketengine.cpp | 40 +- src/network/socket/qnativesocketengine_p.h | 6 +- src/network/socket/qnativesocketengine_unix.cpp | 182 ++++++- src/network/socket/qnativesocketengine_win.cpp | 11 + src/network/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 +- .../network/socket/qsctpsocket/qsctpsocket.pro | 6 + .../network/socket/qsctpsocket/tst_qsctpsocket.cpp | 489 +++++++++++++++++++ tests/auto/network/socket/socket.pro | 4 + tools/configure/configureapp.cpp | 20 + 66 files changed, 3873 insertions(+), 119 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 0000000000..40d7bb1bcb --- /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 0000000000..edcc0a444a --- /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 b13dbe3c27..2ea2a812e4 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 a062477f53..3013a84ccf 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 0000000000..65840accae --- /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 0000000000..cac846757f --- /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 0000000000..fbe138162e --- /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 0000000000..6ec804e9be --- /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 0000000000..0c7c1bbfb6 --- /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 0000000000..c9e0c38d9a --- /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 0000000000..68c7b4a229 --- /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 0000000000..0f4fd7af42 --- /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 0000000000..ed887ab53e --- /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 0000000000..3ad73a8712 --- /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 0000000000..d382e99d86 --- /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 0000000000..f674369efc Binary files /dev/null and b/examples/network/multistreamserver/animation.gif differ diff --git a/examples/network/multistreamserver/chatprovider.cpp b/examples/network/multistreamserver/chatprovider.cpp new file mode 100644 index 0000000000..9a88d491ab --- /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 0000000000..912101ec34 --- /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 0000000000..04350cd13d --- /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 0000000000..cc534e6f40 --- /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 0000000000..d6d8fb7449 --- /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 0000000000..75a7e6bbec --- /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 0000000000..c6ec612275 --- /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 0000000000..1fb18e80b9 --- /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 0000000000..6d01f7a238 --- /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 0000000000..043810cdf3 --- /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 0000000000..b57c066c24 --- /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 86bb08d22a..ff7fbcf4f2 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 94fb68450c..fbee1a223f 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 eed98a8fe3..76bec89ef2 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 0000000000..3783a6f939 --- /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 6cbea13afc..a20d69185a 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 e28be09508..e55651a78b 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 ad78c48fd8..fc3185bd5e 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 c9f4372596..fc919a24a6 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 2727bd9257..c022c718cf 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 256d718df6..75105bd681 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 cbae297278..0615cd1cb3 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 2c32fa046f..73a8f11537 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 41a8cf1c6b..1578d7bb35 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 4b835ce6ef..0cb519ce90 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 f9ff958525..899c02fba6 100644 --- a/src/network/socket/qhttpsocketengine.cpp +++ b/src/network/socket/qhttpsocketengine.cpp @@ -289,34 +289,34 @@ bool QHttpSocketEngine::setMulticastInterface(const QNetworkInterface &) } #endif // QT_NO_NETWORKINTERFACE -qint64 QHttpSocketEngine::readDatagram(char *, qint64, QIpPacketHeader *, PacketHeaderOptions) +bool QHttpSocketEngine::hasPendingDatagrams() const { qWarning("Operation is not supported"); - setError(QAbstractSocket::UnsupportedSocketOperationError, - QLatin1String("Unsupported socket operation")); - return -1; + return false; } -qint64 QHttpSocketEngine::writeDatagram(const char *, qint64, const QIpPacketHeader &) +qint64 QHttpSocketEngine::pendingDatagramSize() const { qWarning("Operation is not supported"); - setError(QAbstractSocket::UnsupportedSocketOperationError, - QLatin1String("Unsupported socket operation")); return -1; } +#endif // QT_NO_UDPSOCKET -bool QHttpSocketEngine::hasPendingDatagrams() const +qint64 QHttpSocketEngine::readDatagram(char *, qint64, QIpPacketHeader *, PacketHeaderOptions) { qWarning("Operation is not supported"); - return false; + setError(QAbstractSocket::UnsupportedSocketOperationError, + QLatin1String("Unsupported socket operation")); + return -1; } -qint64 QHttpSocketEngine::pendingDatagramSize() const +qint64 QHttpSocketEngine::writeDatagram(const char *, qint64, const QIpPacketHeader &) { qWarning("Operation is not supported"); + setError(QAbstractSocket::UnsupportedSocketOperationError, + QLatin1String("Unsupported socket operation")); return -1; } -#endif // QT_NO_UDPSOCKET qint64 QHttpSocketEngine::bytesToWrite() const { diff --git a/src/network/socket/qhttpsocketengine_p.h b/src/network/socket/qhttpsocketengine_p.h index 87400812a7..07815a7e51 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 7c70d664e7..f2bc3cec94 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 8b1a272006..1ca0fa0213 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 fcb8a60c8f..2a9d600630 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 0c5b8d9264..9ae2d8ba8f 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 9817d4c57d..641863b4fd 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 0000000000..a2dc824a09 --- /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 0000000000..fb017a924d --- /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 0000000000..32760caffe --- /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 0000000000..dcfd6806a4 --- /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 0000000000..aaa4e1ecca --- /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 0000000000..f38095330f --- /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 ee3e0d9f0e..518ec21f90 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 bff7e9ecac..864b163489 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 d9ffdbd214..eddf789921 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 34cf9ea9d1..192cbce54c 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 47f45a8404..b11dd93718 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 d78379fb39..37b385dfb5 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 2e3e26d37d..b1c0b6bd6e 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 d159d6d683..bc9d3cc9bf 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 0000000000..49a40ce9b5 --- /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 0000000000..70297f8cc4 --- /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 436ebe5c7f..75f9e3f3a0 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 312316aba3..fa282ea6e6 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; -- cgit v1.2.3