Qt网络通信——TCP和UDP

一、TCP通信

        TCP通信必须先建立 TCP 连接,通信端分为客户端和服务器端。

        Qt 为服务器端提供了 QTcpServer 类用于实现端口监听,QTcpSocket 类则用于服务器和客户端之间建立连接。大致流程如下图所示:

1. 服务器端建立

1.1 监听——listen()

        服务器端程序首先需要用函数 listen() 开始服务器监听,可以设置监听的IP地址和端口,一般一个服务器端程序只监听某个端口的网络连接。函数原型定义如下:

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

        函数返回 true 时,表示监听成功。此时服务器会持续监听来自客户端的连接请求。举例:

if (!tcpServer->listen(QHostAddress::LocalHost, 8080)) {    QMessageBox::information(this, "Error", tcpServer->errorString());return;
}

        那么在什么情况下会监听失败呢?

  1. 端口号太低导致冲突或者没有权限;
  2. 监听除了127.0.0.1和0.0.0.0以外的端口,可能需要管理员权限。
 1.2 接受连接——nextPendingConnection()

        当有新的客户端接入时,QTcpServer的内部有一个受保护函数 incomingConnection(),它会创建一个与客户端连接的QTcpSocket对象,然后发射 newConnection() 信号。

        此时,可以建立自定义槽函数对该信号进行处理,使用 nextPendingConnection() 建立socket连接。

MainWindow::MainWindow(QWidget *parent) :QMainWindow(parent),ui(new Ui::MainWindow)
{ui->setupUi(this);...tcpServer = new QTcpServer(this);connect(tcpServer, SIGNAL(newConnection()), this, SLOT(do_newConnection()));
}void MainWindow::do_newConnection()
{tcpSocket = tcpServer->nextPendingConnection(); //创建socket接收客户端连接   ...
}

2. 客户端建立

        客户端的 QTcpSocket 对象首先通过 connectToHost() 尝试连接到服务器,该函数需要指定服务器的IP地址和端口。值得注意的是,该函数是以异步方式连接到服务器,并不会阻塞整个程序的运行,只有成功连接后 QTcpSocket 对象才会发射 connected() 信号表示已经成功连接。

MainWindow::MainWindow(QWidget *parent) :QMainWindow(parent),ui(new Ui::MainWindow)
{ui->setupUi(this);tcpClient = new QTcpSocket(this);    //创建socket变量connect(tcpClient, SIGNAL(connected()), this, SLOT(do_connected()));
}// 尝试连接并发射connected()信号
void MainWindow::on_actConnect_triggered()
{tcpClient->connectToHost(QHostAddress::LocalHost, 8080);
}// connected()信号的自定义槽函数
void MainWindow::do_connected()
{ QMessageBox::information(this, "Success", "已成功连接到服务器!");...
}

        如果真的需要以阻塞方式连接到服务器,则可以使用函数 waitForConnected(),用法大差不差。

3. 通信

        当 QTcpSocket 对象接收到服务器或客户端数据后会发射 readyRead() 信号。或者可以说,当缓冲区有新数据就会发射此信号。我们可以设计相应的槽函数来接收此信号。举例:

// 客户端发送消息
void MainWindow::on_btnSend_clicked()
{QString msg = ui->editMsg->text();ui->textEdit->appendPlainText("客户端说:" + msg);tcpClient->write(msg.toUtf8() + '\n');
}// 客户端接收消息
void MainWindow::do_socketReadyRead()
{while(tcpClient->canReadLine())ui->textEdit->appendPlainText("收到数据:" + tcpClient->readLine());
}


二、UDP 

        与TCP通信不同,UDP通信不区分客户端和服务器。而且UDP是不可靠、无连接的协议,因此UDP客户端每次发送数据都需要指定目标ip地址和端口。

        QUdpSocket 和 QTcpSocket 有着相同的父类 QAbstractSocket ,因此这两个类的大部分接口函数也会大差不差。要说区别,那就应该是传输数据上,QTcpSocket 使用 write() 函数发送数据流(字节),而 QTcpSocket 使用 writeDatagram() 函数发送数据报。

        UDP发送消息采用单播、广播、组播(多播)3种方式。

1. 单播

1.1 绑定端口——bind()

        因为UDP是无连接的,所以在收发数据前,不需要像TCP那样建立连接。只需要绑定本机的任意一个端口即可,保证对方可以给这个端口发送消息。

udpSocket->bind(1200);  // 绑定
udpSocket->abort();  // 解绑
1.2 发送数据——writeDatagram()

        上面已经讲过,发送消息需要用到 writeDatagram() 这个函数。函数原型如下:

qint64 QUdpSocket::writeDatagram(const QbyteArray &datagram, const QHostAddress &host, quint16 port)
  • datagram:要发出的数据报
  • host:目标主机ip
  • port:目标主机端口
  • 返回值:已经成功发送的字节数,若 <0 则表示发送失败

举个例子: 

void MainWindow::on_btnSend_clicked()
{QHostAddress targetAddr(ui->comboTargetIP->currentText());  //目标IPquint16 targetPort = ui->spinTargetPort->value();     //目标portQString msg = ui->editMsg->text();       //发送的消息内容udpSocket->writeDatagram(msg.toUtf8(), targetAddr, targetPort); //发出数据报ui->textEdit->appendPlainText("[单播消息] 自己:" + msg);
}
1.3 接收数据——readDatagram()

        与 QTcpSocket 类似,在 QUdpSocket 接收到数据报后也发射 readReady() 信号。只要有等待读取的数据报,hasPendingDatagrams() 函数就会返回 true,然后利用 readDatagram() 函数读取到数据报信息。readDatagram() 函数原型如下:

qint64 QUdpSocket::readDatagram(char *data, qint64 maxSize, QHostAddress *address = nullptr, quint16 *port = nullptr)
  • data:数据报的数据缓冲区
  • maxSize:接收获取多少数据报到缓冲区data里

        其中 data 和 maxSize 是必须要有的,而ip地址和端口是可以选择不要的。

举例:

void MainWindow::do_socketReadyRead()
{while(udpSocket->hasPendingDatagrams()){QByteArray datagram;QHostAddress peerAddr;  //格式为:QHostAddress("::ffff:127.0.0.1")quint16 peerPort;// 确保 datagram 能够存储来自 udpSocket 的完整数据报,而不会截断数据或导致内存分配错误。datagram.resize(udpSocket->pendingDatagramSize());udpSocket->readDatagram(datagram.data(),datagram.size(), &peerAddr, &peerPort);QString str = datagram.data();QString peer = "[来自 " + peerAddr.toString() + ":" + QString::number(peerPort) + "] 说:";ui->textEdit->appendPlainText(peer + str);}
}

注:这里的ip地址类型与 TCP 有区别,为 QHostAddress("::ffff:127.0.0.1") 。因为 UDP 是无连接的协议,系统可能会选择将IPv4地址映射为IPv6地址来处理。

2. 广播

        广播与单播类似。只需要注意发送数据时把目标ip改为 QHostAddress::Broadcast 即可。

3. 组播

        QUdpSocket 支持 UDP 组播,joinMulticastGroup() 函数使主机加入多播组,leaveMulticastGroup() 函数使主机离开多播组。UDP 组播的特点就是使用组播地址(D类地址),其他的端口绑定、数据收发等功能的实现与 UDP 单播完全相同。

3.1 设置udp组播的生存周期——MulticastTtlOption
udpSocket = new QUdpSocket(this);
udpSocket->setSocketOption(QAbstractSocket::MulticastTtlOption, 1);
  • 参数1:QAbstractSocket::MulticastTtlOption:udp组播的生存周期,每跨一个路由值-1
  • 参数2:默认值是1,表示只能在同一路由的局域网传播
3.2  绑定端口——bind()

        与单播的绑定端口不同,这里的函数原型如下:

bool QAbstractSocket::bind(QHostAddress::SpecialAddress addr, quint16 port = 0, BindMode mode = DefaultForPlatform)
  • addr:特殊的主机IP地址,如Broadcast,LocalHost,AnyIPv4等。
  • port:绑定的端口
  • mode:绑定模式,如 ShareAddress 允许其他服务使用这个地址和端口,ReuseAddressHint 允许多个套接字绑定到相同的地址和端口。

举例:

quint16 groupPort = 35320;    //组播端口
udpSocket->bind(QHostAddress::AnyIPv4, groupPort, QUdpSocket::ShareAddress);
3.3 加入组播—— joinMulticastGroup()

        加入多播组只需要指定一个组播地址(239.0.0.0~239.255.255.255)即可。修改后的代码如下:

QHostAddress groupAddress = QHostAddress("239.255.43.21");      //D类地址
if (udpSocket->bind(QHostAddress::AnyIPv4, 35320, QUdpSocket::ShareAddress)) {udpSocket->joinMulticastGroup(groupAddress);  //加入多播组ui->textEdit->appendPlainText("**加入组播成功");ui->textEdit->appendPlainText("**组播地址IP:"+IP);ui->textEdit->appendPlainText("**绑定端口:"+QString::number(groupPort));
}
elseui->textEdit->appendPlainText("**绑定端口失败");

        组播类似于QQ群,在加入组播之后, 就可以看到所有人发的消息,包括自己发的消息。

 3.4 退出组播——leaveMulticastGroup()

        在退出指定的组播后,记得还要解除绑定。

udpSocket->leaveMulticastGroup(groupAddress);   //退出组播
udpSocket->abort();     //解除绑定

 

码字不易,看到这里如果给您带来一丢丢的启发,点个赞再走吧!

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

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

相关文章

PPP简介

介绍PPP特性的定义和目的。 定义 PPP&#xff08;Point-to-Point Protocol&#xff09;协议是一种点到点链路层协议&#xff0c;主要用于在全双工的同异步链路上进行点到点的数据传输。 目的 PPP协议是在串行线IP协议SLIP&#xff08;Serial Line Internet Protocol&#x…

代码随想录:动态规划6-10

62、不同路径 题目 一个机器人位于一个 m x n 网格的左上角 &#xff08;起始点在下图中标记为 “Start” &#xff09;。 机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角&#xff08;在下图中标记为 “Finish” &#xff09;。 问总共有多少条不同的路径…

史上最全的软件工厂考试简答题教程

软件工程考试简答题 1. 有人认为软件开发时&#xff0c;一个错误发现得越晚&#xff0c;为改正它所付出的代价越大。提出你的观点并解释原因&#xff1f; &#xff08;1&#xff09;在软件开发的不同阶段进行修改付出的代价是很不相同的&#xff0c;在早期引入变动&#xff0c…

openai whisper使用

whisper使用 介绍 Whisper是一种通用的语音识别模型。它是在大量不同音频数据集上训练的&#xff0c;也是一个多任务模型&#xff0c;可以执行多语言语音识别、语音翻译和语言识别。 GitHub&#xff1a;https://github.com/openai/whisper 论文链接&#xff1a;https://arx…

注册Github账号详细过程

目录 一、准备工作 二、注册步骤 一、准备工作 在注册GitHub账号之前&#xff0c;请确保您已经准备好以下信息&#xff1a; 一个有效的电子邮箱地址&#xff1a;用于接收验证邮件和GitHub的后续通知。 用户名&#xff1a;确保该用户名在GitHub上是唯一的&#xff0c;且符合…

turtle画图知识

Turtle库是Python编程语言中的一个库&#xff0c;用于创建各种类型的图形&#xff0c;包括简单圆形、线条、路径和图片。它支持多种图形类型&#xff0c;并且可以绘制出各种复杂的形状。 以下是一些基本的使用方法&#xff1a; 1. 创建一个新的Turtle对象&#xff1a; pytho…

Leetcode JAVA刷刷站(53)最大子数组和

一、题目概述 二、思路方向 这个问题是一个经典的算法问题&#xff0c;称为“最大子序和”&#xff08;Maximum Subarray Problem&#xff09;。解决这个问题的一个高效方法是使用“Kadanes Algorithm”&#xff0c;它只需要遍历数组一次&#xff0c;就能在 O(n) 时间复杂度内…

CVPR2023《DNF: Decouple and Feedback Network for Seeing in the Dark》暗光图像增强论文阅读笔记

相关链接 论文链接 https://openaccess.thecvf.com/content/CVPR2023/papers/Jin_DNF_Decouple_and_Feedback_Network_for_Seeing_in_the_Dark_CVPR_2023_paper.pdf 代码链接 https://github.com/Srameo/DNF 摘要 RAW数据的独特属性在低光照图像增强方面展现出巨大潜力。…

C语言典型例题47

《C程序设计教程&#xff08;第四版&#xff09;——谭浩强》 习题3.7 输入4个整数&#xff0c;要求按照从小到大的顺序输出 4个数之间进行比较&#xff0c;冒泡排序最最最详细过程&#xff0c;如果想更改为任意数之间相互比较&#xff0c;只需要修改两个地方&#xff08;数组大…

力扣面试经典算法150题:买卖股票的最佳时机 II

买卖股票的最佳时机 II 今天的题目是力扣面试经典150题中的数组的中等难度题&#xff1a;买卖股票的最佳时机 II。 题目链接&#xff1a;https://leetcode.cn/problems/best-time-to-buy-and-sell-stock-ii/description/?envTypestudy-plan-v2&envIdtop-interview-150 问…

教程:postman的平替hoppscotch,又叫postwoman,hoppscotch的docker-compose安装过程

目录 1. 背景2. 前期准备2.1 准备docker-compose文件&#xff0c;两个版本&#xff0c;一个3合1&#xff0c;一个分开2.1.1 3合1版本&#xff08;推荐&#xff09;2.1.2 独立版本 2.2 准备安装nginx-proxy-manager&#xff08;可选&#xff09;2.2 准备.env文件2.2.1 默认ip的.…

Spring Boot OAuth2.0应用

本文展示Spring Boot中&#xff0c;新版本OAuth2.0的简单实现&#xff0c;版本信息&#xff1a; spring-boot 2.7.10 spring-security-oauth2-authorization-server 0.4.0 spring-security-oauth2-client 5.7.7 spring-boot-starter-oauth2-resource-server 2.7.10展示三个服务…

Android高版本抓包总结

方案1 CharlesVirtualXposedJustTrustMe 推荐使用三星手机此方案 VirtualXposed下载链接&#xff1a;https://github.com/android-hacker/VirtualXposed/releases JustTrustMe下载链接&#xff1a;https://github.com/Fuzion24/JustTrustMe/releases/ 下载完成后使用adb命令…

从易车“超级818冠军之夜” 看如何借势体育营销点燃汽车消费热潮

编辑 | 魏力 发布 | 大力财经 导语&#xff1a;这个8月&#xff0c;是属于奥运的8月。 巴黎奥运会虽圆满落幕&#xff0c;但属于奥运健儿们的热度还在持续。在这股奥运热潮的带动下&#xff0c;全民运动热情持续释放&#xff0c;同时也激发出巨大的消费潜力。 赛场外&#…

黄热病疫苗市场调研:预计到 2030 年全球市场规模将达到 1.8 亿美元

一、黄热病疫苗市场研究 &#xff08;一&#xff09;发展趋势 1. 市场规模增长&#xff1a;据调研团队报告所示&#xff0c;预计到 2030 年全球黄热病疫苗市场规模将达到 1.8 亿美元&#xff0c;年复合增长率为 3.0%。这表明市场在未来几年将保持稳定增长态势。增长的原因主要…

LSI-9361阵列卡笔记

背景 要将raid0更改为JBOD直通模式 注意的点是要先将raid模式调整为JBOD之后重启机器&#xff0c;即可 备注&#xff1a;转换过程中硬盘中的数据未丢失。 步骤贴图 refer https://zhiliao.h3c.com/questions/dispcont/123250 https://blog.csdn.net/GreapFruit_J/article/…

Android Activity启动流程(Android 13)

文章目录 Android Activity启动流程(Android 13)概述流程图前提流程分析Activity#startActivity()Activity#startActivityForResult()Instrumentation#execStartActivity()ActivityTaskManager#getService() ActivityTaskManagerService#startActivity()ActivityTaskManagerSer…

解决window 端口的占用问题

netstat -nao | findstr "5554" taskkill -pid 5076 -f 本文资料来自 https://cloud.tencent.com/developer/article/1703982

基于机器学习的二手房房价数据分析与价格预测模型

有需要本项目的可以私信博主&#xff0c;提供远程部署讲解 本研究聚焦重庆二手房市场&#xff0c;通过创新的数据采集和分析方法&#xff0c;深入探讨影响房价的关键因素&#xff0c;并开发了预测模型。 我们首先利用Python编写的爬虫程序&#xff0c;巧妙规避了链家网站的反…

Mac文件需要分卷压缩怎么办 Mac上怎么解压分卷压缩的文件

在处理大型文件的传输和存储的时候&#xff0c;Mac用户常面临文件大小超过限制的问题。为了有效管理这些大文件&#xff0c;分卷压缩成为一种必不可少的解决方案。Mac文件需要分卷压缩怎么办&#xff1f;Mac上怎么解压分卷压缩的文件&#xff1f;本文将向你介绍如何使用BetterZ…