QT中的网络通信

发布于:2025-07-06 ⋅ 阅读:(19) ⋅ 点赞:(0)

一、服务端和客户端怎么连接的?

        比如现实生活中,点餐系统是非常常见的吧,那么他们是怎么将菜单上传到服务端的,客户端又怎么能显示到服务端的菜单信息,服务端又怎么可以拿到客户端下单的菜品详情呢?

        📌📌这其中啊,就涉及了客户端和服务端交互的问题了,一听到这些专有名词,大家是不是觉得很高大尚,很难学懂一样?其实并不难,接下来的内容可能需要有C++的基本知识点才能听懂哦,不过没有也没关系,我会尽量用通俗易懂的图文并茂给大家讲解。

1、首先我们来看一个简单的例子先

这里有视频链接:到时候上传了会放出来

2、我们直接来看代码--服务端

记住这是服务端的代码:我们先来看服务端ui界面

我们先进行第一步:加网络模块

第二步,看服务端的.h文件代码,我都有注释的

第三步:看到这个.cpp文件

 1)实例化一个服务端

2)ui界面启动的按钮槽函数

void MainWindow::on_btn_start_clicked()
{
    //获取单行文本框输入的端口号,toInt()是将字符数据转为整型数据
    int port_num = this->ui->lineEdit_port_num->text().toInt();
    qDebug()<<port_num;  //调试输出

    if(port_num == 0)
    {
        qDebug()<<"请设置端口号";
        //如果没有输入端口号会弹出提示窗口
        QMessageBox::warning(&QMes_Box,"警告","请输入端口号",QMessageBox::No,QMessageBox::Yes);
    }
    else
    {
        //防止第二次重连时启动失败
        if(this->tcp_server->isListening() == false)
        {
            //服务端监听允许任何ip和指定的端口连接
            if(this->tcp_server->listen(QHostAddress::Any,port_num) == false)
            {
                qDebug()<<"启动服务器失败!";
            }
            else
            {
                qDebug()<<"启动服务器成功!";

                //设置信号与槽,当客户端发起连接的时候,这个服务器会自动触发newconnection信号,调用Get_Tcp_Client这个槽函数
                QObject::connect(this->tcp_server,&QTcpServer::newConnection,this,&MainWindow::Get_Tcp_Client);

            }
        }
    }

}

3)该函数判断是否连接成功,然后获取客户端的IP,发送的消息,和释放客户端


bool MainWindow::Get_Tcp_Client()
{
    //信号与槽的机制,如果对接失败,服务器类对象会自动触发一个错误的信号,调用对应的Accept_Error槽函数调试输出
    QObject::connect(this->tcp_server,&QTcpServer::acceptError,this,&MainWindow::Accept_Error);

    //服务器通过nextPendingConnection来获取客户端的类对象
    if((this->tcp_client = this->tcp_server->nextPendingConnection()) != nullptr)
    {
        //打印连接的客户端的地址
        qDebug()<<"有客户端连接了!"<<this->tcp_client->localAddress();

        //当客户端发来信息时,服务器触发一个信号readyRead,然后收到这个信号后去调用read的函数去读取信息
        QObject::connect(this->tcp_client,&QTcpSocket::readyRead,this,&MainWindow::Recv_Msg);


        //当客户端下线的时候识别到,利用信号与槽机制
        QObject::connect(this->tcp_client,&QTcpSocket::disconnected,this,&MainWindow::client_exit);

    }
    return true;
}

 4)服务端处理接收客户端发来的数据和发送数据到客户端中,以及客户端端开的事件

//这个槽用来显示服务端接收失败的结构
void MainWindow::Accept_Error()
{
    qDebug()<<"获取对象服务器失败!";
}

//读取客户端发来的数据
void MainWindow::Recv_Msg()
{
    //读取客户端的所有数据
    QByteArray msg = this->tcp_client->readAll();
    //将QByteArray字节数据转换为字符串数据并append追加显示到文本框中
    this->ui->textEdit_recv->append(QString::fromUtf8(msg));
}

//客户端端开的槽
void MainWindow::client_exit()
{
    //输出是谁的IP下线,这里的client不能和类中的tcp_client命名冲突。
    //QObject::sender() 返回触发当前槽函数的信号发送者对象指针,并强制转换为 QTcpSocket* 类型,因为知道发送者是客户端socket
    QTcpSocket  * client = (QTcpSocket *)QObject::sender();
    //client->peerAddress() 获取客户端的IP地址
    qDebug()<<"IP:"<<client->peerAddress()<<"断开了";
    //移除对应的对等类对象资源
    client->deleteLater();//deleteLater() 是Qt提供的安全删除方法:
}

//服务端发送数据的槽
void MainWindow::on_btn_send_clicked()
{
    //先获取文本框中的数据
    QString msg = this->ui->textEdit_send->toPlainText();

    //调用客户端类对象中的write函数发送给对应的客户端
    if(this->tcp_client->write(msg.toUtf8()) == -1)
    {
        qDebug()<<"发送消息失败";
        return ;
    }
    else
    {
        qDebug()<<"发送消息"<<msg<<"成功";
    }

}

3、客户端的代码

1)首先我们先来看看客户端的ui界面,记住要有印象,这样看代码会很好理解

2)前两步也是跟服务端一样,在.pro配置文件中加网络模块,然后看到.h文件的代码

3)然后看到.cpp文件的代码

当在客户端中点击“连接”的按钮时,则会执行on_btn_connect_clicked(),然后看连接服务器的代码

4)当客户端与服务端连接成功后,会触发下面这个槽

void Tcp_client::connect_seccess()
{
    this->ui->label_flag->setText("连接成功");

    //连接成功后搞一个信号用于服务器给客户端发送数据用
    //QObject::connect(this->tcp_client,&QTcpSocket::readyRead,this,&MainWindow::Recv_Msg);

    QObject::connect(this->client,&QTcpSocket::readyRead,this,&Tcp_client::Recv_Msg);
}

5)客户端发送消息给服务端的槽函数

void Tcp_client::on_btn_send_clicked()
{
    //获取文本框中的字符串并转换为utf-8的字节数组QByteArray,toPlainText()返回控件中纯文本内容(QString类型)
    //因为所有的网络通信都是字节传输的,无论发送还是接收。
    QByteArray msg = this->ui->textEdit_send->toPlainText().toUtf8();

    if(this->client->write(msg) == -1)
    {
        qDebug()<<"发送消息失败";
    }
    else
    {
        qDebug()<<"发送消息"<<msg<<"成功";
    }

}

6)客户端接收服务端发送的消息的槽函数

//客户端接收数据
void Tcp_client::Recv_Msg()
{
    //读取消息服务的发来的消息
    QByteArray msg = this->client->readAll();

    //把消息设置到文本框中
    this->ui->textEdit_recv->append(QString::fromUtf8(msg));

}

7)断开连接的槽函数

//端开于服务端连接
void Tcp_client::on_btn_disconnect_clicked()
{
    //让客户端断开连接,退出,但是在断开前要把套接字关闭
    this->client->close();//返回值是空的,怎么判断它端开了?通过信号来判断,也可以用isOpen来判断
    if(this->client->isOpen() == false)
    {
        //把状态标签设置为未连接
        this->ui->label_flag->setText("断开连接");
    }

}

4、关键点总结

通过上面的代码,我们是不是发现一个很奇怪的事情,为什么

1、客户端和服务端发送信息都是调用client->write()即(QTcpSocket套接字)来发的

2、接收消息都是调用QTcpSocket里的readAll()来读取消息然后转换为字符串显示到对应的文本框中

客户端:

服务端:

3、服务器必须先监听listen,然后客户端才能连接connectToHost

4、信号与槽的机制:

connected-客户端连接成功信号

newConnection-服务器有新连接信号

readyRead-有数据可读信号

disconnected-连接断开信号

5、这里顺便讲一下网络通信中常问的三步握手,四步挥手的原理吧。

看下面这幅图简单明了,不需要右边那样死记硬背,面试的时候通过左边那幅图讲个大概就行了。

三次握手就是:

通俗的回答:客户端握服务端的手,服务端握客户端的手,客户端再次握服务端的手

(即客户端:我喜欢你,服务端:我也喜欢你,客户端:那我们在一起吧)

专业的回答:第一次,客户端调用connect发送连接请求给服务端;第二次服务器回应客户端的连接请求,并告诉客户端可以连接了;第三次客户端回应服务端“我收到你让我连接的消息了”

四步挥手就是:

通俗的回答:

客户端:我们可以分手吗?

服务端:我思考思考!

服务端:我想好了,我们分手吧

客户端:好,正合我意,分就分吧。

专业的回答:

第一次:客户端发送close断开连接请求给服务器(客户端尝试与服务器挥手)

第二次:服务器回应客户端收到了断开连接的请求,准备断开(服务器准备给客户端挥手)

第三次:服务器回应客户端“准备好了,可以断开了”(服务器正式和客户端挥手)

第四次:客户端回应服务器“我收到你的断开请求了”(客户端正式与服务器挥手)

 

1、为什么TCP连接通信是可靠的传输协议?而UDP通信是不可靠的

原因就是:进行TCP连接(connect和accept)的时候要进行三步握手之后才能通信(即write和read),通信完之后要通过四步挥手才能断开的特性。

2、UDP通信协议:是不可靠的,无连接的传输协议,传输速度快。