zl程序教程

您现在的位置是:首页 >  工具

当前栏目

Qt系列文章015-TCP客户端

Qt客户端TCP 系列 文章 015
2023-09-14 09:01:43 时间

1 前言

    上一章主要讲述了服务端的程序设计,本章主要讲述客户端的程序设计和对应的通信交互,有了前面的基础,基本上下面的讲解,我想已经非常容易理解了!

2 客户端项目新建

    还是老样子,先新建一个客户端的程序项目,如下图:
在这里插入图片描述

    取名为TCPClient项目名称!新建工程完毕后,我们开始简单的界面设计,运行效果图如下:
在这里插入图片描述

    客户端程序TCPClient只需要使用一个 QTcpSocket 对象,就可以和服务器端程序TcpServer 进行通信。
    TCPClient也是一个窗口基于 QWidget 的应用程序,其主窗口的定义如下:

class TCPClient : public QWidget
{
    Q_OBJECT

public:
    TCPClient(QWidget *parent = nullptr);
    ~TCPClient();
    //初始化数据和相关槽
     void initDataSlot();
     QString getLocalIP();
     
private slots:
     //自定义槽函数
     void  onConnected() ;
     void  onDisconnected() ;
     void  onSocketStateChange(QAbstractSocket::SocketState socketState) ;
     void  onSocketReadyRead() ; //读取socket传入的数据
private:
    Ui::TCPClient *ui;
    
    QTcpSocket * mTcpClient = nullptr;  //socket
    bool misConnectService = false; //判断是否连接了服务器
};

   这里只定义了一个用于socket 连接和通信的QTcpSocket变量mTcpClient ,自定义了几个槽函数, 用于与mTcpClient的相关信号关联。

   下面是 TCPClient 的构造函数,主要功能是创建mTcpClient,初始化相关数据并建立信号与槽函数的关联。

TCPClient::TCPClient(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::TCPClient)
{
    ui->setupUi(this);
    
    initDataSlot();
}

void TCPClient::initDataSlot()
{
    //创建Socket变量
    if(mTcpClient == nullptr)
    {
        mTcpClient = new QTcpSocket(this);
    }
    
    //设置服务端的ip和端口
    ui->lineEdit_serviceIP->setText(getLocalIP());
    ui->lineEdit_servicePort->setText(QString::number(PORT));
    misConnectService = false;
    ui->label_socketState->setText("Socket 状态:");
    
    //建立信号槽连接
    connect(mTcpClient, &QTcpSocket::connected, this, &TCPClient::onConnected);
    connect(mTcpClient, &QTcpSocket::disconnected, this, &TCPClient::onDisconnected);
    connect(mTcpClient, &QTcpSocket::stateChanged, this, &TCPClient::onSocketStateChange);
    connect(mTcpClient, &QTcpSocket::readyRead, this, &TCPClient::onSocketReadyRead);
}

QString TCPClient::getLocalIP()
{
    //获取本机IPv4地址
    QString hostName=QHostInfo::localHostName() ;//本地主机名
    QHostInfo hostInfo=QHostInfo::fromName (hostName) ;

    QString localIP="";
    QList<QHostAddress> addList=hostInfo.addresses () ;
    if (!addList. isEmpty())
    {
        for (int i=0; i <addList.count() ;i++)
        {
            QHostAddress aHost=addList.at(i) ;
            if (QAbstractSocket::IPv4Protocol == aHost. protocol())
            {
                localIP=aHost. toString() ;
                break;
            }
        }
    }

    return localIP;
}

3 与服务端建立连接

   在窗口上设置服务器IP地址和端口后,调用QTepSocket的函数 connectToHost() 连接到服务器,也可以使用 disconnectFromHost() 函数断开与服务器的连接。
   下面是上图中 连接服务器 按钮的响应代码,以及两个相关槽函数的代码:

void TCPClient::on_pushButton_connectService_clicked()
{
    if(misConnectService)
    {
        misConnectService=  false;

        //断开服务器按钮
        mTcpClient->disconnectFromHost();
        ui->pushButton_connectService->setText("连接服务器");
    }
    else
    {
        misConnectService = true;
         //连接服务器按钮
        QString addr= ui->lineEdit_serviceIP->text();
        quint16  port=ui->lineEdit_servicePort->text().toInt();
        ui->pushButton_connectService->setText("断开服务器");
        mTcpClient->connectToHost (addr,port) ;
    }
}

void TCPClient::onConnected()
{
    //connected()信号槽函数
    ui->plainTextEdit->appendPlainText ("**已连接到服务器") ;
    ui ->plainTextEdit->appendPlainText ("**peer address:"+ mTcpClient->peerAddress().toString()) ;
    ui ->plainTextEdit->appendPlainText ("**peer port:"+ QString::number(mTcpClient->peerPort())) ;
  
}

   槽函数onSocketStateChange( )的功能和代码与TcpServer 中的完全一样,如下代码:

void TCPClient::onSocketStateChange(QAbstractSocket::SocketState socketState)
{
    switch (socketState)
    {
    case QAbstractSocket::UnconnectedState:
        ui->label_socketState->setText ("socket状态: UnconnectedState") ;
        break;
    case QAbstractSocket::HostLookupState:
        ui->label_socketState->setText ("socket状态: HostLookupState") ;
        break;
    case QAbstractSocket::ConnectingState:
        ui->label_socketState->setText ("socket状态: ConnectingState") ;
        break;
    case QAbstractSocket::ConnectedState:
        ui->label_socketState->setText ("socket状态: ConnectedState") ;
        break;
    case QAbstractSocket::BoundState:
        ui->label_socketState->setText ("socket状态: BoundState") ;
        break;
    case QAbstractSocket::ClosingState:
        ui->label_socketState->setText ("socket状态: ClosingState") ;
        break;
    case QAbstractSocket::ListeningState:
        ui->label_socketState->setText ("socket状态: ListeningState") ;
        break;
    }
}

4 与TCPServer的数据收发

   TCPClientTCPServer之间采用基于行的数据通信协议。单击上图中发送按钮将发送一行字符串。在 readyRead() 信号的槽函数里读取行字符串,其相关代码如下:

void TCPClient::on_pushButton_sendMsg_clicked()
{
    //发送数据
    QString  msg=ui->lineEdit_msg->text() ;
    ui->plainTextEdit->appendPlainText ("[out] "+msg) ;
    ui->lineEdit_msg->clear() ;
    ui->lineEdit_msg->setFocus() ;
    QByteArray str=msg.toUtf8() ;
    str.append('\n') ;
    mTcpClient->write(str) ;
}

void TCPClient::onSocketReadyRead()
{
    //readyRead()信号槽函数
    while (mTcpClient->canReadLine())
    {
         ui->plainTextEdit->appendPlainText("[in] "+mTcpClient->readLine()) ;
    }
}

5 交互动态演示

    下面演示一下服务端和客户端的交互通信动态gif图如下:
   
在这里插入图片描述

6 总结

   实例TCPServerTCPClient只是简单演示了TCP通信的基本原理,TCPServer 只允许一个TCPClient客户端接入。而一般的TCP服务器程序允许多个客户端接入,为了使每个socket连接独立通信互不影响,一般采用多线程,即为一个socket连接创建一个线程

    实例TCPServer和TCPClient之间的数据通信采用基于行的通信协议,只能传输字符串数据。QTcpSocket间接继承于QIODevice,可以使用数据流的方式传输二进制数据流,例如传输图片、任意格式文件等,但是这涉及到服务器端和客户端之间通信协议的定义,这里不具体介绍了。