QT5 TCP网络通讯
- 服务器与客户端建立连接listen() - connectToHost(); 触发newPendingConnect信号
- 实时数据通讯write(); read(); 触发readyRead信号
通讯主要使用的类:
QTcpServer Class
QTcpServer类提供了一个基于TCP的服务器。
这个类可以接受传入的TCP连接。您可以指定端口或让QTcpServer自动选择一个端口。您可以收听特定地址或所有机器的地址。
调用listen()让服务器侦听传入的连接。每次客户端连接到服务器时,都会发出newConnection()信号。
QTcpSocket Class
QTcpSocket类提供了一个TCP套接字。
TCP(传输控制协议)是一种可靠的,面向流的,面向连接的传输协议。 它特别适合连续传输数据。
QTcpSocket是QAbstractSocket的一个方便的子类,它允许你建立一个TCP连接并传输数据流。
建立连接:
服务器端以监听的方式监听客服端是否有连接请求
客户端以调用connectToHost()函数主动连接服务器端
tcp协议服务器端实现流程
建立服务器对象
listen服务器, 通过建立的服务器 监听指定地址/端口的客服端;判断是否有客户连接有连接就触发newConnection();
通过connect处理newConnection()信号;
server = new QTcpServer(this); //建立一个服务器对象 server->listen(QHostAddress::Any, 8000);//通过建立的服务器监听指定ip地址及端口号的客服端,如不指定端口号,系统会随机分配 connect(server, QTcpServer::newConnection, [=]() { qDebug() << "有连接进来"; } );
tcp协议客户端实现流程
建立QTcpSocket套节字(ip,端口)
通过套节字connectToHost()函数主动连接服务器;连接成功则触发服务器QTcpServer::newConnection信号;并发送套节字到服务器端;
关闭连接;
QTcpSocket Sc(this); Sc.connectToHost("127.0.0.1", 8888);//实际代码中参数要进行类型转化 Sc.close();
实时通讯:
- 客户端到服务器端通讯
- 当客户端与服务器端建立连接后;
- 客户端与服务器端通讯在客户端通过套节字对象调用write()函数发送上传内容;
- 服务器端有客户端数据写入时服务器端会自动调用readyread信号
- 服务器端在connect中处理readyread信号,并由nextPendingConnection()函数接收客户端发送的套节字;
- 服务器端对接收的套节字进行相应处理,即完成一次客户端到服务器端的通讯
- 服务器端到客户端的通讯
- 当客户端与服务器端建立连接后;
- 服务器通过套节字对象调用write()函数发送上传内容;客户端会触发readyread信号
- 客户端在connect中处理readyread信号
客户端到服务器端实现代码:
//服务器端 connect(server, QTcpServer::newConnection, [=]() { QTcpSocket socket = server->nextPendingConnection(); connect(socket, &QTcpSocket::readyRead, [=]() { tp = socket->readAll(); ui->textrc->append(tp); }); } );
//客户端 void socket::on_buttonsend_clicked() { QString temp = ui->textrc->toPlainText(); if(!temp.isEmpty())sock->write(temp.toUtf8()); }
服务器端到客户端实现代码:
//服务器端 void Widget::on_buttonsend_clicked() { socket->write(ui->textEdit->toPlainText().toUtf8()); }
//客户端 connect(sock, &QTcpSocket::readyRead, [=]() { ui->textdis->append(sock->readAll()); });
完整代码:
服务器ui设计:
服务器端头文件widget.h
#ifndef WIDGET_H #define WIDGET_H #include <QWidget> #include <QTcpServer> #include <QTcpSocket> namespace Ui { class Widget; } class Widget : public QWidget { Q_OBJECT public: explicit Widget(QWidget *parent = 0); ~Widget(); private slots: void on_buttonsend_clicked(); private: Ui::Widget *ui; QTcpServer *server; //建立服务器对象 QTcpSocket *socket; //套节字对象 QByteArray tp; // }; #endif // WIDGET_H
服务器端cpp文件 widget.cpp
#include "widget.h" #include "ui_widget.h" #include <QDebug> Widget::Widget(QWidget *parent) : QWidget(parent), ui(new Ui::Widget) { ui->setupUi(this); setWindowTitle("服务器"); tp = nullptr; server = new QTcpServer(this); server->listen(QHostAddress::Any, 8000); connect(server, QTcpServer::newConnection, [=]() { socket = server->nextPendingConnection(); connect(socket, &QTcpSocket::readyRead, [=]() { tp = socket->readAll(); ui->testdis->append(tp); }); } ); } Widget::~Widget() { delete ui; } void Widget::on_buttonsend_clicked() { socket->write(ui->textEdit->toPlainText().toUtf8()); }
客户端ui:
客户端头文件socket.h:
#ifndef SOCKET_H #define SOCKET_H #include <QWidget> #include <QTcpSocket> #include <QHostAddress> namespace Ui { class socket; } class socket : public QWidget { Q_OBJECT public: explicit socket(QWidget *parent = 0); ~socket(); private slots: void on_buttonLink_clicked(); void on_buttonsend_clicked(); void on_serverclose_clicked(); private: Ui::socket *ui; QTcpSocket *sock; QHostAddress adrs; quint16 port; }; #endif // SOCKET_H
客户端cpp文件socket.cpp
#include "socket.h" #include "ui_socket.h" socket::socket(QWidget *parent) : QWidget(parent), ui(new Ui::socket) { ui->setupUi(this); sock = new QTcpSocket(this); setWindowTitle("张三"); connect(sock, &QTcpSocket::readyRead, [=]() { ui->textdis->append(sock->readAll()); }); } socket::~socket() { delete ui; } void socket::on_buttonLink_clicked() { QString ip = ui->serverIP->text(); QString p = ui->serverPort->text(); sock->connectToHost(ip, p.toUShort()); } void socket::on_buttonsend_clicked() { QString temp = ui->textEdit->toPlainText(); if(!temp.isEmpty())sock->write(temp.toUtf8()); } void socket::on_serverclose_clicked() { sock->close(); }
main.cpp文件
#include "widget.h" #include <QApplication> #include "socket.h" int main(int argc, char *argv[]) { QApplication a(argc, argv); Widget w; w.show(); socket w1; w1.show(); return a.exec(); }
最终运行效果:
当然在具体的实现过程中还有很多很多的细节需要优化;
QT5对tcp协议基本的通讯总结:
- QTcpServer *p = new QTcpServer(this);//建立服务器对象 QTcpSocket *q = new QTcpSocket(this); //客户机建立套节字对象
- p.listen(监听的客户ip , 监听端口port);//监听客户机 q.conncetToHost(要连接的服务器ip, 要连接的服务器端口);
- connect(p, &QTcpServer::newConnection, )连接成功触发信号 q.write();//发送数剧 到服务器
- QTcpSocket skt = p.nextPendingConnection();//获取客户机套节字 connect(q, &QTcpSocket::readyRead, )//服务端发送数据客户端触发信号
- connect(skt, &QTcpSocket::readyRead, )//客户发送数据触发信号 q.readall();//读取客户端发送的数据;
- skt.readall();//读取客户端发送的数据; 客户端处理数据
- 服务器端处理数据
QT5 UDP网络通讯
UDP没有服务器与客户端之分;单纯通过writeDatagram发( 参数1, 参数2,参数3)送指定的内容(参数1)到指定的ip(参数2),端口(参数3)上;
当收取到网络中的数据发送,就会触发自己的readyRead信号;readDatagram(参数1, 参数2,参数3),保存读取的内容(参数1);保存对方ip(参数2);保存对方端口(参数3)
具体实现过程:
- 建立QUdpSocket套节字 QUdpSocket* p = new QUdpSocket(this);
- 绑定本程序端口号 bind() p.bind(8000);
- 通过writeDatagram()发送数据到指定目标 p.writeDatagram();
- 当有readyRead信号发生通过readDatagram()函数读取保存数据 p.readDatagram();
实现代码:
QUdpSocket *udp = new QUdpSocket(this); udp->bind(8000); connect(udp, &QUdpSocket::readyRead, [=]() { char temp[1024] = {0}; QHostAddress q; quint16 p; udp->readDatagram(temp, sizeof(temp), &q, &p); ui->textdis->append(temp); }); /......./ //按键确定发送 void Widget::on_buttonlink_clicked() { udp->writeDatagram(ui->textsend->toPlainText().toUtf8(), QHostAddress(ui->ip->text()), ui->port->text().toUShort()); }
整体代码:
ui设计:
widget.h头文件
#ifndef WIDGET_H #define WIDGET_H #include <QWidget> #include <QUdpSocket> namespace Ui { class Widget; } class Widget : public QWidget { Q_OBJECT public: explicit Widget(QWidget *parent = 0); ~Widget(); private slots: void on_buttonlink_clicked(); private: Ui::Widget *ui; QUdpSocket *udp; }; #endif // WIDGET_H
widget.cpp
#include "widget.h" #include "ui_widget.h" #include <QHostAddress> #include <QDialog> Widget::Widget(QWidget *parent) : QWidget(parent), ui(new Ui::Widget) { ui->setupUi(this); setWindowTitle("8000"); udp = new QUdpSocket(this); udp->bind(8000); udp->joinMulticastGroup(QHostAddress("")); connect(udp, &QUdpSocket::readyRead, [=]() { char temp[1024] = {0}; QHostAddress q; quint16 p; udp->readDatagram(temp, sizeof(temp), &q, &p); ui->textdis->append(temp); }); } Widget::~Widget() { delete ui; } void Widget::on_buttonlink_clicked() { udp->writeDatagram(ui->textsend->toPlainText().toUtf8(), QHostAddress(ui->ip->text()), ui->port->text().toUShort()); }
main.cpp文件
#include "widget.h" #include <QApplication> int main(int argc, char *argv[]) { QApplication a(argc, argv); Widget w; w.show(); return a.exec(); }
以便测试:我们先编译生成一份客户端;再在源文件中改变bind端口号为8888再生成一份客户端;最终就会有两份客户端以便相互通信测试
运行测试结果:
由于UDP不需要服务器,所以,UDP发送的数据,只要能接收到你的ip及端口的客户端就殾能收到信息;所以在局域网内,ip地址栏可输入
255.255.255.255 即整个局域网内的客户端都能收到信息;
UDP通讯组包
为了满足,发送的信息指定ip段内的客户收到信息可以用函数JoinMulticastGroup(IPAddress)加入到组;根据msdn记载,没错是同样的功能;
目前个人理解也不深;详细数据可查msdn;可用leaveMulticastGroup()函数离开组翻;
Tcp 与 Udp的比较:
Udp不需要服务器,只管发送数据,不对数据进行检查,也不对接收者检测;速度快,易丢包,做即时数据传送比较好;
Tcp方式总结:
服务器端:QTcpServer
ss->peerAddress();ss->peerName();ss->peerPort(); 当连接成功可能通过函数获取客户端ip,name,port;
QT5 TCP网络通讯综合案例之文件传送
最终效果图:
1.功能设计
服务器端:
- 有客户端连接进入就提示连接客户ip端口号等基本信息
- [选择文件]按钮被激活可在系统选取任意文件,显示选取的文件路径
- [发送]按钮被激活,点击[发送]向客户端发送文件请求;
- 文件发送完成后 显示发送完成提示
客户端:
- 连接服务器ip,port编辑框
- 按钮[连接服务器]成功后显示提示并激活断开服务器按钮
- 可选文件接收保存路径
- 当收到服务器发送文件请求,显示所要接收的文件名,文件大小等基本信息
- 仅当收到服务器发送的文件请求与选择了有效文件路径前提下,激活[接收文件]按钮
- 点击[接收文件]开始从服务器接收文件,文件接收进度条提示功能,并显示文件传送完成提示
实现原代码:
//main主函数 #include "widget.h" #include "client.h" #include <QApplication> int main(int argc, char *argv[]) { QApplication a(argc, argv); Widget w; w.show(); client w1; w1.show(); return a.exec(); }
//服务器端头文件 #ifndef WIDGET_H #define WIDGET_H #include <QWidget> #include <QTcpServer> #include <QTcpSocket> #include <QTimer> #include <QFile> namespace Ui { class Widget; } class Widget : public QWidget { Q_OBJECT public: explicit Widget(QWidget *parent = 0); ~Widget(); private slots: void on_buttoncheck_clicked(); void on_buttonsend_clicked(); private: Ui::Widget *ui; QTcpServer *sv; QTcpSocket *ss; QString sendFileName; qint64 sendFileSize; qint64 sendedSize; QFile sendFile; }; #endif // WIDGET_H
//服务器cpp #include "widget.h" #include "ui_widget.h" #include <QFileDialog> #include <QFile> #include <QMessageBox> Widget::Widget(QWidget *parent) : QWidget(parent), ui(new Ui::Widget) { ui->setupUi(this); setWindowTitle("服务端-端口:9527"); sv = new QTcpServer(this); sv->listen(QHostAddress::Any, 9527); ui->buttonsend->setEnabled(false); ui->buttoncheck->setEnabled(false); sendedSize = 0; connect(sv, &QTcpServer::newConnection, [=]() { ss = sv->nextPendingConnection(); if(ss->isValid()) { ui->buttoncheck->setEnabled(true); QString pAd = ss->peerAddress().toString().remove(0, 7); QString pNa = ss->peerName(); quint16 pPo = ss->peerPort(); ui->textdis->setText(QString("已成功连接到用户IP:%1:%2:Name:%3").arg(pAd).arg(pPo).arg(pNa)); }else { ui->textdis->setText("连接套节字无效"); } connect(ss, &QTcpSocket::readyRead,[=]() { if(ss->readAll() == "ok") { qint64 lenW = 0; if(sendFile.open(QIODevice::ReadOnly)) { do{ char tempdata[1024] = {0}; lenW = sendFile.read(tempdata, sizeof(tempdata)); ss->write(tempdata, lenW); sendedSize += lenW; }while(lenW > 0); if(sendedSize >= sendFileSize) { ui->textdis->append("发送完成"); ss->disconnectFromHost(); sendFile.close(); } }else { QMessageBox *abot = new QMessageBox(this); abot->setText("打开文件失败"); } } }); }); } Widget::~Widget() { delete ui; } void Widget::on_buttoncheck_clicked() { QString File = QFileDialog::getOpenFileName(this, QString("选择文件"), QString("../")); if(!File.isEmpty()) { ui->buttoncheck->setEnabled(false); ui->textdis->append(File); sendFile.setFileName(File); QFileInfo info(File); sendFileName = info.fileName(); sendFileSize = info.size(); ui->buttonsend->setEnabled(true); } } void Widget::on_buttonsend_clicked() { QString temp = QString("head@@%1@@%2").arg(sendFileName).arg(sendFileSize); ss->write(temp.toUtf8()); ui->buttonsend->setEnabled(false); }
//客户端头文件 #ifndef CLIENT_H #define CLIENT_H #include <QWidget> #include <QTcpSocket> #include <QFile> namespace Ui { class client; } class client : public QWidget { Q_OBJECT public: explicit client(QWidget *parent = 0); ~client(); private slots: void on_Buttonlink_clicked(); void on_Buttonclose_clicked(); void on_ButtonSelect_clicked(); void on_ButtonSave_clicked(); private: Ui::client *ui; QTcpSocket userSocket; QFile acpFile; QString headFile; QString savePath; bool head; bool openFile; QString _name; QString _size; qint64 nowsize; }; #endif // CLIENT_H
//客户端cpp #include "client.h" #include "ui_client.h" #include <QFile> #include <QFileDialog> #include <QMessageBox> client::client(QWidget *parent) : QWidget(parent), ui(new Ui::client) { ui->setupUi(this); setWindowTitle("客户端"); head = true; openFile =false; nowsize = 0; ui->progressBar->setMinimum(0); ui->progressBar->setValue(0); ui->ButtonSave->setEnabled(false); ui->Buttonclose->setEnabled(false); savePath = nullptr; connect(&userSocket, &QTcpSocket::connected, [=](){ ui->Buttonlink->setEnabled(false); ui->Buttonclose->setEnabled(true); ui->lineStatus->setText("连接成功");}); connect(&userSocket, &QTcpSocket::readyRead, [=](){ if(head) { headFile = QString(userSocket.readAll()); if(!headFile.isEmpty()) { head = false; _name = headFile.section("@@", 1, 1); _size = headFile.section("@@", 2, 2); ui->progressBar->setMaximum(_size.toLongLong()); ui->lineStatus->setText(QString("文件名:[%1]总大小:[%2]等待接收").arg(_name).arg(_size)); acpFile.setFileName(QString("%1/%2").arg(savePath).arg(_name)); if(!savePath.isEmpty()) { ui->ButtonSave->setEnabled(true); } } } else { if(openFile) { nowsize += acpFile.write(userSocket.readAll()); ui->progressBar->setValue(nowsize); } else { QMessageBox *abot = new QMessageBox(this); abot->setText("打开文件失败"); } if(acpFile.size() >= _size.toLongLong()) { nowsize = 0; acpFile.close(); ui->lineStatus->setText("文件传送完成"); head = true; } } }); } client::~client() { delete ui; } void client::on_Buttonlink_clicked() { QString linkIp = ui->lineIp->text(); quint16 linkPort = ui->linePort->text().toUShort(); userSocket.connectToHost(linkIp, linkPort); } void client::on_Buttonclose_clicked() { userSocket.disconnectFromHost(); userSocket.close(); ui->lineStatus->setText("断开连接"); head = true; ui->Buttonlink->setEnabled(true); ui->Buttonclose->setEnabled(false); ui->progressBar->setValue(0); } void client::on_ButtonSelect_clicked() { savePath = QFileDialog::getExistingDirectory(this, QString("保存路径"),QString("../")); if(!savePath.isEmpty()) { ui->linePath->setText(savePath); if(!head)ui->ButtonSave->setEnabled(true); } } void client::on_ButtonSave_clicked() { ui->ButtonSave->setEnabled(false); userSocket.write("ok"); openFile = acpFile.open(QIODevice::WriteOnly); }
主要运用函数:
-
QString QFileDialog::getExistingDirectory() //静态函数 获取文件路径
-
QString QFileDialog::getOpenFileName()//静态函数 获取要打开文件的文件路径文件名
-
QTcpSocket -> isValid(); //如果套节字有效返回true,否则返回false;
-
void setenable(); //设置控件激活状态
注意事项:
在服务器端发送文件,采取的是分段读取文件,边读边发的方式发送,而在客户端采取每次接收每次发送的全部内容方式保存文件
起初有采取过在服务器端all = readAall();write(all);一次性读取发送;但在测试中发现,实际发送大文件时还是会自动被分成多次(只是简单测试,不排除受其他因素影响)