新闻| 文章| 资讯| 行情| 企业| wap手机版| article文章| 首页|会员中心|保存桌面|手机浏览
普通会员

邹平市新波科技有限公司

企业列表
新闻列表
  • 暂无新闻
推荐企业新闻
联系方式
  • 联系人:李先生
首页 > 新闻中心 > 03 - Qt 多线程网络通信——套接字
新闻中心
03 - Qt 多线程网络通信——套接字
发布时间:2024-11-16        浏览次数:1        返回列表
  1. 日志1.0 :在QT中如何使用TCP协议进行套接字通信(即指网络通信
  2. TCP 和 UDP是传输层协议, 二者的区别
    • TCP是面向连接的流式传输协议;TCP传输, 数据安全
    • UDP是面向无连接的报式传输协议;UDP的传输, 数据不安全;【比如, 抖音刷视频, 还有直播等等】
  3. 面向连接:TCP在连接的时候, 需要进行3次握手;断开的时候, 需要进行4次挥手;即TCP是一种双向连接, 双向断开的机制
  4. UDP:在通信之前是不需要准备的, 直接通信不需要连接, 所以在UDP中是没有3次握手与4次挥手的
  5. TCP在进行数据传输的时候, 有数据校验机制, 当数据包丢失之后, 会自动进行重传;而UDP中是没有数据校验机制的, 数据丢失就无法找回
  6. 因为TCP流式传输协议, 所以, 发送端和接收端处理的数据量可以不均等, 例如, 在发送端发送10M的数据, 而在接收端可以分10次, 每次抽1M
  7. UDP是报文形式的, 在UDP传输数据的时候, 是以报文的形式发送的, 那么一个报文有多长, 在接收端就收多长, 例如:发送50M的数据包过去, 但是在接收端收不到50M, 就会把整个包都丢了, 不存在先收一部分数据的情况

1.1. TCP的通信流程

03 - Qt 多线程网络通信——套接字

03 - Qt 多线程网络通信——套接字

1.2. UDP的通信流程

03 - Qt 多线程网络通信——套接字

无论是TCP还是UDP通信, 其通信流程都是一样的, 只是使用的语言可能不一样, 而后面要用的QT, 也只是在QT中对通信流程进行封装, 让操作更加简单, 其实底层的通信原理和通信的过程完全相同

如果要进行网络通信, 它和语言是没有任何关系的, 要进行网络通信, 只需要关注到底是基于TCP还是UDP(两个传输层的协议)通信; 在传输层协议定下来后, 往上还有应用层(FTP协议,HTTP协议

1.3. QT 网络通信用到的两个类

  1. 使用QT提供的类进行基于TCP的套接字通信需要用到两个类
  • QTcpServer:服务器端使用, 服务器类, 用于监听客户端连接以及和客户端建立连接
  • QTcpSocket:服务器端和客户端都要用到的, 进行客户端和服务器端的通信, 接收和发送数据;通信的套接字类。
  1. 在QT中进行网络通信, 其实也要进行I/O操作, 这里的I/O是指网络I/O, 而不是磁盘I/O, 操作的是网络数据
  2. 在QT中, 用于套接字通信的QTcpSocket类(网络数据的接收发送, 和QT中进行文件操作的QFile类(文件的读写, 其祖先类是一样的,即QIODevice;二者本质一样, 都是进行I/O操作, 只不过操作的对象不一样。

负责监听, 并且接受客户端的连接

  1. 查阅QT帮助文档, 可以看到QTcpServer类属于network模块, 所以, 在.pro工程文件中, 需要把这个模块加上编译 03 - Qt 多线程网络通信——套接字

1.1. 构造函数

 
  • , 这个构造函数接收一个参数, 即指定一个父对象
  • 父对象这个概念很有用(可以了解一下QT的对象树概念)在QT中存在一个内存回收机制, 通过指定父对象, 就可以将当前的节点挂在父对象的下面, 当父对象析构的时候, 会自动析构其子节点, 这就可以保证将挂在这个父对象下的所有子节点都析构掉(树状结构, 操作时递归完成
  • 父对象和父类不是一个概念父类是有继承关系的, 而父对象没有

1.2. 给监听的套接字设置监听:listen

  • 设置监听 -》 对应前面TCP通信流程中的服务器的bind和listen两步(先绑定再设置监听
 
  • 参数1:const QHostAddress &address = QHostAddress::Any, 要绑定的本地地址, Any指绑定本地任意的IP地址, 可以使用这个默认参数, 这个Any支持IPV4也支持IPV6
  • 参数2:对应主机上的对应端口(会为某个进程绑定固定的端口
  • 确定了IP和端口, 数据就可以发送到某台主机上的某个进程上确定主机的应用程序, 端口就是用来确定应用程序的 | 而IP用来定位主机
  • port = 0, 代表服务器随机分配一个端口(开发者就不知道了, 所以, 最好自己传入固定的端口号;端口号:0 - 65535;最好指定5000(8000, 10000以上)以上的端口号, 5000以下有些可能会被操作系统占用
  • 类封装IPV4, IPV6
    • 03 - Qt 多线程网络通信——套接字

1.3. 判断当前的服务器对象是否已经开始监听了:isListen

 

1.4. 服务器地址和端口信息

  • : 如果当前服务器对象正在监听, 则返回监听的服务器地址信息, 否则返回
  • :如果当前服务器对象正在监听连接, 则返回服务器的端口, 否则返回0

1.5. : 返回下一个挂起的连接作为已连接的QTcpSocket对象。

  • :当服务器启动监听之后, 若有客户端连接上来, 那么服务器就会与客户端建立连接, 建立连接之后就会有一个用于通信的套接字对象
  • 这里一下, 虽然返回的是一个指针, 但是地址在函数体内部分配出来, 所以这里指针指向一块有效的堆内存;所以, 用完后要释放, 你可以自己释放掉,也可以让服务器对象自己释放:前面讲过了对象树的概念, 这里的QTcpSocket对象是由QTcpServer父对象)对象返回的, 二者是父子对象关系, 这就组成了一个对象树, 当QTcpServer对象析构的时候会先析构QTcpSocket对象(结构上的父子关系 ,保证了内存不泄漏,但是这两者是没有继承关系

1.6. 等待新连接

  • :这个函数是一个阻塞函数, 当服务器端启动监听之后, 调用这个函数会阻塞当前的服务器线程, 阻塞线程等待客户端的连接, 若没有客户端来连接, 就会一直阻塞住, 所以可以指定阻塞时长msec(毫秒, 当时间到了还没有客户端连接, 函数就会解除阻塞
  • 而第二个参数timedOut:用来标识是超时解除的阻塞, 还是非超时解除的阻塞
  1. :指定阻塞的最大时长, 单位为毫秒(ms
  2. :传出参数, 如果操作超时timedOut为true, 没有超时为false(这里是要传入地址的, 因为是bool *
  3. 因为这是一个阻塞函数, 所以在开发的时候, 不推荐使用它来检测有没有新的客户端来连接, 而是使用信号和槽的方式, 这样就不会阻塞

2.1.

  • 当有新的客户端连接上来, 这个类就会发出信号, 然后connect绑定槽函数, 在槽函数中处理对应的新连接)调用, 得到一个用于通信的套接字对象, 基于这个对象去接收和发送数据

2.2.

  • 当服务器和客户端建立连接的时候失败了, 就会发出这样的信号, 通过这个信号传递出的参数, 就可以知道和客户端建立连接失败的原因。
  • 用于通信的套接字类, 关于这个类的使用有两种情况
    • case 1: 在服务器端调用, 得到直接用于通信的QTcpSocket
    • case 2: 在客户端创建对象, 创建出来的这个对象不能直接用来通信, 需要先连接服务器, 连接成功之后才能和服务器进行通信

如何连接服务器

:连接服务器

  • 这个函数是重载函数
      • 参数1 : 指定要连接的服务器的地址, IP地址
      • 参数2 : 端口, 是服务器端程序绑定的端口, 服务器绑定了什么端口, 在连接的时候就基于什么端口连接
      • 参数3 : 打开方式, 默认情况下对套接字对象操作的那个内存块是进行写还是读操作
      • 参数4 : 用默认值, IP协议
      • 参数1 :指定要连接的服务器地址, 通过QHostAddress进行封装
      • 参数2 : 端口, 是服务器端程序绑定的端口
      • 参数3 : 打开方式, 默认情况下对套接字对象操作的那个内存块是进行写还是读操作

如何基于进行通信, 通信即是指数据的读或写

  • 是一个套接字通信类, 无论是在客户端还是服务器端都需要使用它。在Qt中发送和接收数据也属于I/O操作(网络I/O, 如下是这个类的继承关系
    • 03 - Qt 多线程网络通信——套接字

1.1. 构造函数

  • : 创建一个状态为UnconnectedState的QTcpSocket对象。

1.2. 连接服务器

需要指定服务器绑定的IP和端口信息 继承自QAbstractSocket

  • 这个函数是重载函数
      • 参数1 : 指定要连接的服务器的地址, IP地址
      • 参数2 : 端口, 是服务器端程序绑定的端口, 服务器绑定了什么端口, 在连接的时候就基于什么端口连接
      • 参数3 : 打开方式, 默认情况下对套接字对象操作的那个内存块是进行写还是读操作
      • 参数4 : 用默认值, IP协议
      • 参数1 :指定要连接的服务器地址, 通过QHostAddress进行封装
      • 参数2 : 端口, 是服务器端程序绑定的端口
      • 参数3 : 打开方式, 默认情况下对套接字对象操作的那个内存块是进行写还是读操作

1.3. 接收数据:相关的read

在 Qt 中不管调用读操作函数接收数据,还是调用写函数发送数据,操作的对象都是本地的由 Qt 框架维护的一块内存。所以,调用了发送函数,数据不一定会马上被发送到网络中,调用了接收函数也不是直接从网络中接收数据。

  • : 指定可接收的最大字节数 maxSize 的数据到指针 data 指向的内存
  • :指定可接收的最大字节数 maxSize,返回接收的字符串
  • :将当前可用操作数据全部读出,通过返回值返回读出的字符串

1.4. 发送数据:相关的write

  • 发送指针 data 指向的内存中的 maxSize 个字节的数据

  • : 发送指针 data 指向的内存中的数据字符串以 ‘0’ 作为结束标记

  • :发送参数指定的字符串

  • 调用read和write操作的都是QT帮我们维护的这块内存, QT框架检测到有数据后, 会把数据发送到网络中,所以read和write不是直接操作的网络中的数据

查阅帮助文档, 可以发现QTcpSocket所用到的信号都是从父类QAbstractSocket继承来的

2.1. QAbstractSocket中的信号

03 - Qt 多线程网络通信——套接字

2.1.1.
  • This signal is emitted after connectToHost() has been called and a connection has been successfully established:当我们调用connectToHost()时, 如果成功连接了服务器, 那么这个信号就会被发射出来
2.1.2.
  • This signal is emitted when the socket has been disconnected:在套接字断开连接时发出 disconnected() 信号
  • 若B端断开了连接, 在A端通信的就会发射出disconnected信号(检测对端有没有断开连接不是当前这端, 而是通信的对端

2.2. QIODevice中的信号

2.2.1.
  • 在使用QTcpSocket进行套接字通信的过程中, 如果该类对象发射出信号, 说明对端发送的数据达到了, 之后就可以调用接收数据了

1.1 通信流程

  1. 创建套接字服务器对象用于监听的套接字对象
  2. 绑定 + 设置监听:通过对象设置监听, 即: IP + 端口
  3. 基于信号检测是否有新的客户端连接
  4. 如果发出了的信号, 说明有新的客户端连接, 就在对应的槽函数中调用 得到通信的套接字对象
  5. 使用通信的套接字对象和客户端进行通信:中提供了接收和发送数据的函数read和write;并且在中提供了3个信号
    • QIODevice::readyRead():可以知道对端有没有发送数据过来, 若有数据过来, 对象会发出信号,在这个信号对应的槽函数中做对应的接收数据的操作即可
    • 还可以通过对象去检测当前的连接是否成功了,这个连接的检测是在客户端进行检测的, 服务器端是不可以使用这个信号的
    • :无论在客户端或者服务器端都是可以使用的, 只要通信的两端(A, B, 其中的某一端(B)断开了连接, 那么在这一端(A)就可以通过对象发射出这个信号, 通知A端, 对端B已经断开了连接

1.2 服务器窗口界面设计

  1. 创建一个服务器的工程, base 基类选择QMainWindow, 这样会带菜单栏, 工具栏和状态栏03 - Qt 多线程网络通信——套接字
  2. 菜单界面设计:详细图解 03 - Qt 多线程网络通信——套接字

1.3 服务器端套接字通信处理

开始开发之前, 先在.pro工程文件中将network模块加入进去;记得保存刷新, 才能将模块重新导入03 - Qt 多线程网络通信——套接字 另外, 在Qt Creator中, 提供了可以在UI设计界面, 直接右键控件转到槽(如下图所示, 由系统自动帮我们去进行信号和槽的连接, 这里为了加深练习, 全部都统一使用手动连接信号和槽03 - Qt 多线程网络通信——套接字

  1. 按照前面的流程, 先在服务器端创建一个用于监听的套接字对象
  2. 将状态栏的两种连接状态图片(连接成功和连接失败)添加到项目的资源文件中

1.4 完整服务器端代码

 
 
 

2.1 通信流程

  1. 创建用于通信的套接字类对象:实例化后要去连接服务器, 否则无法进行套接字通信
  2. 使用服务器端绑定的IP和端口连接服务器:;
  3. 连接成功之后, 就可以使用对象和服务器进行通信

2.2 客户端窗口界面设计

  1. 创建一个客户端的工程, base 基类选择QMainWindow, 这样会带菜单栏, 工具栏和状态栏03 - Qt 多线程网络通信——套接字
  2. 菜单界面设计详细图解 03 - Qt 多线程网络通信——套接字

2.3 客户端套接字通信处理代码

按照通信流程走

 
 
 
  • 根据IP和端口连接服务器, 当点击发送文件按钮后, 会将选择的磁盘文件传输给服务器端
  • 当服务器端接收完毕后, 服务器会断开连接, 客户端这边会发射出一个disconnected信号, 通知服务器已经接收完毕, 客户端断开连接
  • 以上通信流程都是在子线程中完成

03 - Qt 多线程网络通信——套接字 03 - Qt 多线程网络通信——套接字

Qt中提供了两种线程的创建方式;步骤如下,我们使用其中的一种, 更为灵活的创建方式

2.1 过程步骤

  1. 创建一个新的类SendFile, 让这个类从QObject派生 03 - Qt 多线程网络通信——套接字
  2. 在这个类中添加自定义的任务函数(公共的成员函数 | 子线程执行任务, 函数体就是我们要子线程中执行的业务逻辑 03 - Qt 多线程网络通信——套接字
  3. 主线程中创建一个QThread对象, 这就是子线程的对象
  4. 主线程中创建工作的类对象, 并且不要给创建的对象指定父对象
  5. 将创建出来的工作的类对象移动到创建的子线程对象中, 需要调用QObject类提供的方法
 
  1. 启动子线程, 调用, 这时候线程启动了, 但是移动到线程中的对象是没有工作的
  2. 借助信号和槽机制, 调用工作的类对象的工作函数, 让这个函数开始执行, 这时候是在移动到的那个子线程中运行的

2.2 实例代码

 
 
  1. :准备要在子线程中完成的工作, 连接服务器和发送文件给服务器
 
 
 

上面采用了Qt中提供的多线程使用方式的其中一种, 接下来在服务器端使用另外一种较为简单的使用方法, 因为服务器端程序没有太多的功能, 仅仅是在子线程中接收文件

  1. 需要创建一个线程类的子类, 让其继承QT中的线程类QThread, 比如
 
  1. 重写父类的虚函数方法, 在该函数内部编写子线程要处理的具体的业务流程
 
  1. 在主线程中创建子线程对象, new一个
 
  1. 启动子线程, 调用方法
    • 只要调用方法, 子线程中方法就可以被执行
 
 

03 - Qt 多线程网络通信——套接字

 
 
  1. 新建一个recvFile类, 继承自QThread
 
  1. recvfile.h
 
 
  • 前面, 当用于监听的套接字对象发射出一个newConnection信号, 就说明有客户端发起了连接, 就调用得到一个用于通信的套接字对象
  • 然后把这个用于通信的套接字对象, 直接传递给了子线程03 - Qt 多线程网络通信——套接字 但是有可能会遇到通信的套接字对象无法在子线程中工作的问题,下面介绍的就是如何解决(无论哪种使用子线程的方式, 这里解决的方法一样
  1. 在QTcpServer中有一个虚函数:可以得到一个用于通信的文件描述符, 并且这个函数自动被QT框架所调用, 不需要调用;
    • 当客户端向服务器发起了连接, 函数就会被调用, 得到一个用于通信的文件描述符
    • 调用, 其实是对通信的文件描述符进行了封装, 得到一个QTcpSocket对象, 而 没有对文件描述符进行封装, 所以我们需要自己对通信的文件描述符进行封装03 - Qt 多线程网络通信——套接字 03 - Qt 多线程网络通信——套接字
  1. 先在当前服务器端的项目中, 新添加一个类MyTcpServer, 继承自QTcpServer 03 - Qt 多线程网络通信——套接字
  2. 在当前的这个子类中, 重写受保护的虚函数
     
      
     
  3. 在子线程中将通信的文件描述符封装成QTcpSocket
     
      
     
 
  • mainwindow.h