zoukankan      html  css  js  c++  java
  • Qt实现客户端与服务器消息发送

    这里用Qt来简单设计实现一个场景,即:

    (1)两端:服务器QtServer和客户端QtClient

    (2)功能:服务端连接客户端,两者能够互相发送消息,传送文件,并且显示文件传送进度。

    环境:VS20013 + Qt5.11.2 + Qt设计师

    先看效果:


    一、基本概念

    客户端与服务器的基本概念不说了,关于TCP通信的三次握手等等,在《计算机网络》里都有详细介绍。这里说下两者是如何建立起通信连接的。

    (1)IP地址:首先服务器和每一个客户端都有一个地址,即IP地址。(底层的MAC地址,不关心,因为TCP通信以及IP,是七层架构里面的网络层、传输层了,底层透明)。对于服务器来说,客户端的数量及地址是未知的,除非建立了连接。但是对于客户端来说,必须知道服务器的地址,因为两者之间的连接是由客户端主动发起的。

    (1)端口号:软件层面的端口号,指的是 “应用层的各种协议进程与运输实体进行层间交互的一种地址”。简而言之,每一个TCP连接都是一个进程,操作系统需要为每个进程分配一个协议端口(即每一个客户端与服务端的连接,不是两台主机的连接,而是两个端口的连接)。但一台主机通常会有很多服务,很多进程,单靠一个IP地址不能标识某个具体的进程或者连接。所以用端口号来标识访问的目标服务器以及服务器的目标服务类型。端口号也有分类,但这不是本文的重点,详见教材。

    (3)TCP连接:总的来说,TCP的连接管理分为单个阶段:建立连接 -> 数据传送 -> 连接释放。在(2)里说到,每个TCP连接的是具体IP地址的主机的两个端口,即TCP连接的两个端点由IP地址和端口号组成,这即是套接字(socket)的概念:套接字socket = IP + 端口号。

    因此,我们要通过通过套接字来建立服务端与客户端的通信连接。

    二、Qt相关类

    QTcpSocket:提供套接字

    QTcpServer:提供基于TCP的服务端,看官方文档的解释如下:

    This class makes it possible to accept incoming TCP connections. You can specify the port or have QTcpServer pick one automatically. You can listen on a specific address or on all the machine’s addresses.
    

    这个解释里面提到两点:

    (1)指定端口:即开通哪一个端口用于建立TCP连接;

    (2)监听:监听(1)中指定的端口是否有连接的请求。

    三、写服务器和客户端的具体流程

    (1)服务器:

    1. 创建并初始化 QTcpServer 对象;
    2. 启动服务器监听,通过调用成员函数 listen(QHostAddress::Any, 端口号);
    3. 连接 QTcpServer 对象的 newConnection 信号槽,当有客户端链接时,客户端会发送 newConnection 信号给服务器,触发槽函数接受链接(得到一个与客户端通信的套接字 QTcpSocket);
    4. QTcpsocket 对象调用成员函数 write,发送数据给客户端;
    5. 当客户端有数据发送来,QTcpSocket 对象就会发送 readyRead 信号,关联槽函数读取数据;
    6. 连接 QTcpsocket 对象的 disconnected 信号槽,当客户端对象调用成员函数 close,会触发 QTcpsocket 对象的 disconnected 信号,进而触发槽函数进行相应处理。

    (2)客户端:

    1. 创建并初始化 QTcpSocket 对象;
    2. QTcpSocket 调用 connectToHost(QHostAddress("IP"), 端口号),连接服务器IP和端口号;
    3. QTcpsocket 对象调用成员函数 write,发送数据给服务器;
    4. 连接QTcpsocket 对象的 connected() 信号槽,当客服端成功连接到服务器后触发 connected() 信号;
    5. 连接QTcpsocket 对象的 readyread() 信号槽,当客户端接收到服务端发来数据时触发 readyread() 信号;
    6. 连接 QTcpsocket 对象的 disconnected 信号槽,当客户端对象调用成员函数 close,会触发 QTcpsocket 对象的 disconnected 信号,进而触发槽函数进行相应处理。

    四、UI设计

    客户端:


    服务端:


    五、服务端实现

    我们先要在工程文件中加入network

    QT       += core gui network
    

    下面我们来看看服务器程序步骤:

    1、初始化 QTcpServer 对象

    TCP_server = new QTcpServer();
    

    2、启动服务器监听

    TCP_server->listen(QHostAddress::Any,9988);//9988为端口号
    

    3、连接 QTcpServer 对象的 newConnection 信号槽,当有客户端链接时,客户端会发送 newConnection 信号给服务器,触发槽函数接受链接(得到一个与客户端通信的套接字 QTcpSocket)

    connect(TCP_server, SIGNAL(newConnection()), this, SLOT(slot_newconnect()));
    
    // 在与 newConnection 信号连接的槽函数中,获取与相应客户端通信的套接字
    TCP_connectSocket = mServer->nextPendingConnection();
    

    4、QTcpsocket 对象调用成员函数 write,发送数据给客户端

    TCP_connectSocket->write(msg.toUtf8());
    

    5、当客户端有数据发送来,QTcpSocket 对象就会发送 readyRead 信号,关联槽函数读取数据

    connect(mSocket,SIGNAL(readyRead()),this,SLOT(slot_recvmessage()));
    

    6、连接 QTcpsocket 对象的 disconnected 信号槽,当客户端对象调用成员函数 close,会触发 QTcpsocket 对象的 disconnected 信号,进而触发槽函数进行相应处理

    connect(mSocket,SIGNAL(disconnected()),this,SLOT(slot_disconnect()));
    

    TCP_Server.h

    #ifndef TCP_SERVER_H
    #define TCP_SERVER_H
    
    #include <QWidget>
    #include <QTcpServer>
    #include <QTcpSocket>
    
    namespace Ui {
    class TCP_Server;
    }
    
    class TCP_Server : public QWidget
    {
        Q_OBJECT
    
    public:
        explicit TCP_Server(QWidget *parent = nullptr);
        ~TCP_Server();
    
    private slots:
        void slot_newconnect(); //建立新连接的槽
        void slot_sendmessage(); //发送消息的槽
        void slot_recvmessage(); //接收消息的槽
        void slot_disconnect(); //取消连接的槽
    
    private:
        Ui::m_tcpServer *ui;
    
        QTcpServer *TCP_server; //QTcpServer服务器
        QTcpSocket *TCP_connectSocket; //与客户端连接套接字
    };
    
    #endif // TCP_SERVER_H
    
    

     

    TCP_Server.cpp

    #include "tcp_server.h"
    #include "ui_tcp_server.h"
    #include <QMessageBox>
    #include <QDateTime>
    
    TCP_Server::TCP_Server(QWidget *parent) :
        QWidget(parent),
        ui(new Ui::TCP_Server)
    {
        ui->setupUi(this);
    
        //初始化
        TCP_server = new QTcpServer();
        TCP_connectSocket = nullptr;
        connect(ui->pushButton_send,SIGNAL(clicked()),this,SLOT(slot_sendmessage()));
    
        //调用listen函数监听同时绑定IP和端口号
        if(TCP_server->listen(QHostAddress::LocalHost,10000)) //判断listen是否成功,成功则继续执行,连接新接收信号槽
        {
            this->connect(TCP_server,SIGNAL(newConnection()),this,SLOT(slot_newconnect()));  //将服务器的新连接信号连接到接收新连接的槽
        }
        else
        {
            QMessageBox::critical(this,"错误","IP绑定错误,请关闭其它服务端或更改绑定端口号");
        }
    }
    
    TCP_Server::~TCP_Server()
    {
        delete ui;
    }
    
    //建立新连接的槽
    void TCP_Server::slot_newconnect()
    {
        if(TCP_server->hasPendingConnections())  //查询是否有新连接
        {
            TCP_connectSocket = TCP_server->nextPendingConnection(); //获取与真实客户端相连的客户端套接字
            ui->textBrowser->append("client login!"); //若有新连接,则提示
    
            this->connect(TCP_connectSocket,SIGNAL(readyRead()),this,SLOT(slot_recvmessage())); //连接客户端的套接字的有新消息信号到接收消息的槽
            this->connect(TCP_connectSocket,SIGNAL(disconnected()),this,SLOT(slot_disconnect())); //连接客户端的套接字取消连接信号到取消连接槽
        }
    }
    
    //发送消息的槽
    void TCP_Server::slot_sendmessage()
    {
        QString sendMessage = ui->lineEdit->text(); //获取单行文本框内要发送的内容
        if(TCP_connectSocket != nullptr && !sendMessage.isEmpty()) //确保有客户端连接,并且发送内容不为空
        {
            TCP_connectSocket->write(sendMessage.toLatin1());   //发送消息到客户端
    
            QString localDispalyMessage = "send to client: " + sendMessage 
                            + QDateTime::currentDateTime().toString(" yyyy-M-dd hh:mm:ss") + tr("
    ");
            ui->textBrowser->append(localDispalyMessage);   //将要发送的内容显示在listwidget
        }
    
        ui->lineEdit->clear();
    }
    
    //接收消息的槽
    void TCP_Server::slot_recvmessage()
    {
        if(TCP_connectSocket != nullptr) //与客户端连接的socket,不是nullptr,则说明有客户端存在
        {
            QByteArray array = TCP_connectSocket->readAll();    //接收消息
            QHostAddress clientaddr = TCP_connectSocket->peerAddress(); //获得IP
            int port = TCP_connectSocket->peerPort();   //获得端口号
    
            QDateTime datetime = QDateTime::currentDateTime();
    
            QString sendMessage = tr("recv from :") + clientaddr.toString() + tr(" : ") 
                                    + QString::number(port) + tr("   ") + datetime.toString("yyyy-M-dd hh:mm:ss") + tr("
    ");
            sendMessage += array;
    
            ui->textBrowser->append(sendMessage);   //将接收到的内容加入到listwidget
        }
    }
    
    //取消连接的槽
    void TCP_Server::slot_disconnect()
    {
        if(TCP_connectSocket != nullptr)
        {
            ui->textBrowser->append("client logout!");
            TCP_connectSocket->close(); //关闭客户端
            TCP_connectSocket->deleteLater();
        }
    }
    
    

    六、客户端实现

    下面我们来看看客户端程序步骤:

    1、初始化 QTcpSocket 对象

    TCP_server = new QTcpSocket();
    

    2、QTcpSocket 调用 connectToHost(QHostAddress("IP"), 端口号),连接服务器IP和端口号

    TCP_sendMesSocket->connectToHost("127.0.0.1",10000); 
    

    3、QTcpsocket 对象调用成员函数 write,发送数据给服务器

    //取发送信息编辑框内容
    QString msg = ui->sendEdit->toPlainText();
    TCP_sendMesSocket->write(msg.toUtf8());//转编码
    

    4、连接QTcpsocket 对象的 connected() 信号槽,当客服端成功连接到服务器后触发 connected() 信号

    connect(TCP_sendMesSocket,SIGNAL(connected()),this,SLOT(slot_connected()));
    

    5、连接QTcpsocket 对象的 readyread() 信号槽,当客户端接收到服务端发来数据时触发 readyread() 信号

    connect(TCP_sendMesSocket,SIGNAL(readyRead()),this,SLOT(slot_recvmessage()));
    

    6、连接 QTcpsocket 对象的 disconnected 信号槽,当客户端对象调用成员函数 close,会触发 QTcpsocket 对象的 disconnected 信号,进而触发槽函数进行相应处理

    connect(TCP_sendMesSocket, SIGNAL(disconnected()), this, SLOT(slot_disconnect()));
    

    TCP_Client.h

    #ifndef TCP_CLIENT_H
    #define TCP_CLIENT_H
    
    #include <QWidget>
    #include <QTcpSocket>
    
    namespace Ui {
    class TCP_Client;
    }
    
    class TCP_Client : public QWidget
    {
        Q_OBJECT
    
    public:
        explicit TCP_Client(QWidget *parent = nullptr);
        ~TCP_Client();
    
    //与按钮交互,故函数都设置为槽函数
    private slots:
        void slot_connected(); //处理成功连接到服务器的槽
        void slot_sendmessage(); //发送消息到服务器的槽
        void slot_recvmessage(); //接收来自服务器的消息的槽
        void slot_disconnect(); //取消与服务器连接的槽
    
    private:
        Ui::TCP_Client *ui;
    
        bool isconnetion; //判断是否连接到服务器的标志位
        QTcpSocket *TCP_sendMesSocket; //发送消息套接字
    };
    
    #endif // TCP_CLIENT_H
    

    TCP_Client.cpp

    #include "tcp_client.h"
    #include "ui_tcp_client.h"
    #include <QHostAddress>
    #include <QDateTime>
    #include <QMessageBox>
    
    TCP_Client::TCP_Client(QWidget *parent) :
        QWidget(parent),
        ui(new Ui::TCP_Client)
    {
        ui->setupUi(this);
    
        /***    初始化TCP   ***/
        this->setWindowTitle("TCP客户端");
        this->isconnetion = false;
        //初始化sendMesSocket
        this->TCP_sendMesSocket = new QTcpSocket();
    
        //终止之前的连接,重置套接字
        TCP_sendMesSocket->abort();
        //给定IP和端口号,连接服务器
        this->TCP_sendMesSocket->connectToHost("127.0.0.1",10000); //QHostAddress::LocalHost等于127.0.0.1,所以两者都可以互相替换
    
        //成功连接服务器的connected()信号连接到slot_connected() (注意:不是connect()信号)
        connect(TCP_sendMesSocket,SIGNAL(connected()),this,SLOT(slot_connected()));
        //发送按钮的clicked()信号连接到slot_sendmessage()
        connect(ui->pushButton_send,SIGNAL(clicked()),this,SLOT(slot_sendmessage()));
        //有新数据到达时的readyread()信号连接到slot_recvmessage()
        connect(TCP_sendMesSocket,SIGNAL(readyRead()),this,SLOT(slot_recvmessage()));
        //与服务器断开连接的disconnected()信号连接到slot_disconnect()
        connect(TCP_sendMesSocket,SIGNAL(disconnected()),this,SLOT(slot_disconnect()));
    }
    
    TCP_Client::~TCP_Client()
    {
        delete ui;
    }
    
    //处理成功连接到服务器的槽
    void TCP_Client::slot_connected()
    {
        this->isconnetion = true;
        ui->textBrowser->append(tr("与服务器连接成功:") + QDateTime::currentDateTime().toString("yyyy-M-dd hh:mm:ss"));
    }
    
    //发送消息到服务器的槽
    void TCP_Client::slot_sendmessage()
    {
        if(this->isconnetion)
        {
            QString sendMessage = ui->lineEdit->text(); //从单行文本框获得要发送消息
            if(!sendMessage.isEmpty())
            {
                //发送消息到服务器
                this->TCP_sendMesSocket->write(sendMessage.toLatin1());
                //本地显示发送的消息
                QString localDispalyMessage = tr("send to server: ") + sendMessage 
                                                + QDateTime::currentDateTime().toString(" yyyy-M-dd hh:mm:ss") + tr("
    ");
                ui->textBrowser->append(localDispalyMessage);
            }
            else
                QMessageBox::warning(this,"错误","消息不能为空!",QMessageBox::Ok);
        }
        else
            QMessageBox::warning(this,"错误","未连接到服务器!",QMessageBox::Ok);
    
        ui->lineEdit->clear();
    }
    
    //接收来自服务器的消息的槽
    void TCP_Client::slot_recvmessage()
    {
        //接收来自服务器的消息
        QByteArray byteArray = this->TCP_sendMesSocket->readAll();
        QString recvMessage = tr("recv from server: ") + byteArray + QDateTime::currentDateTime().toString(" yyyy-M-dd hh:mm:ss") + tr("
    ");
        ui->textBrowser->append(recvMessage);
    }
    
    //取消与服务器连接的槽
    void TCP_Client::slot_disconnect()
    {
        QMessageBox::warning(this,"警告","与服务器的连接中断",QMessageBox::Ok);
        //关闭并随后删除socket
        TCP_sendMesSocket->close();
        TCP_sendMesSocket->deleteLater();
    }
    
    

    参考:

    QT之TCP通信


  • 相关阅读:
    数据可视化基础专题(九):Matplotlib 基础(一)坐标相关
    PrinterInfo (API: Objects) – Electron 中文开发手册
    Java中的多线程
    简介 (Service Worker) – Angular 中文开发手册
    :不确定 | :indeterminate (Basic User Interface)
    高度 | height (Basic Box Model)
    高度 | @viewport.height (Device Adaptation)
    高度 | @media.height (Media Queries)
    首字 | initial-letter (Inline Layout)
    颜色索引 | @media.color-index (Media Queries)
  • 原文地址:https://www.cnblogs.com/linuxAndMcu/p/10160594.html
Copyright © 2011-2022 走看看