【写在前面】
在前面两篇博客中,都是实现一个扫描的小动画,Ծ‸ Ծ 然鹅却浪费不少时间。
再者,因为最近一直在打游戏,就一直没有怎么做(毕竟只是突然做着玩玩)。
不过今天我决定还是赶紧把它写完(代码已经OK了)。
本次分为两篇来些,上篇为扫描篇。
【正文开始】
效果图太大。。上传不了,不过Github可以,所以想看效果的直接去项目地址看就可以。
首先,想象一下,有两个人在局域网(热点也算)环境下,他们之间有一些文件要发,但是他们互不知道对方地址(IP),因此,第一步我们应该让他们互相知道对方地址。
我将这一步称为扫描,想法是其中一方点击扫描后,发送一个UDP广播,然后另一方进行回复,通过IP头中地址即可知双方地址,下一步通过TCP连接对方,然后就可以在此连接上收发文件。
discoverconnection.h:
#ifndef DISCOVERCONNECTION_H
#define DISCOVERCONNECTION_H
#include <QUdpSocket>
class DiscoverConnection : public QUdpSocket
{
Q_OBJECT
Q_PROPERTY(QString name READ name WRITE setName NOTIFY nameChanged)
public:
static DiscoverConnection* instance();
~DiscoverConnection();
QString getName(const QHostAddress &address) const;
QHostAddress getAddress(const QString &name) const;
QString name() const;
void setName(const QString &name);
Q_INVOKABLE void discover();
Q_INVOKABLE void connectToName(const QString &name);
signals:
void nameChanged();
void newConnection(const QString &name);
void newAccessPoint(const QString &name);
private:
DiscoverConnection(QObject *parent = nullptr);
void processDatagram();
QString m_name = "未命名";
QMap<QString, QHostAddress> m_accessPoints;
};
#endif // DISCOVERCONNECTION_H
这里,有一个name属性,主要是用于区分不同用户的名称。
然后是m_accessPoint,扫描到的访问点都会以<name, ip>的形式进行存储。
接下来看discoverconnection.cpp:
#include "discoverconnection.h"
#include <QNetworkDatagram>
DiscoverConnection::DiscoverConnection(QObject *parent)
: QUdpSocket (parent)
{
bind(QHostAddress::Any, 43801);
connect(this, &QUdpSocket::readyRead, this, &DiscoverConnection::processDatagram);
}
DiscoverConnection *DiscoverConnection::instance()
{
static DiscoverConnection d;
return &d;
}
DiscoverConnection::~DiscoverConnection()
{
}
QString DiscoverConnection::getName(const QHostAddress &address) const
{
return m_accessPoints.key(address);
}
QHostAddress DiscoverConnection::getAddress(const QString &name) const
{
return m_accessPoints[name];
}
QString DiscoverConnection::name() const
{
return m_name;
}
void DiscoverConnection::setName(const QString &name)
{
if (name != m_name)
{
m_name = name;
emit nameChanged();
}
}
void DiscoverConnection::discover()
{
m_accessPoints.clear();
writeDatagram("[DISCOVER]", QHostAddress::Broadcast, 43801);
}
void DiscoverConnection::connectToName(const QString &name)
{
writeDatagram("[CONNECT]##" + m_name.toLocal8Bit(), getAddress(name), 43801);
}
void DiscoverConnection::processDatagram()
{
while (hasPendingDatagrams())
{
QNetworkDatagram datagram = receiveDatagram();
if (!datagram.senderAddress().isNull() && datagram.senderPort() != -1)
{
if (datagram.data() == "[DISCOVER]")
{
writeDatagram("[NAME]##" + m_name.toLocal8Bit(), datagram.senderAddress(),
quint16(datagram.senderPort()));
}
else if (datagram.data().left(8) == "[NAME]##")
{
QString name = QString::fromLocal8Bit(datagram.data().mid(8));
m_accessPoints[name] = datagram.senderAddress();
qDebug() << name << datagram.senderAddress();
emit newAccessPoint(name);
}
else if (datagram.data().left(11) == "[CONNECT]##")
{
QString name = QString::fromLocal8Bit(datagram.data().mid(11));
emit newConnection(name);
}
}
}
}
对于UDP套接字,使用bind()绑定后,只要UDP数据报到达指定的地址和端口,就会发出信号QUdpSocket :: readyRead()信号,这里bind到QHostAddress::Any,并且我们约定UDP数据的端口为43801,TCP数据的端口为43800。
1、discover()函数为开始扫描的函数,使用Q_INVOKABLE修饰,即可在QML中调用,这里就是发送一个UDP广播,内容为[DISCOVER],其实可以弄一个自定义协议,但因为软件比较简单,就直接在代码里体现了。
2、processDatagram()在有UDP数据到达时被调用:
如果为[DISCOVER],我们就将自己的name以[NAME]## + name的形式发送回去。
如果为[NAME]##即另一端的回复,就加入访问点,并发送newAccessPoint()信号,这将会在qml更新访问点。
3、connectToName()如果用户选择了一个name进行连接,那么将会发送[CONNECT]## + name数据报。
如果收到[CONNECT]##即另一端进行连接,就发送newConnection()信号,这将会在qml更新连接名。
至此,UDP扫描结束。
【结语】
其实UDP用起来方便好用还很简单,不过文件传输的话还是选择TCP,这将在下一篇博客进行讲解,当然也远比本篇复杂。
最后,资源地址:https://download.csdn.net/download/u011283226/11135788
也可以访问项目地址:https://github.com/mengps/FileTransfer