作品页面的配套讲解页,主要讲解 Qt 网络编程核心代码。
除了第二个阻塞的案例外,基本上新手一看就能懂。 具备网络基础的话,相当于仅仅是熟悉 Qt 网络编程的API。
访问网络(简单) 这里要完成一个简单的事情,访问网络,然后拿到服务器返回的 HTML 代码。
当然获取网页代码(或者显示HTML代码)并不是真正诉求,重点在于 Qt网络编程中,如何利用QNetworkAccessManager 发出QNetworkRequest 请求,然后处理收到的QNetworkReply 响应。以及如何封装网络访问代码,至少是和业务接口解耦。(让业务接口可以直接调用这边儿的接口,而且做到二进制兼容,即连接库,或者远端API修改时,不用修改源码,也不用重新编译)
封装一下网络访问的工具类:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 #ifndef NETWORKER_H #define NETWORKER_H #include <QNetworkAccessManager> #include <QNetworkReply> #include <QNetworkRequest> class QNetworkReply ;class NetWorker : public QObject{ Q_OBJECT public : static NetWorker *getInstance () ; ~NetWorker(); void get (const QString &url) ; signals: void finished (QNetworkReply *reply) ; public slots:private : class Private ; friend class Private ; Private *d; explicit NetWorker (QObject *parent = nullptr ) ; NetWorker(const NetWorker &) = delete ; NetWorker& operator =(NetWorker worker) =delete ; }; class NetWorker : :Private{ public : Private(NetWorker *q) : manager(new QNetworkAccessManager(q)) {} QNetworkAccessManager *manager; }; #endif
具体实现代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 #include "networker.h" NetWorker::NetWorker(QObject *parent) : QObject(parent), d(new NetWorker::Private(this )) { connect(d->manager, &QNetworkAccessManager::finished, this , &NetWorker::finished); } NetWorker::~NetWorker() { delete d; d = 0 ; } NetWorker *NetWorker::getInstance() { static NetWorker netWorker; return &netWorker; } void NetWorker::get(const QString &url){ d->manager->get(QNetworkRequest(QUrl(url))); }
调用代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) { networker = NetWorker::getInstance(); connect(networker, &NetWorker::finished, [=](QNetworkReply *reply){ qDebug() << reply->readAll().data(); reply->deleteLater(); }); networker->get(QString("http://www.baidu.com/" )); }
运行结果符合预期 :
总结一下,如果不带封装,核心代码不过如下:
1 2 3 4 5 6 7 8 QNetworkReply *replay; QNetworkAccessManager *mgr = new QNetworkAccessManager(); mgr->setNetworkAccessible(QNetworkAccessManager::Accessible); replay = mgr->get(QNetworkRequest(QUrl(url))); replay->readAll();
或者需要验证的时候,大致是这样的:
1 2 3 4 5 6 7 8 9 10 11 12 13 QNetworkAccessManager *manager = new QNetworkAccessManager(this ); connect(manager, SIGNAL(proxyAuthenticationRequired(const QNetworkProxy &, QAuthenticator *)), SLOT(slotProxyAuthenticationRequired(const QNetworkProxy &, QAuthenticator *))); connect(manager, SIGNAL(authenticationRequired(QNetworkReply *, QAuthenticator *)), SLOT(slotAuthenticationRequired(QNetworkReply *, QAuthenticator *))); QNetworkRequest request; request.setUrl(m_url); request.setRawHeader("User-Agent" , "Qt NetworkAccess 1.3" ); m_pReply = manager->get(request);
在发送请求之后 get(),我们进入事件循环并等待finished()来自网络访问管理器的信号。当到达时,请求已经完成 - 成功或失败。接下来,我们应该打电话 reply->error()来查看是否有问题并报告,或者如果一切顺利,请拨打电话reply->readAll()或相似的地方获取我们的数据。
注意在 .pro 文件中添加 DEFINES += QT_NO_SSL
.
同步阻塞下载文件(范例)
“How can I use QNetworkManager for synchronous downloads?”. Several times, the first hint is “Use it in asynchronous mode.” This tends to make me angry, not only because the reply is useless. It also sounds to me like “Don’t you know how to use it asynchronously, stupid?”.
异步回调下载可能引发的问题:
该文件将在下载之前完全下载到内存中finished()。对于大文件,这可能需要一段时间,甚至填满所有内存(导致您的应用程序崩溃)。
使用一个不同的信号,如响应对象提前触发readyRead(),QNetworkRequest将会留下一个大文件的第一部分,但是你无法得到其余的部分。
下载时无法向用户显示进度。
流不能被这样处理,就像finish()永远不会被调用 - 应用程序将锁定,最终耗尽内存和崩溃。
在非 GUI 线程中使用此代码 QEventLoop 时,可能导致死锁。
一般的解决方案是:
使用一个QThread来处理传入的数据。
根据运行在GUI还是非GUI模式,使用不同的方法进行线程同步。
对于GUI线程,使用QEventLoop确保我们正在维护一个活泼的用户界面。
但是如果工作线程处理数据不够快,内存还是容易填满;并且线程还有一个缺陷,QTimers must be created from QThread
, 也就是说定时器是跟实际工作的线程有关。
下面是实际解决方案(提供一种解决思路):
首先,我们需要创建并开始QThread处理传入的数据,同时保持用户界面的快乐和活跃:
1 2 3 4 5 6 7 8 9 webfile::webfile(QObject *parent ) : QObject(parent), m_pNetworkProxy(NULL ) { m_pSocketThread = new QThread(this ); moveToThread(m_pSocketThread); m_pSocketThread->start(QThread::HighestPriority); }
在打开和读取操作中,线程将被使用。请注意,我们必须检查我们是否以 GUI 线程运行,并采取不同的行动。调用 exec() 函数 QEventLoop 可能导致在非GUI线程中发生死锁 - QNetworkRequest 由此发送的信号将不被接受,所以 quit() 插槽将永远不会被激活。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 void webfile::slotOpen(void *pReturnSuccess, void *pLoop, qint64 offset ){ *(bool *)pReturnSuccess = workerOpen(offset); if (pLoop != NULL ) { QMetaObject::invokeMethod((QEventLoop*)pLoop, "quit" , Qt::QueuedConnection); } } bool webfile::open(qint64 offset ){ bool bSuccess = false ; if (isGuiThread()) { QMetaObject::invokeMethod(this , "slotOpen" , Qt::QueuedConnection, Q_ARG(void *, &bSuccess), Q_ARG(void *, &m_loop), Q_ARG(qint64, offset)); m_loop.exec(); } else { QMetaObject::invokeMethod(this , "slotOpen" , Qt::BlockingQueuedConnection, Q_ARG(void *, &bSuccess), Q_ARG(void *, NULL ), Q_ARG(qint64, offset)); } return bSuccess; }
参数通过指针方便地传递Q_ARG()。在这种情况下,这是完全合法的,因为我们等待时隙完成任务,所以在线程仍处于活动状态时,它们不会被无意中断开。这样我们也可以得到结果(成功或数据在read()通话的情况下)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 bool webfile::workerOpen(qint64 offset ){ qDebug() << "webfile::open(): start offset =" << offset; clear(); resetReadFails(); close(); QNetworkAccessManager *manager = new QNetworkAccessManager(this ); if (m_pNetworkProxy != NULL ) manager->setProxy(*m_pNetworkProxy); connect(manager, SIGNAL(proxyAuthenticationRequired(const QNetworkProxy &, QAuthenticator *)), SLOT(slotProxyAuthenticationRequired(const QNetworkProxy &, QAuthenticator *))); connect(manager, SIGNAL(authenticationRequired(QNetworkReply *, QAuthenticator *)), SLOT(slotAuthenticationRequired(QNetworkReply *, QAuthenticator *))); QNetworkRequest request; request.setUrl(m_url); request.setRawHeader("User-Agent" , "Qt NetworkAccess 1.3" ); m_nPos = offset; if (m_nPos) { QByteArray data; QString strData("bytes=" + QString::number(m_nPos) + "-"); data = strData.toLatin1(); request.setRawHeader("Range" , data); } m_pReply = manager->get(request); if (m_pReply == NULL ) { qDebug() << "webfile::open(): network error" ; m_NetworkError = QNetworkReply::UnknownNetworkError; return false ; } m_pReply->setReadBufferSize(m_nBufferSize); connect(m_pReply, SIGNAL(error(QNetworkReply::NetworkError)), SLOT(slotError(QNetworkReply::NetworkError))); connect(m_pReply, SIGNAL(sslErrors(QList<QSslError>)), SLOT(slotSslErrors(QList<QSslError>))); if (!waitForConnect(m_nOpenTimeOutms, manager)) { qDebug() << "webfile::open(): timeout" ; m_NetworkError = QNetworkReply::TimeoutError; return false ; } if (m_pReply == NULL ) { qDebug() << "webfile::open(): cancelled" ; m_NetworkError = QNetworkReply::OperationCanceledError; return false ; } if (m_pReply->error() != QNetworkReply::NoError) { qDebug() << "webfile::open(): error" << m_pReply->errorString(); m_NetworkError = m_pReply->error(); return false ; } m_NetworkError = m_pReply->error(); m_strContentType = m_pReply->header(QNetworkRequest::ContentTypeHeader).toString(); m_LastModified = m_pReply->header(QNetworkRequest::LastModifiedHeader).toDateTime(); m_nSize = m_pReply->header(QNetworkRequest::ContentLengthHeader).toULongLong(); m_nResponse = m_pReply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); m_strResponse = m_pReply->attribute(QNetworkRequest::HttpReasonPhraseAttribute).toString(); m_bHaveSize = (m_nSize ? true : false ); m_nSize += m_nPos; if (error() != QNetworkReply::NoError) { qDebug() << "webfile::open(): error" << error(); return false ; } m_NetworkError = response2error(m_nResponse); qDebug() << "webfile::open(): end response" << response() << "error" << error() << "size" << m_nSize; return (response() == 200 || response() == 206 ); }
不要忘记等待功能:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 bool webfile::waitForConnect(int nTimeOutms, QNetworkAccessManager *manager){ QTimer *timer = NULL ; QEventLoop eventLoop; bool bReadTimeOut = false ; m_bReadTimeOut = false ; if (nTimeOutms > 0 ) { timer = new QTimer(this ); connect(timer, SIGNAL(timeout()), this , SLOT(slotWaitTimeout())); timer->setSingleShot(true ); timer->start(nTimeOutms); connect(this , SIGNAL(signalReadTimeout()), &eventLoop, SLOT(quit())); } connect(manager, SIGNAL(finished(QNetworkReply *)), &eventLoop, SLOT(quit())); if (m_pReply != NULL ) { connect(m_pReply, SIGNAL(readyRead()), &eventLoop, SLOT(quit())); } eventLoop.exec(); if (timer != NULL ) { timer->stop(); delete timer; timer = NULL ; } bReadTimeOut = m_bReadTimeOut; m_bReadTimeOut = false ; return !bReadTimeOut; }
精华都已经全部讲解了,如果需要全部源码,可以发邮件给我。
Tcp通信案例(简单) Qt 这边儿核心通信逻辑经过封装,写起来比较简单,其实整个看起来就像是在槽函数里面填空一样。
建立相关连接逻辑
找到相应的信号,绑定槽函数
实现槽函数
这里演示的是一个简单的 Tcp 建立连接,然后通信的过程,整个运行效果 如下:
先写 server 端:(可以用 nc 代替客户端进行测试)
多个客户端同时连接:
测试发送数据: (S -> C)(这里客户端是局域网的另外一台电脑)
测试发送数据: (C -> S)
测试分别从 C端,S端断开连接:
客户端界面,略。(直接看代码吧,太简单了)
完整的代码如下:
客户端逻辑:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 private slots: void on_btnConnect_clicked () ; void on_btnSend_clicked () ; void on_btnClose_clicked () ; private : Ui::clientwidget *ui; QTcpSocket *tcpSocket; QString currentInfo; clientwidget::clientwidget(QWidget *parent) : QWidget(parent), ui(new Ui::clientwidget) { ui->setupUi(this ); setWindowTitle("客户端" ); tcpSocket = nullptr ; tcpSocket = new QTcpSocket(this ); connect(tcpSocket, &QTcpSocket::connected, [=](){ this ->currentInfo += "成功和服务器建立连接\n" ; ui->txRead->setText(this ->currentInfo); }); connect(tcpSocket, &QTcpSocket::disconnected, [=](){ this ->currentInfo += "和服务器连接断开\n" ; ui->txRead->setText(this ->currentInfo); }); connect(tcpSocket, &QTcpSocket::readyRead, [=](){ QByteArray array = tcpSocket->readAll(); this ->currentInfo += QString(array ); this ->currentInfo += "\n" ; ui->txRead->setText(this ->currentInfo); }); } clientwidget::~clientwidget() { delete ui; } void clientwidget::on_btnConnect_clicked(){ QString ip = ui->leIP->text(); quint16 port = ui->lePort->text().toInt(); if (!tcpSocket) { qDebug() << "new socket" ; tcpSocket = new QTcpSocket(this ); } tcpSocket->connectToHost(QHostAddress(ip), port); } void clientwidget::on_btnSend_clicked(){ if (!tcpSocket) { return ; } QString str = ui->txSend->toPlainText(); tcpSocket->write(str.toUtf8().data()); this ->currentInfo += str; ui->txRead->setText(this ->currentInfo); ui->txSend->clear(); } void clientwidget::on_btnClose_clicked(){ if (tcpSocket) { qDebug() << "delete" ; tcpSocket->close(); } }
注意一下:
c和s端注意检查tcp连接是否存活
断开连接并不是要删除原来分配的套接字,只是关闭文件而已,要用close,而不是delete
服务端代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 private slots: void on_btnClose_clicked () ; void on_btnSend_clicked () ; private : Ui::Widget *ui; QString currentInfo; QTcpServer *tcpServer; QTcpSocket *tcpSocket; Widget::Widget(QWidget *parent) : QWidget(parent), ui(new Ui::Widget) { ui->setupUi(this ); this ->setWindowTitle("服务器" ); tcpServer = nullptr ; tcpSocket = nullptr ; tcpServer = new QTcpServer(this ); tcpServer->listen(QHostAddress::Any, 9999 ); connect(tcpServer, &QTcpServer::newConnection, [=](){ tcpSocket =tcpServer->nextPendingConnection(); QString ip = tcpSocket->peerAddress().toString(); quint16 port = tcpSocket->peerPort(); this ->currentInfo += QString("[%1:%2]:成功连接了" ).arg(ip).arg(port); this ->currentInfo += "\n" ; ui->teRead->setText(currentInfo); connect(tcpSocket, &QTcpSocket::readyRead, [=](){ QByteArray array = tcpSocket->readAll(); this ->currentInfo.append("received from [" + ip + ":" + QString::number(port) +"]" + array .data() + "\n" ); ui->teRead->setText(this ->currentInfo); }); connect(tcpSocket, &QTcpSocket::disconnected, [=](){ this ->currentInfo += "断开当前连接\n" ; ui->teRead->setText(this ->currentInfo); }); }); } Widget::~Widget() { delete ui; } void Widget::on_btnClose_clicked(){ if (tcpSocket) { tcpSocket->disconnectFromHost(); tcpSocket->close(); tcpSocket = nullptr ; } } void Widget::on_btnSend_clicked(){ if (!tcpSocket) { return ; } QString str = ui->teWrite->toPlainText()+"\n" ; tcpSocket->write(str.toUtf8().data()); QString ip = tcpSocket->peerAddress().toString(); quint16 port = tcpSocket->peerPort(); this ->currentInfo = this ->currentInfo + "server sent to [" + ip +" : " + QString::number(port) +"]" + str.toUtf8().data(); ui->teRead->setText(this ->currentInfo); ui->teWrite->clear(); }
服务端写完,可以直接用 nc
工具测试, lsof -i : 9999
查看端口号.
Tcp文件传输(简单) 套接字编程 + 网络文件读写。基本是上一个案例的基础上加上网络文件读写处理。
先说要注意的细节:
服务器发送时注意粘包问题,即发送文件头的时候后面跟着数据呢;(tcp会凑够数再发送, 或者延迟发送)
解决方法先读指定长度字节的头部或者发完头部信息,隔一段时间再发送文件数据或者干脆分两次发送
本案例,采用两段式传输,先传输文件头,后传输文件体,也就是客户端第二次触发 readReay 的时候才读取文件体
传输中途断开连接了,选择文件之后断开连接了等情况, 先检查连接,再传输
进度条显示不要越界等
服务器端写的时候处理的好发送量,例如4K为单位;那么客户端读的时候也方便
绑定 readRead, 一定放在 newConnection 里面(没有连接建立的情况下绑定肯定出异常)
下面看看运行的效果以及需要处理的细节:
没有建立连接的情况下,不能选择传输文件: (更不要提发送文件)
连接建立之后,按钮可用:
不管哪一方断开连接,按钮都不可用:
选择文件后,断开连接,要清理连接信息以及所选文件等:
文件发送完毕,服务器端主动断开连接:(这是没有写客户端的时候测试)
文件发送完毕,客户端发送信息通知服务端,然后服务端收到信息后,显示“客户端”已经收到文件: (这里其实,选择文件也应该 setEnable(false);)
中途遇到的小问题记载 : 1.进度条显示应该是 M 为单位,即 1024 的倍数, 结果写错了:
2.重复连续发送文件,第二次传输文件,发现总是受到的文件少一个字节 应该在第二次传输之前,设置为下一次又应该传输文件头了。
3.粘包问题: 客户端给服务器端发送收到的字节序号,每次服务器是分简单批次字节写出,然后可以看到,服务端每次收到的都是服务端分多次写出的数据。 写就是生产者貌似生产多了,消费者接受太慢了?(其实是客户端这边 TCP 发包时,看到字节太少,这里 delay发送,组包,提高传输效率)
详细代码片段如下:
客户端:
服务端:
两次发送,以避免字节序粘包大致逻辑如下:
核心代码如下 :
客户端:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 private slots: void on_btnConnect_clicked () ; protected : void closeEvent (QCloseEvent *) ; private : Ui::ClientWidget *ui; QTcpSocket *tcpSocket; QFile file; QString fileName; quint64 fileSize; quint64 recvSize; bool isHead; ClientWidget::ClientWidget(QWidget *parent) : QWidget(parent), ui(new Ui::ClientWidget) { ui->setupUi(this ); setWindowTitle("客户端" ); ui->progressBar->setValue(0 ); tcpSocket = new QTcpSocket(this ); isHead = true ; connect(tcpSocket, &QTcpSocket::readyRead, [=](){ QByteArray buf = tcpSocket->readAll(); if (isHead) { isHead = false ; fileName = QString(buf).section("##" , 0 , 0 ); fileSize = QString(buf).section("##" , 1 , 1 ).toInt(); recvSize = 0 ; if (fileName.isEmpty() || fileSize == 0 ){ qDebug() << "收到的文件可能是空文件" ; fileName = "NULL" ; fileSize = 1 ; } file.setFileName(fileName); bool isOpen = file.open(QIODevice::WriteOnly); if (!isOpen) { qDebug() << "创建文件出错了" ; tcpSocket->close(); return ; } ui->progressBar->setMinimum(0 ); ui->progressBar->setMaximum(fileSize/1024 ); ui->progressBar->setValue(0 ); QMessageBox::information(this , "文件信息" , QString("即将接收文件: %1, 大小 %2 kb" ) .arg(fileName).arg(fileSize/1024 )); tcpSocket->write("file" ); } else { quint64 len = file.write(buf); recvSize += len; tcpSocket->write( QString::number(recvSize).toUtf8().data()); qDebug() << "粘包问题 Client: " << QString::number(recvSize); ui->progressBar->setValue(recvSize/1024 ); if (recvSize == fileSize) { QMessageBox::information(this , "完成" , "文件接收完成, 通知服务器" ); tcpSocket->write("done" ); isHead = true ; file.close(); tcpSocket->close(); } } }); } ClientWidget::~ClientWidget() { delete ui; } void ClientWidget::on_btnConnect_clicked(){ QString ip = ui->leIP->text(); quint16 port = ui->lePort->text().toInt(); tcpSocket->connectToHost(QHostAddress(ip), port); ui->progressBar->setValue(0 ); } void ClientWidget::closeEvent(QCloseEvent *e){ if (tcpSocket->isOpen()) { tcpSocket->close(); } if (file.isOpen()) { file.close(); } }
服务端:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 private slots: void on_btnFile_clicked () ; void on_btnSend_clicked () ; private : Ui::ServerWidget *ui; QTcpServer *tcpServer; QTcpSocket *tcpSocket; QFile file; QString fileName; quint64 fileSize; quint64 sendSize; QTimer timer; ServerWidget::ServerWidget(QWidget *parent) : QWidget(parent), ui(new Ui::ServerWidget) { ui->setupUi(this ); setWindowTitle("服务器端端口 9999" ); ui->btnFile->setEnabled(false ); ui->btnSend->setEnabled(false ); tcpServer = new QTcpServer(this ); tcpServer->listen(QHostAddress::Any, 9999 ); connect(tcpServer, &QTcpServer::newConnection, [=](){ tcpSocket = tcpServer->nextPendingConnection(); QString ip = tcpSocket->peerAddress().toString(); quint16 port = tcpSocket->peerPort(); ui->textEdit->setText(QString("[%1:%2] 连接成功" ).arg(ip).arg(port)); ui->btnFile->setEnabled(true ); connect(tcpSocket, &QTcpSocket::disconnected, [=](){ qDebug() << "连接已经断开" ; if (tcpSocket) { ui->textEdit->append("当前连接已经断开, 清理有关信息" ); qDebug() << "我也主动断开当前连接" ; tcpSocket->close(); } fileName.clear(); fileSize = 0 ; sendSize = 0 ; ui->btnFile->setEnabled(false ); ui->btnSend->setEnabled(false ); }); connect(tcpSocket, &QTcpSocket::readyRead, [=](){ QString buf(tcpSocket->readAll()); if (buf == "done" ) { ui->textEdit->append("客户端已经完全接受文件" ); file.close(); tcpSocket->close(); ui->btnFile->setEnabled(false ); ui->btnSend->setEnabled(false ); } else if (buf == "file" ) { timer.start(100 ); } else { qDebug() << "粘包问题 Server: " + buf; } }); }); connect(&timer, &QTimer::timeout, [=](){ timer.stop(); sendFile(); }); } ServerWidget::~ServerWidget() { delete ui; } void ServerWidget::on_btnFile_clicked(){ QString filePath = QFileDialog::getOpenFileName(this , "open" , "../" ); if (!filePath.isEmpty()) { fileName.clear(); fileSize = 0 ; sendSize = 0 ; QFileInfo info (filePath) ; fileName = info.fileName(); fileSize = info.size(); file.setFileName(filePath); bool isOpen = file.open(QIODevice::ReadOnly); if (!isOpen) { qDebug() << "只读模式打开文件失败" ; } ui->textEdit->append(filePath + "已经选定, 文件大小:" + QString::number(fileSize)); ui->btnFile->setEnabled(false ); ui->btnSend->setEnabled(true ); } else { qDebug() << "路径无效的处理代码开始执行" ; } } void ServerWidget::on_btnSend_clicked(){ QString head = QString("%1##%2" ).arg(fileName).arg(fileSize); if (tcpSocket->isWritable()) { quint64 len = tcpSocket->write(head.toUtf8()); if (len > 0 ) { } else { qDebug() << "头部信息发送失败, 直接关闭文件" ; file.close(); ui->btnFile->setEnabled(true ); ui->btnSend->setEnabled(false ); } } else { qDebug() << "连接已经断开, 不能发送" ; } } void ServerWidget::sendFile(){ quint64 len = 0 ; if (!file.isOpen()) { qDebug() << "文件都没有打开,发送不了" ; ui->btnFile->setEnabled(true ); ui->btnSend->setEnabled(false ); return ; } do { char buf[4 *1024 ] = {0 }; len = file.read(buf, sizeof (buf)); sendSize += tcpSocket->write(buf, len); } while (len > 0 ); }
Udp通信案例(简单) 一个 peer to peer 的通信程序(相互发送内容, 广播,组播)但广播需要路由支持 .
最终运行效果大致如下: (建立套接字之后就开始发送数据报)
开发过程阶段截图: 1.测试接收: 2.测试发送 3.测试组播 (先加入组播地址才能收到)
核心代码也比较简单,主要是看其流程: (端对端,只写一份代码即可)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 public : void dealMsg () ; private slots: void on_btnClose_clicked () ; void on_btnSend_clicked () ; private : QUdpSocket *udpSocket; Widget::Widget(QWidget *parent) : QWidget(parent), ui(new Ui::Widget) { ui->setupUi(this ); udpSocket = new QUdpSocket(this ); udpSocket->bind(QHostAddress::AnyIPv4, 8888 ); this ->setWindowTitle("我的端口 8888" ); connect(udpSocket, &QUdpSocket::readyRead, this , &Widget::dealMsg); udpSocket->joinMulticastGroup(QHostAddress("224.0.0.2" )); } void Widget::dealMsg(){ char buf[1024 ] = {0 }; QHostAddress cliAddr; quint16 port; qint64 len; len = udpSocket->readDatagram(buf, sizeof (buf), &cliAddr, &port); if (len > 0 ) { QString str = QString("[%1:%2] %3" ) .arg(cliAddr.toString()) .arg((port)) .arg(buf); ui->textEdit->setText(str); } } void Widget::on_btnClose_clicked(){ this ->udpSocket->close(); delete this ->udpSocket; this ->udpSocket = NULL ; this ->close(); } void Widget::on_btnSend_clicked(){ QString ip = ui->leIP->text(); quint16 port = ui->lePort->text().toInt(); QString str = ui->textEdit->toPlainText(); udpSocket->writeDatagram(str.toUtf8(), QHostAddress(ip), port); } Widget::~Widget() { delete ui; }
Merlin 2017.2 客户端编程谈什么传输层 : )