mirror of
https://github.com/crystalidea/qt6windows7.git
synced 2025-07-02 15:26:00 +08:00
qt 6.5.1 original
This commit is contained in:
42
examples/network/network-chat/CMakeLists.txt
Normal file
42
examples/network/network-chat/CMakeLists.txt
Normal file
@ -0,0 +1,42 @@
|
||||
# Copyright (C) 2022 The Qt Company Ltd.
|
||||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
cmake_minimum_required(VERSION 3.16)
|
||||
project(network-chat LANGUAGES CXX)
|
||||
|
||||
if(NOT DEFINED INSTALL_EXAMPLESDIR)
|
||||
set(INSTALL_EXAMPLESDIR "examples")
|
||||
endif()
|
||||
|
||||
set(INSTALL_EXAMPLEDIR "${INSTALL_EXAMPLESDIR}/network/network-chat")
|
||||
|
||||
find_package(Qt6 REQUIRED COMPONENTS Core Gui Network Widgets)
|
||||
|
||||
qt_standard_project_setup()
|
||||
|
||||
qt_add_executable(network-chat
|
||||
chatdialog.cpp chatdialog.h chatdialog.ui
|
||||
client.cpp client.h
|
||||
connection.cpp connection.h
|
||||
main.cpp
|
||||
peermanager.cpp peermanager.h
|
||||
server.cpp server.h
|
||||
)
|
||||
|
||||
set_target_properties(network-chat PROPERTIES
|
||||
WIN32_EXECUTABLE TRUE
|
||||
MACOSX_BUNDLE TRUE
|
||||
)
|
||||
|
||||
target_link_libraries(network-chat PRIVATE
|
||||
Qt6::Core
|
||||
Qt6::Gui
|
||||
Qt6::Network
|
||||
Qt6::Widgets
|
||||
)
|
||||
|
||||
install(TARGETS network-chat
|
||||
RUNTIME DESTINATION "${INSTALL_EXAMPLEDIR}"
|
||||
BUNDLE DESTINATION "${INSTALL_EXAMPLEDIR}"
|
||||
LIBRARY DESTINATION "${INSTALL_EXAMPLEDIR}"
|
||||
)
|
104
examples/network/network-chat/chatdialog.cpp
Normal file
104
examples/network/network-chat/chatdialog.cpp
Normal file
@ -0,0 +1,104 @@
|
||||
// Copyright (C) 2016 The Qt Company Ltd.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
|
||||
|
||||
#include <QtWidgets>
|
||||
|
||||
#include "chatdialog.h"
|
||||
|
||||
ChatDialog::ChatDialog(QWidget *parent)
|
||||
: QDialog(parent)
|
||||
{
|
||||
setupUi(this);
|
||||
|
||||
lineEdit->setFocusPolicy(Qt::StrongFocus);
|
||||
textEdit->setFocusPolicy(Qt::NoFocus);
|
||||
textEdit->setReadOnly(true);
|
||||
listWidget->setFocusPolicy(Qt::NoFocus);
|
||||
|
||||
connect(lineEdit, &QLineEdit::returnPressed,
|
||||
this, &ChatDialog::returnPressed);
|
||||
connect(&client, &Client::newMessage,
|
||||
this, &ChatDialog::appendMessage);
|
||||
connect(&client, &Client::newParticipant,
|
||||
this, &ChatDialog::newParticipant);
|
||||
connect(&client, &Client::participantLeft,
|
||||
this, &ChatDialog::participantLeft);
|
||||
|
||||
myNickName = client.nickName();
|
||||
newParticipant(myNickName);
|
||||
tableFormat.setBorder(0);
|
||||
QTimer::singleShot(10 * 1000, this, SLOT(showInformation()));
|
||||
}
|
||||
|
||||
void ChatDialog::appendMessage(const QString &from, const QString &message)
|
||||
{
|
||||
if (from.isEmpty() || message.isEmpty())
|
||||
return;
|
||||
|
||||
QTextCursor cursor(textEdit->textCursor());
|
||||
cursor.movePosition(QTextCursor::End);
|
||||
QTextTable *table = cursor.insertTable(1, 2, tableFormat);
|
||||
table->cellAt(0, 0).firstCursorPosition().insertText('<' + from + "> ");
|
||||
table->cellAt(0, 1).firstCursorPosition().insertText(message);
|
||||
QScrollBar *bar = textEdit->verticalScrollBar();
|
||||
bar->setValue(bar->maximum());
|
||||
}
|
||||
|
||||
void ChatDialog::returnPressed()
|
||||
{
|
||||
QString text = lineEdit->text();
|
||||
if (text.isEmpty())
|
||||
return;
|
||||
|
||||
if (text.startsWith(QChar('/'))) {
|
||||
QColor color = textEdit->textColor();
|
||||
textEdit->setTextColor(Qt::red);
|
||||
textEdit->append(tr("! Unknown command: %1")
|
||||
.arg(text.left(text.indexOf(' '))));
|
||||
textEdit->setTextColor(color);
|
||||
} else {
|
||||
client.sendMessage(text);
|
||||
appendMessage(myNickName, text);
|
||||
}
|
||||
|
||||
lineEdit->clear();
|
||||
}
|
||||
|
||||
void ChatDialog::newParticipant(const QString &nick)
|
||||
{
|
||||
if (nick.isEmpty())
|
||||
return;
|
||||
|
||||
QColor color = textEdit->textColor();
|
||||
textEdit->setTextColor(Qt::gray);
|
||||
textEdit->append(tr("* %1 has joined").arg(nick));
|
||||
textEdit->setTextColor(color);
|
||||
listWidget->addItem(nick);
|
||||
}
|
||||
|
||||
void ChatDialog::participantLeft(const QString &nick)
|
||||
{
|
||||
if (nick.isEmpty())
|
||||
return;
|
||||
|
||||
QList<QListWidgetItem *> items = listWidget->findItems(nick,
|
||||
Qt::MatchExactly);
|
||||
if (items.isEmpty())
|
||||
return;
|
||||
|
||||
delete items.at(0);
|
||||
QColor color = textEdit->textColor();
|
||||
textEdit->setTextColor(Qt::gray);
|
||||
textEdit->append(tr("* %1 has left").arg(nick));
|
||||
textEdit->setTextColor(color);
|
||||
}
|
||||
|
||||
void ChatDialog::showInformation()
|
||||
{
|
||||
if (listWidget->count() == 1) {
|
||||
QMessageBox::information(this, tr("Chat"),
|
||||
tr("Launch several instances of this "
|
||||
"program on your local network and "
|
||||
"start chatting!"));
|
||||
}
|
||||
}
|
32
examples/network/network-chat/chatdialog.h
Normal file
32
examples/network/network-chat/chatdialog.h
Normal file
@ -0,0 +1,32 @@
|
||||
// Copyright (C) 2016 The Qt Company Ltd.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
|
||||
|
||||
#ifndef CHATDIALOG_H
|
||||
#define CHATDIALOG_H
|
||||
|
||||
#include "ui_chatdialog.h"
|
||||
#include "client.h"
|
||||
|
||||
class ChatDialog : public QDialog, private Ui::ChatDialog
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
ChatDialog(QWidget *parent = nullptr);
|
||||
|
||||
public slots:
|
||||
void appendMessage(const QString &from, const QString &message);
|
||||
|
||||
private slots:
|
||||
void returnPressed();
|
||||
void newParticipant(const QString &nick);
|
||||
void participantLeft(const QString &nick);
|
||||
void showInformation();
|
||||
|
||||
private:
|
||||
Client client;
|
||||
QString myNickName;
|
||||
QTextTableFormat tableFormat;
|
||||
};
|
||||
|
||||
#endif
|
79
examples/network/network-chat/chatdialog.ui
Normal file
79
examples/network/network-chat/chatdialog.ui
Normal file
@ -0,0 +1,79 @@
|
||||
<ui version="4.0" >
|
||||
<class>ChatDialog</class>
|
||||
<widget class="QDialog" name="ChatDialog" >
|
||||
<property name="geometry" >
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>513</width>
|
||||
<height>349</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle" >
|
||||
<string>Chat</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" >
|
||||
<property name="margin" >
|
||||
<number>9</number>
|
||||
</property>
|
||||
<property name="spacing" >
|
||||
<number>6</number>
|
||||
</property>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" >
|
||||
<property name="margin" >
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="spacing" >
|
||||
<number>6</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QTextEdit" name="textEdit" >
|
||||
<property name="focusPolicy" >
|
||||
<enum>Qt::NoFocus</enum>
|
||||
</property>
|
||||
<property name="readOnly" >
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QListWidget" name="listWidget" >
|
||||
<property name="maximumSize" >
|
||||
<size>
|
||||
<width>180</width>
|
||||
<height>16777215</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="focusPolicy" >
|
||||
<enum>Qt::NoFocus</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" >
|
||||
<property name="margin" >
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="spacing" >
|
||||
<number>6</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QLabel" name="label" >
|
||||
<property name="text" >
|
||||
<string>Message:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLineEdit" name="lineEdit" />
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
100
examples/network/network-chat/client.cpp
Normal file
100
examples/network/network-chat/client.cpp
Normal file
@ -0,0 +1,100 @@
|
||||
// Copyright (C) 2016 The Qt Company Ltd.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
|
||||
|
||||
#include <QtNetwork>
|
||||
|
||||
#include "client.h"
|
||||
#include "connection.h"
|
||||
#include "peermanager.h"
|
||||
|
||||
Client::Client()
|
||||
{
|
||||
peerManager = new PeerManager(this);
|
||||
peerManager->setServerPort(server.serverPort());
|
||||
peerManager->startBroadcasting();
|
||||
|
||||
connect(peerManager, &PeerManager::newConnection,
|
||||
this, &Client::newConnection);
|
||||
connect(&server, &Server::newConnection,
|
||||
this, &Client::newConnection);
|
||||
}
|
||||
|
||||
void Client::sendMessage(const QString &message)
|
||||
{
|
||||
if (message.isEmpty())
|
||||
return;
|
||||
|
||||
for (Connection *connection : std::as_const(peers))
|
||||
connection->sendMessage(message);
|
||||
}
|
||||
|
||||
QString Client::nickName() const
|
||||
{
|
||||
return peerManager->userName() + '@' + QHostInfo::localHostName()
|
||||
+ ':' + QString::number(server.serverPort());
|
||||
}
|
||||
|
||||
bool Client::hasConnection(const QHostAddress &senderIp, int senderPort) const
|
||||
{
|
||||
if (senderPort == -1)
|
||||
return peers.contains(senderIp);
|
||||
|
||||
if (!peers.contains(senderIp))
|
||||
return false;
|
||||
|
||||
const QList<Connection *> connections = peers.values(senderIp);
|
||||
for (const Connection *connection : connections) {
|
||||
if (connection->peerPort() == senderPort)
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void Client::newConnection(Connection *connection)
|
||||
{
|
||||
connection->setGreetingMessage(peerManager->userName());
|
||||
|
||||
connect(connection, &Connection::errorOccurred, this, &Client::connectionError);
|
||||
connect(connection, &Connection::disconnected, this, &Client::disconnected);
|
||||
connect(connection, &Connection::readyForUse, this, &Client::readyForUse);
|
||||
}
|
||||
|
||||
void Client::readyForUse()
|
||||
{
|
||||
Connection *connection = qobject_cast<Connection *>(sender());
|
||||
if (!connection || hasConnection(connection->peerAddress(),
|
||||
connection->peerPort()))
|
||||
return;
|
||||
|
||||
connect(connection, &Connection::newMessage,
|
||||
this, &Client::newMessage);
|
||||
|
||||
peers.insert(connection->peerAddress(), connection);
|
||||
QString nick = connection->name();
|
||||
if (!nick.isEmpty())
|
||||
emit newParticipant(nick);
|
||||
}
|
||||
|
||||
void Client::disconnected()
|
||||
{
|
||||
if (Connection *connection = qobject_cast<Connection *>(sender()))
|
||||
removeConnection(connection);
|
||||
}
|
||||
|
||||
void Client::connectionError(QAbstractSocket::SocketError /* socketError */)
|
||||
{
|
||||
if (Connection *connection = qobject_cast<Connection *>(sender()))
|
||||
removeConnection(connection);
|
||||
}
|
||||
|
||||
void Client::removeConnection(Connection *connection)
|
||||
{
|
||||
if (peers.contains(connection->peerAddress())) {
|
||||
peers.remove(connection->peerAddress());
|
||||
QString nick = connection->name();
|
||||
if (!nick.isEmpty())
|
||||
emit participantLeft(nick);
|
||||
}
|
||||
connection->deleteLater();
|
||||
}
|
45
examples/network/network-chat/client.h
Normal file
45
examples/network/network-chat/client.h
Normal file
@ -0,0 +1,45 @@
|
||||
// Copyright (C) 2016 The Qt Company Ltd.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
|
||||
|
||||
#ifndef CLIENT_H
|
||||
#define CLIENT_H
|
||||
|
||||
#include <QAbstractSocket>
|
||||
#include <QHash>
|
||||
#include <QHostAddress>
|
||||
|
||||
#include "server.h"
|
||||
|
||||
class PeerManager;
|
||||
|
||||
class Client : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
Client();
|
||||
|
||||
void sendMessage(const QString &message);
|
||||
QString nickName() const;
|
||||
bool hasConnection(const QHostAddress &senderIp, int senderPort = -1) const;
|
||||
|
||||
signals:
|
||||
void newMessage(const QString &from, const QString &message);
|
||||
void newParticipant(const QString &nick);
|
||||
void participantLeft(const QString &nick);
|
||||
|
||||
private slots:
|
||||
void newConnection(Connection *connection);
|
||||
void connectionError(QAbstractSocket::SocketError socketError);
|
||||
void disconnected();
|
||||
void readyForUse();
|
||||
|
||||
private:
|
||||
void removeConnection(Connection *connection);
|
||||
|
||||
PeerManager *peerManager;
|
||||
Server server;
|
||||
QMultiHash<QHostAddress, Connection *> peers;
|
||||
};
|
||||
|
||||
#endif
|
231
examples/network/network-chat/connection.cpp
Normal file
231
examples/network/network-chat/connection.cpp
Normal file
@ -0,0 +1,231 @@
|
||||
// Copyright (C) 2016 The Qt Company Ltd.
|
||||
// Copyright (C) 2018 Intel Corporation.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
|
||||
|
||||
#include "connection.h"
|
||||
|
||||
#include <QtNetwork>
|
||||
|
||||
static const int TransferTimeout = 30 * 1000;
|
||||
static const int PongTimeout = 60 * 1000;
|
||||
static const int PingInterval = 5 * 1000;
|
||||
|
||||
/*
|
||||
* Protocol is defined as follows, using the CBOR Data Definition Language:
|
||||
*
|
||||
* protocol = [
|
||||
* greeting, ; must start with a greeting command
|
||||
* * command ; zero or more regular commands after
|
||||
* ]
|
||||
* command = plaintext / ping / pong / greeting
|
||||
* plaintext = { 0 => text }
|
||||
* ping = { 1 => null }
|
||||
* pong = { 2 => null }
|
||||
* greeting = { 3 => text }
|
||||
*/
|
||||
|
||||
Connection::Connection(QObject *parent)
|
||||
: QTcpSocket(parent), writer(this)
|
||||
{
|
||||
greetingMessage = tr("undefined");
|
||||
username = tr("unknown");
|
||||
state = WaitingForGreeting;
|
||||
currentDataType = Undefined;
|
||||
transferTimerId = -1;
|
||||
isGreetingMessageSent = false;
|
||||
pingTimer.setInterval(PingInterval);
|
||||
|
||||
connect(this, &QTcpSocket::readyRead, this,
|
||||
&Connection::processReadyRead);
|
||||
connect(this, &QTcpSocket::disconnected,
|
||||
&pingTimer, &QTimer::stop);
|
||||
connect(&pingTimer, &QTimer::timeout,
|
||||
this, &Connection::sendPing);
|
||||
connect(this, &QTcpSocket::connected,
|
||||
this, &Connection::sendGreetingMessage);
|
||||
}
|
||||
|
||||
Connection::Connection(qintptr socketDescriptor, QObject *parent)
|
||||
: Connection(parent)
|
||||
{
|
||||
setSocketDescriptor(socketDescriptor);
|
||||
reader.setDevice(this);
|
||||
}
|
||||
|
||||
Connection::~Connection()
|
||||
{
|
||||
if (isGreetingMessageSent) {
|
||||
// Indicate clean shutdown.
|
||||
writer.endArray();
|
||||
waitForBytesWritten(2000);
|
||||
}
|
||||
}
|
||||
|
||||
QString Connection::name() const
|
||||
{
|
||||
return username;
|
||||
}
|
||||
|
||||
void Connection::setGreetingMessage(const QString &message)
|
||||
{
|
||||
greetingMessage = message;
|
||||
}
|
||||
|
||||
bool Connection::sendMessage(const QString &message)
|
||||
{
|
||||
if (message.isEmpty())
|
||||
return false;
|
||||
|
||||
writer.startMap(1);
|
||||
writer.append(PlainText);
|
||||
writer.append(message);
|
||||
writer.endMap();
|
||||
return true;
|
||||
}
|
||||
|
||||
void Connection::timerEvent(QTimerEvent *timerEvent)
|
||||
{
|
||||
if (timerEvent->timerId() == transferTimerId) {
|
||||
abort();
|
||||
killTimer(transferTimerId);
|
||||
transferTimerId = -1;
|
||||
}
|
||||
}
|
||||
|
||||
void Connection::processReadyRead()
|
||||
{
|
||||
// we've got more data, let's parse
|
||||
reader.reparse();
|
||||
while (reader.lastError() == QCborError::NoError) {
|
||||
if (state == WaitingForGreeting) {
|
||||
if (!reader.isArray())
|
||||
break; // protocol error
|
||||
|
||||
reader.enterContainer(); // we'll be in this array forever
|
||||
state = ReadingGreeting;
|
||||
} else if (reader.containerDepth() == 1) {
|
||||
// Current state: no command read
|
||||
// Next state: read command ID
|
||||
if (!reader.hasNext()) {
|
||||
reader.leaveContainer();
|
||||
disconnectFromHost();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!reader.isMap() || !reader.isLengthKnown() || reader.length() != 1)
|
||||
break; // protocol error
|
||||
reader.enterContainer();
|
||||
} else if (currentDataType == Undefined) {
|
||||
// Current state: read command ID
|
||||
// Next state: read command payload
|
||||
if (!reader.isInteger())
|
||||
break; // protocol error
|
||||
currentDataType = DataType(reader.toInteger());
|
||||
reader.next();
|
||||
} else {
|
||||
// Current state: read command payload
|
||||
if (reader.isString()) {
|
||||
auto r = reader.readString();
|
||||
buffer += r.data;
|
||||
if (r.status != QCborStreamReader::EndOfString)
|
||||
continue;
|
||||
} else if (reader.isNull()) {
|
||||
reader.next();
|
||||
} else {
|
||||
break; // protocol error
|
||||
}
|
||||
|
||||
// Next state: no command read
|
||||
reader.leaveContainer();
|
||||
if (transferTimerId != -1) {
|
||||
killTimer(transferTimerId);
|
||||
transferTimerId = -1;
|
||||
}
|
||||
|
||||
if (state == ReadingGreeting) {
|
||||
if (currentDataType != Greeting)
|
||||
break; // protocol error
|
||||
processGreeting();
|
||||
} else {
|
||||
processData();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (reader.lastError() != QCborError::EndOfFile)
|
||||
abort(); // parse error
|
||||
|
||||
if (transferTimerId != -1 && reader.containerDepth() > 1)
|
||||
transferTimerId = startTimer(TransferTimeout);
|
||||
}
|
||||
|
||||
void Connection::sendPing()
|
||||
{
|
||||
if (pongTime.elapsed() > PongTimeout) {
|
||||
abort();
|
||||
return;
|
||||
}
|
||||
|
||||
writer.startMap(1);
|
||||
writer.append(Ping);
|
||||
writer.append(nullptr); // no payload
|
||||
writer.endMap();
|
||||
}
|
||||
|
||||
void Connection::sendGreetingMessage()
|
||||
{
|
||||
writer.startArray(); // this array never ends
|
||||
|
||||
writer.startMap(1);
|
||||
writer.append(Greeting);
|
||||
writer.append(greetingMessage);
|
||||
writer.endMap();
|
||||
isGreetingMessageSent = true;
|
||||
|
||||
if (!reader.device())
|
||||
reader.setDevice(this);
|
||||
}
|
||||
|
||||
void Connection::processGreeting()
|
||||
{
|
||||
username = buffer + '@' + peerAddress().toString() + ':'
|
||||
+ QString::number(peerPort());
|
||||
currentDataType = Undefined;
|
||||
buffer.clear();
|
||||
|
||||
if (!isValid()) {
|
||||
abort();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!isGreetingMessageSent)
|
||||
sendGreetingMessage();
|
||||
|
||||
pingTimer.start();
|
||||
pongTime.start();
|
||||
state = ReadyForUse;
|
||||
emit readyForUse();
|
||||
}
|
||||
|
||||
void Connection::processData()
|
||||
{
|
||||
switch (currentDataType) {
|
||||
case PlainText:
|
||||
emit newMessage(username, buffer);
|
||||
break;
|
||||
case Ping:
|
||||
writer.startMap(1);
|
||||
writer.append(Pong);
|
||||
writer.append(nullptr); // no payload
|
||||
writer.endMap();
|
||||
break;
|
||||
case Pong:
|
||||
pongTime.restart();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
currentDataType = Undefined;
|
||||
buffer.clear();
|
||||
}
|
73
examples/network/network-chat/connection.h
Normal file
73
examples/network/network-chat/connection.h
Normal file
@ -0,0 +1,73 @@
|
||||
// Copyright (C) 2016 The Qt Company Ltd.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
|
||||
|
||||
#ifndef CONNECTION_H
|
||||
#define CONNECTION_H
|
||||
|
||||
#include <QCborStreamReader>
|
||||
#include <QCborStreamWriter>
|
||||
#include <QElapsedTimer>
|
||||
#include <QHostAddress>
|
||||
#include <QString>
|
||||
#include <QTcpSocket>
|
||||
#include <QTimer>
|
||||
|
||||
static const int MaxBufferSize = 1024000;
|
||||
|
||||
class Connection : public QTcpSocket
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
enum ConnectionState {
|
||||
WaitingForGreeting,
|
||||
ReadingGreeting,
|
||||
ReadyForUse
|
||||
};
|
||||
enum DataType {
|
||||
PlainText,
|
||||
Ping,
|
||||
Pong,
|
||||
Greeting,
|
||||
Undefined
|
||||
};
|
||||
|
||||
Connection(QObject *parent = nullptr);
|
||||
Connection(qintptr socketDescriptor, QObject *parent = nullptr);
|
||||
~Connection();
|
||||
|
||||
QString name() const;
|
||||
void setGreetingMessage(const QString &message);
|
||||
bool sendMessage(const QString &message);
|
||||
|
||||
signals:
|
||||
void readyForUse();
|
||||
void newMessage(const QString &from, const QString &message);
|
||||
|
||||
protected:
|
||||
void timerEvent(QTimerEvent *timerEvent) override;
|
||||
|
||||
private slots:
|
||||
void processReadyRead();
|
||||
void sendPing();
|
||||
void sendGreetingMessage();
|
||||
|
||||
private:
|
||||
bool hasEnoughData();
|
||||
void processGreeting();
|
||||
void processData();
|
||||
|
||||
QCborStreamReader reader;
|
||||
QCborStreamWriter writer;
|
||||
QString greetingMessage;
|
||||
QString username;
|
||||
QTimer pingTimer;
|
||||
QElapsedTimer pongTime;
|
||||
QString buffer;
|
||||
ConnectionState state;
|
||||
DataType currentDataType;
|
||||
int transferTimerId;
|
||||
bool isGreetingMessageSent;
|
||||
};
|
||||
|
||||
#endif
|
17
examples/network/network-chat/main.cpp
Normal file
17
examples/network/network-chat/main.cpp
Normal file
@ -0,0 +1,17 @@
|
||||
// Copyright (C) 2016 The Qt Company Ltd.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
|
||||
|
||||
#include <QApplication>
|
||||
|
||||
#include "chatdialog.h"
|
||||
|
||||
#include <QtCore/QSettings>
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
QApplication app(argc, argv);
|
||||
|
||||
ChatDialog dialog;
|
||||
dialog.show();
|
||||
return app.exec();
|
||||
}
|
21
examples/network/network-chat/network-chat.pro
Normal file
21
examples/network/network-chat/network-chat.pro
Normal file
@ -0,0 +1,21 @@
|
||||
HEADERS = chatdialog.h \
|
||||
client.h \
|
||||
connection.h \
|
||||
peermanager.h \
|
||||
server.h
|
||||
SOURCES = chatdialog.cpp \
|
||||
client.cpp \
|
||||
connection.cpp \
|
||||
main.cpp \
|
||||
peermanager.cpp \
|
||||
server.cpp
|
||||
FORMS = chatdialog.ui
|
||||
QT += network widgets
|
||||
requires(qtConfig(udpsocket))
|
||||
requires(qtConfig(listwidget))
|
||||
|
||||
# install
|
||||
target.path = $$[QT_INSTALL_EXAMPLES]/network/network-chat
|
||||
INSTALLS += target
|
||||
|
||||
|
149
examples/network/network-chat/peermanager.cpp
Normal file
149
examples/network/network-chat/peermanager.cpp
Normal file
@ -0,0 +1,149 @@
|
||||
// Copyright (C) 2016 The Qt Company Ltd.
|
||||
// Copyright (C) 2018 Intel Corporation.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
|
||||
|
||||
#include <QtNetwork>
|
||||
|
||||
#include "client.h"
|
||||
#include "connection.h"
|
||||
#include "peermanager.h"
|
||||
|
||||
static const qint32 BroadcastInterval = 2000;
|
||||
static const unsigned broadcastPort = 45000;
|
||||
|
||||
PeerManager::PeerManager(Client *client)
|
||||
: QObject(client)
|
||||
{
|
||||
this->client = client;
|
||||
|
||||
static const char *envVariables[] = {
|
||||
"USERNAME", "USER", "USERDOMAIN", "HOSTNAME", "DOMAINNAME"
|
||||
};
|
||||
|
||||
for (const char *varname : envVariables) {
|
||||
username = qEnvironmentVariable(varname);
|
||||
if (!username.isNull())
|
||||
break;
|
||||
}
|
||||
|
||||
if (username.isEmpty())
|
||||
username = "unknown";
|
||||
|
||||
updateAddresses();
|
||||
serverPort = 0;
|
||||
|
||||
broadcastSocket.bind(QHostAddress::Any, broadcastPort, QUdpSocket::ShareAddress
|
||||
| QUdpSocket::ReuseAddressHint);
|
||||
connect(&broadcastSocket, &QUdpSocket::readyRead,
|
||||
this, &PeerManager::readBroadcastDatagram);
|
||||
|
||||
broadcastTimer.setInterval(BroadcastInterval);
|
||||
connect(&broadcastTimer, &QTimer::timeout,
|
||||
this, &PeerManager::sendBroadcastDatagram);
|
||||
}
|
||||
|
||||
void PeerManager::setServerPort(int port)
|
||||
{
|
||||
serverPort = port;
|
||||
}
|
||||
|
||||
QString PeerManager::userName() const
|
||||
{
|
||||
return username;
|
||||
}
|
||||
|
||||
void PeerManager::startBroadcasting()
|
||||
{
|
||||
broadcastTimer.start();
|
||||
}
|
||||
|
||||
bool PeerManager::isLocalHostAddress(const QHostAddress &address) const
|
||||
{
|
||||
for (const QHostAddress &localAddress : ipAddresses) {
|
||||
if (address.isEqual(localAddress))
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void PeerManager::sendBroadcastDatagram()
|
||||
{
|
||||
QByteArray datagram;
|
||||
{
|
||||
QCborStreamWriter writer(&datagram);
|
||||
writer.startArray(2);
|
||||
writer.append(username);
|
||||
writer.append(serverPort);
|
||||
writer.endArray();
|
||||
}
|
||||
|
||||
bool validBroadcastAddresses = true;
|
||||
for (const QHostAddress &address : std::as_const(broadcastAddresses)) {
|
||||
if (broadcastSocket.writeDatagram(datagram, address,
|
||||
broadcastPort) == -1)
|
||||
validBroadcastAddresses = false;
|
||||
}
|
||||
|
||||
if (!validBroadcastAddresses)
|
||||
updateAddresses();
|
||||
}
|
||||
|
||||
void PeerManager::readBroadcastDatagram()
|
||||
{
|
||||
while (broadcastSocket.hasPendingDatagrams()) {
|
||||
QHostAddress senderIp;
|
||||
quint16 senderPort;
|
||||
QByteArray datagram;
|
||||
datagram.resize(broadcastSocket.pendingDatagramSize());
|
||||
if (broadcastSocket.readDatagram(datagram.data(), datagram.size(),
|
||||
&senderIp, &senderPort) == -1)
|
||||
continue;
|
||||
|
||||
int senderServerPort;
|
||||
{
|
||||
// decode the datagram
|
||||
QCborStreamReader reader(datagram);
|
||||
if (reader.lastError() != QCborError::NoError || !reader.isArray())
|
||||
continue;
|
||||
if (!reader.isLengthKnown() || reader.length() != 2)
|
||||
continue;
|
||||
|
||||
reader.enterContainer();
|
||||
if (reader.lastError() != QCborError::NoError || !reader.isString())
|
||||
continue;
|
||||
while (reader.readString().status == QCborStreamReader::Ok) {
|
||||
// we don't actually need the username right now
|
||||
}
|
||||
|
||||
if (reader.lastError() != QCborError::NoError || !reader.isUnsignedInteger())
|
||||
continue;
|
||||
senderServerPort = reader.toInteger();
|
||||
}
|
||||
|
||||
if (isLocalHostAddress(senderIp) && senderServerPort == serverPort)
|
||||
continue;
|
||||
|
||||
if (!client->hasConnection(senderIp)) {
|
||||
Connection *connection = new Connection(this);
|
||||
emit newConnection(connection);
|
||||
connection->connectToHost(senderIp, senderServerPort);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void PeerManager::updateAddresses()
|
||||
{
|
||||
broadcastAddresses.clear();
|
||||
ipAddresses.clear();
|
||||
const QList<QNetworkInterface> interfaces = QNetworkInterface::allInterfaces();
|
||||
for (const QNetworkInterface &interface : interfaces) {
|
||||
const QList<QNetworkAddressEntry> entries = interface.addressEntries();
|
||||
for (const QNetworkAddressEntry &entry : entries) {
|
||||
QHostAddress broadcastAddress = entry.broadcast();
|
||||
if (broadcastAddress != QHostAddress::Null && entry.ip() != QHostAddress::LocalHost) {
|
||||
broadcastAddresses << broadcastAddress;
|
||||
ipAddresses << entry.ip();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
47
examples/network/network-chat/peermanager.h
Normal file
47
examples/network/network-chat/peermanager.h
Normal file
@ -0,0 +1,47 @@
|
||||
// Copyright (C) 2016 The Qt Company Ltd.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
|
||||
|
||||
#ifndef PEERMANAGER_H
|
||||
#define PEERMANAGER_H
|
||||
|
||||
#include <QByteArray>
|
||||
#include <QList>
|
||||
#include <QObject>
|
||||
#include <QTimer>
|
||||
#include <QUdpSocket>
|
||||
|
||||
class Client;
|
||||
class Connection;
|
||||
|
||||
class PeerManager : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
PeerManager(Client *client);
|
||||
|
||||
void setServerPort(int port);
|
||||
QString userName() const;
|
||||
void startBroadcasting();
|
||||
bool isLocalHostAddress(const QHostAddress &address) const;
|
||||
|
||||
signals:
|
||||
void newConnection(Connection *connection);
|
||||
|
||||
private slots:
|
||||
void sendBroadcastDatagram();
|
||||
void readBroadcastDatagram();
|
||||
|
||||
private:
|
||||
void updateAddresses();
|
||||
|
||||
Client *client;
|
||||
QList<QHostAddress> broadcastAddresses;
|
||||
QList<QHostAddress> ipAddresses;
|
||||
QUdpSocket broadcastSocket;
|
||||
QTimer broadcastTimer;
|
||||
QString username;
|
||||
int serverPort;
|
||||
};
|
||||
|
||||
#endif
|
19
examples/network/network-chat/server.cpp
Normal file
19
examples/network/network-chat/server.cpp
Normal file
@ -0,0 +1,19 @@
|
||||
// Copyright (C) 2016 The Qt Company Ltd.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
|
||||
|
||||
#include <QtNetwork>
|
||||
|
||||
#include "connection.h"
|
||||
#include "server.h"
|
||||
|
||||
Server::Server(QObject *parent)
|
||||
: QTcpServer(parent)
|
||||
{
|
||||
listen(QHostAddress::Any);
|
||||
}
|
||||
|
||||
void Server::incomingConnection(qintptr socketDescriptor)
|
||||
{
|
||||
Connection *connection = new Connection(socketDescriptor, this);
|
||||
emit newConnection(connection);
|
||||
}
|
25
examples/network/network-chat/server.h
Normal file
25
examples/network/network-chat/server.h
Normal file
@ -0,0 +1,25 @@
|
||||
// Copyright (C) 2016 The Qt Company Ltd.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
|
||||
|
||||
#ifndef SERVER_H
|
||||
#define SERVER_H
|
||||
|
||||
#include <QTcpServer>
|
||||
|
||||
class Connection;
|
||||
|
||||
class Server : public QTcpServer
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
Server(QObject *parent = nullptr);
|
||||
|
||||
signals:
|
||||
void newConnection(Connection *connection);
|
||||
|
||||
protected:
|
||||
void incomingConnection(qintptr socketDescriptor) override;
|
||||
};
|
||||
|
||||
#endif
|
Reference in New Issue
Block a user