当前位置: 首页 > news >正文

qt tcp通信_(7)基于TCP/IP的网络通信应用程序(TCPClient)

点击上方蓝字,关注微联智控工作室

可点击右上角的 …,分享这篇文章

不管是嵌入式Linux应用程序,还是物联网IoT应用开发,网络通信一定是一个不可或缺的重要环节。可以说,没有网络支持,整个物联网应用体系将产生不了社会价值,没有网络,很多应用程序都会受到限制。

作为全世界最优秀的开源操作系统,Linux内部已经集成了强大的网络协议栈,并向应用层提供丰富的系统调用,开发者可以基于通用的系统调用接口,使用Linux内核提供的网络功能。

如果要分析Linux内部的网络通信机制以及实现原理,相信不是一时半刻或片文只字能描述清楚,一般的应用开发者可以通过网上搜索资料去了解一下,但在初学阶段,不建议去深入研究。因此,本章节只是站在应用开发的角度,描述如何开发嵌入式QT的网络应用程序。

TCP/IP协议模型(Transmission Control Protocol/Internet Protocol),包含了一系列构成互联网基础的网络协议,是Internet的核心协议。它是一种面向连接的、可靠的、基于字节流的传输协议。关于TCP/IP协议的具体实现原理,本文不进行描述(关于TCP/IP的实现原理描述,其复杂度已经可以用一本书来具体阐述了)。本文着重描述在嵌入式QT环境下,如何使用TCP/IP进行数据通信。

对于TCP/IP的客户端(TCP-Client)角色,在进行数据通信之前,一般会经历以下过程:

(1)调用socket套接字函数,创建套接字的文件描述符。

(2)配置连接参数(需要连接的服务器IP,端口,连接协议,等等)。

(3)基于套接字的文件描述符,调用connect函数,连接指定的服务器。

(4)处理connect过程中可能出现的情况。(如连接成功,连接出错,找不到服务器等)

(5)connect成功后,调用数据发送接口,进行数据发送。

(6)调用数据接收接口,并处理接收到的网络数据。(Linux C语言开发,可以阻塞接收或使用select/poll机制接收。QT开发,可以使用信号槽机制进行接收。)

使用嵌入式QT进行TCP/IP的网络通信应用程序开发,对于TCP客户端,只需要关注QT提供的QTcpSocket类,这个类继承于QAbstractSocket类,在QAbstractSocket类里面提供了一系列的网络操作接口函数,如:连接服务器connectToHost()、断开与服务器的连接disconnectFromHost(),等等。提供各种信号(connected()、disconnected()、stateChanged())与槽函数,方便应用开发者调用。具体可以参阅 QtNetwork/qabstractsocket.h 文件的内容。

目标:

使用QT提供的TCP/IP网络通信类,实现一个简单的TCP客户端(TCP-Client)

功能:

(1)开发板界面显示开发板的网络IP地址。

(2)可手动输入需要连接的服务器IP和端口。

(3)界面显示TCP客户端的连接状态。(连接成功,断开连接,连接出错)

(4)界面显示TCP客户端的收发数据,并提供清屏按钮。

(5)提供手动发送按钮和自动发送按钮。

实验现象初览:

服务器端的界面(在windows7运行的网络通信工具):

772bf12cd018267c3c5f759726577310.png

服务端界面描述:

(1)输入本机的服务端IP地址和端口,并开始侦听。笔者服务端是192.168.1.59:4418

(2)界面显示已经连接上的客户端IP地址,可以看出,跟imx6ul本机的IP一致。

(3)数据发送区1里面是服务端自动回复给客户端的内容,表示服务器端收到客户端的数据后,马上自动回复”reply_from_pc_to_imx6ul”。

(4)最底下显示接收到的客户端数据,从上图可以看出,客户端分别以自动方式和手动方式发送了数据。

客户端的界面(在imx6ul上运行):

349c3ab866f3b7a8f304ef4434effbc9.png

客户端界面描述:

(1)程序启动时,在界面右上角显示imx6ul本机的IP地址。

(2)填写客户端需要连接的服务器IP和端口。

(3)点击 [CONNECT] 按钮后,会在按钮后面显示连接的状态。

(4)点击 [START_AUTO_SEND] 后,客户端会自动以1秒的频率向服务器发送数据。

(5)每点击一次 [tcp_send_data] 按钮,客户端都会向服务器发送一次数据。

(6)在数据显示框显示客户端发送/接收到的数据内容。

(7)点击 [TX_CLEAR] 或 [RX_CLEAR] 进行清屏操作。

以下是应用程序的开发过程

1、先用Qt Creator构建一个工程,命名为:007_tcp_client,关于如何构建工程,请参考“第一个嵌入式QT应用程序”的具体内容。

2、创建工程后,修改007_tcp_client.pro里面的内容,添加QT里面的网络通信模块network,使工程支持QT网络类的调用,如下图所示。

797313f41c863df1f377bd886729de9d.png

3、双击打开“widget.ui”文件,构建界面,构建后的界面如下图所示:

82dec6a546e7e6eda520e1496a8cb8c9.png

客户端界面描述如上面内容所示,这里不作重复。

4、创建一个tcp_client.h文件,编写一个TCP_Client类继承于QTcpSocket,这个类提供了一些客户端经常用的操作函数,如连接服务器,获取本机IP,处理连接状态,等等。类的具体实现如下图所示:

9c9b68d3bd87148c4403df62a9d44246.png

5、创建一个tcp_client.cpp文件,这个文件内包含了TCP_Client类里面的所有函数实现。有关tcp_client.cpp的具体内容,请参阅源码。

6、Qt应用程序启动时,跟C语言一样,都是以main函数作为入口。(当然了,真正的程序启动入口并不是main函数,这里忽略了main函数之前的一系列复杂过程,应用程序呈现给开发者的,一般都是以main函数作为入口)。以下是Qt应用程序的main函数入口。

3e957cc3bb40486ab321ab167acb2574.png

这个main函数比较简单,里面定义了一个QApplication和Widget对象,通过Widget::show()函数显示窗体,然后执行QApplication::exec()函数把整个应用程序加入Qt的事件队列,不断循环。

7、在Qt的界面应用程序中,对于Widget类型的窗体,其ui都是通过QWidget类进行构建,在widget.cpp文件中,针对本工程,Widget类的构造函数如下图所示:

3807da1f2e270784da7d1e0f5c1428bd.png

在Widget类的构造函数里面,先对ui里面的某些控件进行配置,比如:在连接按钮点击之前,手动和自动发送数据的按钮不可用,服务器IP和端口可被编辑。然后创建一个TCP_Client类对象,TCP-Client的一系列操作,如连接服务器,收发数据等等,都是基于这个类对象进行。程序开始运行的时候,在界面上显示本机的IP地址,并连接tcp-client对象提供的信号槽。最后,构建一个定时器对象用于数据的自动发送,定时器对象在构建时,并没有启动计时。

8、先在windows7上运行服务器端的程序,设置好监听的IP和端口。imx6ul板卡上的客户端应用程序启动后,设置好需要连接的服务器参数,点击 [CONNECT] 按钮,程序就会调用该按钮的槽函数void Widget::on_pushButton_connect_clicked(),函数的实现内容如下图所示:

1e78d6a985551979b61cd5402d92ec78.png

9、点击按钮后,分两种情况,需要判断当前的客户端网络状态是连接还是断开。如果客户端处于连接状态,则点击按钮后,断开客户端与服务器的连接。如果客户端处于断开状态,则点击按钮后,先检查输入的参数是否有效,如果参数有效,则启动客户端与服务器的连接。客户端与服务器的连接函数connect_server(),如下图所示:

d8e74fed5f3906c2fe201f06d97f6cf7.png

10、客户端与服务器连接过程中,由于在TCP-Client类里面绑定了连接状态的信号槽,因此,连接状态改变后,系统会发送相应的信号,然后调用对应的槽函数进行处理。因为需要把连接状态显示在界面上,因此,需要在Widget类中,编写一个处理连接状态的槽函数,这个槽函数处理了CONNECTED,DISCONNECTED和CONNECT_ERROR这三种状态。如下图所示:

0a67464dd2b401c51a96c964a27644da.png

11、每点击一次手动发送按钮 [tcp_send_data],客户端将会发送一包数据到服务端。点击自动发送按钮 [START_AUTO_SEND] 按钮,客户端将启动定时器,并以1秒的间隔向服务端发送数据。手动发送函数和自动发送函数,如下图所示:

68fc4552aabc7bf8e77c27499656113c.png

12、客户端的发送数据函数tcp_client_send_data(QString),是直接调用了QIODevice::write()进行发送的。我们用面向对象多态的思维,编写两个发送函数,可以分别以QByteArry或QString类型的参数进行数据发送,如下图所示:

a88bed59f4ef8c8f744718589e4641c7.png

13、客户端的接收数据函数,是通过信号槽机制来实现的,当Widget类对象接收到TCP-Client类发送的信号signal_tcp_client_recv_data(QByteArray),就调用槽函数处理接收到的数据,如下图所示:

52c889d5f04ca15887ae34d7f94540b1.png

14、在TCP-Client类对象中,客户端是通过槽函数slot_tcp_client_recv_data()进行网络数据接收的,这个槽函数绑定了QIODevice::readyRead()信号,一旦底层的IO有网络数据接收,则会调用该槽函数处理,然后这个槽函数会把数据从底层驱动的缓冲区中把数据全部读出,再发送signal_tcp_client_recv_data(QByteArray),通知Widget类进行处理。在这里,可能大家会有一个疑惑,为什么不在这个槽函数直接处理数据呢?那是因为考虑到了软件的封装性。TCP-Client类只负责接收数据,然后再把接收到的数据通过信号槽传递出去,至于数据怎样处理,则应该在其他的数据handle类里面进行。比如,这个客户端只进行数据显示,则可以在Widget类里面处理数据,只需要把Widget类里面的数据处理槽函数与TCP-Client类里面的信号绑定就行了。TCP-Client类里面的槽函数slot_tcp_client_recv_data()如下图所示:

aef5ddbee87b8901d060f33fe01b0e7d.png

15、至此,整个TCP-Client连接服务器以及数据收发过程已经描述完成。这个工程只是简单地描述了TCP-Client建立通信和数据收发的简单过程。在真正的TCP-Client网络应用程序中,还需要处理很多突发的网络情况。如:连接过程中的错误处理,心跳包机制,服务端强行断开连接后客户端的处理,数据粘包与断包,数据接收队列,等等。开发者应在工程开发中不断积累经验,才能开发出稳定可靠的网络应用程序。

题外知识:

很多初学者可能会对服务器和客户端没有什么概念,不知道怎样理解服务端/客户端这两个角色,只要记住关键的一点:客户端是请求服务的,服务器是提供服务的。

举一个简单而通俗的例子:汽车去加油站加油。

把汽车比喻为TCP-Client客户端,把加油站的加油机比喻为TCP-Server服务器。当汽车(TCP-Client)需要向加油机(TCP-Server)请求加油服务时,则需要先知道加油站在哪(服务器的IP地址),在哪台加油机(服务器端口)上加油,当指定好加油站和具体的加油机后,就可以向加油机请求连接(向汽车插上加油枪)了。But,总会有特殊情况的时候,比如,当该加油机没有油而导致加不了油(连接不上)了,那么,加油机(服务器)就会通知请求加油的汽车(客户端)进行处理,(这里就涉及到连接不上的情况了)。当汽车加完油之后,就像是服务端已经提供完服务,那么,汽车(客户端)就可以主动端开与加油机(服务端)的连接了。

-- END --


欢迎关注 --- 微联智控工作室

c0934a5795f6c5dfb37d8d03a953bb80.png

相关文章:

  • jwt token长度限制_OAUTH.令牌存储介绍以及JWT实现强制登出、登录个数控制
  • 空间日志代码_蚂蚁金服研发框架日志隔离解析 | SOFABoot 框架剖析
  • c语言音乐小星星代码_慧编程:编程小课堂NO.1——代码也能唱歌!
  • python即将列入高考吗_Python即将被加入浙江、北京、山东高考科目
  • python实时数据流设计_入门指南:用Python实现实时目标检测(内附代码)
  • docker镜像备份恢复_Docker学习笔记
  • runtimeerror什么原因_什么是内存对齐?Go 是否有必要内存对齐?
  • dubbo源码_Dubbo源码-注册中心
  • python运行不了、显示警告_Python xlrd:禁止显示警告消息
  • linux安装python3环境_Linux环境安装python3
  • 用python打印出一个人的照片_Python用dilb提取照片上人脸的示例
  • getdata提取曲线数据_基于Hypergraph创建曲线(矢量)的结果响应
  • ffmpeg 为取经而来_清华,那个穿越百年而来的白衣少年
  • python 数组 动态赋值_在python中使用numpy创建动态数组
  • java filter 是否能拦截到form表单的所有数据_java 知识点总结(框架篇)
  • mysql外键的使用
  • use Google search engine
  • windows下mongoDB的环境配置
  • 官方新出的 Kotlin 扩展库 KTX,到底帮你干了什么?
  • 记录:CentOS7.2配置LNMP环境记录
  • UI设计初学者应该如何入门?
  • ​​​​​​​sokit v1.3抓手机应用socket数据包: Socket是传输控制层协议,WebSocket是应用层协议。
  • ​LeetCode解法汇总2583. 二叉树中的第 K 大层和
  • ​云纳万物 · 数皆有言|2021 七牛云战略发布会启幕,邀您赴约
  • #我与Java虚拟机的故事#连载09:面试大厂逃不过的JVM
  • (1) caustics\
  • (1)Map集合 (2)异常机制 (3)File类 (4)I/O流
  • (10)工业界推荐系统-小红书推荐场景及内部实践【排序模型的特征】
  • (2/2) 为了理解 UWP 的启动流程,我从零开始创建了一个 UWP 程序
  • (附源码)apringboot计算机专业大学生就业指南 毕业设计061355
  • (过滤器)Filter和(监听器)listener
  • (南京观海微电子)——I3C协议介绍
  • (最优化理论与方法)第二章最优化所需基础知识-第三节:重要凸集举例
  • **CI中自动类加载的用法总结
  • .mkp勒索病毒解密方法|勒索病毒解决|勒索病毒恢复|数据库修复
  • .NET 8 中引入新的 IHostedLifecycleService 接口 实现定时任务
  • .NET C#版本和.NET版本以及VS版本的对应关系
  • .NET gRPC 和RESTful简单对比
  • .NET MVC、 WebAPI、 WebService【ws】、NVVM、WCF、Remoting
  • .Net Web项目创建比较不错的参考文章
  • .NET/C# 在 64 位进程中读取 32 位进程重定向后的注册表
  • .NET中GET与SET的用法
  • /etc/sudoers (root权限管理)
  • /var/lib/dpkg/lock 锁定问题
  • ?php echo $logosrc[0];?,如何在一行中显示logo和标题?
  • [ CTF ] WriteUp- 2022年第三届“网鼎杯”网络安全大赛(朱雀组)
  • [20150707]外部表与rowid.txt
  • [C#基础]说说lock到底锁谁?
  • [C语言]一维数组二维数组的大小
  • [HOW TO]怎么在iPhone程序中实现可多选可搜索按字母排序的联系人选择器
  • [HTML]Web前端开发技术30(HTML5、CSS3、JavaScript )JavaScript基础——喵喵画网页
  • [IE编程] WebBrowser控件中设置页面的缩放
  • [JavaWeb]——获取请求参数的方式(全面!!!)
  • [LeetCode 687]最长同值路径
  • [leetcode]Search a 2D Matrix @ Python