Qt学习第4天:TCP/UDP通信 【笔记】

1.Linux下的TCP通信过程

在这里插入图片描述

2.Qt下的TCP通信过程

在这里插入图片描述
注意,服务器有两个套接字:QTcpServerQTcpSocket,即监听套接字和通信套接字.

3. TCP通信

TCP服务器

TextEdit设置只读:
在这里插入图片描述
serverwidget.h

#ifndef SERVERWIDGET_H
#define SERVERWIDGET_H

#include <QWidget>
#include <QTcpServer> //监听套接字
#include <QTcpSocket> //通信套接字

namespace Ui {
class ServerWidget;
}

class ServerWidget : public QWidget
{
    Q_OBJECT

public:
    explicit ServerWidget(QWidget *parent = nullptr);
    ~ServerWidget();

private slots:
    void on_buttonSent_clicked();

    void on_buttonClose_clicked();

private:
    Ui::ServerWidget *ui;

    QTcpServer *tcpServer; //监听套接字
    QTcpSocket *tcpSocket; //通信套接字
};

#endif // SERVERWIDGET_H

serverwidget.cpp

#include "serverwidget.h"
#include "ui_serverwidget.h"


ServerWidget::ServerWidget(QWidget *parent) :
    QWidget(parent),
    ui(new Ui::ServerWidget)
{
    ui->setupUi(this);
    tcpServer = NULL;
    tcpSocket = NULL;

    setWindowTitle("服务器:8888");

    //监听套接字,指定父对象,让其自动回收空间
    tcpServer = new QTcpServer(this);

    tcpServer->listen(QHostAddress::Any,8888);//绑定网卡所有IP,端口8888
    connect(tcpServer,&QTcpServer::newConnection,
            [=]()
        {
            //取出建立好连接的套接字
            tcpSocket = tcpServer->nextPendingConnection();

            //获取对方的IP和端口
            QString ip = tcpSocket->peerAddress().toString();
            qint16 port = tcpSocket->peerPort();
            QString temp = QString("[%1:%2]:成功连接").arg(ip).arg(port);

            ui->textEditRead->setText(temp);

            //注意不要放错位置
            connect(tcpSocket,&QTcpSocket::readyRead,
                    [=]()
                    {
                        //从通信套接字中取出内容
                        QByteArray array = tcpSocket->readAll();
                        ui->textEditRead->append(array);
                    }
                    );
        }

            );


}

ServerWidget::~ServerWidget()
{
    delete ui;
}

void ServerWidget::on_buttonSent_clicked()
{
    if(NULL==tcpSocket)
    {
        return;
    }
    //获取编辑区内容
    QString str = ui->textEditWrite->toPlainText();
    //给对方发送数据,使用套接字tcpSocket
    tcpSocket->write(str.toUtf8().data());
}

void ServerWidget::on_buttonClose_clicked()
{
    if(NULL==tcpSocket)
    {
        return;
    }
    //主动和客户端断开连接
    tcpSocket->disconnectFromHost();
    tcpSocket->close();
    tcpSocket = NULL;
}

TCP客户端

clientwidget.h

#ifndef CLIENTWIDGET_H
#define CLIENTWIDGET_H

#include <QWidget>
#include <QTcpSocket> //通信套接字

namespace Ui {
class ClientWidget;
}

class ClientWidget : public QWidget
{
    Q_OBJECT

public:
    explicit ClientWidget(QWidget *parent = nullptr);
    ~ClientWidget();

private slots:
    void on_buttonConnect_clicked();

    void on_buttonSend_clicked();

    void on_buttonClose_clicked();
    
private:
    Ui::ClientWidget *ui;

    QTcpSocket *tcpSocket;
};

#endif // CLIENTWIDGET_H

clientwidget.cpp

#include "clientwidget.h"
#include "ui_clientwidget.h"
#include <QHostAddress>

ClientWidget::ClientWidget(QWidget *parent) :
    QWidget(parent),
    ui(new Ui::ClientWidget)
{
    ui->setupUi(this);
    setWindowTitle("客户端");

    tcpSocket = NULL;

    //分配空间,指定父对象
    tcpSocket = new QTcpSocket(this);

    connect(tcpSocket,&QTcpSocket::connected,
            [=]()
    {
        ui->textEditRead->setText("成功和服务器建立连接!");
    });

    connect(tcpSocket,&QTcpSocket::readyRead,
            [=]()
    {
        //获取对方发送的内容
        QByteArray array = tcpSocket->readAll();
        //追加到编辑区
        ui->textEditRead->append(array);
    }
            );

}

ClientWidget::~ClientWidget()
{
    delete ui;
}



void ClientWidget::on_buttonConnect_clicked()
{
    //获取服务器IP和端口
    QString ip = ui->lineEditIp->text();
    qint16 port = ui->lineEditPort->text().toInt();

    //主动和服务器建立连接
    tcpSocket->connectToHost(QHostAddress(ip),port);
}

void ClientWidget::on_buttonSend_clicked()
{
    //获取编辑框内容
    QString str = ui->textEditWrite->toPlainText();
    //发送数据
    tcpSocket->write(str.toUtf8().data());

}

void ClientWidget::on_buttonClose_clicked()
{
    //主动和对方断开连接
    tcpSocket->disconnectFromHost();
    tcpSocket->close();
}

4. UDP通信

UDP通信过程
Linux下的UDP

在这里插入图片描述

Qt下的UDP

在这里插入图片描述

UDP与TCP的区别
  • UDP像写信,只要知道地址就可以发
  • TCP像打电话,只有两人同时在线才能通信
UDP 文本发送

widget.h

#ifndef WIDGET_H
#define WIDGET_H

#include <QWidget>
#include <QUdpSocket> //UDP套接字

namespace Ui {
class Widget;
}

class Widget : public QWidget
{
    Q_OBJECT

public:
    explicit Widget(QWidget *parent = nullptr);
    ~Widget();

    void dealMsg();//槽函数,处理对方发过来的数据

private slots:
    void on_buttonSend_clicked();

private:
    Ui::Widget *ui;

    QUdpSocket *udpSocket;//UDP套接字
};

#endif // WIDGET_H

widget.cpp

#include "widget.h"
#include "ui_widget.h"
#include <QHostAddress>

Widget::Widget(QWidget *parent) :
    QWidget(parent),
    ui(new Ui::Widget)
{
    ui->setupUi(this);
    setWindowTitle("服务器端口为:8888");

    //分配空间,指定父对象
    udpSocket = new QUdpSocket(this);

    //绑定
    udpSocket->bind(8888);

    //当对方成功发送数据过来
    //自动触发 readyRead()
    connect(udpSocket,&QUdpSocket::readyRead,this,&Widget::dealMsg);
}

void Widget::dealMsg()
{
    //读取对方发送的内容
    char buf[1024]={0};
    QHostAddress cliAddr;//对方地址
    quint16 port;//对方端口
    qint64 len = udpSocket->readDatagram(buf,sizeof(buf),&cliAddr,&port);
    if(len > 0)
    {
        //格式化 [192.68.2.2:8888]aaaa
        QString  str = QString("[%1:%2] %3")
                .arg(cliAddr.toString())
                .arg(port)
                .arg(buf);
        //给编辑区设置内容
        ui->textEdit->setText(str);
    }
}

Widget::~Widget()
{
    delete ui;
}

void Widget::on_buttonSend_clicked()
{
    //先获取对方的IP和端口
    QString ip = ui->lineEditIP->text();
    qint16 port = ui->lineEditPort->text().toInt();

    //获取编辑区内容
    QString str = ui->textEdit->toPlainText();

    //给指定IP发送数据
    udpSocket->writeDatagram(str.toUtf8(),QHostAddress(ip),port);


}

UDP多播组播

在这里插入图片描述
widget.cpp

#include "widget.h"
#include "ui_widget.h"
#include <QHostAddress>

Widget::Widget(QWidget *parent) :
    QWidget(parent),
    ui(new Ui::Widget)
{
    ui->setupUi(this);
    setWindowTitle("服务器端口为:8888");

    //分配空间,指定父对象
    udpSocket = new QUdpSocket(this);

    //绑定
    //udpSocket->bind(8888);
    //注意,群播时绑定的是IPv4
    udpSocket->bind(QHostAddress::AnyIPv4,8888);

    //加入某个组播
    //组播地址必须是D类地址
    udpSocket->joinMulticastGroup(QHostAddress("224.0.0.2"));
    //udpSocket->leaveMulticastGroup(); //退出组播

    //当对方成功发送数据过来
    //自动触发 readyRead()
    connect(udpSocket,&QUdpSocket::readyRead,this,&Widget::dealMsg);
}

void Widget::dealMsg()
{
    //读取对方发送的内容
    char buf[1024]={0};
    QHostAddress cliAddr;//对方地址
    quint16 port;//对方端口
    qint64 len = udpSocket->readDatagram(buf,sizeof(buf),&cliAddr,&port);
    if(len > 0)
    {
        //格式化 [192.68.2.2:8888]aaaa
        QString  str = QString("[%1:%2] %3")
                .arg(cliAddr.toString())
                .arg(port)
                .arg(buf);
        //给编辑区设置内容
        ui->textEdit->setText(str);
    }
}

Widget::~Widget()
{
    delete ui;
}

void Widget::on_buttonSend_clicked()
{
    //先获取对方的IP和端口
    QString ip = ui->lineEditIP->text();
    qint16 port = ui->lineEditPort->text().toInt();

    //获取编辑区内容
    QString str = ui->textEdit->toPlainText();

    //给指定IP发送数据
    udpSocket->writeDatagram(str.toUtf8(),QHostAddress(ip),port);


}

5. QTimer定时器

5.TCP传文件

在这里插入图片描述
务必注意TCP的黏包问题,通常通过发送头部数据+延时来处理!!!

main.cpp

#include "serverwidget.h"
#include <QApplication>
#include "clientwidget.h"

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    ServerWidget w;
    w.show();

    ClientWidget w2;
    w2.show();


    return a.exec();
}

服务器

serverwidget.h

#ifndef SERVERWIDGET_H
#define SERVERWIDGET_H

#include <QWidget>
#include <QTcpServer> //监听套接字
#include <QTcpSocket> //通信套接字
#include <QFile>
#include <QTimer>


namespace Ui {
class ServerWidget;
}

class ServerWidget : public QWidget
{
    Q_OBJECT

public:
    explicit ServerWidget(QWidget *parent = nullptr);
    ~ServerWidget();

    void sendData();//发送文件数据

private slots:
    void on_buttonFile_clicked();

    void on_buttonSend_clicked();

private:
    Ui::ServerWidget *ui;

    QTcpServer *tcpServer;//监听套接字
    QTcpSocket *tcpSocket;//通信套接字

    QFile file;//文件对象
    QString fileName;//文件名字
    qint64 fileSize;//文件大小
    qint64 sendSize;//已发送的大小

    QTimer timer;//定时器
};

#endif // SERVERWIDGET_H

serverwidget.cpp

#include "serverwidget.h"
#include "ui_serverwidget.h"
#include <QFileDialog>
#include <QDebug>
#include <QFileInfo>

ServerWidget::ServerWidget(QWidget *parent) :
    QWidget(parent),
    ui(new Ui::ServerWidget)
{
    ui->setupUi(this);

    //监听套接字
    tcpServer = new QTcpServer(this);

    //监听
    tcpServer->listen(QHostAddress::Any,8888);
    setWindowTitle("服务器端口为:8888");

    //两个按钮都不能按
    ui->buttonFile->setEnabled(false);
    ui->buttonSend->setEnabled(false);

    //如果客户端成功和服务器连接
    connect(tcpServer,&QTcpServer::newConnection,
            [=]()
    {
        //取出建立好连接的套接字
        tcpSocket = tcpServer->nextPendingConnection();
        //获取对方的IP和端口
        QString ip = tcpSocket->peerAddress().toString();
        quint16 port = tcpSocket->peerPort();

        QString str = QString("[%1:%2] 成功连接").arg(ip).arg(port);
        ui->textEdit->setText(str);//显示到编辑区

        //成功连接后,才能按按钮选择文件
        ui->buttonFile->setEnabled(true);

    });


    connect(&timer,&QTimer::timeout,
            [=]()
    {
        //关闭定时器
        timer.stop();

        //发送文件
        sendData();
    }
    );
}

ServerWidget::~ServerWidget()
{
    delete ui;
}

//选择文件的按钮
void ServerWidget::on_buttonFile_clicked()
{
    QString filePath = QFileDialog::getOpenFileName(this,"open","../");
    if(false == filePath.isEmpty())//如果选择文件路径无效
    {
        fileName.clear();
        fileSize = 0;

        //获取文件信息
        QFileInfo info(filePath);
        fileName = info.fileName();//获取文件名字
        fileSize = info.size();//获取文件大小

        sendSize = 0;//发送文件大小

        //只读方式打开文件
        //指定文件的名字
        file.setFileName(filePath);

        //打开文件
        bool isOk = file.open(QIODevice::ReadOnly);
        if(false == isOk)
        {
            qDebug() << "只读方式打开文件失败";
        }

        //提示打开文件路径
        ui->textEdit->append(filePath);

        ui->buttonFile->setEnabled(false);
        ui->buttonSend->setEnabled(true);

    }
    else {
        qDebug() << "选择文件路径出错!";
    }
}


//发送文件按键
void ServerWidget::on_buttonSend_clicked()
{
    //先发送文件头信息
    QString head = QString("%1##%2").arg(fileName).arg(fileSize);

    qint64 len = tcpSocket->write(head.toUtf8());
    if(len > 0)//说明头部信息发送成功
    {
        //发送真正的文件信息
        //防止TCP黏包文件
        //需要通过定时器延时20ms
        timer.start(20);//加延时,防止黏包

    }
    else {
        qDebug()<<"头部信息发送失败!";
        file.close();
        ui->buttonFile->setEnabled(true);
        ui->buttonSend->setEnabled(false);
    }

}

void ServerWidget::sendData()
{
    qint64 len = 0;
    do
    {
        //每次发送数据的大小
        char buf[4*1024] = {0};
        len = 0;

        //往文件中读数据
        len = file.read(buf,sizeof(buf));
        //发送数据,读多少,发多少
        len = tcpSocket->write(buf,len);


        //发送的数据需要累积
        //sendSize += len;//注意,这种方法不如下面的这种好

    }while(len > 0);//如果len≤0,发送完毕

    //文件是否发送完毕
    if(sendSize == fileSize)
    {
        ui->textEdit->append("文件发送完毕!");
        file.close();

        //把客户端端口关闭
        tcpSocket->disconnectFromHost();
        tcpSocket->close();

    }
}

客户端

clientwidget.h

#ifndef CLIENTWIDGET_H
#define CLIENTWIDGET_H

#include <QWidget>
#include <QTcpSocket>
#include <QFile>

namespace Ui {
class ClientWidget;
}

class ClientWidget : public QWidget
{
    Q_OBJECT

public:
    explicit ClientWidget(QWidget *parent = nullptr);
    ~ClientWidget();

private slots:
    void on_buttonConnect_clicked();

private:
    Ui::ClientWidget *ui;

    QTcpSocket *tcpSocket;

    QFile file;//文件对象
    QString fileName;//文件名字
    qint64 fileSize;//文件大小
    qint64 recvSize;//已接收文件的大小

    bool isStart;

};

#endif // CLIENTWIDGET_H

clientwidget.cpp

#include "clientwidget.h"
#include "ui_clientwidget.h"
#include <QDebug>
#include <QMessageBox>
#include <QHostAddress>

ClientWidget::ClientWidget(QWidget *parent) :
    QWidget(parent),
    ui(new Ui::ClientWidget)
{
    ui->setupUi(this);
    ui->progressBar->setValue(0);//设置进度条当前值为0

    tcpSocket = new QTcpSocket(this);

    isStart = true;

    connect(tcpSocket,&QTcpSocket::readyRead,
            [=]()
    {
        //取出接收的内容
       QByteArray buf = tcpSocket->readAll();

       if(true == isStart)//接收头
       {
           isStart = false;
           //解析头部信息 QString buf = "hello##1024"
           //QString str = "hello##1024#mike";
           //取hello: str.section("##",0,0)
           //取1024:str.section("##",1,1).toInt()
           //取mike:str.section("##",2,2)

           //初始化
           fileName = QString(buf).section("##",0,0);
           fileSize = QString(buf).section("##",1,1).toInt();
           recvSize = 0;

           //打开文件
           file.setFileName(fileName);

           bool isOk = file.open(QIODevice::WriteOnly);
           if(false == isOk)
           {
               qDebug() << "WriteOnly Error!!!";
               tcpSocket->disconnectFromHost();
               tcpSocket->close();//关闭套接字
               return ;//如果打开文件失败,终端函数
           }

           //弹出对话框,显示接收文件的信息
           QString str = QString("接收的文件: [%1: %2kb]").arg(fileName).arg(fileSize/1024);
           QMessageBox::information(this,"文件信息",str);

           //设置进度条
           ui->progressBar->setMinimum(0); //最小值
           ui->progressBar->setMaximum(fileSize/1024);//设置最大值
           ui->progressBar->setValue(0);//当前值

       }
       else //文件信息
       {
           qint64 len = file.write(buf);
           if(len > 0)//接收数据大于0
           {
               recvSize += len;//累计接收大小
               QString str = QString::number(recvSize);
               tcpSocket->write(str.toUtf8().data());
               qDebug()<<"str = "<<str;
           }
           //更新进度条
           ui->progressBar->setValue(recvSize/1024);



           if(recvSize == fileSize)//文件接收完毕
           {
               //先给服务器发送(接收文件完成的信息)
               tcpSocket->write("file done");
               file.close(); //关闭文件
               QMessageBox::information(this,"完成","文件接收完成!");
               //断开连接
               tcpSocket->disconnectFromHost();
               tcpSocket->close();
           }


       }
    });
}

ClientWidget::~ClientWidget()
{
    delete ui;
}

void ClientWidget::on_buttonConnect_clicked()
{
    //获取服务器IP和端口
    QString ip = ui->lineEditIP->text();
    quint16 port = ui->lineEditPort->text().toInt();

    tcpSocket->connectToHost(QHostAddress(ip),port);
}

小技巧:

  • 有时加载完模块不会立即生效,这时可以点下锤子,只编译不运行!或者重新打开项目即可~
  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值