Qt-tcp通信

1  简介

参考视频:https://www.bilibili.com/video/BV1XW411x7NU?p=56

参考视频:https://www.bilibili.com/video/BV1XW411x7NU?p=66

测试1代码github:https://github.com/zhengcixi/Qt_Demo/tree/master/tcp

测试2代码github:https://github.com/zhengcixi/Qt_Demo/tree/master/tcp_file

说明:本文介绍Qt下实现tcp客户端和服务器通信的过程。

Linux下tcp客户端和服务器通信模型可参考我的另一篇博客:https://www.cnblogs.com/mrlayfolk/p/11968446.html。Qt下tcp通信的原理是一样的。

Qt的TCP通信模型如下:

(1)TCP服务器端

1)创建服务器套接字,使用QTcpServer()类;

2)将套接字设置为监听模式;

3)等待客户端连接请求,客户端连接上时会触发newConnection信号,可调用nextPendingConnection函数获取客户端的Socket信息;

4)和客户端进行通信,发送数据可使用write()函数,接收数据可使用read()或readAll函数()。

(2)TCP客户端

1)创建套接字;

2)连接服务器,使用connectToHost()函数;

3)和服务器进行通信,发送数据可使用write()函数,接收数据可使用read()或readAll函数()。

2  代码测试

2.1  基本的tcp通信测试

功能说明:分别创建两个窗口,一个用作TCP服务器端,一个用作TCP客户端,双方进行通信。窗口如下:

服务器窗口:                                                                             客户端窗口:

           

下面分别说明代码实现的步骤:

(1)服务器端

首先创建两个套接字指针。tcpserver用作服务器套接字,tcpsocket用作和客户端通信的通信套接字。

1     QTcpServer *tcpserver = NULL;  //监听套接字
2     QTcpSocket *tcpsocket = NULL;  //通信套接字

然后,创建套接字并启动监听。

1     //监听套接字,指定父对象,自动回收空间
2     tcpserver = new QTcpServer(this);
3     //启动监听
4     tcpserver->listen(QHostAddress::Any, 8888);

捕捉newConnect信号与槽函数,等待客户端连接:

 1     //等待连接
 2     connect(tcpserver, &QTcpServer::newConnection,
 3         [=]() {
 4             //取出建立好连接的套接字
 5             tcpsocket = tcpserver->nextPendingConnection();
 6             //获取对方的ip的端口
 7             QString ip = tcpsocket->peerAddress().toString();
 8             qint16 port = tcpsocket->peerPort();
 9             QString tmp = QString("[%1:%2]:成功连接").arg(ip).arg(port);
10             //在当前对话框显示谁和我连接了
11             ui->textEdit_recv->setText(tmp);
12         }
13     );

发送数据:

1     //获取编辑区内容
2     QString str = ui->textEdit_send->toPlainText();
3     //给对方发送数据
4     //QString -> char*
5     tcpsocket->write(str.toUtf8().data());

接收数据:

1             //接收数据
2             connect(tcpsocket, &QTcpSocket::readyRead,
3                 [=](){
4                     //从通信套接字中取出内容
5                      QByteArray array = tcpsocket->readAll();
6                      ui->textEdit_recv->append(array);
7                 }
8             );

关闭连接:

1     //主动和客户端断开连接
2     tcpsocket->disconnectFromHost();
3     tcpsocket->close();
4     tcpsocket = NULL;

(2)客户端

客户端只需要创建一个套接字,用于和服务器建立连接并通信:

1     QTcpSocket *tcpsocket = NULL;  //通信套接字
2     //分配空间,指定父对象
3     tcpsocket = new QTcpSocket(this);

和服务器端建立连接:

1     //获取服务器ip和端口
2     QString ip = ui->lineEdit_ip->text();
3     qint16 port = ui->lineEdit_port->text().toInt();
4     //主动和服务器建立连接
5     tcpsocket->connectToHost(QHostAddress(ip), port);

发送数据:

1     //获取编辑框内容
2     QString str = ui->textEdit_send->toPlainText();
3     //发送数据
4     tcpsocket->write(str.toUtf8().data());

接收数据:

1     connect(tcpsocket, &QTcpSocket::readyRead,
2         [=](){
3             //获取对方发送的内容
4             QByteArray array = tcpsocket->readAll();
5             //追加到编辑区中
6             ui->textEdit_recv->append(array);
7         }
8     );

断开连接:

1     //主动和对方断开连接
2     tcpsocket->disconnectFromHost();
3     tcpsocket->close();

(3)完整的工程代码:

工程所包含的文件有,serverwidget.cpp和serverwidget.h是服务器端的代码,clientwidget.cpp和clientwidget.h是客户端代码。

 serverwidget.cpp代码:

 1 #include "serverwidget.h"
 2 #include "ui_serverwidget.h"
 3 
 4 ServerWidget::ServerWidget(QWidget *parent) :
 5     QWidget(parent),
 6     ui(new Ui::ServerWidget)
 7 {
 8     ui->setupUi(this);
 9     setWindowTitle("服务器: 8888");
10 
11     //监听套接字,指定父对象,自动回收空间
12     tcpserver = new QTcpServer(this);
13     //启动监听
14     tcpserver->listen(QHostAddress::Any, 8888);
15     //等待连接
16     connect(tcpserver, &QTcpServer::newConnection,
17         [=]() {
18             //取出建立好连接的套接字
19             tcpsocket = tcpserver->nextPendingConnection();
20             //获取对方的ip的端口
21             QString ip = tcpsocket->peerAddress().toString();
22             qint16 port = tcpsocket->peerPort();
23             QString tmp = QString("[%1:%2]:成功连接").arg(ip).arg(port);
24             //在当前对话框显示谁和我连接了
25             ui->textEdit_recv->setText(tmp);
26 
27             //接收数据
28             connect(tcpsocket, &QTcpSocket::readyRead,
29                 [=](){
30                     //从通信套接字中取出内容
31                      QByteArray array = tcpsocket->readAll();
32                      ui->textEdit_recv->append(array);
33                 }
34             );
35         }
36     );
37 }
38 
39 ServerWidget::~ServerWidget()
40 {
41     delete ui;
42 }
43 
44 
45 void ServerWidget::on_pushButton_close_clicked()
46 {
47     if (NULL == tcpsocket) {
48         return;
49     }
50     //主动和客户端断开连接
51     tcpsocket->disconnectFromHost();
52     tcpsocket->close();
53     tcpsocket = NULL;
54 }
55 
56 void ServerWidget::on_pushButton_send_clicked()
57 {
58     if (NULL == tcpsocket) {
59         return;
60     }
61     //获取编辑区内容
62     QString str = ui->textEdit_send->toPlainText();
63     //给对方发送数据
64     //QString -> char*
65     tcpsocket->write(str.toUtf8().data());
66 }
View Code

 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_pushButton_send_clicked();
22 
23     void on_pushButton_close_clicked();
24 
25 private:
26     Ui::ServerWidget *ui;
27     QTcpServer *tcpserver = NULL;  //监听套接字
28     QTcpSocket *tcpsocket = NULL;  //通信套接字
29 };
30 
31 #endif // SERVERWIDGET_H
View Code

clientwidget.cpp代码:

 1 #include "clientwidget.h"
 2 #include "ui_clientwidget.h"
 3 #include <QHostAddress>
 4 
 5 clientwidget::clientwidget(QWidget *parent) :
 6     QWidget(parent),
 7     ui(new Ui::clientwidget)
 8 {
 9     ui->setupUi(this);
10     setWindowTitle("客户端");
11     //分配空间,指定父对象
12     tcpsocket = new QTcpSocket(this);
13     //建立连接
14     connect(tcpsocket, &QTcpSocket::connected,
15         [=]() {
16             ui->textEdit_recv->setText("成功和服务器建立了连接");
17         }
18     );
19     //接收数据
20     connect(tcpsocket, &QTcpSocket::readyRead,
21         [=](){
22             //获取对方发送的内容
23             QByteArray array = tcpsocket->readAll();
24             //追加到编辑区中
25             ui->textEdit_recv->append(array);
26         }
27     );
28     //断开连接
29     connect(tcpsocket, &QTcpSocket::disconnected,
30         [=](){
31             ui->textEdit_recv->append("和服务器断开了连接");
32         }
33     );
34 }
35 
36 clientwidget::~clientwidget()
37 {
38     delete ui;
39 }
40 
41 void clientwidget::on_pushButton_send_clicked()
42 {
43     //获取编辑框内容
44     QString str = ui->textEdit_send->toPlainText();
45     //发送数据
46     tcpsocket->write(str.toUtf8().data());
47 }
48 
49 void clientwidget::on_pushButton_close_clicked()
50 {
51     //主动和对方断开连接
52     tcpsocket->disconnectFromHost();
53     tcpsocket->close();
54 }
55 
56 void clientwidget::on_pushButton_connect_clicked()
57 {
58     //获取服务器ip和端口
59     QString ip = ui->lineEdit_ip->text();
60     qint16 port = ui->lineEdit_port->text().toInt();
61     //主动和服务器建立连接
62     tcpsocket->connectToHost(QHostAddress(ip), port);
63 }
View Code

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_send_clicked();
21 
22     void on_pushButton_close_clicked();
23 
24     void on_pushButton_connect_clicked();
25 
26 private:
27     Ui::clientwidget *ui;
28     QTcpSocket *tcpsocket = NULL;
29 };
30 
31 #endif // CLIENTWIDGET_H
View Code

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     clientwidget w2;
10     w.show();
11     w2.show();
12 
13     return a.exec();
14 }
View Code

运行进行测试:

2.2  使用tcp传输文件

功能:客户端连接到服务器,服务器端再传输文件给客户端,当文件传输完成时,服务器端等待客户端返回成功接收的信息,然后服务器端关闭客户端的连接。

通信模型如下:

 有几点需要说明一下:

(1)服务器端传输文件之前,先传输了一个文件头信息,这个信息是我们自己封装的,格式为:“文件名##文件字节数”,这样客户端就可以根据文件头信息获取文件的信息了,创建相应的文件。

(2)发送文件数据时,如果文件太大,可以一个一个缓冲区的发送,缓冲区的大小是自己设置的,代码中设置的4kb;

代码说明如下:

(1)工程文件有:

clientwidget.cpp和clientwidget.h是客户端的代码,serverwidget.cpp和serverwidget.h是服务器端的代码。

(2)直接给出代码,代码中有注释:

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 
26     QTcpSocket *tcpsocket = NULL;  //通信套接字
27     QFile file;  //文件对象
28     QString filename; //文件名
29     qint64 filesize = 0;  //文件大小
30     qint64 recvsize = 0;  //已发送文件大小
31     bool isStart; //开始接收文件标志位
32 };
33 
34 #endif // CLIENTWIDGET_H
View Code

clientwidget.cpp代码

  1 #include "clientwidget.h"
  2 #include "ui_clientwidget.h"
  3 #include <QDebug>
  4 #include <QMessageBox>
  5 #include <QHostAddress>
  6 #include <QIODevice>
  7 
  8 clientWidget::clientWidget(QWidget *parent) :
  9     QWidget(parent),
 10     ui(new Ui::clientWidget)
 11 {
 12     ui->setupUi(this);
 13     setWindowTitle("客户端");
 14 
 15     isStart = true;
 16     ui->progressBar->setValue(0);  //当前值
 17 
 18     tcpsocket = new QTcpSocket(this);
 19     connect(tcpsocket, &QTcpSocket::readyRead,
 20         [=](){
 21             //取出接收的内容
 22             qDebug() << "isstart:" << isStart;
 23             QByteArray buf = tcpsocket->readAll();
 24             if (isStart == true) {  //接收到头部信息
 25                 isStart = false;
 26                 //解析头部信息  初始化
 27                 filename = QString(buf).section("##", 0, 0);  //文件名
 28                 filesize = QString(buf).section("##", 1, 1).toInt();  //文件大小
 29                 recvsize = 0;   //已经接收文件大小
 30                 qDebug() << "fileName" << filename << "filesize" << filesize;
 31                 //打开文件
 32                 file.setFileName(filename);
 33                 bool isOK = file.open(QIODevice::WriteOnly);
 34                 if (false == isOK) {
 35                     qDebug() << "open error!!!";
 36                     //关闭连接
 37                     tcpsocket->disconnectFromHost();
 38                     tcpsocket->close();
 39                     return;
 40                 }
 41                 //弹出对话框,显示接收文件信息
 42                 QString str = QString("接收的文件:[%1:%2kb]").arg(filename).arg(filesize/1024);
 43                 QMessageBox::information(this, "文件信息", str);
 44                 //设置进度条
 45                 ui->progressBar->setMinimum(0);
 46                 ui->progressBar->setMaximum(filesize/1024);
 47                 ui->progressBar->setValue(0);
 48                 QString str1 = QString("开始接收文件%1,大小为%2bytes").arg(filename).arg(filesize);
 49                 ui->textEdit->append(str1);
 50             } else { //真正的文件信息
 51                 qDebug() << "start write data";
 52                 qint64 len = file.write(buf);
 53                 if (len > 0) {
 54                     recvsize += len;
 55                     qDebug() << len;
 56                 }
 57                 qDebug() << "recvsize" << recvsize << "filesize" << filesize;
 58                 //更新进度条
 59                 ui->progressBar->setValue(recvsize/1024);
 60                 if (recvsize == filesize) {  //文件接收完成
 61                     qDebug() << "recvsize" << recvsize << "filesize" << filesize;
 62                     //先向服务器发送接收文件完成的信息
 63                     tcpsocket->write("file done");
 64                     QMessageBox::information(this, "完成", "文件接收完成");
 65                     file.close();
 66                     tcpsocket->disconnectFromHost();
 67                     tcpsocket->close();
 68                 }
 69             }
 70         }
 71     );
 72     connect(tcpsocket, &QTcpSocket::connected,
 73         [=](){
 74             ui->textEdit->clear();
 75             ui->textEdit->append("已经和服务器建立了连接,等待服务器传输文件...");
 76             ui->pushButton_connect->setEnabled(false);
 77         }
 78     );
 79     connect(tcpsocket, &QTcpSocket::disconnected,
 80         [=](){
 81             ui->textEdit->append("已经和服务器断开了连接");
 82             ui->pushButton_connect->setEnabled(true);
 83         }
 84     );
 85 }
 86 
 87 clientWidget::~clientWidget()
 88 {
 89     delete ui;
 90 }
 91 
 92 void clientWidget::on_pushButton_connect_clicked()
 93 {
 94     //获取服务器的ip和端口
 95     QString ip = ui->lineEdit_ip->text();
 96     quint16 port = ui->lineEdit_port->text().toInt();
 97     //主动和服务器建立连接
 98     tcpsocket->connectToHost(QHostAddress(ip), port);
 99     isStart = true;
100     //设置进度条
101     ui->progressBar->setValue(0);
102 }
View Code

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 namespace Ui {
11 class ServerWidget;
12 }
13 
14 class ServerWidget : public QWidget
15 {
16     Q_OBJECT
17 
18 public:
19     explicit ServerWidget(QWidget *parent = 0);
20     ~ServerWidget();
21     void sendData(); //发送文件数据
22 
23 
24 private slots:
25     void on_pushButton_send_clicked();
26 
27     void on_pushButton_choose_clicked();
28 
29 private:
30     Ui::ServerWidget *ui;
31 
32     QTcpServer *tcpserver = NULL;  //监听套接字
33     QTcpSocket *tcpsocket = NULL;  //通信套接字
34     QFile file;  //文件对象
35     QString filename; //文件名
36     qint64 filesize;  //文件大小
37     qint64 sendsize;  //已发送文件大小
38     QTimer timer;  //定时器
39 
40 };
41 
42 #endif // SERVERWIDGET_H
View Code

serverwidget.cpp代码:

  1 #include "serverwidget.h"
  2 #include "ui_serverwidget.h"
  3 #include <QFileDialog>
  4 #include <QDebug>
  5 #include <QFileInfo>
  6 #include <QTimer>
  7 
  8 ServerWidget::ServerWidget(QWidget *parent) :
  9     QWidget(parent),
 10     ui(new Ui::ServerWidget)
 11 {
 12     ui->setupUi(this);
 13     setWindowTitle("服务器");
 14 
 15     //监听套接字
 16     tcpserver = new QTcpServer(this);
 17     //启动监听
 18     tcpserver->listen(QHostAddress::Any, 8888);
 19 
 20     //两个按钮都不能使用
 21     ui->pushButton_choose->setEnabled(false);
 22     ui->pushButton_send->setEnabled(false);
 23 
 24     //如果客户端成功和服务器连接
 25     connect(tcpserver, &QTcpServer::newConnection,
 26         [=](){
 27             //取出建立好连接的套接字
 28             tcpsocket = tcpserver->nextPendingConnection();
 29             //获取对方的IP和端口
 30             QString ip = tcpsocket->peerAddress().toString();
 31             quint16 port = tcpsocket->peerPort();
 32             QString str = QString("[%1:%2] 成功连接").arg(ip).arg(port);
 33             ui->textEdit->append(str); //显示到编辑区
 34             //成功连接后,才能选择文件
 35             ui->pushButton_choose->setEnabled(true);
 36             ui->pushButton_send->setEnabled(false);
 37             connect(tcpsocket, &QTcpSocket::readyRead,
 38                 [=]() {
 39                     //取客户端的消息
 40                     QByteArray buf = tcpsocket->readAll();
 41                     if (QString(buf) == "file done") {  //文件接收完毕
 42                         ui->textEdit->append("文件接收完成");
 43                         file.close();
 44                         //断开客户端端口
 45                         tcpsocket->disconnectFromHost();
 46                         tcpsocket->close();
 47                         ui->pushButton_choose->setEnabled(false);
 48                         ui->pushButton_send->setEnabled(false);
 49                     }
 50                 }
 51             );
 52         }
 53     );
 54     //定时器事件
 55     connect(&timer, &QTimer::timeout,
 56         [=](){
 57             //关闭定时器
 58             timer.stop();
 59             //发送数据
 60             sendData();
 61         }
 62     );
 63 
 64 }
 65 
 66 ServerWidget::~ServerWidget()
 67 {
 68     delete ui;
 69 }
 70 
 71 //发送文件按钮
 72 void ServerWidget::on_pushButton_send_clicked()
 73 {
 74     //发送文件头信息
 75     QString head = QString("%1##%2").arg(filename).arg(filesize);
 76     qint64 len = tcpsocket->write(head.toUtf8());
 77     if (len > 0) {  //头部信息发送成功
 78         //防止TCP粘包问题,需要通过定时器样式
 79         timer.start(2000);
 80     } else {
 81         qDebug() << "send header data error!!!";
 82         file.close();
 83         ui->pushButton_choose->setEnabled(true);
 84         ui->pushButton_send->setEnabled(false);
 85     }
 86 }
 87 
 88 //选择文件按钮
 89 void ServerWidget::on_pushButton_choose_clicked()
 90 {
 91     //打开文件
 92     QString filepath = QFileDialog::getOpenFileName(this, "open", "../");
 93     if (false == filepath.isEmpty()) {  //选择文件路径有效
 94         filename.clear();
 95         filesize = 0;
 96         sendsize = 0;
 97         //获取文件信息 文件名 文件大小
 98         QFileInfo info(filepath);
 99         filename = info.fileName();
100         filesize = info.size();
101         //只读方式打开文件
102         file.setFileName(filepath);
103         bool isOK = file.open(QIODevice::ReadOnly);
104         if (false == isOK) {
105             qDebug() << "open file error!!!";
106         }
107         QString str = QString("已选择文件:%1").arg(filepath);
108         ui->textEdit->append(str);
109         //设置按钮属性
110         ui->pushButton_choose->setEnabled(false);
111         ui->pushButton_send->setEnabled(true);
112     } else {
113         qDebug() << "选择的文件路径无效!!!";
114     }
115 }
116 
117 //发送文件数据
118 void ServerWidget::sendData()
119 {
120     ui->textEdit->append("正在发送文件...");
121     qint64 len = 0;
122     do {
123         //每次发送数据的大小
124         char buf[4*1024] = {0};
125         //往文件中读数据
126         len = file.read(buf, sizeof(buf));
127         if (len <= 0) {
128             break;
129         }
130         //发送数据,读多少,发多少
131         len = tcpsocket->write(buf, len);
132         //发送数据大小累加
133         sendsize += len;
134         qDebug() << "have send:" << len;
135     } while (len > 0);
136 }
View Code

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     clientWidget w2;
10     w.show();
11     w2.show();
12 
13     return a.exec();
14 }
View Code

运行测试:

遇到的问题:

(1)服务器端发送头部信息之后,会定时一段时间,然后再发送文件,主要是为了解决粘包现象(如果不这么做,服务器端发送头部信息和文件时会封装在同一个包中,这样客户端就不能将其区分出来)。视频中的定时时间是20ms,但是如果我们发送的文件太小,测试发现客户端只会接收到一次readyRead信号,无法接收到文件,所以最好把定时器的时间设置大一些。

存在的bug:

(1)服务器端发送数据时,发送了头部之后,等待定时器时间到达之后,就会发送文件,是不会管客户端是否准备好了接收,那么当客户端还未准备好接收,但是服务器端已经把文件发送完毕了,这种情况下客户端是不能正确收到文件的。演示效果如下:

也就是说,我们必须在接收到头部信息之后,服务器发送文件之前客户端把文件创建好。

posted @ 2020-07-11 14:37  zhengcixi  阅读(2210)  评论(0编辑  收藏  举报
回到顶部