QT学习笔记(12) QT下的TCP通信
一、TCP通信过程
(1)服务器端:
服务器端有QTcpServer的监听套接字,运用listen()方法监听网卡的ip和端口。
如果有新的连接传过来,并且连接成功,服务器会触发newConnection(),通过槽函数取出连接过来的通信套接字QTcpSocket
如果有数据成功传送过来,对方的通信套接字QTcpSocket会触发readyRead(),通过槽函数可以对接收的数据进行处理
(2)客户端
首先根据ip和端口,通过通信套接字QTcpSocket的connectToHost()方法主动和服务器建立连接
如果连接成功,通信套接字QTcpSocket会自动触发connected(),通过槽函数可以进行操作
如果有数据成功传送过来,对方的通信套接字QTcpSocket会触发readyRead(),通过槽函数可以对接收的数据进行处理
二、实例代码如下:
QT_HelloWorld11.pro
1 #------------------------------------------------- 2 # 3 # Project created by QtCreator 2017-08-30T21:18:55 4 # 5 #------------------------------------------------- 6 7 QT += core gui network #添加network模块 8 9 greaterThan(QT_MAJOR_VERSION, 4): QT += widgets 10 11 TARGET = QT_HelloWorld11 12 TEMPLATE = app 13 14 15 SOURCES += main.cpp\ 16 serverwidget.cpp \ 17 clientwidget.cpp 18 19 HEADERS += serverwidget.h \ 20 clientwidget.h 21 22 FORMS += serverwidget.ui \ 23 clientwidget.ui 24 25 CINFIG += C++11
main.cpp
1 #include "serverwidget.h" 2 #include <QApplication> 3 //包含头文件 4 #include "clientwidget.h" 5 6 7 int main(int argc, char *argv[]) 8 { 9 QApplication a(argc, argv); 10 //显示服务器窗口 11 ServerWidget w; 12 w.show(); 13 14 //显示客户端窗口 15 ClientWidget w2; 16 w2.show(); 17 18 return a.exec(); 19 }
serverwidget.h
1 #ifndef SERVERWIDGET_H 2 #define SERVERWIDGET_H 3 4 #include <QWidget> 5 #include <QTcpServer>//监听套接字 6 #include <QTcpSocket>//通信套接字(建立好连接的套接字) 7 8 namespace Ui { 9 class ServerWidget; 10 } 11 12 class ServerWidget : public QWidget 13 { 14 Q_OBJECT 15 16 public: 17 explicit ServerWidget(QWidget *parent = 0); 18 ~ServerWidget(); 19 20 private slots: 21 void on_button_send_clicked(); 22 23 void on_button_close_clicked(); 24 25 private: 26 Ui::ServerWidget *ui; 27 28 QTcpServer * tcpServer;//监听套接字 29 QTcpSocket * tcpSocket;//通信套接字 30 31 }; 32 33 #endif // SERVERWIDGET_H
clientwidget.h
1 #ifndef CLIENTWIDGET_H 2 #define CLIENTWIDGET_H 3 4 #include <QWidget> 5 #include <QTcpSocket>//通信套接字 6 7 namespace Ui { 8 class ClientWidget; 9 } 10 11 class ClientWidget : public QWidget 12 { 13 Q_OBJECT 14 15 public: 16 explicit ClientWidget(QWidget *parent = 0); 17 ~ClientWidget(); 18 19 private slots: 20 void on_pushButton_connect_clicked(); 21 22 void on_pushButton_send_clicked(); 23 24 void on_pushButton_close_clicked(); 25 26 private: 27 Ui::ClientWidget *ui; 28 29 QTcpSocket *tcpSocket; 30 }; 31 32 #endif // CLIENTWIDGET_H
serverwidget.cpp
1 #include "serverwidget.h" 2 #include "ui_serverwidget.h" 3 #include <QTcpServer>//监听套接字 4 #include <QTcpSocket>//通信套接字(建立好连接的套接字) 5 6 ServerWidget::ServerWidget(QWidget *parent) : 7 QWidget(parent), 8 ui(new Ui::ServerWidget) 9 { 10 ui->setupUi(this); 11 12 setWindowTitle(QString::fromLocal8Bit("服务器:8888")); 13 14 tcpServer = NULL;//先赋值为空,在后面做空值判断,防止空指针的错误 15 tcpSocket = NULL; 16 17 //实例化 监听套接字 18 tcpServer = new QTcpServer(this);//指定父对象,让其自动回收空间 19 //监听 20 tcpServer->listen(QHostAddress::Any,8888);//默认绑定当前网卡上的所有ip 21 22 //如果有新的连接传过来,并连接成功,服务器触发newConnection()方法 23 connect(tcpServer,&QTcpServer::newConnection, 24 [=]() 25 { 26 //取出建立好连接的套接字 27 tcpSocket = tcpServer->nextPendingConnection();//取出当前最近的一次连接的套接字 28 //获取对方(客户端)的IP和端口 29 QString ip = tcpSocket->peerAddress().toString(); 30 qint16 port = tcpSocket->peerPort(); 31 QString temp = QString("[%1:%2]:成功连接").arg(ip).arg(port);//组包 32 //在服务器端显示 33 ui->textEdit_read->setText(temp); 34 35 //此处不能直接放在构造函数中,因为如果直接放在那,还没分配tcpSocket空间,会程序异常 36 //如果有数据传送成功,对方的通信套接字会触发readyRead(),需要在对应的槽函数做接收处理 37 connect(tcpSocket,&QTcpSocket::readyRead, 38 [=]() 39 { 40 //从通信套接字中取出内容 41 QByteArray array = tcpSocket->readAll(); 42 //然后将内容追加到显示文本中 43 ui->textEdit_read->append(array); 44 } 45 ); 46 47 //如果对方主动断开连接,通信套接字会自动触发disconnected() 48 connect(tcpSocket,&QTcpSocket::disconnected, 49 [=]() 50 { 51 ui->textEdit_read->append(QString::fromLocal8Bit("对方主动断开连接")); 52 } 53 ); 54 } 55 ); 56 57 } 58 59 ServerWidget::~ServerWidget() 60 { 61 delete ui; 62 } 63 64 void ServerWidget::on_button_send_clicked() 65 { 66 if(tcpSocket == NULL) 67 { 68 return; 69 } 70 71 //获取编辑区内容 72 QString str = ui->textEdit_write->toPlainText(); 73 //给对方发送数据,使用套接字是tcpSocket 74 tcpSocket->write(str.toUtf8().data()); 75 } 76 77 void ServerWidget::on_button_close_clicked() 78 { 79 if(tcpSocket == NULL) 80 { 81 return; 82 } 83 //主动和客户端断开连接 84 tcpSocket->disconnectFromHost(); 85 tcpSocket->close(); 86 87 tcpSocket = NULL; 88 }
clientwidget.cpp
1 #include "clientwidget.h" 2 #include "ui_clientwidget.h" 3 //需要包含头文件 4 #include <QHostAddress> 5 6 ClientWidget::ClientWidget(QWidget *parent) : 7 QWidget(parent), 8 ui(new Ui::ClientWidget) 9 { 10 ui->setupUi(this); 11 setWindowTitle(QString::fromLocal8Bit("客户端")); 12 13 tcpSocket = NULL; 14 15 //分配空间,指定父对象 16 tcpSocket = new QTcpSocket(this); 17 18 //当tcpSocket套接字建立连接成功 19 //如果成功和对方建立连接,通信套接字会自动触发connected() 20 connect(tcpSocket,&QTcpSocket::connected, 21 [=]() 22 { 23 ui->textEdit_read->setText(QString::fromLocal8Bit("成功和服务器建立连接")); 24 } 25 ); 26 27 //如果有数据传送成功,对方的通信套接字会触发readyRead(),需要在对应的槽函数做接收处理 28 connect(tcpSocket,&QTcpSocket::readyRead, 29 [=]() 30 { 31 //从通信套接字中取出内容 32 QByteArray array = tcpSocket->readAll(); 33 //然后将内容追加到显示文本中 34 ui->textEdit_read->append(array); 35 } 36 ); 37 38 //如果对方主动断开连接,通信套接字会自动触发disconnected() 39 connect(tcpSocket,&QTcpSocket::disconnected, 40 [=]() 41 { 42 ui->textEdit_read->append(QString::fromLocal8Bit("对方主动断开连接")); 43 } 44 ); 45 46 } 47 48 ClientWidget::~ClientWidget() 49 { 50 delete ui; 51 } 52 53 void ClientWidget::on_pushButton_connect_clicked() 54 { 55 //获取服务器ip和端口 56 QString ip = ui->lineEdit_IP->text(); 57 qint16 port = ui->lineEdit_port->text().toInt(); 58 59 //主动和服务器建立连接 60 tcpSocket->connectToHost(QHostAddress(ip),port); 61 } 62 63 void ClientWidget::on_pushButton_send_clicked() 64 { 65 //获取编辑框内容 66 QString str = ui->textEdit_Write->toPlainText(); 67 //发送数据 68 tcpSocket->write( str.toUtf8().data() ); 69 } 70 71 void ClientWidget::on_pushButton_close_clicked() 72 { 73 //主动和服务器断开连接 74 tcpSocket->disconnectFromHost(); 75 tcpSocket->close(); 76 }
serverwidget.ui
clientwidget.ui
三、TCP传递文件
实现以下功能:
客户端连接到服务器
服务器端选择文件,然后发送
客户端接收文件,并提示接收成功
大概流程图如下:
代码如下:
QT_HelloWorld14.pro
1 #------------------------------------------------- 2 # 3 # Project created by QtCreator 2017-08-31T19:08:18 4 # 5 #------------------------------------------------- 6 7 QT += core gui network 8 9 greaterThan(QT_MAJOR_VERSION, 4): QT += widgets 10 11 TARGET = QT_HelloWorld14 12 TEMPLATE = app 13 14 15 SOURCES += main.cpp\ 16 serverwidget.cpp \ 17 clientwidget.cpp 18 19 HEADERS += serverwidget.h \ 20 clientwidget.h 21 22 FORMS += serverwidget.ui \ 23 clientwidget.ui 24 25 CONFIG += C++11
main.cpp
1 #include "serverwidget.h" 2 #include <QApplication> 3 #include "clientwidget.h" 4 5 int main(int argc, char *argv[]) 6 { 7 QApplication a(argc, argv); 8 ServerWidget w; 9 w.show(); 10 //显示客户端窗口 11 ClientWidget w2; 12 w2.show(); 13 14 return a.exec(); 15 }
clientwidget.h
1 #ifndef CLIENTWIDGET_H 2 #define CLIENTWIDGET_H 3 4 #include <QWidget> 5 #include <QTcpSocket> 6 #include <QFile> 7 8 namespace Ui { 9 class ClientWidget; 10 } 11 12 class ClientWidget : public QWidget 13 { 14 Q_OBJECT 15 16 public: 17 explicit ClientWidget(QWidget *parent = 0); 18 ~ClientWidget(); 19 20 private slots: 21 void on_pushButton_connect_clicked(); 22 23 private: 24 Ui::ClientWidget *ui; 25 QTcpSocket *tcpSocket;//通信套接字 26 27 QFile file;//文件对象 28 QString fileName;//文件名字 29 qint64 fileSize;//文件大小 30 31 qint64 receiveSize;//已经接收文件的大小 32 bool isStart ;//标志位,判断是不是开始的部分(文件名和文件大小) 33 }; 34 35 #endif // CLIENTWIDGET_H
serverwidget.h
1 #ifndef SERVERWIDGET_H 2 #define SERVERWIDGET_H 3 4 #include <QWidget> 5 #include <QTcpServer>//监听套接字 6 #include <QTcpSocket>//通信套接字 7 #include <QFile> 8 #include <QTimer> 9 10 11 namespace Ui { 12 class ServerWidget; 13 } 14 15 class ServerWidget : public QWidget 16 { 17 Q_OBJECT 18 19 public: 20 explicit ServerWidget(QWidget *parent = 0); 21 ~ServerWidget(); 22 23 private slots: 24 void on_pushButton_file_clicked(); 25 26 void on_pushButton_send_clicked(); 27 28 void sendDate();//发送文件数据 29 30 private: 31 Ui::ServerWidget *ui; 32 QTcpServer *tcpServer;//监听套接字 33 QTcpSocket *tcpSocket;//通信套接字 34 35 QFile file;//文件对象 36 QString fileName;//文件名字 37 qint64 fileSize;//文件大小 38 39 qint64 sendSize;//已经发送文件的大小 40 QTimer timer; //定时器,用来间隔头文件和文件正文 41 }; 42 43 #endif // SERVERWIDGET_H
clientwidget.cpp
1 #include "clientwidget.h" 2 #include "ui_clientwidget.h" 3 #include <QTcpSocket> 4 #include <QByteArray> 5 #include <QDebug> 6 #include <QFile> 7 #include <QMessageBox> 8 #include <QHostAddress> 9 #include <QIODevice>//需要包含此头文件,防止出现"device not open"的错误 10 11 ClientWidget::ClientWidget(QWidget *parent) : 12 QWidget(parent), 13 ui(new Ui::ClientWidget) 14 { 15 ui->setupUi(this); 16 17 setWindowTitle(QString::fromLocal8Bit("客户端")); 18 tcpSocket = new QTcpSocket(this); 19 isStart = true;//刚开始时,为true 20 ui->progressBar->setValue(0);//初始化,进度条为0 21 22 //数据传送过来成功,自动触发QTcpSocket::readyRead信号 23 connect(tcpSocket,&QTcpSocket::readyRead, 24 [=]() 25 { 26 //取出接收的内容 27 QByteArray buf = tcpSocket->readAll(); 28 //接收头文件 29 if(isStart == true) 30 { 31 //接收头文件 32 isStart = false; 33 //解析头部信息 (文件名##文件大小) 34 //拆包 35 //初始化工作 36 fileName = QString(buf).section("##",0,0);//以"##"分割,获取从第1部分开始,到第1部分结束的字符串 37 fileSize = QString(buf).section("##",1,1).toInt(); 38 receiveSize = 0; 39 qDebug() << QString::fromLocal8Bit("客户端接收的头文件:%1 ").arg(fileName) ; 40 41 //打开文件,并向里面写内容 42 file.setFileName(fileName);//关联文件名字 43 bool isOK = file.open(QIODevice::WriteOnly); 44 if(isOK == false)//如果打开文件失败,中断函数 45 { 46 qDebug() << "WriteOnly error 37" ; 47 tcpSocket->disconnectFromHost();//断开连接 48 tcpSocket->close();//关闭套接字 49 return; 50 } 51 52 //弹出对话框,显示接收文件的信息 53 //QString str = QString::fromLocal8Bit("接收的文件:[%1 %2kb]").arg(fileName).arg(fileSize); 54 //QMessageBox::information(this,QString::fromLocal8Bit("文件信息"),str); 55 56 //设置进度条 57 ui->progressBar->setMinimum(0);//最小值 58 ui->progressBar->setMaximum(fileSize);//最大值,因为进度条最大值是int类型,防止范围不够用,最好除以一个数,使最大的范围变小一些 59 ui->progressBar->setValue(0);//当前值 60 61 } 62 //接收文件正文 63 else 64 { 65 //接收文件正文 66 qint64 len = file.write(buf);//向新文件中写buf数据,并返回写入的数据长度 67 if(len > 0) 68 { 69 //计算累计接收的数据大小 70 receiveSize += len; 71 //每接收一部分数据,就向服务器端发送一个信息 72 //QString str = QString::number(receiveSize); 73 //tcpSocket->write( str.toUtf8().data() ); 74 } 75 //更新进度条 76 //ui->progressBar->setValue(receiveSize);//当前值 77 //接收数据完成 78 if(receiveSize == fileSize) 79 { 80 //先给服务器发送(接收文件完成的信息) 81 //tcpSocket->write("file done"); 82 //弹出对话框,提示文件接收完成 83 QMessageBox::information(this,"done",QString::fromLocal8Bit("文件接收完成 50")); 84 file.close();//关闭文件 85 tcpSocket->disconnectFromHost();//断开连接 86 tcpSocket->close(); 87 } 88 } 89 } 90 ); 91 } 92 93 ClientWidget::~ClientWidget() 94 { 95 delete ui; 96 } 97 98 void ClientWidget::on_pushButton_connect_clicked() 99 { 100 //获取服务器的ip和端口 101 QString ip = ui->lineEdit_ip->text(); 102 qint16 port = ui->lineEdit_port->text().toInt(); 103 104 //主动和服务器建立连接 105 tcpSocket->connectToHost(QHostAddress(ip),port); 106 }
serverwidget.cpp
1 #include "serverwidget.h" 2 #include "ui_serverwidget.h" 3 #include <QHostAddress> 4 #include <QFileDialog> 5 #include <QFileInfo> 6 #include <QDebug> 7 #include <QTimer> 8 #include <QIODevice> 9 10 ServerWidget::ServerWidget(QWidget *parent) : 11 QWidget(parent), 12 ui(new Ui::ServerWidget) 13 { 14 ui->setupUi(this); 15 setWindowTitle(QString::fromLocal8Bit("服务器端口为:9999")); 16 17 //两个按钮都不能按 18 ui->pushButton_file->setEnabled(false); 19 ui->pushButton_send->setEnabled(false); 20 21 //监听套接字 22 tcpServer = new QTcpServer(this); 23 //监听 24 tcpServer->listen(QHostAddress::Any,9999); 25 //如果客户端成功和服务器连接 26 //tcpServer会自动触发newConnection() 27 connect(tcpServer,&QTcpServer::newConnection, 28 [=]() 29 { 30 //取出建立好连接的套接字 31 tcpSocket = tcpServer->nextPendingConnection(); 32 //获取对方的ip和端口 33 QString ip = tcpSocket->peerAddress().toString(); 34 quint16 port = tcpSocket->peerPort(); 35 //QString str = QString("[%1:%2] 成功连接").arg(ip).arg(port); 36 QString str = QString::fromLocal8Bit("[%1:%2] 成功连接").arg(ip).arg(port); 37 //显示到编辑区 38 ui->textEdit->setText(str); 39 40 //成功连接后,才能按选择文件按钮 41 ui->pushButton_file->setEnabled(true); 42 43 //此处不能直接放在构造函数中,因为如果直接放在那,还没分配tcpSocket空间,会程序异常 44 //如果有数据传送成功,对方的通信套接字会触发readyRead(),需要在对应的槽函数做接收处理 45 /*connect(tcpSocket,&QTcpSocket::readyRead, 46 [=]() 47 { 48 //从通信套接字中取出内容 49 QByteArray array = tcpSocket->readAll(); 50 //如果接收到"文件接收完毕"的信号 51 if(QString(array) == "file done") 52 { 53 ui->textEdit->append(QString::fromLocal8Bit("文件发送完毕")); 54 file.close();//关闭文件 55 tcpSocket->disconnectFromHost();//断开与客户端的连接 56 tcpSocket->close(); 57 } 58 else 59 { 60 qDebug() << array; 61 } 62 } 63 ); 64 */ 65 } 66 ); 67 68 69 //此处定时器只是起到延迟传送的作用,启动之后,立刻关掉,执行发送文件方法 70 connect(&timer,&QTimer::timeout, 71 [=]() 72 { 73 //关闭定时器 74 timer.stop(); 75 //发送文件 76 sendDate(); 77 } 78 ); 79 80 } 81 82 ServerWidget::~ServerWidget() 83 { 84 delete ui; 85 } 86 //选择文件按钮 87 void ServerWidget::on_pushButton_file_clicked() 88 { 89 //打开文件对话框,选择文件 90 QString filePath = QFileDialog::getOpenFileName(this,"open","../"); 91 //如果选择的文件有效 92 if(filePath.isEmpty() == false) 93 { 94 fileName.clear(); 95 fileSize = 0; 96 97 //获取文件信息 98 QFileInfo info(filePath); 99 fileName = info.fileName(); 100 fileSize = info.size();//获取文件名字和大小 101 sendSize = 0;//发送文件的大小 102 103 //只读方式打开 104 //指定文件的名字 105 file.setFileName(filePath);//指定文件,然后才能打开 106 bool isOK = file.open(QIODevice::ReadOnly); 107 if(isOK == false) 108 { 109 qDebug() << QString::fromLocal8Bit("只读方式打开文件失败 77"); 110 } 111 //在编辑区提示打开文件的路径 112 ui->textEdit->append(filePath); 113 //把选择文件按钮禁用 114 ui->pushButton_file->setEnabled(false); 115 //让发送文件按钮可用 116 ui->pushButton_send->setEnabled(true); 117 } 118 else 119 { 120 qDebug() << QString::fromLocal8Bit("选择文件出错 59"); 121 } 122 } 123 //发送文件按钮 124 void ServerWidget::on_pushButton_send_clicked() 125 { 126 //先发送文件头信息 文件名##文件大小 127 QString head = QString("%1##%2").arg(fileName).arg(fileSize); 128 //发送头部信息 129 qint64 len = tcpSocket->write( head.toUtf8() ); 130 131 qDebug() << head.toUtf8() << len ; 132 if(len > 0)//头部信息发送成功 133 { 134 //发送真正的文件 135 //防止TCP黏包文件 136 //需要通过定时器延时20ms(保证头文件先发送过去,然后再发送文件正文) 137 timer.start(20);//启动定时器 138 } 139 else 140 { 141 qDebug() << QString::fromLocal8Bit("头部信息发送失败 110"); 142 file.close(); 143 //改变按钮可用状态 144 ui->pushButton_file->setEnabled(true); 145 ui->pushButton_send->setEnabled(false); 146 } 147 //再发送真正的文件信息 148 } 149 //发送文件数据 150 void ServerWidget::sendDate() 151 { 152 qint64 len = 0; 153 do 154 { 155 //每次发送数据的大小(4kb) 156 char buf[4*1024] = {0}; 157 len = 0; 158 //往文件中读数据 159 len = file.read(buf,sizeof(buf)); 160 //发送数据,读多少发多少 161 len = tcpSocket->write(buf,len); 162 //发送的数据累计 163 sendSize += len; 164 }while(len>0); 165 166 //是否发送文件完毕 167 if(sendSize == fileSize) 168 { 169 ui->textEdit->append(QString::fromLocal8Bit("文件发送完毕 128")); 170 file.close(); 171 //把客户端断开 172 tcpSocket->disconnectFromHost(); 173 tcpSocket->close(); 174 } 175 }
clientwidget.ui
serverwidget.ui