QT 学习笔记(十四)

文章目录

  • 一、TCP/IP 通信过程简介
    • 1. Socket 通信
    • 2. Linux 下的 TCP/IP 通信过程
    • 3. QT 下的 TCP/IP 通信过程
      • 3.1 在 QT 中实现 TCP/IP 服务器端通信的流程
      • 3.2 在 QT 中实现 TCP/IP 客户端通信的流程
  • 二、TCP/IP 通信过程操作实现
    • 1. 服务器端
    • 2. 客户端
  • 三、服务器端和客户端实现代码
    • 1. 主函数 main.c
    • 2. 服务器端头文件 widget.h
    • 3. 服务器端源文件 widget.cpp
    • 4. 客户端头文件 clientwidget.h
    • 5. 客户端源文件 clientwidget.cpp

由于每次代码都是在原有程序上修改,因此除了新建项目,不然一般会在学完后统一展示代码。
提示:具体项目创建流程和注意事项见QT 学习笔记(一)
提示:具体项目准备工作和细节讲解见QT 学习笔记(二)

一、TCP/IP 通信过程简介

1. Socket 通信

  • QT 中提供的所有的 Socket 类都是非阻塞的。
  • QT 中常用的用于 socket 通信的套接字类:
  • QTcpServer:用于 TCP/IP 通信,作为服务器端套接字使用。
  • QTcpSocket:用于 TCP/IP 通信,作为客户端套接字使用。
  • QUdpSocket:用于 UDP 通信,服务器,客户端均使用此套接字。

2. Linux 下的 TCP/IP 通信过程

QT 学习笔记(十四)_第1张图片

3. QT 下的 TCP/IP 通信过程

QT 学习笔记(十四)_第2张图片

3.1 在 QT 中实现 TCP/IP 服务器端通信的流程

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

3.2 在 QT 中实现 TCP/IP 客户端通信的流程

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

二、TCP/IP 通信过程操作实现

  • 生成一个新的项目,具体步骤过程见提示。
  • 在生成新项目的过程中,我们选择基类为 QWidget ,这是因为 QWidget 当中比较干净,不存在别的东西,而 QMainWindow 虽然也可以实现,但其中存在工具栏,菜单栏,核心控件等东西,比较复杂。

1. 服务器端

  • 首先,我们在 ui 界面布置出服务器端的窗口界面,包含两个按钮(发送 send 和 关闭 close)和两个文本编辑区(分别用来输入和显示),具体界面布局如下图所示。

QT 学习笔记(十四)_第3张图片

  • 同时,我们将上面的文本编辑区设置为 readOnly(只读)。这里需要注意的是只修改上面的文本编辑区,下面的是用来输入信息的。

QT 学习笔记(十四)_第4张图片

  • 我们在 TCP.pro 当中加入 QT += network 代码,以便后续工作可以顺利开展。
  • 通过 QT 提供的 QTcpServer 类实现服务器端的 socket 通信。
  • 只有服务器端需要两个套接字,分别是监听套接字 QTcpServer 和通信套接字 QTcpSocket。
  • 这里要使用 Lambda 表达式,需要提前在 TCP.pro 当中加入 CONFIG += C++11 代码。
  • 我们还需要对两个按钮在 ui 界面进行转到槽函数的操作,并进行代码的编写,由于这里需要服务器端和客户端相结合才可以看到现象,故在此处不过多展示实现现象(主要在客户端展示)。
  • 当我们完成基础代码的编写后,运行程序,直接点击 send 按钮,会产生程序异常结束的现象,具体实现现象如下图所示。

QT 学习笔记(十四)_第5张图片

  • 这是因为当我们按下按钮时 tcpsocket 并没有内容,因此,我们需要在代码当中添加 if 判断,tcpsocket 是否为空,若为空,就直接返回,不进入按钮操作。
  • 同时,当我们断开与客户端的连接后,也需要将 tcpsocket 置为空。

2. 客户端

  • 客户端通过使用 QT 提供的 QTcpSocket 类可以方便的实现与服务器端的通信。
  • 在完成服务器端后,我们进行客户端的编写,客户端只有一个套接字就是通信套接字 tcpsocket。
  • 这里我们不重新开一个项目,而是将两部分放在一块。但是,服务器端和客户端的 ui 界面并不相同,因此,我们需要在添加一个 QT 设计师界面类,对客户端的 ui 界面进行设计。

QT 学习笔记(十四)_第6张图片

  • 界面模板默认 Widget 即可。

QT 学习笔记(十四)_第7张图片

  • 随后,我们在 ui 界面布置出客户端的窗口界面,包含两个标签(分别是服务器端口和服务器 IP )以及他们对应的行编辑区,三个按钮(分别是发送 send ,关闭 close 和连接 connect)和两个文本编辑区(分别用来输入和显示)。
  • 同时,我们将上面的文本编辑区设置为 readOnly(只读)。这里需要注意的是只修改上面的文本编辑区,下面的是用来输入信息的。
  • 其中的服务器端口的行编辑区直接默认值是 8888(与服务器端相对应),服务器 IP 的行编辑区直接默认值是 127.0.0.1(本地连接),具体界面布局如下图所示。

QT 学习笔记(十四)_第8张图片

  • 在完成 ui 界面的设计后,我们先编写按钮 connect ,并测试是否可以与服务器端连接,具体实现现象如下图所示。

QT 学习笔记(十四)_第9张图片

  • 但是,这里存在一个问题,就是客户端自己本身并不知道是否连接成功,对此进行优化,使客户端显示成功和服务器端建立好连接,得到如下的实现现象。

QT 学习笔记(十四)_第10张图片

  • 在确保可以完成服务器端和客户端的连接之后,我们进行信息收发功能的实现(主要包括客户端发送信息和服务器端获取客户端编辑区信息并展现),具体实现现象如下图所示。

QT 学习笔记(十四)_第11张图片

  • 最后,实现断开服务器端和客户端连接的功能,就是无法继续发送信息,但是窗口仍然保存,不会关闭,可再次通信发送。

三、服务器端和客户端实现代码

1. 主函数 main.c

#include "widget.h"
#include 
#include "clientwidget.h"

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

    CLientWidget w2;
    w2.show();

    return a.exec();
}

2. 服务器端头文件 widget.h

#ifndef WIDGET_H
#define WIDGET_H

#include 
#include //监听套接字
#include //通信套接字

namespace Ui {
class Widget;
}

class Widget : public QWidget
{
    Q_OBJECT

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

private slots:
    void on_buttonsend_clicked();

    void on_pushButton_2_clicked();

private:
    Ui::Widget *ui;

    QTcpServer *tcpserver;//监听套接字
    QTcpSocket *tcpsocket;//通信套接字
};

#endif // WIDGET_H

3. 服务器端源文件 widget.cpp

#include "widget.h"
#include "ui_widget.h"

Widget::Widget(QWidget *parent) :
    QWidget(parent),
    ui(new Ui::Widget)
{
    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);
                           }
                           );

               }
               );
}

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

void Widget::on_buttonsend_clicked()
{
    if(NULL == tcpsocket)
    {
        return;
    }

    //获取编辑区内容
    QString str = ui->textEditread->toPlainText();
    //给对方发送数据,使用套接字是 tcpsocket
    tcpsocket->write(str.toUtf8().data());
}

void Widget::on_pushButton_2_clicked()
{
    if(NULL == tcpsocket)
    {
        return;
    }

    //主动和客户端断开连接
    tcpsocket->disconnectFromHost();
    tcpsocket->close();
    tcpsocket = NULL;
}

4. 客户端头文件 clientwidget.h

#ifndef CLIENTWIDGET_H
#define CLIENTWIDGET_H

#include 
#include //通信套接字

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_pushButtonsend_clicked();

    void on_pushButtonclose_clicked();

private:
    Ui::CLientWidget *ui;

    QTcpSocket *tcpsocket;//通信套接字
};

#endif // CLIENTWIDGET_H

5. 客户端源文件 clientwidget.cpp

#include "clientwidget.h"
#include "ui_clientwidget.h"
#include 

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_pushButtonsend_clicked()
{
    //获取编辑框内容
    QString str = ui->textEditwrite->toPlainText();

    //发送数据
    tcpsocket->write(str.toUtf8().data());
}

void CLientWidget::on_pushButtonclose_clicked()
{
    //主动和对方断开连接
    tcpsocket->disconnectFromHost();
    tcpsocket->close();
}

你可能感兴趣的:(QT,qt,学习,开发语言)