Qt网络编程-TCP与UDP

网络基础

TCP与UDP基础

关于TCP与UDP的基础这里就不过多介绍了,具体可以查看对应百度百科介绍:

TCP(传输控制协议)_百度百科 (baidu.com)

UDP_百度百科 (baidu.com)

需要知道这两者的区别:

  1. 可靠性:

    • TCP:TCP 是一种面向连接的协议,它提供可靠的数据传输。它使用序号、确认和重传等机制来确保数据的可靠性,以及按序传递数据包。如果数据包丢失或损坏,TCP 会自动进行重传,直到数据被正确接收。
    • UDP:UDP 是一种无连接的协议,它不提供数据包的可靠性保证。UDP 发送的数据包可能丢失、重复或无序,因此它不适合对数据可靠性要求很高的应用。
  2. 连接性:

    • TCP:TCP 是面向连接的,它在通信双方建立连接后(有客户端与服务器之分)才能进行数据传输。TCP 连接是可靠的、有序的、全双工的,通信双方可以进行双向通信。
    • UDP:UDP 是无连接的,它不需要在通信双方之间建立连接(没有客户端与服务器之分)。每个 UDP 数据包都是独立的,发送者和接收者之间没有持久的连接。
  3. 效率:

    • TCP:TCP 通过使用流量控制和拥塞控制等机制,以及连接的建立和维护,会产生一定的开销。因此,TCP 在可靠性和有序性方面提供了较高的保证,但可能会牺牲一些效率。
    • UDP:UDP 不需要进行连接的建立和维护,也不需要进行重传或流量控制等操作,因此它通常比 TCP 具有更低的开销和更高的效率。

网络通信以上两者都绕不开IP地址与端口这两个。

开发调试所需工具

一般情况需要网络调试助手或者wireshark抓包工具,网络调试助手我用的是NetAssist。关于NetAssist和WireShark怎么使用,后面会介绍。

windows与linux如何查看和修改本地的IP端口

Window

cmd命令行:ipconfig/all

修改:

设置选中“网络和Internet”

Linux

命令行:ifconfig -a:

static QStringList getIPAddresses() {QStringList addresses;for (const QHostAddress &address : QNetworkInterface::allAddresses()) {if (address.protocol() == QAbstractSocket::IPv4Protocol)addresses.append(address.toString());}return addresses;
}

或者直接查看网络设置。

使用Qt函数获取

Qt要使用网络模块记得工程文件添加:QT += network

static QStringList getIPAddresses() {QStringList addresses;for (const QHostAddress &address : QNetworkInterface::allAddresses()) {if (address.protocol() == QAbstractSocket::IPv4Protocol)addresses.append(address.toString());}return addresses;
}

编译运行查看打印:

Windows与Linux查看本地连接情况

Windows和Linux都需要借助netstat命令,但是两者稍微有一些不一样。

Windows

比如我使用刚刚的调试助手NetAssist,建立一个tcp服务器,然后监听IP:192.168.5.1,端口:8080。

查看一下这个服务器是否监听成功,命令行输入:netstat -antp TCP(‘p’指定对应协议,后面需要接协议类型TCP或UDP)

 或者直接输入命令 netstat -antp TCP|findstr 8080:

再起一个调试助手以客户端的形式连接这个服务器:

 再次输入: netstat -antp TCP|findstr 8080:

能够查看到刚刚建立的连接。

查看对应链接是哪个应用建立的,先输入: netstat -antpo TCP|findstr 8080:

然后使用tasklist查看对应进程: 

如果是UDP改为 netstat -antp UDP|findstr 8080 即可

Linux 

Linux 的netstat的命令指定对应协议不需要 -p TCP或者-p UDP,而是-t就是TCP,-u就是UDP,如下图所示:

 Qt实现TCP

因为TCP是需要建立链接,分客户端和服务器端的,所以需要分别编写。

服务器端

服务器由QTcpServer来实现,QTcpServer的信号:

需要注意newConnection这个信号,当有客户端连接这个服务器时,会触发这个信号。

所有的方法:


需要注意的几个方法:


bool  listen(const QHostAddress &address = QHostAddress::Any, quint16 port = 0)
 
监听对应IP和端口,IP为空则默认监听any

close()

停止监听

bool isListening() const
 
是否正在监听

QHostAddress serverAddress() const
 
监听的IP地址

quint16 serverPort() const
 
监听的端口

void setMaxPendingConnections(int numConnections)
 
设置允许建立的最大连接数

比如监听IP 127.0.0.1 端口 8080:

QTcpServer server;

server.listen(QHostAddress("127.0.0.1"),8080); 

写一个简单的例子。

ui:

头文件:

#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
#include <QTcpServer>
#include <QTcpSocket>
QT_BEGIN_NAMESPACE
namespace Ui {
class MainWindow;
}
QT_END_NAMESPACE
class MainWindow : public QMainWindow
{Q_OBJECT
public:MainWindow(QWidget *parent = nullptr);~MainWindow();
private slots:void on_listen_clicked();void on_disconnect_clicked();void on_send_clicked();void newConnection();
private:Ui::MainWindow *ui;QTcpServer *m_Server;QTcpServer *m_Server1;QList<QTcpSocket *> m_Sockets;void showLog(const QString &log);
};
#endif // MAINWINDOW_H

 源文件:

#include "mainwindow.h"
#include <QDateTime>
#include <QHostAddress>
#include <QNetworkInterface>
#include "ui_mainwindow.h"
static QStringList getIPAddresses() {QStringList addresses;for (const QHostAddress &address : QNetworkInterface::allAddresses()) {if (address.protocol() == QAbstractSocket::IPv4Protocol)addresses.append(address.toString());}return addresses;
}MainWindow::MainWindow(QWidget *parent): QMainWindow(parent), ui(new Ui::MainWindow) {ui->setupUi(this);setWindowTitle("TcpServer");ui->tableWidget->setEditTriggers(QAbstractItemView::NoEditTriggers);ui->localIp->addItems(getIPAddresses());ui->tableWidget->setSelectionBehavior(QAbstractItemView::SelectRows);m_Server = new QTcpServer;connect(m_Server, &QTcpServer::newConnection, this,&MainWindow::newConnection);for (QTcpSocket *socket : m_Sockets) {connect(socket, &QTcpSocket::readyRead, [=]() {showLog(QString("%1:%2:%3").arg(socket->peerAddress().toString()).arg(socket->peerPort()).arg(QString(socket->readAll().toHex())));});connect(socket, &QTcpSocket::disconnected, [=]() {showLog(QString("disconnect:%1:%2:%3").arg(socket->peerAddress().toString()).arg(socket->peerPort()).arg(QString(socket->readAll().toHex())));for (int i = 0; i < ui->tableWidget->rowCount(); i++) {QTableWidgetItem *ipItem = ui->tableWidget->item(i, 0);QTableWidgetItem *portItem = ui->tableWidget->item(i, 1);if (nullptr != ipItem && nullptr != portItem) {if (ipItem->text() == socket->peerAddress().toString() &&portItem->text() == socket->peerPort()) {ui->tableWidget->removeRow(i);break;}}}});connect(socket,static_cast<void (QTcpSocket::*)(const QAbstractSocket::SocketError)>(&QTcpSocket::error),[=](QAbstractSocket::SocketError error) {qDebug() << "error:" << error;showLog("error:" + QString::number(int(error)));});connect(socket, &QTcpSocket::stateChanged,[=](QAbstractSocket::SocketState state) {qDebug() << "stateChanged:" << state;showLog("stateChanged:" + QString::number(int(state)));});}
}MainWindow::~MainWindow() { delete ui; }void MainWindow::on_listen_clicked() {if (ui->listen->text() == "listen") {if (m_Server->listen(QHostAddress(ui->localIp->currentText()),ui->localPort->value()))ui->listen->setText("listening");elseui->textEdit->append("listen fail");} else {for (QTcpSocket *socket : m_Sockets) {socket->close();socket->disconnectFromHost();}m_Server->close();ui->listen->setText("listen");}
}void MainWindow::on_disconnect_clicked() {int row = ui->tableWidget->currentRow();if (-1 != row) {QTableWidgetItem *ipItem = ui->tableWidget->item(row, 0);QTableWidgetItem *portItem = ui->tableWidget->item(row, 1);if (nullptr != ipItem && nullptr != portItem) {QString ip = ipItem->text();quint16 port = portItem->text().toUShort();for (QTcpSocket *socket : m_Sockets) {if (ip == socket->peerAddress().toString() &&port == socket->peerPort()) {socket->close();socket->disconnectFromHost();break;}}}}
}void MainWindow::on_send_clicked() {QByteArray ba = ui->send->text().toUtf8();int row = ui->tableWidget->currentRow();if (-1 != row) {QTableWidgetItem *ipItem = ui->tableWidget->item(row, 0);QTableWidgetItem *portItem = ui->tableWidget->item(row, 1);if (nullptr != ipItem && nullptr != portItem) {QString ip = ipItem->text();quint16 port = portItem->text().toUShort();for (QTcpSocket *socket : m_Sockets) {if (ip == socket->peerAddress().toString() &&port == socket->peerPort()) {socket->write(ba);break;}}}}
}void MainWindow::newConnection() {QTcpSocket *socket = m_Server->nextPendingConnection();m_Sockets.append(socket);// m_TcpSocket = socket;int row = ui->tableWidget->rowCount();ui->tableWidget->insertRow(row);QTableWidgetItem *ipItem =new QTableWidgetItem(socket->peerAddress().toString());QTableWidgetItem *portItem =new QTableWidgetItem(QString::number(socket->peerPort()));qDebug() << socket->peerAddress().toString() << "," << socket->peerPort()<< "," << socket->peerName();ui->tableWidget->setItem(row, 0, ipItem);ui->tableWidget->setItem(row, 1, portItem);connect(socket, &QTcpSocket::disconnected, [=]() {showLog(QString("disconnected:%1:%2").arg(socket->peerAddress().toString()).arg(socket->peerPort()));// m_TcpSocket = nullptr;for (int i = 0; i < ui->tableWidget->rowCount(); i++) {QTableWidgetItem *ipItem = ui->tableWidget->item(i, 0);QTableWidgetItem *portItem = ui->tableWidget->item(i, 1);if (nullptr != ipItem && nullptr != portItem) {if (ipItem->text() == socket->peerAddress().toString() &&portItem->text() == QString::number(socket->peerPort())) {ui->tableWidget->removeRow(i);break;}}}});
}void MainWindow::showLog(const QString &log) {ui->textEdit->append(QString("%1:%2").arg(QDateTime::currentDateTime().toString("yyyy/MM/dd hh:mm:ss.zzz")).arg(log));
}

有客户端连接后,会触发newConnection这个信号,然后在槽函数中使用nextPendingConnection()这个方法获取对应的客户端QTcpSocket 指针对象。客户端发送消息后会触发readyRead这个信号,使用QTcpSocket的readAll获取发送的信息。编译运行,输入监听的ip和端口然后点击listen按钮然后使用命令查看是否开始监听对应ip和端口:

然后使用调试助手作为客户端连接这个服务器:

 

可以看到触发了newConnection信号,然后获取对应客户端对象将其信息显示到了的表格上面。

实验链接的建立与取消以及消息的互相发送:

可以实现对应的通讯。 

以上是使用调试助手,也可以使用WireShark抓包查看发送和接受的数据。需要注意的是如果客户端和服务器都在本地自己发自己收是用WireShark抓不到的。

这里简单说一下wireshark的用法,首先选择需要抓取的网卡:

比如ping就是走的tcp,测试时我是用 虚拟机ping我的主机,如何使vmware虚拟机和主机ping通可以参考这位博主的博客:实现虚拟机(VM15.5.0)与本机相互通信_vmware和主机怎样才能ping通-CSDN博客

然后查看wireshark可以看到ping的报文:

因为我的主机ip192.168.1.3,虚拟机ip是192.168.1.4,可以输入“ip.src==192.168.1.4 && ip.dst==192.168.1.3” 来过滤:

 同样,我使用虚拟机和主机建立tcp连接然后发送消息也是可以抓到:

 

 

X

客户端

客户端由QTcpSocket实现,QTcpSocket继承自QAbstractSocket,比如上文中在虚拟机中建立一个tcp服务器,监听ip192.168.1.3,端口12345:

QTcpSocket *socket =new QTcpSocket;
socket->connectToHost(QHostAddress(),12345);
if(socket->waitForConnected())
{//TODO 连接成功
}
else
{//TODO 连接失败
}connect(socket,&QTcpSocket::disconnected,[=](){
//TODO 处理连接断开
});
connect(socket, &QTcpSocket::readyRead, [=]() {QByteArray receiveData=socket->readAll();//TODO 处理接收的数据});QByteArray sendData;
//TODO 处理发送数据
//发送数据
socket->write(sendData);
//断开连接,两种方式
socket->abort;//强制中断连接
socket->disconnectFromHost();//不会马上关闭连接,等待资源释放后才会中断连接

另外客户端套接字可以绑定bind对应ip和端口,如果没有绑定,则系统会使用之绑定一个随即的可用的ip和端口 :

Qt实现UDP

因为UDP不用建立连接,不用分服务器和客户端,所以对应Qt的UDP部分,只需要使用QUdpSocket一个即可:

QUdpSocket同QTcpSocket一样都继承自QAbstractSocket,使用UDP通信前,对应udp套接字需要绑定对应ip与端口,然后发送数据时需要知道对方的ip与端口(UDP分单播、组播与广播,这里只说单播,组播与广播后面博客再写):

QUdpSocket *udpSocket = new QUdpSocket(this);
udpSocket->bind(QHostAddress("192.168.1.3"), 12345);
connect(udpSocket, &QUdpSocket::readyRead,[=](){while (udpSocket->hasPendingDatagrams()) {QByteArray data;QHostAddress host;quint16 port;data.resize(udpSocket->pendingDatagramSize());udpSocket->readDatagram(data.data(), data.size(), &host, &port);//TODO 处理接受数据}
});
QByteArray sendData;
//TODO 处理发送数据
udpSocket->writeDatagram(sendData, QHostAddress("192.168.1.4"),12345);
//取消绑定
udpSocket->unbind();

使用网络助手模拟udp通信:

使用自己写的udp程序在虚拟机中与之通信:

 使用wireshark抓包:

 

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

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

相关文章

每天一个数据分析题(一百五十七)

当对多个连续型变量进行主成分分析&#xff08;PCA&#xff09;以降维并且简化数据时&#xff0c;以下哪项原则通常被用来决定应当保留多少个主成分&#xff1f; A. 保留的主成分个数应当使得累积方差解释比例至少达到50%。 B. 保留的主成分个数应当使得每个主成分的特征值大…

Java面向对象 方法的重写

目录 重写重写的规则实例创建Person类创建Student类测试 重载和重写的区别 重写 发生在子类和父类中&#xff0c;当子类对父类提供的方法不满意的时候&#xff0c;要对父类的方法进行重写。 重写的规则 子类的方法名字和父类必须一致&#xff0c;参数列表&#xff08;个数&…

IMU模型介绍

IMU模型 中文名惯性测量单元。 与一般的认知不同&#xff0c;机器人状态估计问题一般将IMU的测量作为运动信息&#xff0c;根据运动方程预测状态&#xff0c;这与里程计相似&#xff0c;尽管是传感器&#xff0c;但被看成执行器&#xff08;当然与真正的执行机构是不同的&…

15章-Python编程:从入门到实践

第15章生成数据 数据可视化指的是通过可视化表示来探索数据&#xff0c;它与数据挖掘数紧密相关&#xff0c;而数据挖掘指的是使用代码来探索数据集的规律和关联。 数据集可以是用一行代码就能表示的小型数字列表&#xff0c;也可以是数以吉字节的数据。漂亮地呈现数据关乎的并…

List 差集

文章目录 基本类型对象类型 基本类型 ListUtils.subtract 方法用于计算两个集合的差集&#xff0c;即返回 list1 中有但 list2 中没有的元素。 其中&#xff0c;list1 指向第一个集合&#xff0c;list2 指向第二个集合。该方法返回一个新的 List 对象&#xff0c;它包含所有在…

C++:二叉搜索树模拟实现(KV模型)

C&#xff1a;二叉搜索树模拟实现&#xff08;KV模型&#xff09; 前言模拟实现KV模型1. 节点封装2、前置工作&#xff08;默认构造、拷贝构造、赋值重载、析构函数等&#xff09;2. 数据插入&#xff08;递归和非递归版本&#xff09;3、数据删除&#xff08;递归和非递归版本…

开源免费的物联网网关 IoT Gateway

1. 概述 物联网网关&#xff0c;也被称为IOT网关&#xff0c;是一种至关重要的网络设备。在物联网系统中&#xff0c;它承担着连接和控制各种设备的重要任务&#xff0c;将这些设备有效地连接到云端、本地服务器或其他设备上。它既能够在广域范围内实现互联&#xff0c;也能在…

P1297 [国家集训队] 单选错位 对期望的理解

[国家集训队] 单选错位 - 洛谷 思路&#xff1a; 其实每个位置的得分只和前一个位置有关。 而他们俩的所有情况的期望就是答案的这部分。 ——这是难想的&#xff0c;我期望学的不好。 &#xff08;题目给的是每种情况的所有位置的和&#xff0c;全加起来是答案&#xff1…

【龙年大礼】| 2023中国开源年度报告!

【中国开源年度报告】由开源社从 2015 年发起&#xff0c;是国内首个结合多个开源社区、高校、媒体、风投、企业与个人&#xff0c;以纯志愿、非营利的理念和开源社区协作的模式&#xff0c;携手共创完成的开源研究报告。后来由于一些因素暂停&#xff0c;在 2018 年重启了这个…

基于 Python opencv 的人脸识别的酒店客房入侵系统的检测

博主介绍&#xff1a;✌程序员徐师兄、7年大厂程序员经历。全网粉丝12W、csdn博客专家、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ &#x1f345;文末获取源码联系&#x1f345; &#x1f447;&#x1f3fb; 精彩专栏推荐订阅&#x1f447;…

掌握C++中的动态数据:深入解析list的力量与灵活性

1. 引言 简介std::list和其在C中的角色 std::list是C标准模板库&#xff08;STL&#xff09;中提供的一个容器类&#xff0c;实现了双向链表的数据结构。与数组或向量等基于连续内存的容器不同&#xff0c;std::list允许非连续的内存分配&#xff0c;使得元素的插入和删除操作…

排序算法---插入排序

原创不易&#xff0c;转载请注明出处。欢迎点赞收藏~ 插入排序是一种简单直观的排序算法&#xff0c;它的基本思想是将待排序的元素分为已排序和未排序两部分&#xff0c;每次从未排序部分中选择一个元素插入到已排序部分的合适位置&#xff0c;直到所有元素都插入到已排序部分…

php 如何判断是否上传了文件、图片

假设前端有字段 <input type"file" name"user_avatar_image"/> php使用$_FILES进行判断 1. 当没有文件上传时&#xff0c;打印$_FILES ^ array:1 [▼"user_profile_image" > array:5 [▼"name" > """ty…

FANUC机器人外部远程启动的相关参数设置示例

FANUC机器人外部远程启动的相关参数设置示例 如下图所示,在MENU---设置---选择程序中,设置程序选择模式:RSR(这个根据自己实际使用的自动启动方式来决定,你用RSR选RSR,用PNS就选PNS), 自动运行开始方法:选择UOP,即RSR1-RSR8的启动信号分别对应UI9-UI16, 最后,点击…

数字图像处理实验记录六(图像的傅里叶变换和频域处理)

前言&#xff1a; 一、基础知识 1&#xff0c;傅里叶变换是什么 傅里叶变换是一种线性积分变换&#xff0c;通俗来说&#xff0c;通过傅里叶变换就是把一段信号分解成若干个简谐波。 二、实验要求 1&#xff0e;产生一幅如图所示亮块图像f(x,y)&#xff08;256256 大小、…

真正免费的文件恢复软件easyrecovery2024中文版

easyrecovery数据恢复软件是一款广受好评的数据恢复工具&#xff0c;它能够有效地帮助用户恢复各种类型的文件。无论是照片、视频、音乐还是文档&#xff0c;都能轻松地找回这些重要文件。操作安全、用户可自主操作的数据恢复方案&#xff0c;它支持从各种各样的存储介质恢复删…

【芯片设计- RTL 数字逻辑设计入门 11 -- 移位运算与乘法】

请阅读【嵌入式开发学习必备专栏 】 文章目录 移位运算与乘法Verilog Codeverilog 拼接运算符&#xff08;{}&#xff09;Testbench CodeVCS 波形仿真 问题小结 移位运算与乘法 已知d为一个8位数&#xff0c;请在每个时钟周期分别输出该数乘1/3/7/8,并输出一个信号通知此时刻输…

飞书上传图片

飞书上传图片 1. 概述1.1 访问凭证2. 上传图片获取image_key1. 概述 飞书开发文档上传图片: https://open.feishu.cn/document/server-docs/im-v1/image/create 上传图片接口,支持上传 JPEG、PNG、WEBP、GIF、TIFF、BMP、ICO格式图片。 在请求头上需要获取token(访问凭证) …

前端入门:(五)JavaScript 续

10. 浏览器存储 10.1 Cookie的概念和使用 Cookie是一种存储在用户计算机上的小型文本文件&#xff0c;用于跟踪和识别用户。Cookie通常用于存储用户的偏好设置、会话信息等&#xff0c;可以通过JavaScript进行读取和设置。 // 示例&#xff1a;设置和读取Cookie document.co…

侵入式智能指针和非侵入式智能指针

一直有个疑问&#xff0c;为什么chromium代码没有使用shared_ptr呢&#xff1f; 在这里讨论&#xff1a;https://groups.google.com/a/chromium.org/g/cxx/c/aT2wsBLKvzI 不过我在这里找到了一个简单的答案&#xff1a;https://groups.google.com/a/chromium.org/g/chromium-d…