目录
引言
Qt 是一个跨平台的 C++ 图形界面开发库,它提供了丰富的模块来支持各种开发需求,其中网络编程是一个重要的部分。TCP(Transmission Control Protocol,传输控制协议)是一种面向连接的、可靠的、基于字节流的传输层通信协议。在Qt中,通过QTcpSocket和QTcpServer类可以实现TCP通信。本文将详细介绍TCP协议的工作原理以及如何在Qt中进行TCP通信的实战。
一、TCP协议基础知识
1. TCP协议特点
- 面向连接:TCP在数据传输之前需要建立连接,即进行三次握手。
- 可靠性:TCP通过确认和重传机制确保数据的准确性和完整性。
- 顺序性:TCP确保数据按照发送的顺序到达接收方。
- 流量控制和拥塞控制:TCP通过滑动窗口机制和AIMD(Additive Increase Multiplicative Decrease)算法来控制数据流量和避免网络拥塞。
2. TCP连接的三个阶段
- 建立连接:客户端和服务器之间进行三次握手,确认彼此的身份并建立一个可靠的连接。
- 数据传输:数据被分成小的数据包,每个数据包都包含序列号,接收方根据序列号对数据包进行排序和重组。
- 连接终止:通过四次挥手关闭连接,释放资源。
3. 三次握手和四次挥手
- 三次握手是TCP协议中用于建立连接的过程。以下是它的详细步骤:
- SYN(同步序列编号)发送:
- 客户端发送一个SYN包(SYN=j)到服务器,并进入SYN_SENT状态,等待服务器确认。
- SYN-ACK(同步序列编号确认)应答:
- 服务器收到SYN包后,发送一个SYN-ACK包(SYN=k, ACK=j+1)作为应答,表示对SYN包的确认,并发送自己的同步序列号。此时,服务器进入SYN_RCVD状态。
- ACK(确认)发送:
- 客户端收到服务器的SYN-ACK包后,发送一个ACK包(ACK=k+1)作为应答,表示对SYN-ACK包的确认。此时,TCP连接建立,客户端和服务器都进入ESTABLISHED状态,可以开始传输数据。
- 四次挥手是TCP协议中用于关闭连接的过程。以下是它的详细步骤:
- FIN(结束)发送:
- 当一方(假设为客户端)想要关闭连接时,它会发送一个FIN包(FIN=M)给对方,并进入FIN_WAIT_1状态,等待对方的确认。
- ACK确认:
- 接收方(服务器)收到FIN包后,发送一个ACK包(ACK=M+1)作为确认,表示它已经收到了客户端的关闭请求。此时,客户端进入FIN_WAIT_2状态,等待服务器关闭连接。
- FIN发送:
- 服务器在关闭连接之前可能需要完成一些清理工作(如传输缓冲区中的数据),完成后,它也会发送一个FIN包(FIN=N)给客户端,并进入LAST_ACK状态,等待客户端的确认。
- ACK确认:
- 客户端收到服务器的FIN包后,发送一个ACK包(ACK=N+1)作为确认,表示它已经收到了服务器的关闭请求。此时,服务器收到ACK包后关闭连接,客户端在发送完ACK包后也会关闭连接。双方都进入了CLOSED状态,连接彻底关闭。
二、Qt中的TCP编程
1. 引入Qt网络模块
在使用Qt进行TCP编程之前,需要在项目的.pro文件中引入网络模块:
QT += network
2. QTcpServer 类
QTcpServer 类用于在服务器端监听端口,等待客户端的连接请求。当客户端连接时,QTcpServer 会创建一个新的 QTcpSocket 实例来处理这个连接。
常用函数
listen(const QHostAddress &address = QHostAddress::Any, quint16 port = 0)
: 在指定的地址和端口上开始监听连接。waitForNewConnection(int msec = 0, bool *timedOut = nullptr)
: 等待新的连接到达,可设置超时时间。nextPendingConnection()
: 获取下一个已接受的连接的套接字(QTcpSocket)。
3. QTcpSocket 类
QTcpSocket 类用于在客户端发起连接,以及在连接建立后发送和接收数据。
常用函数
connectToHost(const QString &hostName, quint16 port, OpenMode openMode = ReadWrite)
: 连接到指定的主机和端口。write(const char *data, qint64 maxSize)
: 向套接字写入数据。readAll()
: 读取所有可用的数据并返回一个 QByteArray 对象。disconnectFromHost()
: 断开与服务器的连接。
三、TCP网络通信流程
TCP服务器
- 创建
QTcpServer
对象。#include <QTcpServer> QTcpServer *server = new QTcpServer(this);
- 调用
listen()
方法监听指定端口。bool ok = server->listen(QHostAddress::Any, 1234); if (!ok) { qDebug() << "无法启动服务器:" << server->errorString(); } else { qDebug() << "服务器正在监听端口 1234..."; }
- 等待
newConnection()
信号的到来,表明有新的连接请求。// 通常,您会在类的构造函数或某个初始化函数中连接这个信号到槽函数。 connect(server, &QTcpServer::newConnection, this, &YourClass::handleNewConnection); // 槽函数 void YourClass::handleNewConnection() { QTcpSocket *socket = server->nextPendingConnection(); connect(socket, &QTcpSocket::readyRead, this, &YourClass::readData); // 可以在这里添加更多的socket连接处理,如断开连接处理 connect(socket, &QTcpSocket::disconnected, socket, &QTcpSocket::deleteLater); }
- 调用
nextPendingConnection()
获取新的连接对象QTcpSocket
。这一步已在上一步handleNewConnection
槽函数中展示。 - 通过
QTcpSocket
对象与客户端进行通信。// 这通常包括读取数据、发送数据等。 void YourClass::readData() { QTcpSocket *socket = qobject_cast<QTcpSocket *>(sender()); if (socket) { QByteArray data = socket->readAll(); qDebug() << "收到数据:" << data; // 可以在这里处理数据,然后发送响应等 } } // 发送数据示例 void YourClass::sendData(QTcpSocket *socket, const QByteArray &data) { if (socket->state() == QAbstractSocket::ConnectedState) { socket->write(data); } }
TCP客户端
- 创建
QTcpSocket
对象。QTcpSocket *socket = new QTcpSocket(this);
- 调用
connectToHost()
方法连接到服务器。socket->connectToHost("127.0.0.1", 1234); // 可以选择性地检查连接状态 if (!socket->waitForConnected()) { qDebug() << "无法连接到服务器:" << socket->errorString(); socket->deleteLater(); } else { qDebug() << "连接到服务器成功"; }
- 等待
readyRead()
信号的到来,表明有新的数据可读。connect(socket, &QTcpSocket::readyRead, this, &YourClass::readDataFromServer); // 槽函数 void YourClass::readDataFromServer() { QByteArray data = socket->readAll(); qDebug() << "从服务器收到数据:" << data; }
- 调用
readAll()
方法读取数据。这一步已在readDataFromServer
槽函数中展示。 - 通过
QTcpSocket
对象与服务器进行通信。// 客户端发送数据示例 void YourClass::sendDataToServer(const QByteArray &data) { if (socket->state() == QAbstractSocket::ConnectedState) { socket->write(data); } }
四、实战示例
-
UI界面
服务端:
客户端:
-
核心代码
服务端:
#include "mainwindow.h"
#include "ui_mainwindow.h"
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::MainWindow)
{
ui->setupUi(this);
QString strip = GetLocalIpAddress();
ui->comboBoxIp->addItem(strip);
tcpserver = new QTcpServer(this);
connect(tcpserver, SIGNAL(newConnection()), this, SLOT(onnewconnect()));
}
MainWindow::~MainWindow()
{
delete ui;
}
void MainWindow::on_pushButton_Start_clicked()
{
QString ip = ui->comboBoxIp->currentText();
quint16 port = ui->spinBoxPort->value();
QHostAddress address(ip);
tcpserver->listen(address, port);
ui->plainTextEdit_DispMsg->appendPlainText("~~~~~~~~正在监听~~~~~~~~");
ui->plainTextEdit_DispMsg->appendPlainText("服务器地址:" +
tcpserver->serverAddress().toString());
ui->plainTextEdit_DispMsg->appendPlainText("服务器端口:" +
QString::number(tcpserver->serverPort()));
ui->pushButton_Start->setEnabled(false);
ui->pushButton_Stop->setEnabled(true);
}
void MainWindow::on_pushButton_Stop_clicked()
{
if(tcpserver->isListening())
{
tcpserver->close();
ui->pushButton_Stop->setEnabled(false);
ui->pushButton_Start->setEnabled(true);
}
}
void MainWindow::on_pushButton_Send_clicked()
{
QString strmsg = ui->lineEdit_InpuMsg->text();
ui->plainTextEdit_DispMsg->appendPlainText("[J.]:" + strmsg);
ui->lineEdit_InpuMsg->clear();
QByteArray str = strmsg.toUtf8();
str.append("\n");
tcpsocket->write(str);
}
QString MainWindow::GetLocalIpAddress() // 获取本地IP地址
{
QString hostname = QHostInfo::localHostName();
QHostInfo hostinfo = QHostInfo::fromName(hostname);
QString localip = "";
QList<QHostAddress> addresslist = hostinfo.addresses();
if(!addresslist.isEmpty())
{
for(int i = 0; i < addresslist.count(); i++)
{
QHostAddress hostaddress = addresslist.at(i);
if(hostaddress.protocol() == QAbstractSocket::IPv4Protocol)
{
localip = hostaddress.toString();
break;
}
}
}
return localip;
}
void MainWindow::clientconnect() // 客户端连接
{
ui->plainTextEdit_DispMsg->appendPlainText("****客户端Socket连接****");
ui->plainTextEdit_DispMsg->appendPlainText("peer address:"+tcpsocket->peerAddress().toString());
ui->plainTextEdit_DispMsg->appendPlainText("peer port:"+QString::number(tcpsocket->peerPort()) + "\n");
}
void MainWindow::clientdisconnect() // 不连接
{
ui->plainTextEdit_DispMsg->appendPlainText("****客户端Socket断开连接****\n");
tcpsocket->deleteLater();
}
void MainWindow::socketreaddata() // 读数据
{
while (tcpsocket->canReadLine()) {
ui->plainTextEdit_DispMsg->appendPlainText("[T.]:" + tcpsocket->readLine());
}
}
/*
* 当有客户端接入时,tcpServer会发射newConnection信号,触发槽函数
* nextPendingConnection函数获取与接入连接进行通信的QTcpSocket.
* connected():客户端socket 连接建立时发射此信号;
* disconnected():客户端socket 连接断开时发射此信号;
* readyRead():本程序的socket的读取缓冲区有新数据时发射此信号。
*/
void MainWindow::onnewconnect() // 新连接
{
tcpsocket = tcpserver->nextPendingConnection();
connect(tcpsocket, SIGNAL(connected()), this, SLOT(clientconnect())); // 连接
clientconnect();
connect(tcpsocket, SIGNAL(disconnected()), this, SLOT(clientdisconnect())); // 不连接
connect(tcpsocket, SIGNAL(readyRead()), this, SLOT(socketreaddata())); // 读数据
}
void MainWindow::closeEvent(QCloseEvent *event) // 关闭事件
{
if(tcpserver->isListening())
tcpserver->close();
event->accept();
}
客户端:
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include <QHostInfo>
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::MainWindow)
{
ui->setupUi(this);
tcpclient = new QTcpSocket(this);
QString strip = GetLocalIpAddress();
ui->comboBoxIp->addItem(strip);
connect(tcpclient, SIGNAL(connected()), this, SLOT(connectFunc())); // 连接
connect(tcpclient, SIGNAL(disconnected()), this, SLOT(disconnectFunc())); // 不连接
connect(tcpclient, SIGNAL(readyRead()), this, SLOT(socketreaddata())); // 读数据
}
MainWindow::~MainWindow()
{
delete ui;
}
QString MainWindow::GetLocalIpAddress() // 获取本地IP地址
{
QString hostname = QHostInfo::localHostName();
QHostInfo hostinfo = QHostInfo::fromName(hostname);
QString localip = "";
QList<QHostAddress> addresslist = hostinfo.addresses();
if(!addresslist.isEmpty())
{
for(int i = 0; i < addresslist.count(); i++)
{
QHostAddress hostaddress = addresslist.at(i);
if(hostaddress.protocol() == QAbstractSocket::IPv4Protocol)
{
localip = hostaddress.toString();
break;
}
}
}
return localip;
}
void MainWindow::connectFunc() // 连接服务器
{
ui->plainTextEdit_DispMsg->appendPlainText("****成功连接服务器****");
ui->plainTextEdit_DispMsg->appendPlainText("peer address:"+tcpclient->peerAddress().toString());
ui->plainTextEdit_DispMsg->appendPlainText("peer port:"+QString::number(tcpclient->peerPort()) + "\n");
ui->pushButton_Connect->setEnabled(false);
ui->pushButton_Stop->setEnabled(true);
}
void MainWindow::disconnectFunc() // 不连接
{
ui->plainTextEdit_DispMsg->appendPlainText("***已断开服务器连接***\n");
ui->pushButton_Connect->setEnabled(true);
ui->pushButton_Stop->setEnabled(false);
}
void MainWindow::socketreaddata() // 读数据
{
while (tcpclient->canReadLine()) {
ui->plainTextEdit_DispMsg->appendPlainText("[J.]:" + tcpclient->readLine());
}
}
void MainWindow::on_pushButton_Connect_clicked()
{
QString ip = ui->comboBoxIp->currentText();
quint16 port = ui->spinBoxPort->value();
//QHostAddress address(ip);
tcpclient->connectToHost(ip, port);
}
void MainWindow::on_pushButton_Stop_clicked()
{
if(tcpclient->state() == QAbstractSocket::ConnectedState)
tcpclient->disconnectFromHost();
}
void MainWindow::on_pushButton_Send_clicked()
{
QString strmsg = ui->lineEdit_InpuMsg->text();
ui->plainTextEdit_DispMsg->appendPlainText("[T.]:" + strmsg);
ui->lineEdit_InpuMsg->clear();
QByteArray str = strmsg.toUtf8();
str.append("\n");
tcpclient->write(str);
}
void MainWindow::closeEvent(QCloseEvent *event)
{
if(tcpclient->state() == QAbstractSocket::ConnectedState)
tcpclient->disconnectFromHost();
event->accept();
}
-
运行结果
五、总结
通过本篇文章的学习,通过QTcpSocket和QTcpServer类可以实现TCP通信。下一篇会介绍UDP协议的工作原理及实战!
传送门1:QT网络编程(一)——基础知识体系
标签:QT,编程,TCP,连接,ui,QTcpSocket,MainWindow,客户端 From: https://blog.csdn.net/weixin_62621696/article/details/140668150