【Qt】系统相关(七)网络编程讲解,UDP回显服务器的实现,QUdpSocket和QNetworkDatagram的讲解
小编个人主页详情—请点击小编个人gitee代码仓库—请点击Qt系列专栏—请点击倘若命中无此运孤身亦可登昆仑送给屏幕面前的读者朋友们和小编自己!目录前言一、网络编程讲解二、UDP对应QUdpSocket和QNetworkDatagram的接口介绍三、UDP回显服务器的实现总结前言【Qt】系统相关六QMutex锁QMutexLocker的使用QWaitCondition条件变量QSemaphore信号量的讲解——书接上文 详情请点击——本文会在上文的基础上进行讲解所以对上文不了解的读者友友请点击前方的蓝字链接进行学习本文由小编为大家介绍——【Qt】系统相关七网络编程讲解UDP回显服务器的实现QUdpSocket和QNetworkDatagram的讲解一、网络编程讲解接下来我们就要学习Qt网络编程了那么这里小编在之前讲解过Linux网络编程而Qt网络编程其实也就是对Linux网络编程的封装也就是说Qt网络编程其实是和我们之前学习的Linux网络编程强相关的所以各有读者友友在学习Qt网络编程之前一定一定要把Linux网络编程学习扎实再来学习 关于Linux网络编程的讲解详情请点击——那么后面小编在进行讲解Qt网络编程的时候默认大家已经掌握了Linux网络编程的相关知识并且由于Linux网络编程内容很多这里小编不便于贴出链接而是直接拿Linux网络编程的知识进行讲解Qt网络编程了对于网络编程其实是操作系统提供的一组网络编程API也就是Socket API对于Linux中也就是Socket编程Qt网络编程也就是对操作系统提供的网络编程API进行的封装和之前不同C标准库中并没有提供对于系统的网络编程API的封装也就是说C标准库并没有提供网络库而在日常编码的过程中如果使用系统的网络编程在接口调用上进行传参十分的不便并且在日常开发中对于网络编程的使用场景有很多而你C标准库却又不提供网络库所以这就很让人恼火更恼火的是在C新标准中放着网络库不进行提供反而去提供一些无关紧要奇奇怪怪的东西关于这背后的原因我们也不得而知当然小编只是站在现在C目前最新的C23标准来看至于是否C26C29会出网络库我们同样也不得而知那么我们在进行网络编程的时候本质是在编写应用层代码需要传输层提供支持传输层中最为核心的协议就是UDP和TCP并且这两个协议的差别还比较大所以Qt就提供了两套API而要想使用Qt网络编程的API就需要现在.pro文件中添加network模块同样的之前我们学习过的Qt各种控件各种内容例如信号槽窗口事件等都是包含在QtCore模块中的而我们没有手动包含QtCore模块就可以使用Qt各种控件各种内容的原因是Qt已经帮我们自动在.pro文件中包含了QtCore模块那么为什么Qt要划分出这些模块呢因为Qt本身是一个非常庞大包罗万象的框架如果把所有的Qt的功能都放在一起即使我们只写一个简单的hello world此时生成的可执行程序也会很庞大而在这个可执行程序中就包含了大量没有使用的功能所以此时就会有问题了例如在嵌入式系统上对于嵌入式系统来讲本身内存硬盘CPU资源就紧张此时再来一个很庞大的可执行程序那么硬盘中都可能放不下这个很庞大的可执行程序所以Qt引入了模块化处理可以使得Qt生成的可执行程序较为轻量化所谓的模块化处理也就是将不同的功能封装成不同的模块默认.pro文件中只包含和Qt各种控件各种内容相关的QtCore模块默认情况下对于其它额外的模块不会参与编译此时只包含QtCore模块的代码生成的Qt的可执行程序就比较轻量化后面如果需要使用其它模块的功能了那么就在.pro文件中包含引入对应的模块把对应模块的功能给编辑加载进来即可此时代码中就包含几个模块而Qt是一个非常庞大包罗万象的框架一共被划分了大约60个模块所以仅仅包含几个模块生成的代码被编译形成的可执行程序还是相较于比较轻量化的同样的Qt还提供了静态库的版本和动态库的版本1对于静态库的版本那么引入的模块都会被编入到最终的可执行程序中2对于动态库的版本其实是将每一个模块都编成了一个动态库此时如果我们如果要使用动态库的版本就需要将动态库加载到共享区在我们引入好我们需要使用的对应的模块之后引入的模块并不会被编入到对应的可执行程序中而是根据动态库的特性在程序运行时如果需要使用到动态库的代码那么拿着虚拟地址加上共享库的起始地址就可以找到共享库对应的方法或者变量进行使用3关于动态库和静态库的讲解如下……1【linux】linux基础IO七静态库的制作与使用……2【linux】linux基础IO八动态库的制作与使用……3【linux】linux基础IO九动态库是如何被加载的二、UDP对应QUdpSocket和QNetworkDatagram的接口介绍在Qt中给UDP协议的使用封装了一套APIQt对于UDP也封装成了类分别是QUdpSocket和QNetworkDatagram1QUdpSocket表示一个UDP的socket文件这样说可能有点抽象那么小编通俗一点QUdpSocket和UDP的创建绑定发送读取有关2QNetworkDatagram表示一个UDP的数据报别忘了UDP是面向数据报的所以Qt中使用QNetworkDatagram类表示UDP的数据报下面我们来认识一下QUdpSocket如下是相关的接口1bind(const QHostAddress, quint16)用于给服务器绑定IP地址端口号2receiveDatagram()用于读取一个UDP数据报自然的返回值就是QNetworkDatagram3writeDatagram(const QNetworkDatagram)用于发送一个UDP数据报4readyRead这个readyRead是一个信号……1当socket收到请求的时候QUdpSocket就会触发这个信号此时就可以在这个readyRead信号绑定的对应的槽函数中完成读取请求的操作了……2那么这里对比于在Linux中默认采用的阻塞式读取数据来讲一旦Linux中调用了readrecvfrom这样的读取接口一旦对端客户端没有给我发送请求那么此时服务器就只能阻塞等待一旦阻塞等待就带来了消耗与其阻塞等待倒不如去做一下其它的事情而在Qt中没有采取这种阻塞式读取的方式而是巧妙的借助信号槽就天然的达成了事件驱动这样的一种网络编程的方式下面我们来认识一下QNetworkDatagram如下是相关接口1QNetworkDatagram(const QByteArray, const QHostArray, quint16)构造函数通过传参QByteArray目标IP地址目标端口号port来构造一个UDP数据报并且UDP数据报的构建通常用于发送数据的时候2data用于获取UDP数据报内部的有效载荷也就是用户实际要发送的数据以QByteArray的形式返回3senderAddress用于获取数据报中对端的IP地址4senderPort用于获取数据报中对端的端口号port三、UDP回显服务器的实现下面我们就来编写一个带有界面的UDP回显服务器其实这里我们要清楚一个正经的服务器很少会有图形化界面因为图形化界面会给服务器的性能带来消耗造成服务器服务的客户端的数量减少而服务器的作用就是给客户端进行服务器所以服务器一般都是命令行的这里我们为了便于演示现象所以给服务器带有图形化界面同样的我们也要认识到虽然Qt用于编写图形化界面程序但是Qt同样也可以编写控制台程序的也就是可以编写命令行的服务器所以接下来我们创建一个项目名为UdpServer基类为QWidget派生类为Widget的项目接下来我们点击ui文件进入Qt Designer由于我们要实现的是UDP回显服务器也就是将来自客户端的信息回显到界面上而消息是有多条的那么我们可以将每一条消息作为一个item放到QListWidget列表中作为一个元素所以多条消息就在QListWidget列表上添加多个元素item以列表形式显示消息正好 关于QListWidget的讲解详情请点击——所以此时我们拖拽左侧红框内的QListWidget列表控件然后调整成上图界面即可objectName保持不变由于我们要创建UDP服务器自然的就要使用QUdpSocket创建服务器那么我们先包含一下QUdpSocket对应的头文件#include QUdpSocket可是此时小编一进行包含就提示找不到#include QUdpSocket头文件这是为什么呢之前包含控件什么的头文件都没有问题其实这是由于我们之前讲解的Qt模块化的设计包含控件的头文件没有问题是因为Qt默认在.pro文件中已经引入了和控件相关的模块QtCort了而这里由于我们要创建UDP服务器也就意味着要使用Qt网络模块的功能所以此时就要给.pro文件中引入网络模块相关的network那么我们在.pro文件的第一行在core gui后面使用空格间隔然后添加network即可可是小编此时尽管我们在.pro文件中引入了网络模块相关的network可是为什么#include QUdpSocket头文件还是提示找不到呢其实这是由于.pro文件还没有被解析也就意味着此时网络模块的相关功能还没有真正引入所以我们该如何让Qt解析.pro文件引入网络模块的相关功能呢此时我们左下角直接运行项目此时Qt就会自动解析.pro文件引入网络模块的相关功能如上此时#include QUdpSocket头文件就找到了也就意味这此时我们可以正常使用Qt网络相关的功能了#ifndefWIDGET_H#defineWIDGET_H#includeQWidget#includeQUdpSocketQT_BEGIN_NAMESPACEnamespaceUi{classWidget;}QT_END_NAMESPACEclassWidget:publicQWidget{Q_OBJECTpublic:Widget(QWidget*parentnullptr);~Widget();private:Ui::Widget*ui;QUdpSocket*socket;};#endif// WIDGET_H那么在Widget的.h头文件中我们声明一个UDP的服务器所以这里我们声明QUdpSocket*指针类型的私有成员变量socket同样这也是进行网络编程的常用写法#includewidget.h#includeui_widget.h#includeQMessageBoxWidget::Widget(QWidget*parent):QWidget(parent),ui(newUi::Widget){ui-setupUi(this);socketnewQUdpSocket(this);this-setWindowTitle(服务器);connect(socket,QUdpSocket::readyRead,this,Widget::processRequest);boolretsocket-bind(QHostAddress::Any,9090);if(retfalse){QMessageBox::critical(this,服务器启动出错,socket-errorString());return;}}Widget::~Widget(){deleteui;}voidWidget::processRequest(){}在Widget的.cpp源文件中在Widget的构造函数中我们就可以给socket创建实例也就是new一个QUdpSocket的对象给socket此时我们来看对于QUdpSocket的构造函数中出现了我们熟悉的parent也就意味着我们可以传入this指针指定UDP对应socket的父元素为this指针对应的Widget窗口将socket对象挂接到对象树上那么在Widget窗口被关闭销毁的时候就会自动调用delete去释放socket对象树机制也可以完美的在Qt网络编程中进行使用所以这里对于QUdpSocket的构造函数我们传入this指针那么是否我们可以什么都不传呢可以也就意味着此时我们不使用Qt的对象树机制此时对于socket的生命周期就需要我们手动进行管理那么就要在Widget的析构函数中手动delete这个socket进行析构其实这里比较推荐的方法还是使用对象树机制对于QUdpSocket的构造函数我们传入this指针让Qt的对象树帮我们管理socket的生命周期接下来为了区分服务器与客户端那么我们使用setWindowTitle给服务器设置标题为服务器即可#ifndefWIDGET_H#defineWIDGET_H#includeQWidget#includeQUdpSocketQT_BEGIN_NAMESPACEnamespaceUi{classWidget;}QT_END_NAMESPACEclassWidget:publicQWidget{Q_OBJECTpublic:Widget(QWidget*parentnullptr);~Widget();private:voidprocessRequest();private:Ui::Widget*ui;QUdpSocket*socket;};#endif// WIDGET_H接下来我们就要先连接信号槽了当socket收到请求报文之后会触发readyRead信号那么我们就可以使用connect给这个readyRead信号绑定对应的槽函数processRequest所以我们在Widget的.h文件中声明一下槽函数processRequest然后在Widget的.cpp源文件中定义一下对应的槽函数processRequest即可对于这个槽函数processRequest也就是用于读取报文数据进行业务逻辑的处理再将相应报文发送给客户端那么此时问题来了有没有可能此时没有请求报文进而造成这里直接进行读取请求报文阻塞呢不会一定不会此时通过信号槽机制进行读取的请求报文一定不会被阻塞因为Qt的机制可以保证当socket收到请求报文之后会触发readyRead信号所以此时在对应的槽函数processRequest中直接进行读取报文数据就可以将报文数据读取上来这也就完美的体现了Qt中基于事件驱动这样的一种网络编程方式所以此时我们使用connect将socket的readyRead信号绑定到对应Widget中的槽函数processRequest上即可此时如果触发readyRead信号就会执行绑定的槽函数processRequest接下来我们使用bind绑定IP地址和端口号port即可那么对于绑定的IP地址这里我们绑定QHostAddress提供的QHostAddress::And其实这个QHostAddress::And本质上就是0.0.0.0我们当前是一个服务器给服务器绑定了0.0.0.0这个IP地址由于一个主机上可能有多个网卡一个网卡对应一个IP地址所以一个主机上可能有多个IP地址而每一个IP地址都有可能接收到来自客户端的请求报文如果服务器仅仅绑定了某一个具体的IP那么此时服务器仅仅只能收到这一个IP上来自客户端的请求报文而主机上有多个IP地址呀那么其它的IP地址上来自客户端的请求报文就接收不到了所以我服务器想要接收到该如何做呢给服务器绑定了0.0.0.0这个IP地址即可此时操作系统可以保证对于这台主机上的所有IP地址收到的来自客户端的请求报文都可以被服务器接收到那么服务器发送报文的时候会使用主机上的哪一个IP地址呢我们不需要关心如果当前服务器的主机上只有一个IP地址那么系统就会采用这个IP地址作为源IP发送报文如果当前服务器主机上有多个IP地址那么由系统自动选择其中的一个IP地址作为源IP发送报文也就意味着我们不需要关心服务器发送报文的时候会采用主机上的哪个IP地址系统会帮我们自动选择其中的一个IP地址作为源IP发送报文所以此时那么对于bind绑定的IP地址这里我们绑定QHostAddress提供的QHostAddress::And其实这个QHostAddress::And本质上就是0.0.0.0然后对于bind绑定的端口号port我们随便填写一个9090即可注意这里随便填写的端口号port的范围要在[1024, 65535]之间因为端口号是16位比特位也就是说最大表示的范围是65535端口号可以从0开始而系统占用的端口号port是[0, 1023]所以我们填写的端口号port的范围要在[1024, 65535]之间那么观察仔细的读者友友可能会观察小编是先connect连接了信号槽再进行了bind绑定了IP地址和端口号port这是为什么呢能不能先bind绑定IP地址和端口号port然后再connect连接信号槽呢不能其实一旦bind绑定了IP地址和端口号也就意味着客户端的请求报文可以被服务器接收到了而connect连接信号槽是为了当服务器收到客户端的请求的时候可以触发readyRead信号进而去执行关联的槽函数处理需求的所以一旦先bind绑定IP地址和端口号port然后再connect连接信号槽那么在先bind绑定IP地址和端口号port之后然后在connect连接信号槽之前这个窗口期有客户端把请求报文发送过来了此时就有可能由于信号槽没有完成连接所以对于服务器对于收到客户端的请求的时候可以触发readyRead信号不会进行处理进而此时来自客户端的请求报文就无法进行读取了自然也就无法进行业务逻辑的处理也就无法给客户端响应了所以先bind绑定IP地址和端口号port然后再connect连接信号槽本身就是不合理的对于客户端来讲你服务器bind绑定IP地址和端口号port也就意味着你服务器要给我客户端提供服务了可是我客户端把请求报文发送给你了你服务器却无法处理我的请求无法给我响应所以这本身就是不科学的而先connect连接信号槽然后再bind绑定IP地址和端口号port是合理的此时先connect连接信号槽也就意味着此时服务器先准备好当服务器收到客户端的请求的时候可以触发readyRead信号进而去执行关联的槽函数处理需求的然后给客户端发送响应报文此时由于服务器没有bind绑定也就意味着此时服务器还没有提供服务所以自然的客户端界面上发送请求报文之后会显示服务器还没有提供服务接下来我们bind绑定IP地址和端口号port代表着服务器此时可以提供服务了那么客户端发送请求报文就可以被服务器收到此时服务器是socket触发readyRead信号进而去执行关联的槽函数将客户端的请求报文读取上来进行业务逻辑的处理构建响应报文并发送给客户端没有问题#includewidget.h#includeui_widget.h#includeQMessageBoxWidget::Widget(QWidget*parent):QWidget(parent),ui(newUi::Widget){ui-setupUi(this);socketnewQUdpSocket(this);this-setWindowTitle(服务器);connect(socket,QUdpSocket::readyRead,this,Widget::processRequest);boolretsocket-bind(QHostAddress::Any,9090);if(retfalse){QMessageBox::critical(this,服务器启动出错,socket-errorString());return;}}Widget::~Widget(){deleteui;}voidWidget::processRequest(){}而对于bind给服务器绑定IP地址和端口号port来讲我们知道一个端口号port只能被一个socket绑定所以此时万一我们随机填写的9090被主机上的其它服务绑定了呢所以此时服务器绑定端口号port就会失败此时也就意味着服务器启动出错了服务器也就无法给客户端提供服务了所以我们可以看出对于服务器绑定端口号port失败是一个很严重的问题所以我们期望对bind绑定是否失败的结果进行判定而恰好bind的返回值就是bool类型的变量所以此时我们使用bool类型的变量ret接收bind的返回值如果bind的返回值为false代表bind绑定失败那么就代表此时端口号port被占用或者其它的因素那么此时我们就使用QMessageBox::critical弹出一个严重问题的消息对话框接下来传参父元素为Widget窗口对应的this指针挂接到对象树上然后继续传参严重问题的消息对话框的标题为服务器启动出错接下来传参严重问题的消息对话框中显示的文本此时我们知道bind绑定失败的原因吗好像不太清楚对于此时bind绑定失败的原因有可能是端口号port被占用或者IP地址错误或者权限不足或者端口号非法等原因所以对于bind绑定失败的原因我们确实不太清楚那么谁清楚呢系统清楚系统调用bind失败时候会设置错误码errno而在C语言中使用perror就可以将errno对应的错误信息以字符串的形式进行打印恰好Qt中对于errno机制同样封装成了errorString这里的Qt中的errnoString的作用就和C语言中的perror类似所以我们这里就调用errnoString将errno对应的错误信息转换成字符串的形式进行传参那么在弹出严重问题的消息对话框之后由于此时服务器绑定端口失败也就意味着此时服务器启动失败了即此时服务器无法为客户端提供服务了所以后面的逻辑我们也不用执行了直接return即可如果bind的返回值为true走到下面代表此时服务器bind绑定IP地址和端口号port成功所以我们就继续执行后面的逻辑即可当然在我们的代码中Widget的构造函数中好像没有后续的逻辑了实则不然后续就要到main.cpp中的main函数中了show显示服务器的界面然后exec执行后续的逻辑服务器基于信号槽进行事件驱动式的收到来自客户端的请求报文触发readyRead信号执行对应的槽函数进而处理来自客户端的请求然后进行业务逻辑构建响应报文并且发送给客户端#includewidget.h#includeui_widget.h#includeQMessageBox#includeQNetworkDatagramWidget::Widget(QWidget*parent):QWidget(parent),ui(newUi::Widget){ui-setupUi(this);socketnewQUdpSocket(this);this-setWindowTitle(服务器);connect(socket,QUdpSocket::readyRead,this,Widget::processRequest);boolretsocket-bind(QHostAddress::Any,9090);if(retfalse){QMessageBox::critical(this,服务器启动出错,socket-errorString());return;}}Widget::~Widget(){deleteui;}voidWidget::processRequest(){// 读取来自客户端的请求报文并且解析请求报文constQNetworkDatagramrequestDatagramsocket-receiveDatagram();QString requestrequestDatagram.data();// 进行业务逻辑处理并且构建响应报文QString responsethis-process(request);QNetworkDatagramresponseDatagram(response.toUtf8(),requestDatagram.senderAddress(),requestDatagram.senderPort());// 将响应报文发送给客户端socket-writeDatagram(responseDatagram);// 把交互的信息回显到界面上QString log[requestDatagram.senderAddress().toString():QString::number(requestDatagram.senderPort())] req: request, resp: response;ui-listWidget-addItem(log);}QStringWidget::process(constQStringrequest){returnrequest;}接下来我们就该进行socket的readyRead信号对应的槽函数processRequest的编写了了而这个槽函数是要处理来自客户端的请求的对于服务器处理客户端的请求一般都是按照如下三个步骤来进行处理下面小编逐一进行讲解1读取来自客户端的请求报文并且解析请求报文2进行业务逻辑处理并且构建响应报文3将响应报文发送给客户端对于第一步就是读取来自客户端的请求报文并且解析请求报文所以此时我们先使用receiveDatagram读取客户端的请求报文而receiveDatagram的返回值就是客户端的请求报文所以我们使用QNetworkDatagram类型的变量requestDatagram接收receiveDatagram的返回值即接收客户端的请求报文接下来我们就要解析请求报文了如何解析呢其实就是获取请求报文中的有效载荷的内容那么我们可以调用data进行获取如上我们可以看出data的返回值是一个QByteArray类型的变量这里其实我们可以使用QString类型的变量request进行接收因为在前文中小编讲解过QString类型的构造函数可以支持使用QByteArray类型的对象进行构造所以这里此时我们使用QString类型的变量接收data为QByteArray类型的变量即可所以此时我们就拿到了用户发来的请求报文中的有效载荷这个有效载荷也就是要交给应用层来进行处理的也就是要进行如下的第二步#ifndefWIDGET_H#defineWIDGET_H#includeQWidget#includeQUdpSocketQT_BEGIN_NAMESPACEnamespaceUi{classWidget;}QT_END_NAMESPACEclassWidget:publicQWidget{Q_OBJECTpublic:Widget(QWidget*parentnullptr);~Widget();private:voidprocessRequest();QStringprocess(constQStringrequest);private:Ui::Widget*ui;QUdpSocket*socket;};#endif// WIDGET_H对于第二步也就是要进行业务逻辑处理并且构建响应报文对于第二步也就是整个服务器的核心的业务逻辑一般我们都封装成一个函数所以这里在Widget的.h头文件中我们声明一个私有成员函数process参数为const QString的request返回值为QString类型对于这里的返回值就是要返回响应#includewidget.h#includeui_widget.h#includeQMessageBox#includeQNetworkDatagramWidget::Widget(QWidget*parent):QWidget(parent),ui(newUi::Widget){ui-setupUi(this);socketnewQUdpSocket(this);this-setWindowTitle(服务器);connect(socket,QUdpSocket::readyRead,this,Widget::processRequest);boolretsocket-bind(QHostAddress::Any,9090);if(retfalse){QMessageBox::critical(this,服务器启动出错,socket-errorString());return;}}Widget::~Widget(){deleteui;}voidWidget::processRequest(){// 读取来自客户端的请求报文并且解析请求报文constQNetworkDatagramrequestDatagramsocket-receiveDatagram();QString requestrequestDatagram.data();// 进行业务逻辑处理并且构建响应报文QString responsethis-process(request);QNetworkDatagramresponseDatagram(response.toUtf8(),requestDatagram.senderAddress(),requestDatagram.senderPort());// 将响应报文发送给客户端socket-writeDatagram(responseDatagram);// 把交互的信息回显到界面上QString log[requestDatagram.senderAddress().toString():QString::number(requestDatagram.senderPort())] req: request, resp: response;ui-listWidget-addItem(log);}QStringWidget::process(constQStringrequest){returnrequest;}那么此时我们就可以在Widget的.cpp源文件中定义这个业务处理函数process在process中由于当前我们实现的是UDP回显服务器所以请求和响应一样这里我们直接将请求作为响应直接返回即可虽然我们这里的业务处理函数process编写的十分简单但是我们要认识到对于一个成熟的商业服务器来讲这里的请求到响应的业务逻辑可能是非常复杂的所以此时使用QString类型的response接收业务处理函数process返回的响应即可接下来我们就可以开始构建响应报文了此时使用QNetworkDatagram数据报类型创建responseDatagram作为响应报文那么我们对于第一个参数需要传入有效载荷的内容这里我们来看如上第一个参数是QByteArray类型可是我们实际给客户端发送的响应内容是QString类型的此时我们可以使用toUtf8将QString类型取出内部的字节数组转换为QByteArray类型进行传参接下来对于第二个参数我们需要传入客户端的IP地址我们手头有吗有的就在当初接收的客户端的请求报文中包含使用senderAddress即可取出请求报文中客户端的IP地址传参给第二个参数同理使用senderPort即可取出请求报文中客户端的端口号port传参给第三个参数那么对于第三步将响应报文发送给客户端此时我们调用writeDatagram将我们构建好的响应报文传参发送给客户端即可到了第三步其实对于一般的服务器流程就已经结束了但是我们的流程还没有结束因为我们实现的服务器是UDP回显服务器还要把交互的信息回显到界面上所以此时我们就构建QString类型的log进行字符串拼接将客户端的IP地址端口号请求对应的字符串内容响应对应的字符串内容都进行拼接构建log即可这里我们需要关注我们使用senderAddress取出的IP地址是QHostAddress类型的而这里进行字符串拼接需要的类型是QString类型的所以我们需要将QHostAddress类型转换为QString类型的如何转化呢我们可以使用QHostAddress类提供的方法toString将QHostAddress类型转换为QString类型的进行字符串的拼接同样的使用senderPort取出的端口号Port是int类型的而这里进行字符串拼接需要的类型是QString类型的所以我们需要将int类型转换为QString类型的如何转换小编在前文讲解过QString中提供了静态函数QString::number可以将int类型转换为QString类型进行字符串的拼接此时要添加到QListWidget列表上的元素log已经准备好了所以我们调用addItem将和客户端交互的元素log添加到界面上的列表中进行回显即可此时我们的UDP回显服务器就实现完成了此时我们还没有办法进行测试需要等下一篇小编实现UDP客户端之后再来进行统一的测试总结以上就是今天的博客内容啦希望对读者朋友们有帮助水滴石穿坚持就是胜利读者朋友们可以点个关注点赞收藏加关注找到小编不迷路