QT学习笔记(13)-QT Socket通信

Socket通信基础知识

前言

TCP通信

下面这个是服务端与客户端的通信流程。基本编程也是按照这个顺序进行编程。
服务端在Qt中实现TCP/IP服务器端通信的流程:

  1. 创建套接字
  2. 将套接字设置为监听模式
  3. 等待并接受客户端请求
    可以通过QTcpServer提供的void newConnection()信号来检测是否有连接请求,如果有可以在对应的槽函数中调用nextPendingConnection函数获取到客户端的Socket信息(返回值为QTcpSocket*类型指针),通过此套接字与客户端之间进行通信。
  4. 接收或者向客户端发送数据
  5. 接收数据:使用read()或者readAll()函数
  6. 发送数据:使用write()函数

客户端通信流程:

  1. 创建套接字
  2. 连接服务器
    可以使用QTcpSocket类的connectToHost()函数来连接服务器。
  3. 向服务器发送或者接受数据

这里要注意,一般是客户端去连接服务端的,所以,服务端要多一个tcpServer 进行监听的。

TCP通信小知识

  1. 在.pro文件中要添加QT += network,否则无法使用Qt的网络功能。类似于下面这张图的这个位置。
    在这里插入图片描述

  2. 监听套接字与已连接套接字:参考于监听套接字与已连接套接字

内核会认为socket函数创建的套接字是主动套接字(active socket),它存在于一个连接的客户端。而服务器调用listen函数告诉内核,该套接字是被服务器而不是客户端使用的,即listen函数将一个主动套接字转化为监听套接字。监听套接字可以接受来自客户端的连接请求。

TCP通信实例一之字符通信

运行效果

在这里插入图片描述

主要代码讲解
  1. 这里,首先设置好监听套接字和连接套接字,将服务器设置为监听状态,为客服端发送信息给服务端做准备,当客户端信息发送到服务端时,二者将建立连接套接字,从而建立连接。接下来,便借助于信号槽,当套接字已准备好readRead时,发出信号,便可以将套接字中的内容取出。
    另外三个槽函数,主要是connect,send,close三个函数。
//serverwidget.cpp
ui->setupUi(this);
    tcpServer = NULL;
    tcpSocket = NULL;
    //监听套接字,指定父对象,让其自动回收空间
    tcpServer = new QTcpServer(this);
    tcpServer->listen(QHostAddress::Any, 8888);
    setWindowTitle("服务器: 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);
                        }
                        );
            }
            );
  1. clientwidget.cpp:在与服务端建立好连接后,使用这里的槽函数,主要是使用connect和readyRead的信号进行使用,进行信息的通信。
ui->setupUi(this);
    tcpSocket = NULL;
    //分配空间,指定父对象
    tcpSocket = new QTcpSocket(this);
    setWindowTitle("客户端");
    connect(tcpSocket, &QTcpSocket::connected,
            [=]()
            {
                ui->textEditRead->setText("成功和服务器建立好连接");
            }
            );

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

下面这个是客户端主动和服务器建立连接的语句。

void ClientWidget::on_buttonConnect_clicked()
{
    //获取服务器ip和端口
    QString ip = ui->lineEditIP->text();
    qint16 port = ui->lineEditPort->text().toInt();
    //主动和服务器建立连接
    tcpSocket->connectToHost(QHostAddress(ip), port);
}
代码地址

https://download.csdn.net/download/weixin_38809485/12623790里面的01_TCP,若急需,但却没有积分,可评论下留言。

TCP通信实例二之文件通信

运行效果

在这里插入图片描述

主要代码讲解
ServerWidget.h
#ifndef SERVERWIDGET_H
#define SERVERWIDGET_H

#include <QWidget>
#include <QtWidgets>
#include <QTcpServer> //监听套接字
#include <QTcpSocket> //通信套接字
#include <QFile>
#include <QTimer>
class CServerWidget : public QWidget
{
    Q_OBJECT

public:
    CServerWidget(QWidget *parent = 0);
    ~CServerWidget();
    void sendData(); //发送文件数据
protected:
    void createUi();
//    QPushButton *buttonFile;
//    QPushButton *buttonSend;
private:
    QWidget *ServerWidget;
    QGridLayout *gridLayout;
    QSpacerItem *horizontalSpacer;
    QLabel *label;
    QSpacerItem *horizontalSpacer_2;
    QTextEdit *textEdit;
    QPushButton *buttonFile;
    QSpacerItem *horizontalSpacer_3;
    QPushButton *buttonSend;

private slots:
    void on_buttonFile_clicked();
    void on_buttonSend_clicked();
private:
    QTcpServer *tcpServer; //监听套接字
    QTcpSocket *tcpSocket; //通信套接字

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

    QTimer timer; //定时器
};

#endif // SERVERWIDGET_H

ClientWidget.h
#ifndef CLIENTWIDGET_H
#define CLIENTWIDGET_H

#include <QWidget>
#include <QtWidgets>
#include <QTcpServer> //监听套接字
#include <QTcpSocket> //通信套接字
#include <QFile>
#include <QTimer>
class CClientWidget : public QWidget
{
    Q_OBJECT
public:
    explicit CClientWidget(QWidget *parent = nullptr);
    ~CClientWidget();
signals:


private slots:
    void on_buttonConnect_clicked();
    void readFile();
protected:
    void createUi();
//    QPushButton *buttonFile;
//    QPushButton *buttonSend;
public:
    QWidget *ClientWidget;
    QGridLayout *gridLayout_2;
    QWidget *widget_2;
    QHBoxLayout *horizontalLayout;
    QSpacerItem *horizontalSpacer;
    QLabel *label_3;
    QSpacerItem *horizontalSpacer_2;
    QWidget *widget;
    QGridLayout *gridLayout;
    QLabel *label;
    QLineEdit *lineEditPort;
    QLineEdit *lineEditIP;
    QLabel *label_2;
    QPushButton *buttonConnect;
    QProgressBar *progressBar;
    QTextEdit *textEdit;

private:

    QTcpSocket *tcpSocket;

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

    bool isStart;   //标志位,是否为头部信息
};

#endif // CLIENTWIDGET_H

ServerWidget.cpp

该文件主要提供的是监听客户端发来的请求,并与客户端建立连接,在建立连接后,选择文件并使用tcpSocket发送数据。

#include "ServerWidget.h"
CServerWidget::CServerWidget(QWidget *parent)
    : QWidget(parent)
{
    createUi();

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

    //监听
    tcpServer->listen(QHostAddress::Any, 8888);
    setWindowTitle("Server Port:8888");
    //两个按钮都不能按
    buttonFile->setEnabled(false);
    buttonSend->setEnabled(false);

    //如果客户端成功和服务器连接
    //tcpServer会自动触发 newConnection()
    connect(tcpServer, &QTcpServer::newConnection,
            [=]()
    {
        qDebug()<<"has a new Connnection";

        //取出建立好连接的套接字
        tcpSocket = tcpServer->nextPendingConnection();
        //获取对方的ip和端口
        QString ip = tcpSocket->peerAddress().toString();
        quint16 port = tcpSocket->peerPort();

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

        //成功连接后,才能按选择文件
        buttonFile->setEnabled(true);
        connect(buttonFile,SIGNAL(pressed()), this, SLOT(on_buttonFile_clicked()));
        connect(buttonSend,SIGNAL(pressed()), this, SLOT(on_buttonSend_clicked()));
        connect(tcpSocket, &QTcpSocket::readyRead,
                [=]()
        {
            //取客户端的信息
            QByteArray buf = tcpSocket->readAll();
            if(QString(buf) == "file done")
            {//文件接收完毕
                textEdit->append("File send over");
                file.close();

                //断开客户端端口
                tcpSocket->disconnectFromHost();
                tcpSocket->close();
            }
        }
        );
    }
    );

    connect(&timer, &QTimer::timeout,
            [=]()
    {
        //关闭定时器
        timer.stop();
        //发送文件
        sendData();
    }
    );
}
//选择文件的按钮
void CServerWidget::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() << "open file fail with readonly 106";
        }

        //提示打开文件的路径
        textEdit->append(filePath);
        buttonFile->setEnabled(false);
        buttonSend->setEnabled(true);
    }
    else
    {
        qDebug() << "Chose file path is wrong! 118";
    }

}
//发送文件按钮
void CServerWidget::on_buttonSend_clicked()
{
    buttonSend->setEnabled(false);
    qDebug()<<"buttonSend";
    //先发送文件头信息  文件名##文件大小
    QString head = QString("%1##%2").arg(fileName).arg(fileSize);
    //发送头部信息
    qint64 len = tcpSocket->write( head.toUtf8() );
    if(len > 0)//头部信息发送成功
    {
        //发送真正的文件信息
        //防止TCP黏包
        //需要通过定时器延时 20 ms
        timer.start(20);
    }
    else
    {
        qDebug() << "The head message send fail 142";
        file.close();
        buttonFile->setEnabled(true);
        buttonSend->setEnabled(false);
    }
}

void CServerWidget::sendData()
{
    textEdit->append("is sending");
    qDebug()<<"exec server 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);
    //     //是否发送文件完毕
    //     if(sendSize == fileSize)
    //     {
    //         ui->textEdit->append("文件发送完毕");
    //         file.close();

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

CServerWidget::~CServerWidget()
{

}
void CServerWidget::createUi()
{
    ServerWidget = new QWidget(this);
    ServerWidget->resize(574, 422);
    buttonFile = new QPushButton();
    buttonSend = new QPushButton();
    ServerWidget->resize(574, 422);
    gridLayout = new QGridLayout(ServerWidget);
    gridLayout->setSpacing(6);
    gridLayout->setContentsMargins(11, 11, 11, 11);
    gridLayout->setObjectName(QString::fromUtf8("gridLayout"));
    horizontalSpacer = new QSpacerItem(130, 20, QSizePolicy::Expanding, QSizePolicy::Minimum);
    gridLayout->addItem(horizontalSpacer, 0, 0, 1, 2);
    label = new QLabel(ServerWidget);
    label->setObjectName(QString::fromUtf8("label"));
    QFont font;
    font.setFamily(QString::fromUtf8("\346\245\267\344\275\223"));
    font.setPointSize(24);
    label->setFont(font);
    label->setText("Server");
    label->setAlignment(Qt::AlignCenter);
    gridLayout->addWidget(label, 0, 2, 1, 1);
    horizontalSpacer_2 = new QSpacerItem(138, 20, QSizePolicy::Expanding, QSizePolicy::Minimum);
    gridLayout->addItem(horizontalSpacer_2, 0, 3, 1, 2);
    textEdit = new QTextEdit(ServerWidget);
    textEdit->setObjectName(QString::fromUtf8("textEdit"));
    textEdit->setReadOnly(true);
    gridLayout->addWidget(textEdit, 1, 0, 1, 5);
    buttonFile = new QPushButton(ServerWidget);
    buttonFile->setText("Chose File");
    buttonFile->setObjectName(QString::fromUtf8("buttonFile"));
    gridLayout->addWidget(buttonFile, 2, 0, 1, 1);
    horizontalSpacer_3 = new QSpacerItem(217, 20, QSizePolicy::Expanding, QSizePolicy::Minimum);
    gridLayout->addItem(horizontalSpacer_3, 2, 1, 1, 3);
    buttonSend = new QPushButton(ServerWidget);
    buttonSend->setText("Send File");
    buttonSend->setObjectName(QString::fromUtf8("buttonSend"));
    gridLayout->addWidget(buttonSend, 2, 4, 1, 1);
    ServerWidget->setLayout(gridLayout);
    ServerWidget->setWindowTitle(QApplication::translate("ServerWidget", "ServerWidget", nullptr));
    label->setText(QApplication::translate("ServerWidget", "\346\234\215\345\212\241\345\231\250", nullptr));
    buttonFile->setText(QApplication::translate("ServerWidget", "\351\200\211\346\213\251\346\226\207\344\273\266", nullptr));
    buttonSend->setText(QApplication::translate("ServerWidget", "\345\217\221\351\200\201\346\226\207\344\273\266", nullptr));
}

ClientWidget.cpp

本文件完成是发起连接请求,并接受服务端发来的文件,将文件接受进度已ProgressBar的形式显示在客户端。

#include "ClientWidget.h"
CClientWidget::CClientWidget(QWidget *parent) : QWidget(parent)
{
    createUi();
    connect(buttonConnect,SIGNAL(clicked()), this, SLOT(on_buttonConnect_clicked()));

    tcpSocket = new QTcpSocket(this);
    isStart = true;
    progressBar->setValue(0); //当前值
    setWindowTitle("Server");
    connect(tcpSocket, &QTcpSocket::connected,
            [=]()
    {
        //提示连接成功
        textEdit->clear();
        textEdit->append("connect success with server,waiting for sending……");
    }
    );

    connect(tcpSocket, SIGNAL(readyRead()),this,SLOT(readFile()));
}

//本小类用于当Socket中readyRead()时,执行读取socket中文件
void CClientWidget::readFile()
{
    //取出接收的内容
    QByteArray buf = tcpSocket->readAll();

    if(true == isStart)
    {//接收头
        isStart = false;
        //解析头部信息 QString buf = "hello##1024"
        //                    QString str = "hello##1024#mike";
        //                            str.section("##", 0, 0)
        //初始化
        //文件名
        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 49";

            tcpSocket->disconnectFromHost(); //断开连接
            tcpSocket->close(); //关闭套接字

            return; //如果打开文件失败,中断函数
        }

        //弹出对话框,显示接收文件的信息
        qDebug()<<"filesize"<<fileSize;
        QString str = QString("Receive file: [%1: %2kb]").arg(fileName).arg(fileSize/1024);
        //QMessageBox::information(this, "文件信息", str);
        textEdit->append(str);
        textEdit->append("is receiving..");

        //设置进度条
        progressBar->setMinimum(0); //最小值
        progressBar->setMaximum(fileSize/1024); //最大值
        progressBar->setValue(0); //当前值
    }
    else //文件信息
    {
        qint64 len = file.write(buf);
        if(len >0) //接收数据大于0
        {
            recvSize += len; //累计接收大小
            qDebug() << len;
        }

        //更新进度条
        qDebug()<<"client progressbar";
        progressBar->setValue(recvSize/1024);

        if(recvSize == fileSize) //文件接收完毕
        {
            //先给服务发送(接收文件完成的信息)
            tcpSocket->write("file done");
            textEdit->append("file receive over");
            QMessageBox::information(this, "over", "file receive over");
            file.close(); //关闭文件
            //断开连接
            tcpSocket->disconnectFromHost();
            tcpSocket->close();
        }
    }
}
void CClientWidget::on_buttonConnect_clicked()
{
    qDebug()<<"buttonConnect";
    //获取服务器的ip和端口
    QString ip = lineEditIP->text();
    quint16 port = lineEditPort->text().toInt();

    //主动和服务器连接
    tcpSocket->connectToHost(QHostAddress(ip), port);
    isStart = true;
    //设置进度条
    progressBar->setValue(0);
}

CClientWidget::~CClientWidget()
{

}
void CClientWidget::createUi()
{
    ClientWidget = new QWidget(this);
    ClientWidget->resize(400, 300);
    if (ClientWidget->objectName().isEmpty())
        ClientWidget->setObjectName(QString::fromUtf8("ClientWidget"));
    ClientWidget->resize(400, 300);
    gridLayout_2 = new QGridLayout(ClientWidget);
    gridLayout_2->setObjectName(QString::fromUtf8("gridLayout_2"));
    widget_2 = new QWidget(ClientWidget);
    widget_2->setObjectName(QString::fromUtf8("widget_2"));
    horizontalLayout = new QHBoxLayout(widget_2);
    horizontalLayout->setObjectName(QString::fromUtf8("horizontalLayout"));
    horizontalSpacer = new QSpacerItem(125, 20, QSizePolicy::Expanding, QSizePolicy::Minimum);
    horizontalLayout->addItem(horizontalSpacer);
    label_3 = new QLabel(widget_2);
    label_3->setObjectName(QString::fromUtf8("label_3"));
    QFont font;
    font.setFamily(QString::fromUtf8("\346\245\267\344\275\223"));
    font.setPointSize(24);
    label_3->setFont(font);
    label_3->setAlignment(Qt::AlignCenter);
    horizontalLayout->addWidget(label_3);
    horizontalSpacer_2 = new QSpacerItem(125, 20, QSizePolicy::Expanding, QSizePolicy::Minimum);
    horizontalLayout->addItem(horizontalSpacer_2);
    gridLayout_2->addWidget(widget_2, 0, 0, 1, 1);
    widget = new QWidget(ClientWidget);
    widget->setObjectName(QString::fromUtf8("widget"));
    gridLayout = new QGridLayout(widget);
    gridLayout->setObjectName(QString::fromUtf8("gridLayout"));
    label = new QLabel(widget);
    label->setObjectName(QString::fromUtf8("label"));
    gridLayout->addWidget(label, 0, 0, 1, 1);
    lineEditPort = new QLineEdit(widget);
    lineEditPort->setObjectName(QString::fromUtf8("lineEditPort"));
    gridLayout->addWidget(lineEditPort, 2, 1, 1, 1);
    lineEditIP = new QLineEdit(widget);
    lineEditIP->setObjectName(QString::fromUtf8("lineEditIP"));
    gridLayout->addWidget(lineEditIP, 0, 1, 1, 1);
    label_2 = new QLabel(widget);
    label_2->setObjectName(QString::fromUtf8("label_2"));
    gridLayout->addWidget(label_2, 1, 0, 2, 1);
    buttonConnect = new QPushButton(widget);
    buttonConnect->setObjectName(QString::fromUtf8("buttonConnect"));
    gridLayout->addWidget(buttonConnect, 0, 2, 3, 1);
    gridLayout_2->addWidget(widget, 1, 0, 1, 1);
    progressBar = new QProgressBar(ClientWidget);
    progressBar->setObjectName(QString::fromUtf8("progressBar"));
    progressBar->setValue(24);
    gridLayout_2->addWidget(progressBar, 2, 0, 1, 1);
    textEdit = new QTextEdit(ClientWidget);
    textEdit->setObjectName(QString::fromUtf8("textEdit"));
    textEdit->setReadOnly(true);
    gridLayout_2->addWidget(textEdit, 3, 0, 1, 1);
    // retranslateUi(ClientWidget);
    QMetaObject::connectSlotsByName(ClientWidget);

    ClientWidget->setWindowTitle(QApplication::translate("ClientWidget", "Form", nullptr));
    label_3->setText(QApplication::translate("ClientWidget", "\345\256\242\346\210\267\347\253\257", nullptr));
    label->setText(QApplication::translate("ClientWidget", "\346\234\215\345\212\241\345\231\250\347\232\204IP:", nullptr));
    lineEditPort->setText(QApplication::translate("ClientWidget", "8888", nullptr));
    lineEditIP->setText(QApplication::translate("ClientWidget", "127.0.0.1", nullptr));
    label_2->setText(QApplication::translate("ClientWidget", "\346\234\215\345\212\241\345\231\250\347\232\204\347\253\257\345\217\243:", nullptr));
    buttonConnect->setText(QApplication::translate("ClientWidget", "connect", nullptr));
}

代码地址

https://download.csdn.net/download/weixin_38809485/12623790里面的MyTcpFile

TCP黏包问题

前言

黏包,通俗来讲就是按照使用数据报的形式使用了数据流。网络通信方式主要有两种:TCP与UDP。 UDP是基于报文传输的,发送几次Write(),接收端就会用几次Read(),每次读取一个报文,报文间不合并,多余缓冲区的报文会丢弃。TCP是基于数据流传输的,Write()和Read()的次数不固定,报文间会以随机的方式合并,这就需要在接收时处理粘包了。通过上面的分析,我们可以发现,粘包只可能出现在流式传输中。 其粘包原因可能是下面两种情况:

  1. 发送端需要等缓冲区才能发送数据,造成发送时就粘包;
  2. 接收端未及时接收缓冲区的数据,多包一起接收,造成粘包;
粘包的解决方法

为了避免粘包,我们一般可以采取以下三种措施:

  1. 发送端粘包,可以通过程序设置push指令,不等缓冲区满就立即发送数据(默认情况是等缓冲区满后再发送)。这种方法对于通信的传输效率会降低,有时也不是百分百能可靠。
  2. 接收端粘包,可以通过优化程序、精简进程工作量、提高进程优先级等措施,使其及时接收数据。这种方法,你可以发现,有时候也是无法优化的,实现起来会比较难。
  3. 采用自定义包头结构,人为控制多次合并,来避免粘包问题。这是常用的做法。

一般选择用拆包放入buffer的方式,来读取socket数据。
拆包过程总结:

在这里插入图片描述

TCP传输大文件的问题

首先,应该明白整个socket传输的机制,我们可以了解到,每次服务端发送给客户端的都是一部分数据,从来都不可能直接把整个文件一次性发过去,这个发的操作也就是tcpSocket->write(buf, len);这样的操作。然后,客户端使用tcp->readall进行读取socket里面的全部字节,但你可千万别被all这个关键词给麻痹了。这个tcp->readall是在每次socket每次有内容,就会调用的,也就是当客户端有一个函数叫readyread这个触发后,就会执行这样的操作。

所以,我在处理这个事情遇到的两个问题是:

  1. 如何存储多次传输过来的字节流。使用字节数组allByteArray,但每次都被自动刷新了。
  2. 传输结束后,是否能够只获取数据的那一部分,用于读取,并显示成一个xml文件。

解决办法:

1.关于第一个的问题出在我重复new了对象,所以,在平常写bug的过程中,要尽量在构造函数去new对象,尽量不要在程序的运行过程中去new对象,除非你保证你能够hold的住。这里也在讲述一个找问题的比较好的方法:
在这里插入图片描述
关于你要查找的东西,右键点击这个,在该文件中所存在的这个东西都会被列出来,那个时候,你就知道是否,你有做了什么奇怪的事情没有,对于某个东西。
2. 第二个问题的话,还是比较简单的,直接在后面用一个if判断是文件头信息还是文件数据信息,若是文件数据则将所有的字节流进行累加,就可以得到所以的数据字节流了。

readyRead的注意事项

readyRead的解释是:每一次,在当前用来存放读数据的位置中(也就是可读缓冲区),有可读数据可用,都会发送这个信号。当新的数据到来的时候,这个信号还会再发送且仅发送一次。比如:新的网络数据到达你的网络socket,或者新的数据块添加到你的设备。
但需要注意的点是:当新的数据到来的时候,这个信号还会再发送且仅发送一次。”很多朋友对新的数据到来的错误理解是,发送端对应的QTcpSokcet写一次,也就是write一次,那么接收方就会有新的数据到达,于是readyread信号被触发一次。这个理解是错误的。经过测试,我们发现事实是这样的:发送和接收是没有一一对应关系的。发送端write函数调用一次,假如这一次write了2M的数据,那么接收方的readyread信号就往往会触发两次以上。反过来,如过发送端write函数被调用两次或两次以上,那么接收方的readyread信号也有可能只触发一次。参考:https://blog.csdn.net/whiskey_wei/article/details/80790428

UDP通信

前言

使用Qt提供的QUdpSocket进行UDP通信。在UDP方式下,客户端并不与服务器建立连接,它只负责调用发送函数向服务器发送数据。类似的服务器也不从客户端接收连接,只负责调用接收函数,等待来自客户端的数据的到达。
在UDP通信中,服务器端和客户端的概念已经显得有些淡化,两部分做的工作都大致相同:

  1. 创建套接字
  2. 绑定套接字
    在UDP中如果需要接收数据则需要对套接字进行绑定,只发送数据则不需要对套接字进行绑定。
    通过调用bind()函数将套接字绑定到指定端口上。
  3. 接收或者发送数据
  4. 接收数据:使用readDatagram()接收数据,函数声明如下:
qint64	readDatagram(char * data, qint64 maxSize, 
QHostAddress * address = 0, quint16 * port = 0)

参数:
5. data: 接收数据的缓存地址
6. maxSize: 缓存接收的最大字节数
7. address: 数据发送方的地址(一般使用提供的默认值)
8. port: 数据发送方的端口号(一般使用提供的默认值)
使用pendingDatagramSize()可以获取到将要接收的数据的大小,根据该函数返回值来准备对应大小的内存空间存放将要接收的数据。

发送数据: 使用writeDatagram()函数发送数据,函数声明如下:

qint64	writeDatagram(const QByteArray & datagram, 
const QHostAddress & host, quint16 port)

参数:
9. datagram:要发送的字符串
10. host:数据接收方的地址
11. port:数据接收方的端口号

广播

在使用QUdpSocket类的writeDatagram()函数发送数据的时候,其中第二个参数host应该指定为广播地址:QHostAddress::Broadcast此设置相当于QHostAddress(“255.255.255.255”)
使用UDP广播的的特点:

  1. 使用UDP进行广播,局域网内的其他的UDP用户全部可以收到广播的消息。
  2. UDP广播只能在局域网范围内使用。

组播

我们再使用广播发送消息的时候会发送给所有用户,但是有些用户是不想接受消息的,这时候我们就应该使用组播,接收方只有先注册到组播地址中才能收到组播消息,否则则接受不到消息。另外组播是可以在Internet中使用的。
在使用QUdpSocket类的writeDatagram()函数发送数据的时候,其中第二个参数host应该指定为组播地址,关于组播地址的分类:

224.0.0.0~224.0.0.255为预留的组播地址(永久组地址),地址224.0.0.0保留不做分配,其它地址供路由协议使用;
224.0.1.0~224.0.1.255是公用组播地址,可以用于Internet;
224.0.2.0~238.255.255.255为用户可用的组播地址(临时组地址),全网范围内有效;
239.0.0.0~239.255.255.255为本地管理组播地址,仅在特定的本地范围内有效。

注册加入到组播地址需要使用QUdpSocket类的成员函数:

bool	joinMulticastGroup(const QHostAddress & groupAddress)

UDP通信小知识

  1. 在UDP中如果需要接收数据则需要对套接字进行绑定,只发送数据则不需要对套接字进行绑定。通过调用bind()函数将套接字绑定到指定端口上。
  2. 使用UDP广播的的特点:

1.使用UDP进行广播,局域网内的其他的UDP用户全部可以收到广播的消息.
2.UDP广播只能在局域网范围内使用
3. 接收方只有先注册到组播地址中才能收到组播消息,否则则接受不到消息。

参考文献

  1. TCP粘包产生的原因、解决方法及Qt项目代码实现(点赞+收藏)

P.S:如有错误,欢迎指正~

  • 3
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值