Qt之TCP编程
介绍
TCP(传输控制协议)和套接字(socket)是网络通信中两个相关但不同的概念。下面是对它们的简要解释:
TCP(传输控制协议)
-
定义:
- TCP 是一种面向连接的、可靠的传输协议。它在计算机网络中负责在两个主机之间建立和维护一个可靠的连接,并保证数据的完整性和顺序。
- TCP 提供错误检测、流量控制、拥塞控制等机制,以确保数据在传输过程中的可靠性。
-
特点:
- 连接导向:在发送数据之前,必须先建立连接。
- 可靠性:确保数据的正确传输,重新传输丢失的数据。
- 顺序性:确保数据按发送顺序到达目标。
- 流量控制:防止发送方过快发送数据导致接收方处理不过来。
- 拥塞控制:防止网络过载。
-
使用场景:
- 适用于需要可靠数据传输的应用,如网页浏览、电子邮件和文件传输等。
套接字(Socket)
-
定义:
- 套接字是网络通信中的一种抽象概念,它是程序与网络之间的接口。通过套接字,应用程序可以发送和接收数据。
- 套接字提供了与网络连接的方式,可以是 TCP、UDP 等不同的协议。
-
类型:
- 流式套接字(Stream Socket):基于 TCP 协议,提供面向连接的可靠数据流。
- 数据报套接字(Datagram Socket):基于 UDP 协议,提供无连接的、不可靠的数据报服务。
-
操作:
- 创建:使用系统调用或库函数创建一个套接字。
- 绑定:将套接字绑定到特定的地址和端口。
- 监听:在服务端,监听客户端的连接请求。
- 连接:在客户端,连接到服务端的套接字。
- 发送/接收:通过套接字发送和接收数据。
- 关闭:关闭套接字,释放资源。
总结
- TCP 是一种传输协议,定义了在网络中如何可靠地传输数据。
- 套接字 是一种网络编程接口,它允许应用程序通过特定的协议(如 TCP)进行网络通信。
在实际编程中,您可能会使用套接字来实现 TCP 连接或其他类型的网络连接。例如,在 Qt 框架中,QTcpSocket
类是一个套接字类,它提供了对 TCP 连接的支持。
QTcpServer
QTcpServer
是 Qt 框架中用于创建和管理 TCP 服务器的类。它允许你在指定的端口上监听来自客户端的连接,并在有客户端连接时处理它们。下面是 QTcpServer
相关功能的详细说明,包括构造函数和常用 API:
构造函数
QTcpServer::QTcpServer(QObject *parent = nullptr);
- 参数:
parent
是父对象指针,通常设置为nullptr
。 - 功能:创建一个新的
QTcpServer
实例。
listen
方法
bool QTcpServer::listen(const QHostAddress &address, quint16 port);
- 参数:
address
:监听的地址,可以是本地地址、IP 地址或QHostAddress::Any
。port
:监听的端口号。
- 功能:开始在指定的地址和端口上监听传入的连接请求。
- 返回值:如果成功开始监听,返回
true
;否则返回false
。
newConnection
信号
signals:
void newConnection();
- 功能:当有新的客户端连接请求到达时发出。可以通过
connect
函数连接到一个槽,以处理新的连接。
nextPendingConnection
方法
QTcpSocket* QTcpServer::nextPendingConnection();
- 返回值:返回一个新的
QTcpSocket
实例,代表与新客户端的连接。 - 功能:获取下一个待处理的客户端连接。通常在接收到
newConnection
信号时调用。
QTcpSocket::readyRead
信号
signals:
void readyRead();
- 功能:当有数据从客户端读取到时发出。可以连接到一个槽来处理接收到的数据。
clientConnection->state()
方法
QAbstractSocket::SocketState QTcpSocket::state() const;
- 返回值:返回当前套接字的状态,可以是以下之一:
QAbstractSocket::UnconnectedState
:未连接状态QAbstractSocket::HostLookupState
:正在查找主机QAbstractSocket::ConnectingState
:正在连接QAbstractSocket::ConnectedState
:已连接QAbstractSocket::BoundState
:已绑定QAbstractSocket::ClosingState
:正在关闭QAbstractSocket::ListeningState
:正在监听(对于服务器端)
clientConnection->write
方法
qint64 QTcpSocket::write(const QByteArray &data);
- 参数:
data
要发送的数据。 - 返回值:返回实际写入的字节数;如果失败,则返回 -1。
- 功能:将数据发送到连接的对端。
clientConnection->readAll
方法
QByteArray QTcpSocket::readAll();
- 返回值:返回从套接字读取的所有数据。
- 功能:读取并返回套接字中的所有可用数据。
clientConnection->disconnectFromHost
方法
void QTcpSocket::disconnectFromHost();
- 功能:断开当前与主机的连接。它会发送断开连接的请求,并关闭套接字。
clientConnection->deleteLater
方法
void QObject::deleteLater();
- 功能:标记对象在事件循环的适当时机删除,确保在对象被销毁时不会造成不稳定的状态。
示例用法
以下是一个简单的使用示例:
// 创建服务器
QTcpServer *server = new QTcpServer(this);
// 开始监听端口
if (server->listen(QHostAddress::Any, 4545)) {
qDebug() << "服务器启动成功!";
} else {
qDebug() << "服务器启动失败:" << server->errorString();
}
// 处理新连接
connect(server, &QTcpServer::newConnection, [=]() {
QTcpSocket *clientConnection = server->nextPendingConnection();
// 处理客户端数据
connect(clientConnection, &QTcpSocket::readyRead, [=]() {
QByteArray data = clientConnection->readAll();
qDebug() << "接收到数据:" << data;
});
// 发送数据
clientConnection->write("Hello, Client!");
// 断开连接
connect(clientConnection, &QTcpSocket::disconnected, [=]() {
clientConnection->deleteLater();
});
});
这个示例创建了一个 TCP 服务器,监听端口 4545。它处理新的客户端连接、读取客户端发送的数据、向客户端发送数据,并在连接断开时清理资源。
QTcpSocket
QTcpSocket
是 Qt 框架中的一个类,用于实现 TCP 客户端。它允许你通过网络连接到远程主机,并进行数据传输。QTcpSocket
提供了诸如连接、断开连接、发送数据、接收数据等基本的 TCP 网络操作功能,并且支持异步操作,使得在 GUI 应用中不会阻塞用户界面。
QTcpSocket
构造函数
QTcpSocket::QTcpSocket(QObject *parent = nullptr);
- 参数:
parent
是父对象指针,通常设置为nullptr
。 - 功能:创建一个新的
QTcpSocket
实例,用于客户端 TCP 连接。
QTcpSocket::connected
信号
signals:
void connected();
- 功能:当与远程主机成功建立连接时发出。可以连接到槽函数以处理连接建立后的逻辑。
QTcpSocket::readyRead
信号
signals:
void readyRead();
- 功能:当有数据可供读取时发出。通常在此信号的槽函数中调用
readAll()
方法来获取数据。
QTcpSocket::connectToHost
方法
void QTcpSocket::connectToHost(const QHostAddress &address, quint16 port);
- 参数:
address
:要连接的主机地址。port
:要连接的端口号。
- 功能:发起到指定地址和端口的连接。连接成功后会发出
connected
信号。
QTcpSocket::disconnectFromHost
方法
void QTcpSocket::disconnectFromHost();
- 功能:断开与当前主机的连接。可以用于关闭连接并释放资源。
QTcpSocket::state
方法
QAbstractSocket::SocketState QTcpSocket::state() const;
- 返回值:返回当前套接字的状态,如
QAbstractSocket::ConnectedState
表示已连接,QAbstractSocket::UnconnectedState
表示未连接等。 - 功能:用于检查套接字的当前状态。
QTcpSocket::write
方法
qint64 QTcpSocket::write(const QByteArray &data);
- 参数:
data
要发送的数据。 - 返回值:返回实际写入的字节数;如果失败,则返回 -1。
- 功能:将数据写入套接字,并发送到远程主机。
QTcpSocket::readAll
方法
QByteArray QTcpSocket::readAll();
- 返回值:返回一个
QByteArray
对象,包含套接字中所有可用的数据。 - 功能:从套接字中读取所有可用的数据。通常在
readyRead
信号的槽函数中使用。
示例用法
#include <QCoreApplication>
#include <QTcpSocket>
#include <QHostAddress>
#include <QDebug>
class TcpClient : public QObject
{
Q_OBJECT
public:
TcpClient() {
// 创建 QTcpSocket 实例
socket = new QTcpSocket(this);
// 连接信号与槽
connect(socket, &QTcpSocket::connected, this, &TcpClient::onConnected);
connect(socket, &QTcpSocket::readyRead, this, &TcpClient::onReadyRead);
connect(socket, &QTcpSocket::disconnected, this, &TcpClient::onDisconnected);
// 连接到主机
socket->connectToHost(QHostAddress("127.0.0.1"), 1234);
}
private slots:
void onConnected() {
qDebug() << "Connected to host";
// 发送数据
socket->write("Hello, Server!");
}
void onReadyRead() {
QByteArray data = socket->readAll();
qDebug() << "Received data:" << data;
}
void onDisconnected() {
qDebug() << "Disconnected from host";
}
private:
QTcpSocket *socket;
};
TCP编程
整体设计如图所示:
创建一个项目,它们都有各自的窗口类和对应的ui文件,目录如图。
接下来分别对服务端和客服端使用TCP编程。
服务端
服务端只需要引入QTcpServer
的指针以及QTcpSocket
指针作为类的成员变量,一个用于监听客户端,一个用于获取客服端的连接。然后调用相应的api来完成简单的设计。
界面设计
界面的ui如图所示。
比较简单,就两个消息框和两个按钮。
代码设计
tcpserver.h
#ifndef TCPSERVER_H
#define TCPSERVER_H
#include <QWidget>
#include <QTcpServer>
namespace Ui {
class TcpServer;
}
class TcpServer : public QWidget
{
Q_OBJECT
public:
explicit TcpServer(QWidget *parent = nullptr);
~TcpServer();
private:
Ui::TcpServer *ui;
QTcpServer *server; // QTcpServer指针
QTcpSocket *clientConnection; // QTcpSocket指针
};
#endif // TCPSERVER_H
tcpserver.cpp
#include "tcpserver.h"
#include "ui_tcpserver.h"
#include <QTcpSocket>
TcpServer::TcpServer(QWidget *parent) :
QWidget(parent),
ui(new Ui::TcpServer)
{
ui->setupUi(this);
this->resize(400, 500);
this->setWindowTitle("服务端");
// 创建一个 QTcpServer 实例
server = new QTcpServer(this);
// 让服务器在本地地址的4545端口上监听连接请求
bool succeed = server->listen(QHostAddress::LocalHost, 4545);
if (!succeed) {
qDebug() << "无法启动服务器:" << server->errorString();
return;
}
// 当有新的客户端连接时,触发 newConnection 信号
connect(server, &QTcpServer::newConnection, this, [=] {
ui->textEdit->setText("端口监听成功!");
// 获取新的客户端连接
clientConnection = server->nextPendingConnection();
// 接收数据
connect(clientConnection, &QTcpSocket::readyRead, this, [=] {
QByteArray data = clientConnection->readAll();
if (data.isEmpty()) {
ui->textEdit->append("客户端发送空数据!");
} else {
ui->textEdit->append("客户端:" + QString::fromUtf8(data));
}
});
// 发送数据
connect(ui->pushButton_3, &QPushButton::clicked, this, [=] {
QString data = ui->textEdit_2->toPlainText();
if(clientConnection && clientConnection->state() == QAbstractSocket::ConnectedState) {
QByteArray byteArray = data.toUtf8();
clientConnection->write(byteArray);
ui->textEdit->append("服务端:" + data);
} else {
ui->textEdit->append("未连接到客户端!");
}
});
});
// 断开连接
connect(ui->pushButton_2, &QPushButton::clicked, this, [=] {
if (clientConnection) {
clientConnection->disconnectFromHost();
clientConnection->deleteLater();
clientConnection = nullptr;
ui->textEdit->append("连接已断开!");
}
});
}
TcpServer::~TcpServer()
{
delete ui;
}
注意,断开连接的部分不要放在newConnection
的信号对应的槽中,防止意外情况。
客服端
只需要引入QTcpSocket
套接字作为类的成员指针。
界面设计
界面设计,如图所示。
代码设计
tcpclient.h
#ifndef TCPCLIENT_H
#define TCPCLIENT_H
#include <QWidget>
#include <QTcpSocket>
namespace Ui {
class TcpClient;
}
class TcpClient : public QWidget
{
Q_OBJECT
public:
explicit TcpClient(QWidget *parent = nullptr);
~TcpClient();
public slots:
void connectServer();
void disconnectFromServer();
//发送数据
void sendData();
// 接收数据
void receiveData();
private:
Ui::TcpClient *ui;
// 套接字指针
QTcpSocket *socket;
// ip地址和端口号
QString hostAddress;
quint16 port;
};
#endif // TCPCLIENT_H
tcpclient.cpp
#include "tcpclient.h"
#include "ui_tcpclient.h"
TcpClient::TcpClient(QWidget *parent) :
QWidget(parent),
ui(new Ui::TcpClient)
{
ui->setupUi(this);
this->resize(450,550);
this->setWindowTitle("客服端");
socket = new QTcpSocket(this);
// 回显默认端口和ip地址
ui->lineEdit->setText("4545");
ui->lineEdit_2->setText("127.0.0.1");
// 从输入框读取端口号和ip地址
bool ok;
port = ui->lineEdit->text().toShort(&ok);
hostAddress = ui->lineEdit_2->text();
// 当连接到服务器成功时执行以下操作
connect(socket, &QTcpSocket::connected, this, [=] {//1
ui->textEdit->setText("服务器连接成功!");
// 发送数据
connect(ui->pushButton_3, &QPushButton::clicked, this, &TcpClient::sendData);
// 接收数据
connect(socket, &QTcpSocket::readyRead, this, &TcpClient::receiveData);
// 断开连接
connect(ui->pushButton_2, &QPushButton::clicked, this, &TcpClient::disconnectFromServer);
});
// 连接到客服端
connect(ui->pushButton, &QPushButton::clicked, this, &TcpClient::connectServer);//2
}
TcpClient::~TcpClient()
{
delete ui;
}
// 连接到服务器
void TcpClient::connectServer() {
socket->connectToHost(hostAddress, port); // 连接到指定的主机和端口
}
// 断开与服务器的连接
void TcpClient::disconnectFromServer() {
ui->textEdit->setText("连接已断开!");
socket->disconnectFromHost();
if (socket->state() == QAbstractSocket::UnconnectedState) {
ui->textEdit->append("已断开连接,不能再发送数据。");
}
}
// 发送数据到服务器
void TcpClient::sendData() {
if (socket->state() == QAbstractSocket::ConnectedState) {
QString text = ui->textEdit_2->toPlainText();
QString formattedText = QString("<div style='color:blue;'>客服端:%1</div>").arg(text);
ui->textEdit->append(formattedText);
socket->write(text.toUtf8());
} else {
ui->textEdit->append("连接已断开,无法发送数据。");
}
}
// 接收服务器发来的数据
void TcpClient::receiveData() {
QByteArray data = socket->readAll();
QString text = QString("<div style='color:purple;'>%1</div>").arg(QString::fromUtf8(data));
ui->textEdit->append("服务端:"+text);
}
// 2和1的顺序应该不要交换
不同于服务端的地方在于,把槽函数从lambda中抽离出来。
验证
在项目启动之前,一定要保证,服务端在客服端之前启动,如下面的main函数所示:
#include "widget.h"
#include "tcpclient.h"
#include "tcpserver.h"
#include <QApplication>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
Widget w;
w.show();
TcpServer ts;// 先启动服务端
ts.show();
TcpClient t;
t.show();
return a.exec();
}
在客户端界面点击连接服务器,进行验证。
连接之后分别发送数据。
从客服端断开。
重新连接,然后从服务端断开。