一、说明:
    在标准 C++ 没有提供专门用于套接字通信的类,所以只能使用操作系统提供的基于 C 的 API 函数,基于这些 C 的 API 函数我们也可以封装自己的 C++ 类
    但是 Qt 就不一样了,它是 C++ 的一个框架并且里边提供了用于套接字通信的类(TCP、UDP)这样就使得我们的操作变得更加简单了(当然,在Qt中使用标准C的API进行套接字通信也是完全没有问题的)。以下使用相关类的进行 TCP 通信
    使用 Qt 提供的类进行基于 TCP 的套接字通信需要用到两个类:
    1、QTcpServer:服务器类,用于监听客户端连接以及和客户端建立连接
    2、QTcpSocket:通信的套接字类,客户端、服务器端都需要使用

    二、QTcpServer 类:
    1、公共成员函数:

    1. //构造一个用于监听的服务器端对象
    2. QTcpServer::QTcpServer(QObject *parent = Q_NULLPTR);
    1. //设置监听,参数一:绑定本地的ip,参数二:指定的端口
    2. bool QTcpServer::listen(const QHostAddress &address = QHostAddress::Any, quint16 port = 0);
    3. // 判断当前对象是否在监听, 是返回true,没有监听返回false
    4. bool QTcpServer::isListening() const;
    5. // 如果当前对象正在监听返回监听的服务器地址信息, 否则返回 QHostAddress::Null
    6. QHostAddress QTcpServer::serverAddress() const;
    7. // 如果服务器正在侦听连接,则返回服务器的端口; 否则返回0
    8. quint16 QTcpServer::serverPort() const

    参数:
    address:通过类 QHostAddress 可以封装 IPv4、IPv6 格式的 IP 地址,QHostAddress::Any 表示自动绑定
    port:如果指定为 0 表示随机绑定一个可用端口
    返回值:绑定成功返回 true,失败返回 false

    得到和客户端建立连接之后用于通信的 QTcpSocket 套接字对象,它是 QTcpServer 的一个子对象,当 QTcpServer 对象析构的时候会自动析构这个子对象,当然也可自己手动析构,建议用完之后自己手动析构这个通信的 QTcpSocket 对象。

    1. QTcpSocket *QTcpServer::nextPendingConnection();

    阻塞等待客户端发起的连接请求,不推荐在单线程程序中使用,建议使用非阻塞方式处理新连接,即使用信号 newConnection()

    1. bool QTcpServer::waitForNewConnection(int msec = 0, bool *timedOut = Q_NULLPTR);

    参数:
    msec:指定阻塞的最大时长,单位为毫秒(ms)
    timeout:传出参数,如果操作超时 timeout 为 true,没有超时 timeout 为 false

    2、信号:

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

    三、QTcpSocket类
    1、说明:
    QTcpSocket 是一个套接字通信类,不管是客户端还是服务器端都需要使用。在 Qt 中发送和接收数据也属于 IO 操作(网络 IO),其继承关系如下:
    【QT】1.7-套接字通信(网络通信) - 图1
    2、公共成员函数:

    1. QTcpSocket::QTcpSocket(QObject *parent = Q_NULLPTR);
    1. [virtual] void QAbstractSocket::connectToHost(const QString &hostName, quint16 port, OpenMode openMode = ReadWrite, NetworkLayerProtocol protocol = AnyIPProtocol);
    2. [virtual] void QAbstractSocket::connectToHost(const QHostAddress &address, quint16 port, OpenMode openMode = ReadWrite);

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

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

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

    1. /*
    2. 在使用 QTcpSocket 进行套接字通信的过程中,如果该类对象发射出 readyRead() 信号,
    3. 说明对端发送的数据达到了,之后就可以调用read 函数接收数据了
    4. */
    5. [signal] void QIODevice::readyRead();
    1. //调用 connectToHost() 函数并成功建立连接之后发出 connected() 信号
    2. [signal] void QAbstractSocket::connected();
    1. //在套接字断开连接时发出 disconnected() 信号
    2. [signal] void QAbstractSocket::disconnected();

    四、通信流程:
    1、流程:
    ①、创建套接字服务器 QTcpServer 对象
    ②、通过 QTcpServer 对象设置监听,即:QTcpServer::listen()
    ③、基于 QTcpServer::newConnection() 信号检测是否有新的客户端连接
    ④、如果有新的客户端连接调用 QTcpSocket *QTcpServer::nextPendingConnection() 得到通信的套接字对象
    ⑤、使用通信的套接字对象 QTcpSocket 和客户端进行通信

    2、服务器端程序:

    1. QT += core gui network
    2. greaterThan(QT_MAJOR_VERSION, 4): QT += widgets
    3. CONFIG += c++11
    4. # You can make your code fail to compile if it uses deprecated APIs.
    5. # In order to do so, uncomment the following line.
    6. #DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0
    7. SOURCES += \
    8. main.cpp \
    9. mainwindow.cpp
    10. HEADERS += \
    11. mainwindow.h
    12. FORMS += \
    13. mainwindow.ui
    14. # Default rules for deployment.
    15. qnx: target.path = /tmp/$${TARGET}/bin
    16. else: unix:!android: target.path = /opt/$${TARGET}/bin
    17. !isEmpty(target.path): INSTALLS += target
    18. RESOURCES += \
    19. ear.qrc
    1. #ifndef MAINWINDOW_H
    2. #define MAINWINDOW_H
    3. #include <QMainWindow>
    4. #include <QTcpServer> //套接字模块的头文件
    5. #include <QTcpSocket> //套接字通信的头文件
    6. #include <QLabel>
    7. QT_BEGIN_NAMESPACE
    8. namespace Ui { class MainWindow; }
    9. QT_END_NAMESPACE
    10. class MainWindow : public QMainWindow
    11. {
    12. Q_OBJECT
    13. public:
    14. MainWindow(QWidget *parent = nullptr);
    15. ~MainWindow();
    16. private slots:
    17. void on_jtfw_clicked();
    18. void on_fsxx_clicked();
    19. private:
    20. Ui::MainWindow *ui;
    21. QTcpServer* m_s;
    22. QTcpSocket* m_tcp; //创建套接字通信对象
    23. QLabel* m_status;
    24. };
    25. #endif // MAINWINDOW_H
    1. #include "mainwindow.h"
    2. #include "ui_mainwindow.h"
    3. MainWindow::MainWindow(QWidget *parent)
    4. : QMainWindow(parent)
    5. , ui(new Ui::MainWindow)
    6. {
    7. ui->setupUi(this);
    8. setWindowTitle("服务器"); //给窗口添加标题
    9. //2.1、设置默认端口,用于方便测试
    10. ui->duankou->setText("7788");
    11. //1、创建监听的服务器对象
    12. m_s = new QTcpServer(this); //需要在指定父对象this
    13. //等待客户端连接的槽函数,如果有客户端连接,则m_s会发出newConnection信号
    14. connect(m_s,&QTcpServer::newConnection,this,[=]()
    15. {
    16. m_tcp = m_s->nextPendingConnection(); //得到一个用于通讯的套接字对象:m_tcp
    17. m_status->setPixmap(QPixmap(":/img/pppp.jpg").scaled(20,20)); //连接状态,scaled函数设置图标大小
    18. //检测是否可以接受数据 */
    19. connect(m_tcp,&QTcpSocket::readyRead,this,[=]() //当有客户端发起连接,就发射出 readyRead 信号
    20. {
    21. QByteArray data = m_tcp->readAll(); //读取接收的数据
    22. ui->record->append("客户端:" + data); //将接收到的是数据显示(追加)在信息框
    23. });
    24. //4.1、当m_tcp发射出eradyRead信号,就说明客户端断开了连接
    25. connect(m_tcp,&QTcpSocket::disconnected,this,[=]()
    26. {
    27. //m_tcp->close(); //服务器也断开连接(关闭连接)
    28. ui->record->append("客户端已经断开了连接");
    29. m_tcp->deleteLater(); //释放m_tcp指向的内存,可直接调用deleteLater,因为里面封装了delete
    30. m_status->setPixmap(QPixmap(":/img/guoqi.jpg").scaled(20,20)); //断开状态,scaled函数设置图标大小
    31. });
    32. });
    33. //4、状态栏的处理的动作
    34. m_status = new QLabel(this); //可以不指定父对象,因为是再状态栏里
    35. //默认状态,scaled函数设置图标大小
    36. m_status->setPixmap(QPixmap(":/img/guoqi.jpg").scaled(20,20));
    37. //设置到状态栏中
    38. ui->statusbar->addWidget(new QLabel("连接状态:"));
    39. ui->statusbar->addWidget(m_status);
    40. }
    41. MainWindow::~MainWindow()
    42. {
    43. delete ui;
    44. }
    45. //2、当点击”启动监听服务“按钮,启动监听
    46. void MainWindow::on_jtfw_clicked()
    47. {
    48. //2.2、读取控件的端口号,是unsigned short类型,toUShort()是类型转换
    49. unsigned short duankou = ui->duankou->text().toUShort();
    50. //2.3、调用listen()进行监听,参数一:绑定本地的ip,参数二:指定的端口
    51. m_s->listen(QHostAddress::Any,duankou);
    52. //2.4、当启动监听后,”启动监听服务“按钮不可再按
    53. ui->jtfw->setDisabled(true);
    54. }
    55. //3、当点击”发送信息“按钮,
    56. void MainWindow::on_fsxx_clicked()
    57. {
    58. QString msg = ui->msg->toPlainText(); //以toPlainText(纯文本)的方式读取“发送的信息”框的内容
    59. m_tcp->write(msg.toUtf8());
    60. ui->record->append("服务器端:" + msg); //将接收到的是数据显示(追加)在信息框
    61. ui->msg->clear(); //发送完后清空输入框的内容
    62. }

    image.png

    3、客户端程序:

    1. QT += core gui network
    2. greaterThan(QT_MAJOR_VERSION, 4): QT += widgets
    3. CONFIG += c++11
    4. # You can make your code fail to compile if it uses deprecated APIs.
    5. # In order to do so, uncomment the following line.
    6. #DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0
    7. SOURCES += \
    8. main.cpp \
    9. mainwindow.cpp
    10. HEADERS += \
    11. mainwindow.h
    12. FORMS += \
    13. mainwindow.ui
    14. # Default rules for deployment.
    15. qnx: target.path = /tmp/$${TARGET}/bin
    16. else: unix:!android: target.path = /opt/$${TARGET}/bin
    17. !isEmpty(target.path): INSTALLS += target
    18. RESOURCES += \
    19. ear.qrc
    1. #ifndef MAINWINDOW_H
    2. #define MAINWINDOW_H
    3. #include <QMainWindow>
    4. #include <QTcpSocket> //套接字通信的头文件
    5. #include <QLabel>
    6. QT_BEGIN_NAMESPACE
    7. namespace Ui { class MainWindow; }
    8. QT_END_NAMESPACE
    9. class MainWindow : public QMainWindow
    10. {
    11. Q_OBJECT
    12. public:
    13. MainWindow(QWidget *parent = nullptr);
    14. ~MainWindow();
    15. private slots:
    16. void on_jtfw_clicked();
    17. void on_dklj_clicked();
    18. void on_fsxx_clicked();
    19. private:
    20. Ui::MainWindow *ui;
    21. QTcpSocket* m_tcp; //创建套接字通信对象
    22. QLabel* m_status;
    23. };
    24. #endif // MAINWINDOW_H
    1. #include "mainwindow.h"
    2. #include "ui_mainwindow.h"
    3. MainWindow::MainWindow(QWidget *parent)
    4. : QMainWindow(parent)
    5. , ui(new Ui::MainWindow)
    6. {
    7. ui->setupUi(this);
    8. //设置默认端口,用于方便测试
    9. ui->duankou->setText("7788");
    10. ui->ip->setText("192.168.194.172");
    11. setWindowTitle("客户端"); //给窗口添加标题
    12. ui->dklj->setDisabled(true); //“断开连接”按钮不可按
    13. //ui->dklj->setEnabled(false);
    14. //创建监听的服务器对象
    15. m_tcp = new QTcpSocket; //需要在指定父对象this
    16. //检测是否可以接受数据 */
    17. connect(m_tcp,&QTcpSocket::readyRead,this,[=]() //当有客户端发起连接,就发射出 readyRead 信号
    18. {
    19. QByteArray data = m_tcp->readAll(); //读取接收的数据
    20. ui->record->append("客户端:" + data); //将接收到的是数据显示(追加)在信息框
    21. });
    22. //当m_tcp发射出eradyRead信号,就说明客户端断开了连接
    23. connect(m_tcp,&QTcpSocket::disconnected,this,[=]()
    24. {
    25. m_tcp->close(); //服务器也断开连接
    26. m_tcp->deleteLater(); //释放m_tcp指向的内存,可直接调用deleteLater,因为里面封装了delete
    27. m_status->setPixmap(QPixmap(":/img/guoqi.jpg").scaled(20,20)); //断开状态,scaled函数设置图标大小
    28. ui->record->append("服务器已和客户端断开了连接");
    29. ui->dklj->setDisabled(true); //“断开连接”按钮不可按
    30. ui->jtfw->setEnabled(false); //“连接服务器”按钮可按
    31. });
    32. connect(m_tcp,&QTcpSocket::connected,this,[=]()
    33. {
    34. m_status->setPixmap(QPixmap(":/img/pppp.jpg").scaled(20,20));
    35. //将连接成功的信息显示(追加)在信息框
    36. ui->record->append("已经连接到了服务器");
    37. ui->dklj->setDisabled(false);
    38. //ui->dklj->setEnabled(true); //“断开连接”按钮可按
    39. ui->jtfw->setEnabled(false); //“连接服务器”按钮不可按
    40. });
    41. //状态栏的处理的动作
    42. m_status = new QLabel(this); //可以不指定父对象,因为是再状态栏里
    43. m_status->setPixmap(QPixmap(":/img/guoqi.jpg").scaled(20,20)); //默认状态,scaled函数设置图标大小
    44. //设置到状态栏中
    45. ui->statusbar->addWidget(new QLabel("连接状态:"));
    46. ui->statusbar->addWidget(m_status);
    47. }
    48. MainWindow::~MainWindow()
    49. {
    50. delete ui;
    51. }
    52. //3、当点击”发送信息“按钮,
    53. void MainWindow::on_fsxx_clicked()
    54. {
    55. QString msg = ui->msg->toPlainText(); //以toPlainText(纯文本)的方式读取“发送的信息”框的内容
    56. m_tcp->write(msg.toUtf8());
    57. ui->record->append("客户端:" + msg); //将接收到的是数据显示(追加)在信息框
    58. ui->msg->clear(); //发送完后清空输入框的内容
    59. }
    60. //当点击”连接服务器“按钮,与服务器建立联接
    61. void MainWindow::on_jtfw_clicked()
    62. {
    63. QString ip = ui->ip->text(); //获取输入的IP
    64. unsigned short duankou = ui->duankou->text().toUShort(); //获取输入的端口
    65. //连接服务器
    66. m_tcp->connectToHost(QHostAddress(ip),duankou);
    67. }
    68. void MainWindow::on_dklj_clicked()
    69. {
    70. m_tcp->close(); //关闭连接
    71. ui->dklj->setDisabled(true); //“断开连接”按钮不可按
    72. ui->jtfw->setEnabled(true); //“连接服务器”按钮不可按
    73. }

    image.png