Qt 的网络通信(TCP)

基于TCP Qt的网络通信

在标准 C++ 没有提供专门用于套接字通信的类,所以只能使用操作系统提供的基于 C 的 API 函数,基于这些 C 的 API 函数我们也可以封装自己的 C++ 类 , 但是Qt 提供了封装好的套接字通信类:

  • QTcpServer:服务器类,用于监听客户端连接以及和客户端建立连接
  • QTcpServer:服务器类,用于监听客户端连接以及和客户端建立连接

这两个套接字通信类都属于网络模块 network ,使用前切记在项目的 .pro文件中添加 network 模块

文章目录

  • 基于TCP Qt的网络通信
    • 1、QTcpSever
      • 1.1 成员函数
      • 1.2 信号
    • 2、 QTcpSocket
      • 2.1 成员函数
      • 2.2 信号
    • 3、 通信流程
      • 3.1 服务器端
      • 3.2 客户端

1、QTcpSever

QTcpServer 类用于监听客户端连接以及和客户端建立连接,在使用之前先介绍一下这个类提供的一些常用 API 函数:

1.1 成员函数

  • 构造函数
QTcpServer::QTcpServer(QObject *parent = Q_NULLPTR);
  • 给监听的套接字设置监听
bool QTcpServer::listen(const QHostAddress &address = QHostAddress::Any, quint16 port = 0);
// 判断当前对象是否在监听, 是返回true,没有监听返回false
bool QTcpServer::isListening() const;
// 如果当前对象正在监听返回监听的服务器地址信息, 否则返回 QHostAddress::Null
QHostAddress QTcpServer::serverAddress() const;
// 如果服务器正在侦听连接,则返回服务器的端口; 否则返回0
quint16 QTcpServer::serverPort() const

参数:

  1. address: 通过类 QHostAddress 可以封装 IPv4 、 IPv6 格式的 IP地址,QHostAddress :: Any 表示自动绑定;
  2. port: 端口号 , 指定为 0 表示随机绑定一个可用端口;
  3. 返回值: 绑定失败返回 false , 成功 true;
  • 得到和客户端建立连接之后用于通信的 QTcpSocket 套接字对象,它是 QTcpServer 的一个子对象,当 QTcpServer 对象析构的时候会自动析构这个子对象,当然也可自己手动析构,建议用完之后自己手动析构这个通信的 QTcpSocket 对象。
QTcpSocket *QTcpServer::nextPendingConnection();
  • 阻塞等待客户端发起的连接请求,不推荐在单线程程序中使用,建议使用非阻塞方式处理新连接,即使用信号 newConnection()
bool QTcpServer::waitForNewConnection(int msec = 0, bool *timedOut = Q_NULLPTR);

参数:

  1. mes: 指定阻塞的最大时间 , 单位毫秒;
  2. timeout: 传出参数,如果操作超时 timeout 为 true,没有超时 timeout 为 false;

1.2 信号

  • 当接受新连接导致错误时,将发射如下信号。socketError 参数描述了发生的错误相关的信息;
[signal] void QTcpServer::acceptError(QAbstractSocket::SocketError socketError);
  • 每次有新连接可用时都会发出 newConnection () 信号。
[signal] void QTcpServer::newConnection();

2、 QTcpSocket

QTcpSocket 是一个套接字通信类,不管是客户端还是服务器端都需要使用。在 Qt 中发送和接收数据也属于 IO 操作(网络 IO),先来看一下这个类的继承关系:

QTcpSocket 继承于  QAbstractSocket  继承于  QIODevice

2.1 成员函数

  • 构造函数
QTcpSocket::QTcpSocket(QObject *parent = Q_NULLPTR);
  • 连接服务器,需要指定服务器端绑定的IP和端口信息。
[virtual] void QAbstractSocket::connectToHost(const QString &hostName, quint16 port, OpenMode openMode = ReadWrite, NetworkLayerProtocol protocol = AnyIPProtocol);

[virtual] void QAbstractSocket::connectToHost(const QHostAddress &address, quint16 port, OpenMode openMode = ReadWrite);

在 Qt 中不管调用读操作函数接收数据,还是调用写函数发送数据,操作的对象都是本地的由 Qt 框架维护的一块内存。因此,调用了发送函数数据不一定会马上被发送到网络中,调用了接收函数也不是直接从网络中接收数据,关于底层的相关操作是不需要使用者来维护的

  • 接受数据
// 指定可接收的最大字节数 maxSize 的数据到指针 data 指向的内存中
qint64 QIODevice::read(char *data, qint64 maxSize);
// 指定可接收的最大字节数 maxSize,返回接收的字符串
QByteArray QIODevice::read(qint64 maxSize);
// 将当前可用操作数据全部读出,通过返回值返回读出的字符串
QByteArray QIODevice::readAll();
  • 发送数据
// 发送指针 data 指向的内存中的 maxSize 个字节的数据
qint64 QIODevice::write(const char *data, qint64 maxSize);
// 发送指针 data 指向的内存中的数据,字符串以 \0 作为结束标记
qint64 QIODevice::write(const char *data);
// 发送参数指定的字符串
qint64 QIODevice::write(const QByteArray &byteArray);

2.2 信号

在使用 QTcpSocket 进行套接字通信的过程中,如果该类对象发射出 readyRead() 信号,说明对端发送的数据达到了,之后就可以调用 read 函数接收数据了

[signal] void QIODevice::readyRead();
  • 调用 connectToHost() 函数并成功建立连接之后发出 connected() 信号
[signal] void QAbstractSocket::connected();
  • 在套接字断开连接时发出 disconnected() 信号
[signal] void QAbstractSocket::disconnected();

3、 通信流程

代码百度连接,自行提取

链接:https://pan.baidu.com/s/1W-ixxxt66IsmDLXUTD0PsA
提取码:1234

3.1 服务器端

  1. 创建套接字服务器 QTcpServer 对象
  2. 通过 QTcpServer 对象设置监听,即:QTcpServer::listen()
  3. 基于 QTcpServer::newConnection() 信号检测是否有新的客户端连接
  4. 如果有新的客户端连接调用 QTcpSocket *QTcpServer::nextPendingConnection() 得到通信的套接字对象
  5. 使用通信的套接字对象 QTcpSocket 和客户端进行通信

Qt 的网络通信(TCP)_第1张图片

头文件:

#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include 
#include 
#include 
#include 
#include 

namespace Ui {
class MainWindow;
}

class MainWindow : public QMainWindow
{
    Q_OBJECT

public:
    explicit MainWindow(QWidget *parent = 0);
    ~MainWindow();

private slots:
    void on_setlisten_clicked();

    void on_pushButton_clicked();

private:
    Ui::MainWindow *ui;
    QTcpServer* m_s;
    QTcpSocket* m_tcp;
    QLabel* m_status;
};

#endif // MAINWINDOW_H

源文件:

#include "mainwindow.h"
#include "ui_mainwindow.h"

MainWindow::MainWindow(QWidget *parent) :
    QMainWindow(parent),
    ui(new Ui::MainWindow)
{
    ui->setupUi(this);
    setWindowTitle("服务器");
    ui->pushButton->setDisabled(true); // 初始化连接前,不能按发送按钮

    m_s = new QTcpServer(this);// 创建监听的套接字

    ui->port->setText("8899"); // 默认资源
    connect(m_s ,&QTcpServer::newConnection ,this,[=](){
        // 创建用于通信的套接字
        m_tcp = m_s->nextPendingConnection();

        m_status->setPixmap(QPixmap(":/green.png").scaled(20,20));// 切换状态栏图片

        // 检测是否可以接受数据
        connect(m_tcp,&QTcpSocket::readyRead,this,[=](){
            QByteArray data = m_tcp->readAll();
            ui->record->append("客户端 : " + data);
        });

        // 断开连接
        connect(m_tcp,&QTcpSocket::disconnected ,this,[=](){
            m_tcp->close();
            m_tcp->deleteLater(); // 封装 delete
            m_status->setPixmap(QPixmap(":/red.png").scaled(20,20));

            ui->pushButton->setDisabled(true);

        });

        // 切换发送按钮状态
        ui->pushButton->setDisabled(false);

    });

    // 状态栏
    m_status = new QLabel;
    m_status->setPixmap(QPixmap(":/red.png").scaled(20,20));
    ui->statusBar->addWidget(new QLabel("连接状态: "));
    ui->statusBar->addWidget(m_status);
}

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

// 启动监听服务按钮
void MainWindow::on_setlisten_clicked()
{
    unsigned short port = ui->port->text().toUShort();
    m_s->listen(QHostAddress::Any ,port); // 设置监听
    ui->setlisten->setDisabled(true);

}

// 发送数据按钮
void MainWindow::on_pushButton_clicked()
{
    QString msg = ui->msg->toPlainText();
    m_tcp->write(msg.toUtf8()); // 输入客户端
    ui->record->append("服务器 : " + msg);

}

3.2 客户端

  1. 创建通信的套接字类 QTcpSocket 对象
  2. 使用服务器端绑定的 IP 和端口连接服务器 QAbstractSocket::connectToHost()
  3. 使用 QTcpSocket 对象和服务器进行通信

Qt 的网络通信(TCP)_第2张图片

头文件:

#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include 

#include 
#include 
#include 
#include 

namespace Ui {
class MainWindow;
}

class MainWindow : public QMainWindow
{
    Q_OBJECT

public:
    explicit MainWindow(QWidget *parent = 0);
    ~MainWindow();

    void readData();


private slots:

    void on_pushButton_clicked();

    void on_connect_clicked();

    void on_disconnect_clicked();



private:
    Ui::MainWindow *ui;
    QTcpSocket* m_tcp;
    QLabel* m_status;
};

#endif // MAINWINDOW_H

源文件:

#include "mainwindow.h"
#include "ui_mainwindow.h"

MainWindow::MainWindow(QWidget *parent) :
    QMainWindow(parent),
    ui(new Ui::MainWindow)
{
    ui->setupUi(this);
    setWindowIcon(QIcon(":/m4.ico"));
    setWindowTitle("客户端");
    ui->disconnect->setDisabled(true); // 初始时 ,断开连接按钮不可用

    ui->port->setText("30003"); // 默认端口
    ui->ip->setText("192.168.79.129"); // 默认ip
    ui->msg->setPlaceholderText("请输入指令");

    // 创建通信的套接字
    m_tcp =new QTcpSocket(this);

    // 接受到服务端消息
    connect(m_tcp,&QTcpSocket::readyRead,this,[=](){
        QByteArray data = m_tcp->readAll();
        // ui->record->append("服务器 : " + data);
        // readData();
     });

    // 服务器端断开连接
    connect(m_tcp,&QTcpSocket::disconnected ,this,[=](){
        m_tcp->close();
        //m_tcp->deleteLater(); // 封装 delete
        m_status->setPixmap(QPixmap(":/red.png").scaled(20,20));
        ui->record->append("已经和服务器断开连接....");

        ui->connect->setDisabled(false);// 设置连接按钮不可用
        ui->disconnect->setDisabled(true);
    });

    // 检测连接状态
    connect(m_tcp,&QTcpSocket::connected ,this,[this](){
        m_status->setPixmap(QPixmap(":/green.png").scaled(20,20));
        ui->record->append("连接服务器成功...");

        ui->connect->setDisabled(true);
        ui->disconnect->setDisabled(false);
    });

    // 状态栏
    m_status = new QLabel;
    m_status->setPixmap(QPixmap(":/red.png").scaled(20,20));
    ui->statusBar->addWidget(new QLabel("连接状态: "));
    ui->statusBar->addWidget(m_status);
}

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

void MainWindow::readData()
{
    QByteArray byteArray;//临时变量,用来存储每次从机械臂读进来的数据
    byteArray.resize(this->m_tcp->bytesAvailable());//先设置每次读取的大小为可接收缓存的大小
    int byteRead = this->m_tcp->read(byteArray.data(), byteArray.size());//把缓存区所有数据读取回来,可以用readAll替代,但是现在用的函数可以判断读取结果

    if (byteRead == -1) {//读取错误
        ui->record->append("read data from robot error!") ;
        return;
    } else if (byteRead == 0) {//没读到东西
        ui->record->append("read data from robot error!") ;
        return;
    }
}

// 发送数据按钮
void MainWindow::on_pushButton_clicked()
{
    QString msg = ui->msg->toPlainText();
    m_tcp->write(msg.toUtf8()+"\n"); // 输入客户端
    ui->record->append("客户端 : " + msg);
    ui->msg->clear();
}

// 连接服务器按钮
void MainWindow::on_connect_clicked()
{
    QString ip = ui->ip->text();
    unsigned short port = ui->port->text().toUShort();
    m_tcp->connectToHost(QHostAddress(ip) , port);
}

// 断开连接
void MainWindow::on_disconnect_clicked()
{
    m_tcp->close();
    ui->disconnect->setDisabled(true);
    ui->connect->setDisabled(false);
}

你可能感兴趣的:(网络编程,Qt,qt,tcp/ip,网络)