qt中Qtcpserver服务端_qt websocket

qt中Qtcpserver服务端_qt websocket0.前言本文主要讲解QtTCP相关接口的基本应用,一些实践相关的后面会单独写。TCP协议是一种面向连接的、可靠的、基于字节流的传输层通信协议。TCP通过检验和、序列号、确认应答、重发控制、连接管理以及窗口控制等机制实现可靠性传输。TCP通过三次握手来建立可靠的连接。TCP四次挥手断开连接。TCP连接是双向的,在四次挥手中,前两次挥手用于断开一个方向的连接,后两次挥手用于断开另一方向的连接。TCP知识参考:https://blog.csdn.net/sinat_36

大家好,又见面了,我是你们的朋友全栈君。如果您正在找激活码,请点击查看最新教程,关注关注公众号 “全栈程序员社区” 获取激活教程,可能之前旧版本教程已经失效.最新Idea2022.1教程亲测有效,一键激活。

Jetbrains全系列IDE使用 1年只要46元 售后保障 童叟无欺

0.前言

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

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

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

qt中Qtcpserver服务端_qt websocket

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

qt中Qtcpserver服务端_qt websocket

TCP 知识参考:https://blog.csdn.net/sinat_36629696/article/details/80740678

TCP 知识参考:https://www.jianshu.com/p/ca64764e4a26

1.准备工作

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

QT += network

引入相关类的头文件:

#include <QTcpServer>
#include <QTcpSocket>
#include <QHostAddress>

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

Qt TCP 的操作流程:

qt中Qtcpserver服务端_qt websocket

图片参考:https://blog.csdn.net/qq_32298647/article/details/74834254

2.认识QTcpSocket的接口

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

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

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

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

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

void QAbstractSocket::connected()
void QAbstractSocket::disconnected()

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

qint64 QIODevice::read(char *data, qint64 maxSize)
QByteArray QIODevice::read(qint64 maxSize)
QByteArray QIODevice::readAll()
qint64 QIODevice::write(const char *data, qint64 maxSize)
qint64 QIODevice::write(const char *data)
qint64 QIODevice::write(const QByteArray &byteArray)

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

void QIODevice::readyRead()

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

void QAbstractSocket::disconnectFromHost()
void QAbstractSocket::close()
void QAbstractSocket::abort()

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

文档:https://doc.qt.io/qt-5/qtcpserver.html

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

文档:https://doc.qt.io/qt-5/qabstractsocket.html

文档:https://doc.qt.io/qt-5/qtcpsocket.html

4.Qt Tcp的简单示例

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

https://github.com/gongjianbo/HelloQtNetwork

运行效果:

qt中Qtcpserver服务端_qt websocket

服务端主要实现代码:

#ifndef WIDGET_H
#define WIDGET_H

#include <QWidget>
#include <QTcpServer>
#include <QTcpSocket>

QT_BEGIN_NAMESPACE
namespace Ui { class Widget; }
QT_END_NAMESPACE

//simple Tcp 服务端
class Widget : public QWidget
{
    Q_OBJECT

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

private:
    //初始化server操作
    void initServer();
    //close server
    void closeServer();
    //更新当前状态
    void updateState();

private:
    Ui::Widget *ui;
    //server用于监听端口,获取新的tcp连接的描述符
    QTcpServer *server;
    //存储已连接的socket对象
    QList<QTcpSocket*> clientList;
};
#endif // WIDGET_H
#include "widget.h"
#include "ui_widget.h"

#include <QHostAddress>

Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);
    setWindowTitle("Server");

    initServer();
}

Widget::~Widget()
{
    //关闭server
    closeServer();
    delete ui;
}

void Widget::initServer()
{
    //创建Server对象
    server = new QTcpServer(this);

    //点击监听按钮,开始监听
    connect(ui->btnListen,&QPushButton::clicked,[this]{
        //判断当前是否已开启,是则close,否则listen
        if(server->isListening()){
            //server->close();
            closeServer();
            //关闭server后恢复界面状态
            ui->btnListen->setText("Listen");
            ui->editAddress->setEnabled(true);
            ui->editPort->setEnabled(true);
        }else{
            //从界面上读取ip和端口
            //可以使用 QHostAddress::Any 监听所有地址的对应端口
            const QString address_text=ui->editAddress->text();
            const QHostAddress address=(address_text=="Any")
                    ?QHostAddress::Any
                   :QHostAddress(address_text);
            const unsigned short port=ui->editPort->text().toUShort();
            //开始监听,并判断是否成功
            if(server->listen(address,port)){
                //连接成功就修改界面按钮提示,以及地址栏不可编辑
                ui->btnListen->setText("Close");
                ui->editAddress->setEnabled(false);
                ui->editPort->setEnabled(false);
            }
        }
        updateState();
    });

    //监听到新的客户端连接请求
    connect(server,&QTcpServer::newConnection,this,[this]{
        //如果有新的连接就取出
        while(server->hasPendingConnections())
        {
            //nextPendingConnection返回下一个挂起的连接作为已连接的QTcpSocket对象
            //套接字是作为服务器的子级创建的,这意味着销毁QTcpServer对象时会自动删除该套接字。
            //最好在完成处理后显式删除该对象,以避免浪费内存。
            //返回的QTcpSocket对象不能从另一个线程使用,如有需要可重写incomingConnection().
            QTcpSocket *socket=server->nextPendingConnection();
            clientList.append(socket);
            ui->textRecv->append(QString("[%1:%2] Soket Connected")
                                 .arg(socket->peerAddress().toString())
                                 .arg(socket->peerPort()));

            //关联相关操作的信号槽
            //收到数据,触发readyRead
            connect(socket,&QTcpSocket::readyRead,[this,socket]{
                //没有可读的数据就返回
                if(socket->bytesAvailable()<=0)
                    return;
                //注意收发两端文本要使用对应的编解码
                const QString recv_text=QString::fromUtf8(socket->readAll());
                ui->textRecv->append(QString("[%1:%2]")
                                     .arg(socket->peerAddress().toString())
                                     .arg(socket->peerPort()));
                ui->textRecv->append(recv_text);
            });

            //error信号在5.15换了名字
#if QT_VERSION < QT_VERSION_CHECK(5, 15, 0)
            //错误信息
            connect(socket, static_cast<void(QAbstractSocket::*)(QAbstractSocket::SocketError)>(&QAbstractSocket::error),
                    [this,socket](QAbstractSocket::SocketError){
                ui->textRecv->append(QString("[%1:%2] Soket Error:%3")
                                     .arg(socket->peerAddress().toString())
                                     .arg(socket->peerPort())
                                     .arg(socket->errorString()));
            });
#else
            //错误信息
            connect(socket,&QAbstractSocket::errorOccurred,[this,socket](QAbstractSocket::SocketError){
                ui->textRecv->append(QString("[%1:%2] Soket Error:%3")
                                     .arg(socket->peerAddress().toString())
                                     .arg(socket->peerPort())
                                     .arg(socket->errorString()));
            });
#endif

            //连接断开,销毁socket对象,这是为了开关server时socket正确释放
            connect(socket,&QTcpSocket::disconnected,[this,socket]{
                socket->deleteLater();
                clientList.removeOne(socket);
                ui->textRecv->append(QString("[%1:%2] Soket Disonnected")
                                     .arg(socket->peerAddress().toString())
                                     .arg(socket->peerPort()));
                updateState();
            });
        }
        updateState();
    });

    //server向client发送内容
    connect(ui->btnSend,&QPushButton::clicked,[this]{
        //判断是否开启了server
        if(!server->isListening())
            return;
        //将发送区文本发送给客户端
        const QByteArray send_data=ui->textSend->toPlainText().toUtf8();
        //数据为空就返回
        if(send_data.isEmpty())
            return;
        for(QTcpSocket *socket:clientList)
        {
            socket->write(send_data);
            //socket->waitForBytesWritten();
        }
    });

    //server的错误信息
    //如果发生错误,则serverError()返回错误的类型,
    //并且可以调用errorString()以获取对所发生事件的易于理解的描述
    connect(server,&QTcpServer::acceptError,[this](QAbstractSocket::SocketError){
        ui->textRecv->append("Server Error:"+server->errorString());
    });
}

void Widget::closeServer()
{
    //停止服务
    server->close();
    for(QTcpSocket * socket:clientList)
    {
        //断开与客户端的连接
        socket->disconnectFromHost();
        if(socket->state()!=QAbstractSocket::UnconnectedState){
            socket->abort();
        }
    }
}

void Widget::updateState()
{
    //将当前server地址和端口、客户端连接数写在标题栏
    if(server->isListening()){
        setWindowTitle(QString("Server[%1:%2] connections:%3")
                       .arg(server->serverAddress().toString())
                       .arg(server->serverPort())
                       .arg(clientList.count()));
    }else{
        setWindowTitle("Server");
    }
}

客户端主要实现代码:

#ifndef WIDGET_H
#define WIDGET_H

#include <QWidget>
#include <QTcpSocket>

QT_BEGIN_NAMESPACE
namespace Ui { class Widget; }
QT_END_NAMESPACE

//simple Tcp 客户端
class Widget : public QWidget
{
    Q_OBJECT

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

private:
    //初始化client操作
    void initClient();
    //更新当前状态
    void updateState();

private:
    Ui::Widget *ui;
    //socket对象
    QTcpSocket *client;
};
#endif // WIDGET_H
#include "widget.h"
#include "ui_widget.h"

#include <QHostAddress>

Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);
    setWindowTitle("Client");

    initClient();
}

Widget::~Widget()
{
    //析构关闭连接
    //client->disconnectFromHost();
    //if(client->state()!=QAbstractSocket::UnconnectedState){
    //    client->waitForDisconnected();
    //}
    //关闭套接字的I/O设备,并调用disconnectFromHost()关闭套接字的连接。
    //client->close();
    //中止当前连接并重置套接字。与disconnectFromHost()不同,
    //此函数立即关闭套接字,并丢弃写缓冲区中的所有待处理数据。
    client->abort();
    delete ui;
}

void Widget::initClient()
{
    //创建client对象
    client = new QTcpSocket(this);

    //点击连接,根据ui设置的服务器地址进行连接
    connect(ui->btnConnect,&QPushButton::clicked,[this]{
        //判断当前是否已连接,连接了就断开
        if(client->state()==QAbstractSocket::ConnectedState){
            //如果使用disconnectFromHost()不会重置套接字,isValid还是会为true
            client->abort();
        }else if(client->state()==QAbstractSocket::UnconnectedState){
            //从界面上读取ip和端口
            const QHostAddress address=QHostAddress(ui->editAddress->text());
            const unsigned short port=ui->editPort->text().toUShort();
            //连接服务器
            client->connectToHost(address,port);
        }else{
            ui->textRecv->append("It is not ConnectedState or UnconnectedState");
        }
    });

    //连接状态
    connect(client,&QTcpSocket::connected,[this]{
        //已连接就设置为不可编辑
        ui->btnConnect->setText("Disconnect");
        ui->editAddress->setEnabled(false);
        ui->editPort->setEnabled(false);
        updateState();
    });
    connect(client,&QTcpSocket::disconnected,[this]{
        //断开连接还原状态
        ui->btnConnect->setText("Connect");
        ui->editAddress->setEnabled(true);
        ui->editPort->setEnabled(true);
        updateState();
    });

    //发送数据
    connect(ui->btnSend,&QPushButton::clicked,[this]{
        //判断是可操作,isValid表示准备好读写
        if(!client->isValid())
            return;
        //将发送区文本发送给客户端
        const QByteArray send_data=ui->textSend->toPlainText().toUtf8();
        //数据为空就返回
        if(send_data.isEmpty())
            return;
        client->write(send_data);
        //client->waitForBytesWritten();
    });

    //收到数据,触发readyRead
    connect(client,&QTcpSocket::readyRead,[this]{
        //没有可读的数据就返回
        if(client->bytesAvailable()<=0)
            return;
        //注意收发两端文本要使用对应的编解码
        const QString recv_text=QString::fromUtf8(client->readAll());
        ui->textRecv->append(QString("[%1:%2]")
                             .arg(client->peerAddress().toString())
                             .arg(client->peerPort()));
        ui->textRecv->append(recv_text);
    });

    //error信号在5.15换了名字
#if QT_VERSION < QT_VERSION_CHECK(5, 15, 0)
    //错误信息
    connect(client, static_cast<void(QAbstractSocket::*)(QAbstractSocket::SocketError)>(&QAbstractSocket::error),
            [this](QAbstractSocket::SocketError){
        ui->textRecv->append("Socket Error:"+client->errorString());
    });
#else
    //错误信息
    connect(client,&QAbstractSocket::errorOccurred,[this](QAbstractSocket::SocketError){
        ui->textRecv->append("Socket Error:"+client->errorString());
    });
#endif
}

void Widget::updateState()
{
    //将当前client地址和端口写在标题栏
    if(client->state()==QAbstractSocket::ConnectedState){
        setWindowTitle(QString("Client[%1:%2]")
                       .arg(client->localAddress().toString())
                       .arg(client->localPort()));
    }else{
        setWindowTitle("Client");
    }
}

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

发布者:全栈程序员-用户IM,转载请注明出处:https://javaforall.cn/195426.html原文链接:https://javaforall.cn

【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛

【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...

(0)
blank

相关推荐

  • FFmpeg从入门到精通读书笔记(1)

    FFmpeg从入门到精通读书笔记(1)笔者才开始学习音视频开发,FFmpeg从入门到精通读书笔记系列主要是基于阅读刘歧、赵文杰编著的《FFmpeg从入门到精通》以及雷霄骅博士博客总结写的入门心得体会。官方文档资料FFmpeg官方文档:https://ffmpeg.org/documentation.htmlFFmpeg官方wiki:http://trac.ffmpeg.org/wiki中文经典资料雷霄骅博士csdn链…

  • 什么是java?为什么要学Java? 学Java有什么用?

    什么是java?为什么要学Java? 学Java有什么用?肯定会有许多人或多或少都和我一样,当偶然在电视上看到程序员对着电脑飞快敲下一串代码,不禁为他们发出惊叹,想象着有一天自己也能这样。是的,有一天你也能这样!因为我已经实现了!下面我们来看看到底什么是JAVA?为什么要学习,学习了有什么用?先给大家看一下19年计算机语言排行什么是java?为什么要学Java?学Java有什么用?1.什么是JavaJava它就是一种编程语言,别的编程语言能干的事,它都能干。2.编程语言这么多,我为什么选择Java呢?简单易学首先Java是一个面向对象的编程语言

  • Tomcat部署WAR包访问不带项目名的方式

    Tomcat部署WAR包访问不带项目名的方式1、将项目打成WAR包放在Tomcat的webapps目录下2、在Tomcat的安装目录的conf下找到server.xml的文件,如:D:\apache-tomcat-9.0.8\conf\server.xml3、在Host标签里边添加<Hostname=”localhost”appBase=”webapps”unpackWARs=”true”…

  • OJ平台各个简写的含义

    OJ平台各个简写的含义简写字符的含义简写全称中文称谓ACAccepted通过WAWrongAnswer答案错误TLETimeLimitExceed超时OLEOutputLimitExceed超出输出限制MLEMemoryLimitExceed超出内存RERuntimeError运行时错误PEPresentationError格式错误CECompileError无法编译…

  • Android开发 – 基本UI设计

    Android开发 – 基本UI设计文章目录Android开发-基本UI设计1.页面部分占用1/N的情况2.分割线的实现Android开发-基本UI设计本博客记录本人在安卓开发时候遇到的一些UI设计的问题以及解决方法记录来自Project-FoodList1.页面部分占用1/N的情况示例:完整界面 界面顶部要求顶部界面只占1/3解决方案使用线性布局,其属性android:orienta…

  • SQL基础教程_一般sql培训多少钱

    SQL基础教程_一般sql培训多少钱下面是我看到其他博主写的博客,感觉sql总结的不错,对我很有帮助,就把它拿过来了,以便下次使用,借鉴网址:http://www.cnblogs.com/minuobaci/p/7224956.html

发表回复

您的电子邮箱地址不会被公开。

关注全栈程序员社区公众号