• 首页 首页 icon
  • 工具库 工具库 icon
    • IP查询 IP查询 icon
  • 内容库 内容库 icon
    • 快讯库 快讯库 icon
    • 精品库 精品库 icon
    • 问答库 问答库 icon
  • 更多 更多 icon
    • 服务条款 服务条款 icon

qtQtcphotoshoperver服务端_qt websocket

武飞扬头像
音视频开发老舅
帮助1

0.前言

本文主要讲解 Qt TCP 相关接口的基本应用,一些实践相关的后面会单独写。

TCP 协议是一种面向连接的、可靠的、基于字节流的传输层通信协议。TCP 通过检验和、序列号、确认应答、重发控制、连接管理以及窗口控制等机制实现可靠性传输。

TCP 通过三次握手来建立可靠的连接。

学新通

TCP 四次挥手断开连接。TCP连接是双向的,在四次挥手中,前两次挥手用于断开一个方向的连接,后两次挥手用于断开另一方向的连接。

学新通

 本文福利,费领取Qt开发学习资料包、技术视频,内容包括(C 语言基础,Qt编程入门,QT信号与槽机制,QT界面开发-图像绘制,QT网络,QT数据库编程,QT项目实战,QT嵌入式开发,Quick模块等等)↓↓↓↓↓↓见下面↓↓文章底部点击费领取↓↓

1.准备工作

首先,要使用 Qt 的网络模块需要在 pro 中加上 network(如果是 VS IDE 就在模块选择里勾选上 network):

QT  = network

引入相关类的头文件:

  1.  
    #include <QTcpServer>
  2.  
    #include <QTcpSocket>
  3.  
    #include <QHostAddress>

另外, Qt 在 windows 下使用的 select 模型,在 linux 下新版本的改为了 poll 模型(具体版本待查)。

Qt TCP 的操作流程:

学新通

2.认识QTcpSocket的接口

QTcpSocket 是 QAbstractSocket 的子类,用于建立 TCP 连接并传输数据流。

对于 QTcpServer 服务端,可通过 nextPendingConnection() 接口获取到建立了 TCP 连接的 QTcpSocket 对象。

对于客户端,创建好 QTcpSocket 对象后,调用 connectToHost() 连接到服务端:

  1.  
    void QAbstractSocket::connectToHost(const QString &hostName, quint16 port, QIODevice::OpenMode openMode = ReadWrite, QAbstractSocket::NetworkLayerProtocol protocol = AnyIPProtocol)
  2.  
    void QAbstractSocket::connectToHost(const QHostAddress &address, quint16 port, QIODevice::OpenMode openMode = ReadWrite)

连接成功和连接断开会触发 connected() 和 disconnected() 信号:

  1.  
    void QAbstractSocket::connected()
  2.  
    void QAbstractSocket::disconnected()

连接成功之后,可以调用 QIODevice 继承来的 read,write 等接口:

  1.  
    qint64 QIODevice::read(char *data, qint64 maxSize)
  2.  
    QByteArray QIODevice::read(qint64 maxSize)
  3.  
    QByteArray QIODevice::readAll()
  4.  
    qint64 QIODevice::write(const char *data, qint64 maxSize)
  5.  
    qint64 QIODevice::write(const char *data)
  6.  
    qint64 QIODevice::write(const QByteArray &byteArray)

当有新的数据到来,会触发 readyRead() 信号,此时在槽函数中进行读取即可:

void QIODevice::readyRead()

操作完之后,调用相关接口关闭 TCP 连接:

  1.  
    void QAbstractSocket::disconnectFromHost()
  2.  
    void QAbstractSocket::close()
  3.  
    void QAbstractSocket::abort()

其中, abort 调用了 close, close 调用了 disconnectFromHost。 abort 立即关闭套接字,并丢弃写缓冲区中的所有待处理数据。close 关闭套接字的 IO,以及套接字的连接。

3.认识QTcpServer的接口

QTcpServer 类提供基于 TCP 的服务器。

首先,调用 listen() 监听指定的地址和端口:

bool QTcpServer::listen(const QHostAddress &address = QHostAddress::Any, quint16 port = 0)

当有新的 TCP 连接,会触发 newConnection() 信号,此时可以调用 nextPendingConnection() 以将挂起的连接接受为已连接的 QTcpSocket,通过该对象可以与客户端通信。

QTcpSocket *QTcpServer::nextPendingConnection()

注意,返回的 QTcpSocket 对象不能在另一个线程使用,如果需要在别的线程管理这个 socket 连接,需要重写 Server 的 incomingConnection() ,将 sokcet 描述符传递给别的线程并创建 QTcpSocket:

void QTcpServer::incomingConnection(qintptr socketDescriptor)

最后,调用 close() 停止监听:

void QTcpServer::close()

4.Qt Tcp的简单示例

完整代码链接(分为SimpleTcpServer和SimpleTcpClient两个子项目):

运行效果:

学新通

服务端主要实现代码:

  1.  
    #ifndef WIDGET_H
  2.  
    #define WIDGET_H
  3.  
     
  4.  
    #include <QWidget>
  5.  
    #include <QTcpServer>
  6.  
    #include <QTcpSocket>
  7.  
     
  8.  
    QT_BEGIN_NAMESPACE
  9.  
    namespace Ui { class Widget; }
  10.  
    QT_END_NAMESPACE
  11.  
     
  12.  
    //simple Tcp 服务端
  13.  
    class Widget : public QWidget
  14.  
    {
  15.  
    Q_OBJECT
  16.  
     
  17.  
    public:
  18.  
    Widget(QWidget *parent = nullptr);
  19.  
    ~Widget();
  20.  
     
  21.  
    private:
  22.  
    //初始化server操作
  23.  
    void initServer();
  24.  
    //close server
  25.  
    void closeServer();
  26.  
    //更新当前状态
  27.  
    void updateState();
  28.  
     
  29.  
    private:
  30.  
    Ui::Widget *ui;
  31.  
    //server用于监听端口,获取新的tcp连接的描述符
  32.  
    QTcpServer *server;
  33.  
    //存储已连接的socket对象
  34.  
    QList<QTcpSocket*> clientList;
  35.  
    };
  36.  
    #endif // WIDGET_H
学新通
  1.  
    #include "widget.h"
  2.  
    #include "ui_widget.h"
  3.  
    #include <QHostAddress>
  4.  
    Widget::Widget(QWidget *parent)
  5.  
    : QWidget(parent)
  6.  
    , ui(new Ui::Widget)
  7.  
    {
  8.  
    ui->setupUi(this);
  9.  
    setWindowTitle("Server");
  10.  
    initServer();
  11.  
    }
  12.  
    Widget::~Widget()
  13.  
    {
  14.  
    //关闭server
  15.  
    closeServer();
  16.  
    delete ui;
  17.  
    }
  18.  
    void Widget::initServer()
  19.  
    {
  20.  
    //创建Server对象
  21.  
    server = new QTcpServer(this);
  22.  
    //点击监听按钮,开始监听
  23.  
    connect(ui->btnListen,&QPushButton::clicked,[this]{
  24.  
    //判断当前是否已开启,是则close,否则listen
  25.  
    if(server->isListening()){
  26.  
    //server->close();
  27.  
    closeServer();
  28.  
    //关闭server后恢复界面状态
  29.  
    ui->btnListen->setText("Listen");
  30.  
    ui->editAddress->setEnabled(true);
  31.  
    ui->editPort->setEnabled(true);
  32.  
    }else{
  33.  
    //从界面上读取ip和端口
  34.  
    //可以使用 QHostAddress::Any 监听所有地址的对应端口
  35.  
    const QString address_text=ui->editAddress->text();
  36.  
    const QHostAddress address=(address_text=="Any")
  37.  
    ?QHostAddress::Any
  38.  
    :QHostAddress(address_text);
  39.  
    const unsigned short port=ui->editPort->text().toUShort();
  40.  
    //开始监听,并判断是否成功
  41.  
    if(server->listen(address,port)){
  42.  
    //连接成功就修改界面按钮提示,以及地址栏不可编辑
  43.  
    ui->btnListen->setText("Close");
  44.  
    ui->editAddress->setEnabled(false);
  45.  
    ui->editPort->setEnabled(false);
  46.  
    }
  47.  
    }
  48.  
    updateState();
  49.  
    });
  50.  
    //监听到新的客户端连接请求
  51.  
    connect(server,&QTcpServer::newConnection,this,[this]{
  52.  
    //如果有新的连接就取出
  53.  
    while(server->hasPendingConnections())
  54.  
    {
  55.  
    //nextPendingConnection返回下一个挂起的连接作为已连接的QTcpSocket对象
  56.  
    //套接字是作为服务器的子级创建的,这意味着销毁QTcpServer对象时会自动删除该套接字。
  57.  
    //最好在完成处理后显式删除该对象,以避免浪费内存。
  58.  
    //返回的QTcpSocket对象不能从另一个线程使用,如有需要可重写incomingConnection().
  59.  
    QTcpSocket *socket=server->nextPendingConnection();
  60.  
    clientList.append(socket);
  61.  
    ui->textRecv->append(QString("[%1:%2] Soket Connected")
  62.  
    .arg(socket->peerAddress().toString())
  63.  
    .arg(socket->peerPort()));
  64.  
    //关联相关操作的信号槽
  65.  
    //收到数据,触发readyRead
  66.  
    connect(socket,&QTcpSocket::readyRead,[this,socket]{
  67.  
    //没有可读的数据就返回
  68.  
    if(socket->bytesAvailable()<=0)
  69.  
    return;
  70.  
    //注意收发两端文本要使用对应的编解码
  71.  
    const QString recv_text=QString::fromUtf8(socket->readAll());
  72.  
    ui->textRecv->append(QString("[%1:%2]")
  73.  
    .arg(socket->peerAddress().toString())
  74.  
    .arg(socket->peerPort()));
  75.  
    ui->textRecv->append(recv_text);
  76.  
    });
  77.  
    //error信号在5.15换了名字
  78.  
    #if QT_VERSION < QT_VERSION_CHECK(5, 15, 0)
  79.  
    //错误信息
  80.  
    connect(socket, static_cast<void(QAbstractSocket::*)(QAbstractSocket::SocketError)>(&QAbstractSocket::error),
  81.  
    [this,socket](QAbstractSocket::SocketError){
  82.  
    ui->textRecv->append(QString("[%1:%2] Soket Error:%3")
  83.  
    .arg(socket->peerAddress().toString())
  84.  
    .arg(socket->peerPort())
  85.  
    .arg(socket->errorString()));
  86.  
    });
  87.  
    #else
  88.  
    //错误信息
  89.  
    connect(socket,&QAbstractSocket::errorOccurred,[this,socket](QAbstractSocket::SocketError){
  90.  
    ui->textRecv->append(QString("[%1:%2] Soket Error:%3")
  91.  
    .arg(socket->peerAddress().toString())
  92.  
    .arg(socket->peerPort())
  93.  
    .arg(socket->errorString()));
  94.  
    });
  95.  
    #endif
  96.  
    //连接断开,销毁socket对象,这是为了开关server时socket正确释放
  97.  
    connect(socket,&QTcpSocket::disconnected,[this,socket]{
  98.  
    socket->deleteLater();
  99.  
    clientList.removeOne(socket);
  100.  
    ui->textRecv->append(QString("[%1:%2] Soket Disonnected")
  101.  
    .arg(socket->peerAddress().toString())
  102.  
    .arg(socket->peerPort()));
  103.  
    updateState();
  104.  
    });
  105.  
    }
  106.  
    updateState();
  107.  
    });
  108.  
    //server向client发送内容
  109.  
    connect(ui->btnSend,&QPushButton::clicked,[this]{
  110.  
    //判断是否开启了server
  111.  
    if(!server->isListening())
  112.  
    return;
  113.  
    //将发送区文本发送给客户端
  114.  
    const QByteArray send_data=ui->textSend->toPlainText().toUtf8();
  115.  
    //数据为空就返回
  116.  
    if(send_data.isEmpty())
  117.  
    return;
  118.  
    for(QTcpSocket *socket:clientList)
  119.  
    {
  120.  
    socket->write(send_data);
  121.  
    //socket->waitForBytesWritten();
  122.  
    }
  123.  
    });
  124.  
    //server的错误信息
  125.  
    //如果发生错误,则serverError()返回错误的类型,
  126.  
    //并且可以调用errorString()以获取对所发生事件的易于理解的描述
  127.  
    connect(server,&QTcpServer::acceptError,[this](QAbstractSocket::SocketError){
  128.  
    ui->textRecv->append("Server Error:" server->errorString());
  129.  
    });
  130.  
    }
  131.  
    void Widget::closeServer()
  132.  
    {
  133.  
    //停止服务
  134.  
    server->close();
  135.  
    for(QTcpSocket * socket:clientList)
  136.  
    {
  137.  
    //断开与客户端的连接
  138.  
    socket->disconnectFromHost();
  139.  
    if(socket->state()!=QAbstractSocket::UnconnectedState){
  140.  
    socket->abort();
  141.  
    }
  142.  
    }
  143.  
    }
  144.  
    void Widget::updateState()
  145.  
    {
  146.  
    //将当前server地址和端口、客户端连接数写在标题栏
  147.  
    if(server->isListening()){
  148.  
    setWindowTitle(QString("Server[%1:%2] connections:%3")
  149.  
    .arg(server->serverAddress().toString())
  150.  
    .arg(server->serverPort())
  151.  
    .arg(clientList.count()));
  152.  
    }else{
  153.  
    setWindowTitle("Server");
  154.  
    }
  155.  
    }
学新通

客户端主要实现代码:

  1.  
    #ifndef WIDGET_H
  2.  
    #define WIDGET_H
  3.  
    #include <QWidget>
  4.  
    #include <QTcpSocket>
  5.  
    QT_BEGIN_NAMESPACE
  6.  
    namespace Ui { class Widget; }
  7.  
    QT_END_NAMESPACE
  8.  
    //simple Tcp 客户端
  9.  
    class Widget : public QWidget
  10.  
    {
  11.  
    Q_OBJECT
  12.  
    public:
  13.  
    Widget(QWidget *parent = nullptr);
  14.  
    ~Widget();
  15.  
    private:
  16.  
    //初始化client操作
  17.  
    void initClient();
  18.  
    //更新当前状态
  19.  
    void updateState();
  20.  
    private:
  21.  
    Ui::Widget *ui;
  22.  
    //socket对象
  23.  
    QTcpSocket *client;
  24.  
    };
  25.  
    #endif // WIDGET_H
学新通
  1.  
    #include "widget.h"
  2.  
    #include "ui_widget.h"
  3.  
    #include <QHostAddress>
  4.  
    Widget::Widget(QWidget *parent)
  5.  
    : QWidget(parent)
  6.  
    , ui(new Ui::Widget)
  7.  
    {
  8.  
    ui->setupUi(this);
  9.  
    setWindowTitle("Client");
  10.  
    initClient();
  11.  
    }
  12.  
    Widget::~Widget()
  13.  
    {
  14.  
    //析构关闭连接
  15.  
    //client->disconnectFromHost();
  16.  
    //if(client->state()!=QAbstractSocket::UnconnectedState){
  17.  
    // client->waitForDisconnected();
  18.  
    //}
  19.  
    //关闭套接字的I/O设备,并调用disconnectFromHost()关闭套接字的连接。
  20.  
    //client->close();
  21.  
    //中止当前连接并重置套接字。与disconnectFromHost()不同,
  22.  
    //此函数立即关闭套接字,并丢弃写缓冲区中的所有待处理数据。
  23.  
    client->abort();
  24.  
    delete ui;
  25.  
    }
  26.  
    void Widget::initClient()
  27.  
    {
  28.  
    //创建client对象
  29.  
    client = new QTcpSocket(this);
  30.  
    //点击连接,根据ui设置的服务器地址进行连接
  31.  
    connect(ui->btnConnect,&QPushButton::clicked,[this]{
  32.  
    //判断当前是否已连接,连接了就断开
  33.  
    if(client->state()==QAbstractSocket::ConnectedState){
  34.  
    //如果使用disconnectFromHost()不会重置套接字,isValid还是会为true
  35.  
    client->abort();
  36.  
    }else if(client->state()==QAbstractSocket::UnconnectedState){
  37.  
    //从界面上读取ip和端口
  38.  
    const QHostAddress address=QHostAddress(ui->editAddress->text());
  39.  
    const unsigned short port=ui->editPort->text().toUShort();
  40.  
    //连接服务器
  41.  
    client->connectToHost(address,port);
  42.  
    }else{
  43.  
    ui->textRecv->append("It is not ConnectedState or UnconnectedState");
  44.  
    }
  45.  
    });
  46.  
    //连接状态
  47.  
    connect(client,&QTcpSocket::connected,[this]{
  48.  
    //已连接就设置为不可编辑
  49.  
    ui->btnConnect->setText("Disconnect");
  50.  
    ui->editAddress->setEnabled(false);
  51.  
    ui->editPort->setEnabled(false);
  52.  
    updateState();
  53.  
    });
  54.  
    connect(client,&QTcpSocket::disconnected,[this]{
  55.  
    //断开连接还原状态
  56.  
    ui->btnConnect->setText("Connect");
  57.  
    ui->editAddress->setEnabled(true);
  58.  
    ui->editPort->setEnabled(true);
  59.  
    updateState();
  60.  
    });
  61.  
    //发送数据
  62.  
    connect(ui->btnSend,&QPushButton::clicked,[this]{
  63.  
    //判断是可操作,isValid表示准备好读写
  64.  
    if(!client->isValid())
  65.  
    return;
  66.  
    //将发送区文本发送给客户端
  67.  
    const QByteArray send_data=ui->textSend->toPlainText().toUtf8();
  68.  
    //数据为空就返回
  69.  
    if(send_data.isEmpty())
  70.  
    return;
  71.  
    client->write(send_data);
  72.  
    //client->waitForBytesWritten();
  73.  
    });
  74.  
    //收到数据,触发readyRead
  75.  
    connect(client,&QTcpSocket::readyRead,[this]{
  76.  
    //没有可读的数据就返回
  77.  
    if(client->bytesAvailable()<=0)
  78.  
    return;
  79.  
    //注意收发两端文本要使用对应的编解码
  80.  
    const QString recv_text=QString::fromUtf8(client->readAll());
  81.  
    ui->textRecv->append(QString("[%1:%2]")
  82.  
    .arg(client->peerAddress().toString())
  83.  
    .arg(client->peerPort()));
  84.  
    ui->textRecv->append(recv_text);
  85.  
    });
  86.  
    //error信号在5.15换了名字
  87.  
    #if QT_VERSION < QT_VERSION_CHECK(5, 15, 0)
  88.  
    //错误信息
  89.  
    connect(client, static_cast<void(QAbstractSocket::*)(QAbstractSocket::SocketError)>(&QAbstractSocket::error),
  90.  
    [this](QAbstractSocket::SocketError){
  91.  
    ui->textRecv->append("Socket Error:" client->errorString());
  92.  
    });
  93.  
    #else
  94.  
    //错误信息
  95.  
    connect(client,&QAbstractSocket::errorOccurred,[this](QAbstractSocket::SocketError){
  96.  
    ui->textRecv->append("Socket Error:" client->errorString());
  97.  
    });
  98.  
    #endif
  99.  
    }
  100.  
    void Widget::updateState()
  101.  
    {
  102.  
    //将当前client地址和端口写在标题栏
  103.  
    if(client->state()==QAbstractSocket::ConnectedState){
  104.  
    setWindowTitle(QString("Client[%1:%2]")
  105.  
    .arg(client->localAddress().toString())
  106.  
    .arg(client->localPort()));
  107.  
    }else{
  108.  
    setWindowTitle("Client");
  109.  
    }
  110.  
    }
学新通

本文福利,费领取Qt开发学习资料包、技术视频,内容包括(C 语言基础,Qt编程入门,QT信号与槽机制,QT界面开发-图像绘制,QT网络,QT数据库编程,QT项目实战,QT嵌入式开发,Quick模块等等)↓↓↓↓↓↓见下面↓↓文章底部点击费领取↓↓

这篇文章转载于:学新通

  • 版权申明: 本站部分内容来自互联网,仅供学习及演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,请提供相关证据及您的身份证明,我们将在收到邮件后48小时内删除。
  • 本站站名: 学新通
  • 本文地址: https://www.swvq.com/boutique/detail/tanhehfgfh
  • 联系方式: luke.wu●vfv.cc
系列文章
更多 icon
同类精品
更多 icon
我要评论
我的头像
精彩评论
继续加载