当前位置:   article > 正文

Qt网络编程-TCP与UDP

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:

  1. static QStringList getIPAddresses() {
  2. QStringList addresses;
  3. for (const QHostAddress &address : QNetworkInterface::allAddresses()) {
  4. if (address.protocol() == QAbstractSocket::IPv4Protocol)
  5. addresses.append(address.toString());
  6. }
  7. return addresses;
  8. }

或者直接查看网络设置。

使用Qt函数获取

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

  1. static QStringList getIPAddresses() {
  2. QStringList addresses;
  3. for (const QHostAddress &address : QNetworkInterface::allAddresses()) {
  4. if (address.protocol() == QAbstractSocket::IPv4Protocol)
  5. addresses.append(address.toString());
  6. }
  7. return addresses;
  8. }

编译运行查看打印:

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:

头文件:

  1. #ifndef MAINWINDOW_H
  2. #define MAINWINDOW_H
  3. #include <QMainWindow>
  4. #include <QTcpServer>
  5. #include <QTcpSocket>
  6. QT_BEGIN_NAMESPACE
  7. namespace Ui {
  8. class MainWindow;
  9. }
  10. QT_END_NAMESPACE
  11. class MainWindow : public QMainWindow
  12. {
  13. Q_OBJECT
  14. public:
  15. MainWindow(QWidget *parent = nullptr);
  16. ~MainWindow();
  17. private slots:
  18. void on_listen_clicked();
  19. void on_disconnect_clicked();
  20. void on_send_clicked();
  21. void newConnection();
  22. private:
  23. Ui::MainWindow *ui;
  24. QTcpServer *m_Server;
  25. QTcpServer *m_Server1;
  26. QList<QTcpSocket *> m_Sockets;
  27. void showLog(const QString &log);
  28. };
  29. #endif // MAINWINDOW_H

 源文件:

  1. #include "mainwindow.h"
  2. #include <QDateTime>
  3. #include <QHostAddress>
  4. #include <QNetworkInterface>
  5. #include "ui_mainwindow.h"
  6. static QStringList getIPAddresses() {
  7. QStringList addresses;
  8. for (const QHostAddress &address : QNetworkInterface::allAddresses()) {
  9. if (address.protocol() == QAbstractSocket::IPv4Protocol)
  10. addresses.append(address.toString());
  11. }
  12. return addresses;
  13. }
  14. MainWindow::MainWindow(QWidget *parent)
  15. : QMainWindow(parent), ui(new Ui::MainWindow) {
  16. ui->setupUi(this);
  17. setWindowTitle("TcpServer");
  18. ui->tableWidget->setEditTriggers(QAbstractItemView::NoEditTriggers);
  19. ui->localIp->addItems(getIPAddresses());
  20. ui->tableWidget->setSelectionBehavior(QAbstractItemView::SelectRows);
  21. m_Server = new QTcpServer;
  22. connect(m_Server, &QTcpServer::newConnection, this,
  23. &MainWindow::newConnection);
  24. for (QTcpSocket *socket : m_Sockets) {
  25. connect(socket, &QTcpSocket::readyRead, [=]() {
  26. showLog(QString("%1:%2:%3")
  27. .arg(socket->peerAddress().toString())
  28. .arg(socket->peerPort())
  29. .arg(QString(socket->readAll().toHex())));
  30. });
  31. connect(socket, &QTcpSocket::disconnected, [=]() {
  32. showLog(QString("disconnect:%1:%2:%3")
  33. .arg(socket->peerAddress().toString())
  34. .arg(socket->peerPort())
  35. .arg(QString(socket->readAll().toHex())));
  36. for (int i = 0; i < ui->tableWidget->rowCount(); i++) {
  37. QTableWidgetItem *ipItem = ui->tableWidget->item(i, 0);
  38. QTableWidgetItem *portItem = ui->tableWidget->item(i, 1);
  39. if (nullptr != ipItem && nullptr != portItem) {
  40. if (ipItem->text() == socket->peerAddress().toString() &&
  41. portItem->text() == socket->peerPort()) {
  42. ui->tableWidget->removeRow(i);
  43. break;
  44. }
  45. }
  46. }
  47. });
  48. connect(
  49. socket,
  50. static_cast<void (QTcpSocket::*)(const QAbstractSocket::SocketError)>(
  51. &QTcpSocket::error),
  52. [=](QAbstractSocket::SocketError error) {
  53. qDebug() << "error:" << error;
  54. showLog("error:" + QString::number(int(error)));
  55. });
  56. connect(socket, &QTcpSocket::stateChanged,
  57. [=](QAbstractSocket::SocketState state) {
  58. qDebug() << "stateChanged:" << state;
  59. showLog("stateChanged:" + QString::number(int(state)));
  60. });
  61. }
  62. }
  63. MainWindow::~MainWindow() { delete ui; }
  64. void MainWindow::on_listen_clicked() {
  65. if (ui->listen->text() == "listen") {
  66. if (m_Server->listen(QHostAddress(ui->localIp->currentText()),
  67. ui->localPort->value()))
  68. ui->listen->setText("listening");
  69. else
  70. ui->textEdit->append("listen fail");
  71. } else {
  72. for (QTcpSocket *socket : m_Sockets) {
  73. socket->close();
  74. socket->disconnectFromHost();
  75. }
  76. m_Server->close();
  77. ui->listen->setText("listen");
  78. }
  79. }
  80. void MainWindow::on_disconnect_clicked() {
  81. int row = ui->tableWidget->currentRow();
  82. if (-1 != row) {
  83. QTableWidgetItem *ipItem = ui->tableWidget->item(row, 0);
  84. QTableWidgetItem *portItem = ui->tableWidget->item(row, 1);
  85. if (nullptr != ipItem && nullptr != portItem) {
  86. QString ip = ipItem->text();
  87. quint16 port = portItem->text().toUShort();
  88. for (QTcpSocket *socket : m_Sockets) {
  89. if (ip == socket->peerAddress().toString() &&
  90. port == socket->peerPort()) {
  91. socket->close();
  92. socket->disconnectFromHost();
  93. break;
  94. }
  95. }
  96. }
  97. }
  98. }
  99. void MainWindow::on_send_clicked() {
  100. QByteArray ba = ui->send->text().toUtf8();
  101. int row = ui->tableWidget->currentRow();
  102. if (-1 != row) {
  103. QTableWidgetItem *ipItem = ui->tableWidget->item(row, 0);
  104. QTableWidgetItem *portItem = ui->tableWidget->item(row, 1);
  105. if (nullptr != ipItem && nullptr != portItem) {
  106. QString ip = ipItem->text();
  107. quint16 port = portItem->text().toUShort();
  108. for (QTcpSocket *socket : m_Sockets) {
  109. if (ip == socket->peerAddress().toString() &&
  110. port == socket->peerPort()) {
  111. socket->write(ba);
  112. break;
  113. }
  114. }
  115. }
  116. }
  117. }
  118. void MainWindow::newConnection() {
  119. QTcpSocket *socket = m_Server->nextPendingConnection();
  120. m_Sockets.append(socket);
  121. // m_TcpSocket = socket;
  122. int row = ui->tableWidget->rowCount();
  123. ui->tableWidget->insertRow(row);
  124. QTableWidgetItem *ipItem =
  125. new QTableWidgetItem(socket->peerAddress().toString());
  126. QTableWidgetItem *portItem =
  127. new QTableWidgetItem(QString::number(socket->peerPort()));
  128. qDebug() << socket->peerAddress().toString() << "," << socket->peerPort()
  129. << "," << socket->peerName();
  130. ui->tableWidget->setItem(row, 0, ipItem);
  131. ui->tableWidget->setItem(row, 1, portItem);
  132. connect(socket, &QTcpSocket::disconnected, [=]() {
  133. showLog(QString("disconnected:%1:%2")
  134. .arg(socket->peerAddress().toString())
  135. .arg(socket->peerPort()));
  136. // m_TcpSocket = nullptr;
  137. for (int i = 0; i < ui->tableWidget->rowCount(); i++) {
  138. QTableWidgetItem *ipItem = ui->tableWidget->item(i, 0);
  139. QTableWidgetItem *portItem = ui->tableWidget->item(i, 1);
  140. if (nullptr != ipItem && nullptr != portItem) {
  141. if (ipItem->text() == socket->peerAddress().toString() &&
  142. portItem->text() == QString::number(socket->peerPort())) {
  143. ui->tableWidget->removeRow(i);
  144. break;
  145. }
  146. }
  147. }
  148. });
  149. }
  150. void MainWindow::showLog(const QString &log) {
  151. ui->textEdit->append(
  152. QString("%1:%2")
  153. .arg(QDateTime::currentDateTime().toString("yyyy/MM/dd hh:mm:ss.zzz"))
  154. .arg(log));
  155. }

有客户端连接后,会触发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:

  1. QTcpSocket *socket =new QTcpSocket;
  2. socket->connectToHost(QHostAddress(),12345);
  3. if(socket->waitForConnected())
  4. {
  5. //TODO 连接成功
  6. }
  7. else
  8. {
  9. //TODO 连接失败
  10. }
  11. connect(socket,&QTcpSocket::disconnected,[=](){
  12. //TODO 处理连接断开
  13. });
  14. connect(socket, &QTcpSocket::readyRead, [=]() {
  15. QByteArray receiveData=socket->readAll();
  16. //TODO 处理接收的数据
  17. });
  18. QByteArray sendData;
  19. //TODO 处理发送数据
  20. //发送数据
  21. socket->write(sendData);
  22. //断开连接,两种方式
  23. socket->abort;//强制中断连接
  24. socket->disconnectFromHost();//不会马上关闭连接,等待资源释放后才会中断连接

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

Qt实现UDP

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

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

  1. QUdpSocket *udpSocket = new QUdpSocket(this);
  2. udpSocket->bind(QHostAddress("192.168.1.3"), 12345);
  3. connect(udpSocket, &QUdpSocket::readyRead,[=](){
  4. while (udpSocket->hasPendingDatagrams()) {
  5. QByteArray data;
  6. QHostAddress host;
  7. quint16 port;
  8. data.resize(udpSocket->pendingDatagramSize());
  9. udpSocket->readDatagram(data.data(), data.size(), &host, &port);
  10. //TODO 处理接受数据
  11. }
  12. });
  13. QByteArray sendData;
  14. //TODO 处理发送数据
  15. udpSocket->writeDatagram(sendData, QHostAddress("192.168.1.4"),
  16. 12345);
  17. //取消绑定
  18. udpSocket->unbind();

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

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

 使用wireshark抓包:

 

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/盐析白兔/article/detail/71895
推荐阅读
相关标签
  

闽ICP备14008679号