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)服務器端發送數據時,發送了頭部之后,等待定時器時間到達之后,就會發送文件,是不會管客戶端是否准備好了接收,那么當客戶端還未准備好接收,但是服務器端已經把文件發送完畢了,這種情況下客戶端是不能正確收到文件的。演示效果如下:

也就是說,我們必須在接收到頭部信息之后,服務器發送文件之前客戶端把文件創建好。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM