qt中Qtcpserver服务端_qt websocket

news2024/11/26 10:33:46

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

引入相关类的头文件:

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

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

Qt TCP 的操作流程:

 

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,以及套接字的连接。

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两个子项目):

运行效果:

 

服务端主要实现代码:

#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");
}
}

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/50334.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Feign的使用

1、Feigin接口&#xff1a; ProductClientService import com.mengxuegu.springcloud.entities.Product; import org.springframework.cloud.openfeign.FeignClient; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.anno…

SIMetrix导入MOS管SPICE参数进行仿真的快速方法

问题的提出 在采用SIMetrix 8.3软件进行E类放大器的仿真过程中&#xff0c;用到了NEXPERIA公司的NMOS管器件PMH550UNE, 但在SIMetrix 8.3的库中没有该器件&#xff0c;因此需要导入第三方库文件. 通常的办法是从生产该器件的公司网站上下载器件库文件&#xff0c;导入到SIMet…

MKS上游和下游集成式压力控制器的技术分析及其替代解决方案

摘要&#xff1a;目前的MKS系列集成式压力控制器本质上是一种流量调节和测量装置&#xff0c;无法直接用来进行准确的压力控制&#xff0c;而且MKS压力控制器还存在测量精度不高、压力控制范围有限和对工作介质洁净度要求很高的不足。为此&#xff0c;为了弥补这些不足&#xf…

Java 并发编程之ConcurrentHashMap源码详解

Java 并发编程之ConcurrentHashMap原理详解 文章目录Java 并发编程之ConcurrentHashMap原理详解原理剖析源码剖析一、构造方法分析二、初始化三、put()实现分析四、扩容原理剖析 HashMap通常的实现方式是“数组链表”&#xff0c;这种方式被称为“拉链法”。 ConcurrentHashMa…

K_A08_001 基于 STM32等单片机驱动L298N模块按键控制直流电机启停正反转加减速

目录 一、资源说明 二、基本参数 1、参数 2、引脚说明 三、驱动说明 L298N模块驱动时序 对应程序: ENA ENB输出PWM 四、部分代码说明 接线说明 1、STC89C52RCL298N模块 2、STM32F103C8T6L298N模块 五、基础知识学习与相关资料下载 六、视频效果展示与程序资料获取 七、项…

宇视雷视工勘指导(卡口电警篇)

雷视工勘指导&#xff08;卡口电警篇&#xff09; 卡口和电子警察具备车辆信息采集、违法驾驶行为检测等功能&#xff0c;是城市道路交通治理的利器。为了提升现场工勘效率并保障工勘的质量&#xff0c;本次为大家介绍一款宇视的前端工勘神器——《宇视智能交通工勘计算表》&a…

SpringMVC-全面详解(学习总结---从入门到深化)

目录 SpringMVC简介 MVC模型 SpringMVC SpringMVC入门案例 SpringMVC执行流程 SpringMVC的组件 组件的工作流程 SpringMVC参数获取_封装为简单数据类型 SpringMVC参数获取_封装为对象类型 封装单个对象 封装关联对象 SpringMVC参数获取_封装为集合类型 封装为Lis…

Qt报错总结

转载 Qt报错 widget.obj的问题 遇到这种情况可能是链接出错造成的&#xff0c;所以需要首选就是需要将生成的bulid文件进行删除&#xff0c;然后运行&#xff0c;基本可以了 补充知识&#xff1a; QtCreator中qmake、构建、运行、清理等区别与联系 qt执行流程&#xff1a;qma…

模式也能开盲盒,”盲返“模式带动电商平台共享经济

今年元月份&#xff0c;国务院也是提出消费返利、消费优惠、利润分享属于电商平台共享经济的促销模式&#xff0c;属于合法合规的新业态经济以及新零售重大变革的突破&#xff0c;全民参与共同富裕。 而最近市场上出了个很火的电商模式——消费盲返&#xff0c;是一个针对每个…

MacBook Pro 耗电严重的终极解决办法2022年

背景&#xff1a; 最近在用mac时发现一个问题&#xff0c;合上盖子之后&#xff0c;明天打开&#xff0c;没有插电源的情况下&#xff0c;就会没电&#xff01;&#xff01;&#xff01;&#xff01;&#xff01;&#xff01;&#xff01;非常影响使用&#xff0c;最后才发现是…

第十一章:Java对象内存布局和对象头

对象内存布局对象头运行时元数据类型指针&#xff08;类元数据&#xff09;实例数据对齐填充对象内存布局之JOL 证明GC分代年龄说明压缩指针参数对象内存布局 兄弟们感兴趣的话&#xff0c;在 JVM 篇有对 对象的详细介绍&#xff1a;对象实例化内存布局 对象头 运行时元数据 …

datax-hdfsReader 学习

今天同事遇到了一个问题。 就是hdfsreader->mysqlwriter这种的时候。 有的分区没有数据会报错。 com.tencent.s2.dataingestion.common.exception.DataXException: Code:[HdfsReader-08], Description:[您尝试读取的文件目录为空.]. - 未能找到待读取的文件,请确认您的配…

持久层框架设计实现及MyBatis源码分析 ---- MyBatis基础回顾及高级应用

一、基本应用 基本开发步骤&#xff1a; ① 添加MyBatis的坐标 ② 创建xxx数据表 ③ 编写Xxx实体类 ④ 编写sql映射⽂件XxxMapper.xml ⑤ 编写核⼼配置⽂件SqlMapConfig.xml ⑥ 编写测试类 二、配置文件介绍 1. sql映射配置文件 XxxMapper.xml (1) 基础使用 (2) 动态SQL w…

卡尔曼滤波:The Scaler Kalman Filter常量卡尔曼滤波器

本文是Quantitative Methods and Analysis: Pairs Trading此书的读书笔记。 估计一个常数的通常做法是&#xff0c;做多次测量&#xff08;measurement)&#xff0c;然后使用测量的平均值作为估计值。从统计学的思想上来说&#xff0c;这种做法可以尽量减小估计的误差。这种方…

SpringBoot+Vue实现前后端分离的宠物医院管理系统

文末获取源码 开发语言&#xff1a;Java 使用框架&#xff1a;spring boot 前端技术&#xff1a;JavaScript、Vue.js 、css3 开发工具&#xff1a;IDEA/MyEclipse/Eclipse、Visual Studio Code 数据库&#xff1a;MySQL 5.7/8.0 数据库管理工具&#xff1a;phpstudy/Navicat JD…

Fiddler抓包工具是最强大最好用的 Web 调试工具之一

Fiddler是最强大最好用的Web调试工具之一&#xff0c;它能记录所有客户端和服务器的http和https请求&#xff0c;允许你监视&#xff0c;设置断点&#xff0c;甚至修改输入输出数据. 使用Fiddler无论对开发还是测试来说&#xff0c;都有很大的帮助。 目录 Fiddler的基本介绍 …

【C++11重点语法上】lambda表达式,初始化列表

目录 引子&#xff1a;C11为什么的源来 语法1&#xff1a;初始化列表 1.2.2 多个对象的列表初始化 语法3&#xff1a;默认成员函数控制&#xff08;delete&#xff0c;default&#xff09; 语法4&#xff1a;lambda表达式 引子&#xff1a;C11为什么的源来 在2003年C标准…

22年11月-外包-面试题

目录背景题目Spring怎么解决循环依赖&#xff1f;什么是循环依赖第一种&#xff1a;互相依赖第二种&#xff1a;三者间依赖第三种&#xff1a;自我依赖三级缓存补充&#xff1a;那第三级缓存的作用是什么&#xff1f;补充&#xff1a;Spring 中哪些情况下&#xff0c;不能解决循…

【Hadoop】在云服务器上部署Hadoop2.7.1伪分布式集群

文章目录一、准备Hadoop压缩包并安装1、安装Hadoop&#xff08;1&#xff09;准备好hadoop压缩包&#xff08;2&#xff09;安装hadoop&#xff08;3&#xff09;查看是否安装成功2、将hadoop添加到环境变量&#xff08;1&#xff09;在文件末尾添加以下内容&#xff08;2&…

怎样图片转文字?两分钟让你实现快速转文字

在日常的办公中&#xff0c;我们经常会遇到需要将纸质文件里的文字提取出来&#xff0c;再转换为电子档的情况&#xff0c;如果我们采用手动输入的话&#xff0c;不仅速度太慢&#xff0c;而且还可能因此耽误到后边的工作&#xff0c;是不是已经有小伙伴遇到这种现象&#xff0…