一、说明:
在标准 C++ 没有提供专门用于套接字通信的类,所以只能使用操作系统提供的基于 C 的 API 函数,基于这些 C 的 API 函数我们也可以封装自己的 C++ 类
但是 Qt 就不一样了,它是 C++ 的一个框架并且里边提供了用于套接字通信的类(TCP、UDP)这样就使得我们的操作变得更加简单了(当然,在Qt中使用标准C的API进行套接字通信也是完全没有问题的)。以下使用相关类的进行 TCP 通信
使用 Qt 提供的类进行基于 TCP 的套接字通信需要用到两个类:
1、QTcpServer:服务器类,用于监听客户端连接以及和客户端建立连接
2、QTcpSocket:通信的套接字类,客户端、服务器端都需要使用
二、QTcpServer 类:
1、公共成员函数:
//构造一个用于监听的服务器端对象QTcpServer::QTcpServer(QObject *parent = Q_NULLPTR);
//设置监听,参数一:绑定本地的ip,参数二:指定的端口bool QTcpServer::listen(const QHostAddress &address = QHostAddress::Any, quint16 port = 0);// 判断当前对象是否在监听, 是返回true,没有监听返回falsebool QTcpServer::isListening() const;// 如果当前对象正在监听返回监听的服务器地址信息, 否则返回 QHostAddress::NullQHostAddress QTcpServer::serverAddress() const;// 如果服务器正在侦听连接,则返回服务器的端口; 否则返回0quint16 QTcpServer::serverPort() const
参数:
address:通过类 QHostAddress 可以封装 IPv4、IPv6 格式的 IP 地址,QHostAddress::Any 表示自动绑定
port:如果指定为 0 表示随机绑定一个可用端口
返回值:绑定成功返回 true,失败返回 false
得到和客户端建立连接之后用于通信的 QTcpSocket 套接字对象,它是 QTcpServer 的一个子对象,当 QTcpServer 对象析构的时候会自动析构这个子对象,当然也可自己手动析构,建议用完之后自己手动析构这个通信的 QTcpSocket 对象。
QTcpSocket *QTcpServer::nextPendingConnection();
阻塞等待客户端发起的连接请求,不推荐在单线程程序中使用,建议使用非阻塞方式处理新连接,即使用信号 newConnection()
bool QTcpServer::waitForNewConnection(int msec = 0, bool *timedOut = Q_NULLPTR);
参数:
msec:指定阻塞的最大时长,单位为毫秒(ms)
timeout:传出参数,如果操作超时 timeout 为 true,没有超时 timeout 为 false
2、信号:
//当接受新连接导致错误时,将发射如下信号。socketError 参数描述了发生的错误相关的信息[signal] void QTcpServer::acceptError(QAbstractSocket::SocketError socketError);
//每次有新连接可用时都会发出 newConnection () 信号[signal] void QTcpServer::newConnection();
三、QTcpSocket类:
1、说明:
QTcpSocket 是一个套接字通信类,不管是客户端还是服务器端都需要使用。在 Qt 中发送和接收数据也属于 IO 操作(网络 IO),其继承关系如下:
2、公共成员函数:
QTcpSocket::QTcpSocket(QObject *parent = Q_NULLPTR);
[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);
3、信号:
在使用 QTcpSocket 进行套接字通信的过程中,如果该类对象发射出 readyRead() 信号,说明对端发送的数据达到了,之后就可以调用 read 函数接收数据了
/*在使用 QTcpSocket 进行套接字通信的过程中,如果该类对象发射出 readyRead() 信号,说明对端发送的数据达到了,之后就可以调用read 函数接收数据了*/[signal] void QIODevice::readyRead();
//调用 connectToHost() 函数并成功建立连接之后发出 connected() 信号[signal] void QAbstractSocket::connected();
//在套接字断开连接时发出 disconnected() 信号[signal] void QAbstractSocket::disconnected();
四、通信流程:
1、流程:
①、创建套接字服务器 QTcpServer 对象
②、通过 QTcpServer 对象设置监听,即:QTcpServer::listen()
③、基于 QTcpServer::newConnection() 信号检测是否有新的客户端连接
④、如果有新的客户端连接调用 QTcpSocket *QTcpServer::nextPendingConnection() 得到通信的套接字对象
⑤、使用通信的套接字对象 QTcpSocket 和客户端进行通信
2、服务器端程序:
QT += core gui networkgreaterThan(QT_MAJOR_VERSION, 4): QT += widgetsCONFIG += c++11# You can make your code fail to compile if it uses deprecated APIs.# In order to do so, uncomment the following line.#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0SOURCES += \main.cpp \mainwindow.cppHEADERS += \mainwindow.hFORMS += \mainwindow.ui# Default rules for deployment.qnx: target.path = /tmp/$${TARGET}/binelse: unix:!android: target.path = /opt/$${TARGET}/bin!isEmpty(target.path): INSTALLS += targetRESOURCES += \ear.qrc
#ifndef MAINWINDOW_H#define MAINWINDOW_H#include <QMainWindow>#include <QTcpServer> //套接字模块的头文件#include <QTcpSocket> //套接字通信的头文件#include <QLabel>QT_BEGIN_NAMESPACEnamespace Ui { class MainWindow; }QT_END_NAMESPACEclass MainWindow : public QMainWindow{Q_OBJECTpublic:MainWindow(QWidget *parent = nullptr);~MainWindow();private slots:void on_jtfw_clicked();void on_fsxx_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("服务器"); //给窗口添加标题//2.1、设置默认端口,用于方便测试ui->duankou->setText("7788");//1、创建监听的服务器对象m_s = new QTcpServer(this); //需要在指定父对象this//等待客户端连接的槽函数,如果有客户端连接,则m_s会发出newConnection信号connect(m_s,&QTcpServer::newConnection,this,[=](){m_tcp = m_s->nextPendingConnection(); //得到一个用于通讯的套接字对象:m_tcpm_status->setPixmap(QPixmap(":/img/pppp.jpg").scaled(20,20)); //连接状态,scaled函数设置图标大小//检测是否可以接受数据 */connect(m_tcp,&QTcpSocket::readyRead,this,[=]() //当有客户端发起连接,就发射出 readyRead 信号{QByteArray data = m_tcp->readAll(); //读取接收的数据ui->record->append("客户端:" + data); //将接收到的是数据显示(追加)在信息框});//4.1、当m_tcp发射出eradyRead信号,就说明客户端断开了连接connect(m_tcp,&QTcpSocket::disconnected,this,[=](){//m_tcp->close(); //服务器也断开连接(关闭连接)ui->record->append("客户端已经断开了连接");m_tcp->deleteLater(); //释放m_tcp指向的内存,可直接调用deleteLater,因为里面封装了deletem_status->setPixmap(QPixmap(":/img/guoqi.jpg").scaled(20,20)); //断开状态,scaled函数设置图标大小});});//4、状态栏的处理的动作m_status = new QLabel(this); //可以不指定父对象,因为是再状态栏里//默认状态,scaled函数设置图标大小m_status->setPixmap(QPixmap(":/img/guoqi.jpg").scaled(20,20));//设置到状态栏中ui->statusbar->addWidget(new QLabel("连接状态:"));ui->statusbar->addWidget(m_status);}MainWindow::~MainWindow(){delete ui;}//2、当点击”启动监听服务“按钮,启动监听void MainWindow::on_jtfw_clicked(){//2.2、读取控件的端口号,是unsigned short类型,toUShort()是类型转换unsigned short duankou = ui->duankou->text().toUShort();//2.3、调用listen()进行监听,参数一:绑定本地的ip,参数二:指定的端口m_s->listen(QHostAddress::Any,duankou);//2.4、当启动监听后,”启动监听服务“按钮不可再按ui->jtfw->setDisabled(true);}//3、当点击”发送信息“按钮,void MainWindow::on_fsxx_clicked(){QString msg = ui->msg->toPlainText(); //以toPlainText(纯文本)的方式读取“发送的信息”框的内容m_tcp->write(msg.toUtf8());ui->record->append("服务器端:" + msg); //将接收到的是数据显示(追加)在信息框ui->msg->clear(); //发送完后清空输入框的内容}

3、客户端程序:
QT += core gui networkgreaterThan(QT_MAJOR_VERSION, 4): QT += widgetsCONFIG += c++11# You can make your code fail to compile if it uses deprecated APIs.# In order to do so, uncomment the following line.#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0SOURCES += \main.cpp \mainwindow.cppHEADERS += \mainwindow.hFORMS += \mainwindow.ui# Default rules for deployment.qnx: target.path = /tmp/$${TARGET}/binelse: unix:!android: target.path = /opt/$${TARGET}/bin!isEmpty(target.path): INSTALLS += targetRESOURCES += \ear.qrc
#ifndef MAINWINDOW_H#define MAINWINDOW_H#include <QMainWindow>#include <QTcpSocket> //套接字通信的头文件#include <QLabel>QT_BEGIN_NAMESPACEnamespace Ui { class MainWindow; }QT_END_NAMESPACEclass MainWindow : public QMainWindow{Q_OBJECTpublic:MainWindow(QWidget *parent = nullptr);~MainWindow();private slots:void on_jtfw_clicked();void on_dklj_clicked();void on_fsxx_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);//设置默认端口,用于方便测试ui->duankou->setText("7788");ui->ip->setText("192.168.194.172");setWindowTitle("客户端"); //给窗口添加标题ui->dklj->setDisabled(true); //“断开连接”按钮不可按//ui->dklj->setEnabled(false);//创建监听的服务器对象m_tcp = new QTcpSocket; //需要在指定父对象this//检测是否可以接受数据 */connect(m_tcp,&QTcpSocket::readyRead,this,[=]() //当有客户端发起连接,就发射出 readyRead 信号{QByteArray data = m_tcp->readAll(); //读取接收的数据ui->record->append("客户端:" + data); //将接收到的是数据显示(追加)在信息框});//当m_tcp发射出eradyRead信号,就说明客户端断开了连接connect(m_tcp,&QTcpSocket::disconnected,this,[=](){m_tcp->close(); //服务器也断开连接m_tcp->deleteLater(); //释放m_tcp指向的内存,可直接调用deleteLater,因为里面封装了deletem_status->setPixmap(QPixmap(":/img/guoqi.jpg").scaled(20,20)); //断开状态,scaled函数设置图标大小ui->record->append("服务器已和客户端断开了连接");ui->dklj->setDisabled(true); //“断开连接”按钮不可按ui->jtfw->setEnabled(false); //“连接服务器”按钮可按});connect(m_tcp,&QTcpSocket::connected,this,[=](){m_status->setPixmap(QPixmap(":/img/pppp.jpg").scaled(20,20));//将连接成功的信息显示(追加)在信息框ui->record->append("已经连接到了服务器");ui->dklj->setDisabled(false);//ui->dklj->setEnabled(true); //“断开连接”按钮可按ui->jtfw->setEnabled(false); //“连接服务器”按钮不可按});//状态栏的处理的动作m_status = new QLabel(this); //可以不指定父对象,因为是再状态栏里m_status->setPixmap(QPixmap(":/img/guoqi.jpg").scaled(20,20)); //默认状态,scaled函数设置图标大小//设置到状态栏中ui->statusbar->addWidget(new QLabel("连接状态:"));ui->statusbar->addWidget(m_status);}MainWindow::~MainWindow(){delete ui;}//3、当点击”发送信息“按钮,void MainWindow::on_fsxx_clicked(){QString msg = ui->msg->toPlainText(); //以toPlainText(纯文本)的方式读取“发送的信息”框的内容m_tcp->write(msg.toUtf8());ui->record->append("客户端:" + msg); //将接收到的是数据显示(追加)在信息框ui->msg->clear(); //发送完后清空输入框的内容}//当点击”连接服务器“按钮,与服务器建立联接void MainWindow::on_jtfw_clicked(){QString ip = ui->ip->text(); //获取输入的IPunsigned short duankou = ui->duankou->text().toUShort(); //获取输入的端口//连接服务器m_tcp->connectToHost(QHostAddress(ip),duankou);}void MainWindow::on_dklj_clicked(){m_tcp->close(); //关闭连接ui->dklj->setDisabled(true); //“断开连接”按钮不可按ui->jtfw->setEnabled(true); //“连接服务器”按钮不可按}

