QT:TCP示例
QT 中 TCP 服务器端和客户端
在 QT 中,TCP(传输控制协议)是一种可靠的网络通信协议,常用于在客户端和服务器之间进行数据传输。服务器端负责监听特定端口,等待客户端连接;客户端则主动发起连接请求,连接到服务器端指定的 IP 地址和端口。
服务器
recvimg.h
#ifndef RECV_H
#define RECV_H
#include <QTcpServer>
#include <QTcpSocket>
#include <QObject>
#include <QByteArray>
constexpr int TCP_BUFFER_SIZE = 32 * 1024 * 1024;
class RecvServer : public QTcpServer
{
Q_OBJECT
public:
explicit RecvServer(QObject *parent = nullptr);//explicit 禁止隐式类型转换
~RecvServer();
bool startServer(quint16 port);
signals:
void get_img(unsigned char*, struct U2DRealImgHead*);
private slots:
void onNewConnection();
void onReadyRead();
void onDisconnected();
private:
QByteArray buffer;
};
#endif // RECV_H
recvimg.cpp
#include "recvimg.h"
#include <QDebug>
RecvServer::RecvServer(QObject *parent) : QTcpServer(parent)
{
qDebug() << "RecvServer";
}
RecvServer::~RecvServer()
{
if (isListening()) {
close();
}
}
bool RecvServer::startServer(quint16 port)
{
bool result = listen(QHostAddress::Any, port);
if (result) {
qDebug() << "Server started and listening on port" << port;
} else {
qDebug() << "Failed to start server on port" << port;
}
connect(this, &RecvServer::newConnection, this, &RecvServer::onNewConnection);
return result;
}
void RecvServer::onNewConnection()
{
QTcpSocket *socket = nextPendingConnection();
connect(socket, &QTcpSocket::readyRead, this, &RecvServer::onReadyRead);
connect(socket, &QTcpSocket::disconnected, this, &RecvServer::onDisconnected);
qDebug() << "New client connected";
}
void RecvServer::onReadyRead()
{
QTcpSocket *socket = qobject_cast<QTcpSocket*>(sender());
if (!socket) return;
QByteArray data = socket->readAll();
buffer.append(data);
qDebug() << "Received" << data.size() << "bytes of data";
}
void RecvServer::onDisconnected()
{
QTcpSocket *socket = qobject_cast<QTcpSocket*>(sender());
if (!socket) return;
socket->deleteLater();
qDebug() << "Client disconnected";
}
main.cpp
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::MainWindow)
{
ui->setupUi(this);
xQLabel = new QLabel(this);
//给服务器指针实例化对象
server = new RecvServer(this); //服务器创建完成
quint16 port = 27015;
if (server->startServer(27015)) {
qDebug() << "Server started on port 27015";
} else {
qDebug() << "Failed to start server";
}
connect(server, &RecvServer::get_img, this, &MainWindow::imgCallback);
}
client
client.h
#ifndef CLIENT_H
#define CLIENT_H
#include <QObject>
#include <QTcpSocket>
class Client : public QObject
{
Q_OBJECT
public:
explicit Client(QObject *parent = nullptr);
~Client();
bool connectToServer(const QString &host, quint16 port);
void sendData(const QByteArray &data);
private slots:
void onConnected();
void onDisconnected();
void onReadyRead();
void onError(QAbstractSocket::SocketError socketError);
private:
QTcpSocket *m_socket;
};
#endif // CLIENT_H
client.cpp
#include "fileclient.h"
#include <QDebug>
Client::Client(QObject *parent) : QObject(parent)
{
m_socket = new QTcpSocket(this);
connect(m_socket, &QTcpSocket::connected, this, &Client::onConnected);
connect(m_socket, &QTcpSocket::disconnected, this, &Client::onDisconnected);
connect(m_socket, &QTcpSocket::readyRead, this, &Client::onReadyRead);
// 使用 errorOccurred 信号替代已弃用的 error 信号
connect(m_socket, &QTcpSocket::errorOccurred, this, &Client::onError);
}
Client::~Client()
{
if (m_socket->state() == QAbstractSocket::ConnectedState) {
m_socket->disconnectFromHost();
m_socket->waitForDisconnected();
}
}
bool Client::connectToServer(const QString &host, quint16 port)
{
m_socket->connectToHost(host, port);
return m_socket->waitForConnected();
}
void Client::sendData(const QByteArray &data)
{
if (m_socket->state() == QAbstractSocket::ConnectedState) {
m_socket->write(data);
m_socket->waitForBytesWritten();
}
}
void Client::onConnected()
{
qDebug() << "Connected to server";
}
void Client::onDisconnected()
{
qDebug() << "Disconnected from server";
}
void Client::onReadyRead()
{
QByteArray data = m_socket->readAll();
qDebug() << "Received from server:" << data;
}
void Client::onError(QAbstractSocket::SocketError socketError)
{
qDebug() << "Socket error:" << m_socket->errorString();
}
main.cpp
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::MainWindow)
{
ui->setupUi(this);
Client client;
if (client.connectToServer("192.168.20.48", 27015)) {
// 发送测试数据,这里添加分隔符 "23456"
QByteArray testData = "This is a test data segmen23456Another test data 23456";
client.sendData(testData);
}
}
定义的区别
RecvServer* server; server = new RecvServer(this);
RecvServer server; 定义的区别
- 内存分配
RecvServer* server; server = new RecvServer(this);:
首先声明了一个RecvServer类型的指针server。
接着使用new操作符在堆(heap)上分配内存来创建一个RecvServer对象,并将对象的地址赋值给server指针。堆内存的分配较为灵活,适用于需要在程序运行期间动态创建和销毁对象,且对象生命周期可能跨越多个函数调用或作用域的场景。例如,在一个需要长期运行的服务器程序中,服务器对象可能需要在整个程序生命周期内持续存在,此时在堆上分配内存更为合适。
RecvServer server;:
直接在栈(stack)上创建一个RecvServer对象。栈内存的分配和释放由编译器自动管理,速度相对较快。栈内存的大小通常是有限的,且对象的生命周期与声明它的作用域紧密相关。例如,在一个简单的函数内部,如果只是临时需要一个RecvServer对象来完成一些短期任务,在栈上创建即可,当函数执行结束,对象会自动被销毁,释放占用的栈空间。
2. 生命周期管理
RecvServer* server; server = new RecvServer(this);:
由于对象在堆上分配,其生命周期不由声明它的作用域决定。需要程序员手动调用delete操作符来释放内存,否则会导致内存泄漏。例如,如果在一个类的成员函数中创建了这样的对象,在类的析构函数中需要检查该指针并调用delete,以确保对象占用的内存被正确释放。
RecvServer server;:
当对象所在的作用域结束时,编译器会自动调用对象的析构函数,释放对象占用的栈内存。例如,在一个函数内部声明的RecvServer对象,当函数执行完毕,对象会被自动销毁,无需手动干预。
3. 作用域
RecvServer* server; server = new RecvServer(this);:
只要指针server在作用域内有效,指向的对象就可以被访问,即使创建对象的作用域已经结束。例如,在一个函数中创建了堆上的RecvServer对象并返回指针,在调用该函数的其他函数中仍然可以通过该指针访问对象。
RecvServer server;:
对象的作用域仅限于声明它的代码块。例如,在一个函数内部的if语句块中声明的RecvServer对象,当if语句块结束,对象就超出了作用域,无法再被访问。
QByteArray buffer 的接口使用
QByteArray 是 Qt 框架中用于处理字节数组的类,它提供了丰富的接口,方便进行数据的存储、操作和转换。
构造函数
QByteArray 有多种构造函数,可用于创建不同类型的字节数组。
#include <QCoreApplication>
#include <QByteArray>
#include <QDebug>
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
// 构造一个空的 QByteArray
QByteArray emptyArray;
// 从 const char* 构造 QByteArray
const char* str = "Hello, World!";
QByteArray arrayFromCStr(str);
// 构造指定大小并填充特定字符的 QByteArray
QByteArray filledArray(10, 'A');
qDebug() << "Empty Array:" << emptyArray;
qDebug() << "Array from C string:" << arrayFromCStr;
qDebug() << "Filled Array:" << filledArray;
return a.exec();
}
追加数据
可以使用 append() 方法向 QByteArray 中追加数据
#include <QCoreApplication>
#include <QByteArray>
#include <QDebug>
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
QByteArray array("Hello");
array.append(", World!");
qDebug() << "Appended Array:" << array;
return a.exec();
}
插入数据
使用 insert() 方法在指定位置插入数据。
#include <QCoreApplication>
#include <QByteArray>
#include <QDebug>
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
QByteArray array("Hello World!");
array.insert(5, " Qt");
qDebug() << "Inserted Array:" << array;
return a.exec();
}
删除数据
使用 remove() 方法删除指定位置和长度的数据。
#include <QCoreApplication>
#include <QByteArray>
#include <QDebug>
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
QByteArray array("Hello Qt World!");
array.remove(5, 3);
qDebug() << "Removed Array:" << array;
return a.exec();
}
访问单个字节
可以使用 [] 运算符或 at() 方法访问 QByteArray 中的单个字节。
#include <QCoreApplication>
#include <QByteArray>
#include <QDebug>
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
QByteArray array("Hello");
// 使用 [] 运算符访问
char firstChar1 = array[0];
// 使用 at() 方法访问
char firstChar2 = array.at(0);
qDebug() << "First character using []:" << firstChar1;
qDebug() << "First character using at():" << firstChar2;
return a.exec();
}
转换为 const char*
使用 constData() 方法将 QByteArray 转换为 const char*。
#include <QCoreApplication>
#include <QByteArray>
#include <QDebug>
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
QByteArray array("Hello, World!");
const char* cStr = array.constData();
qDebug() << "Converted C string:" << cStr;
return a.exec();
}
转换为整数
使用 toInt() 方法将 QByteArray 转换为整数。
#include <QCoreApplication>
#include <QByteArray>
#include <QDebug>
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
QByteArray array("123");
bool ok;
int num = array.toInt(&ok);
if (ok) {
qDebug() << "Converted integer:" << num;
} else {
qDebug() << "Conversion failed.";
}
return a.exec();
}
signals
在 Qt 中,signals 是一个特殊的关键字,用于标识接下来的内容是信号的声明。信号是 Qt 实现事件驱动编程的一种机制,当特定的事件发生时,对象可以发出信号,其他对象可以连接到这些信号并执行相应的槽函数(slot)。信号只能在继承自 QObject 类的子类中声明,并且需要在类定义中使用 Q_OBJECT 宏。