QT学习笔记(12) QT下的TCP通信

一、TCP通信过程

(1)服务器端:

  服务器端有QTcpServer的监听套接字,运用listen()方法监听网卡的ip和端口。

  如果有新的连接传过来,并且连接成功,服务器会触发newConnection(),通过槽函数取出连接过来的通信套接字QTcpSocket

  如果有数据成功传送过来,对方的通信套接字QTcpSocket会触发readyRead(),通过槽函数可以对接收的数据进行处理

(2)客户端

  首先根据ip和端口,通过通信套接字QTcpSocket的connectToHost()方法主动和服务器建立连接

  如果连接成功,通信套接字QTcpSocket会自动触发connected(),通过槽函数可以进行操作

  如果有数据成功传送过来,对方的通信套接字QTcpSocket会触发readyRead(),通过槽函数可以对接收的数据进行处理

 

    QT学习笔记(12) QT下的TCP通信

 

二、实例代码如下:

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

QT学习笔记(12) QT下的TCP通信

clientwidget.ui

QT学习笔记(12) QT下的TCP通信

 

三、TCP传递文件

  实现以下功能:

    客户端连接到服务器

    服务器端选择文件,然后发送

    客户端接收文件,并提示接收成功

  大概流程图如下:

    QT学习笔记(12) QT下的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

QT学习笔记(12) QT下的TCP通信

serverwidget.ui

QT学习笔记(12) QT下的TCP通信