Qt开发:QTcpSocket的详解

一、QTcpSocket 简介

  QTcpSocket 是 Qt 网络模块中用于实现基于 TCP 协议的客户端通信的类。它提供了一个面向流的接口,允许程序通过套接字连接到远程主机,发送和接收数据。

  • 所属模块:QtNetwork
  • 用于建立 TCP 客户端连接,读取/写入数据。
  • 特点:异步非阻塞(配合信号槽机制)、支持事件驱动编程模型。

二、常用方法的介绍和使用

2.1 void QTcpSocket::connectToHost(hostname, port)
用于发起 TCP 连接的核心函数,常用于客户端向服务器发起连接请求。

参数说明:
在这里插入图片描述
使用示例:

QTcpSocket* socket = new QTcpSocket(this);

// 连接信号槽
connect(socket, &QTcpSocket::connected, this, [](){
    qDebug() << "Connected to server.";
});
connect(socket, &QTcpSocket::errorOccurred, this, [](QAbstractSocket::SocketError err){
    qDebug() << "Connection error:" << err;
});

// 发起连接
socket->connectToHost("127.0.0.1", 12345);

阻塞等待连接:
如果在非 GUI 程序中使用,或者想同步等待连接结果,可以使用:

socket->connectToHost("127.0.0.1", 12345);
if (socket->waitForConnected(3000)) {
    qDebug() << "Connected!";
} else {
    qDebug() << "Connection failed:" << socket->errorString();
}

常见错误排查:
在这里插入图片描述
2.2 void QAbstractSocket::disconnectFromHost()
用于主动断开 TCP 连接的函数。它的作用是通知对方:客户端希望关闭连接,然后进行连接释放。

使用说明:
当调用 disconnectFromHost() 后:

  1. 客户端发送 TCP 的 FIN 包;
  2. 如果连接关闭成功,触发 disconnected() 信号;
  3. 如果对方已经关闭连接,你仍然可以调用它释放资源;
  4. 若要强制关闭连接,可以使用 abort()。

基本示例:

QTcpSocket* socket = new QTcpSocket(this);

// 建立连接
socket->connectToHost("127.0.0.1", 12345);
connect(socket, &QTcpSocket::connected, this, [socket]() {
    qDebug() << "Connected to server.";

    // 写入数据后断开
    socket->write("Bye Server!");
    socket->flush();  // 确保发送出去了
    socket->disconnectFromHost();
});

connect(socket, &QTcpSocket::disconnected, this, []() {
    qDebug() << "Disconnected from server.";
});

同步等待断开:

socket->disconnectFromHost();
if (socket->state() != QAbstractSocket::UnconnectedState) {
    socket->waitForDisconnected(3000);  // 最多等 3 秒
}

与 abort() 的区别:
在这里插入图片描述
建议:正常退出时用 disconnectFromHost();发生错误/异常时使用 abort() 立即关闭连接。

2.3 qint64 QIODevice::write(const char *data, qint64 maxSize)
它在客户端和服务端之间发送字节流,是实现 socket 通信的核心方法之一。

参数说明:

  • data:指向要发送的原始数据。
  • maxSize:要发送的数据长度(字节数)。

返回值:返回实际写入的字节数,如果返回 -1,表示写入失败。

注意事项:

  • write() 只将数据写入到内部缓冲区,并不保证立刻发送。
  • 若想确保数据被真正写出,可使用 flush() 或等待 bytesWritten(qint64) 信号。
  • 如果 socket 未连接,调用 write() 会失败。

示例代码:
示例 1:发送一个 C 风格字符串

QTcpSocket *socket = new QTcpSocket(this);
socket->connectToHost("127.0.0.1", 8888);

if (socket->waitForConnected(3000)) {
    const char *msg = "Hello from client!";
    qint64 bytesWritten = socket->write(msg, strlen(msg));
    qDebug() << "Bytes written:" << bytesWritten;
}

示例 2:发送结构体(二进制数据)

struct MyData {
    int id;
    float value;
    char name[16];
};

MyData data = {42, 3.14f, "example"};
qint64 size = sizeof(data);

QTcpSocket *socket = new QTcpSocket(this);
socket->connectToHost("127.0.0.1", 8888);

if (socket->waitForConnected(3000)) {
    qint64 bytesWritten = socket->write(reinterpret_cast<const char*>(&data), size);
    qDebug() << "Sent struct of size:" << bytesWritten;
}

示例3:写入大量数据
如果是大量数据,可能无法一次性全部写入完。这时候返回值可能小于你提供的 maxSize,即只写入了一部分数据。这是因为:

  • QTcpSocket 的底层发送缓冲区是有限的;
  • 写入时如果缓冲区满了,write() 只写部分内容或返回 -1;
  • 它是非阻塞的,不会等待所有数据写入成功才返回。

正确处理方式:分段循环写入 + 监听 bytesWritten()

class MySender : public QObject {
    Q_OBJECT

public:
    MySender(QTcpSocket *sock, QObject *parent = nullptr)
        : QObject(parent), socket(sock), totalSize(0), bytesSent(0), buffer(nullptr) {
	connect(socket, &QTcpSocket::bytesWritten, this, &MySender::onBytesWritten);
}

    void sendLargeData(const char *data, qint64 size) {
        buffer = data;
        totalSize = size;
        bytesSent = 0;
        
        writeMore();
    }

private slots:
    void onBytesWritten(qint64 bytes) {
        Q_UNUSED(bytes);
    }

private:
    void writeMore() {
        const int CHUNK_SIZE = 64 * 1024; // 每次最多写 64KB

        while (bytesSent < totalSize) {
            qint64 remaining = totalSize - bytesSent;
            qint64 toWrite = qMin(remaining, static_cast<qint64>(CHUNK_SIZE));

            qint64 written = socket->write(buffer + bytesSent, toWrite);
            if (written == -1) {
                qDebug() << "Write failed!";
                return;
            }

            bytesSent += written;

            if (written < toWrite) {
                // 写缓冲区满了,等下次 bytesWritten 再继续
                break;
            }
        }

        if (bytesSent == totalSize) {
            qDebug() << "All data sent.";
            disconnect(socket, &QTcpSocket::bytesWritten, this, &MySender::onBytesWritten);
        }
    }

    QTcpSocket *socket;
    const char *buffer;
    qint64 totalSize;
    qint64 bytesSent;
};

使用方式:

MySender *sender = new MySender(socket, this);
sender->sendLargeData(bigBuffer, bigSize);

2.4 qint64 QIODevice::write(const QByteArray &byteArray)
参数说明:byteArray是要写入的数据,封装在一个 QByteArray 对象中。

返回值:

  • 返回实际写入的字节数。
  • 返回 -1 表示写入失败(如设备未打开或不可写)。

使用示例:
示例 1:使用 QByteArray 向服务器发送文本数据

QTcpSocket *socket = new QTcpSocket(this);
socket->connectToHost("127.0.0.1", 12345);

if (socket->waitForConnected(3000)) {
    QByteArray data = "Hello, server!";
    qint64 bytesWritten = socket->write(data);
    socket->flush(); // 建议 flush 确保尽快发送
    qDebug() << "Bytes written:" << bytesWritten;
}

示例 2:构造二进制数据并发送

QByteArray data;
QDataStream stream(&data, QIODevice::WriteOnly);
stream.setByteOrder(QDataStream::LittleEndian);

// 写入数据(可用来发送结构化内容)
int id = 42;
float value = 3.14f;
stream << id << value;

socket->write(data);

示例 3:文件读取并通过 socket 发送(推荐使用 QByteArray)

QFile file("image.jpg");
if (file.open(QIODevice::ReadOnly)) {
    QByteArray fileData = file.readAll(); // 读入整个文件内容
    socket->write(fileData);
}

使用 QByteArray + 分段写入大数据的完整示例:
LargeDataSender.h

#ifndef LARGEDATASENDER_H
#define LARGEDATASENDER_H

#include <QObject>
#include <QTcpSocket>

class LargeDataSender : public QObject
{
    Q_OBJECT

public:
    explicit LargeDataSender(QTcpSocket *socket, QObject *parent = nullptr);
    void send(const QByteArray &data);  // 启动发送过程

private slots:
    void onBytesWritten(qint64 bytes);

private:
    void writeNextChunk();

    QTcpSocket *m_socket;
    QByteArray m_data;
    qint64 m_totalSize;
    qint64 m_bytesSent;
    const int CHUNK_SIZE = 64 * 1024; // 每次写 64KB
};

#endif // LARGEDATASENDER_H

LargeDataSender.cpp

#include "LargeDataSender.h"
#include <QDebug>

LargeDataSender::LargeDataSender(QTcpSocket *socket, QObject *parent)
    : QObject(parent), m_socket(socket), m_totalSize(0), m_bytesSent(0)
{
    connect(m_socket, &QTcpSocket::bytesWritten, this, &LargeDataSender::onBytesWritten);
}

void LargeDataSender::send(const QByteArray &data)
{
    m_data = data;
    m_totalSize = data.size();
    m_bytesSent = 0;

    writeNextChunk(); // 立即尝试写第一段
}

void LargeDataSender::writeNextChunk()
{
    while (m_bytesSent < m_totalSize) {
        qint64 remaining = m_totalSize - m_bytesSent;
        qint64 toWrite = qMin(remaining, static_cast<qint64>(CHUNK_SIZE));

        qint64 written = m_socket->write(m_data.constData() + m_bytesSent, toWrite);
        if (written == -1) {
            qWarning() << "Socket write failed!";
            return;
        }

        m_bytesSent += written;

        if (written < toWrite) {
            // 写入未完全,可能缓冲区满了,等 bytesWritten 再继续
            break;
        }
    }

    if (m_bytesSent == m_totalSize) {
        qDebug() << "All data sent (" << m_totalSize << " bytes)";
        disconnect(m_socket, &QTcpSocket::bytesWritten, this, &LargeDataSender::onBytesWritten);
    }
}

void LargeDataSender::onBytesWritten(qint64 /*bytes*/)
{
    writeNextChunk(); // 写入下一段数据
}

使用方法:

QTcpSocket *socket = new QTcpSocket(this);
socket->connectToHost("127.0.0.1", 12345);

if (socket->waitForConnected(3000)) {
    QFile file("largefile.bin");
    if (file.open(QIODevice::ReadOnly)) {
        QByteArray fileData = file.readAll();

        LargeDataSender *sender = new LargeDataSender(socket, this);
        sender->send(fileData); // 启动分段发送
    }
}

2.5 QByteArray QIODevice::read(qint64 maxSize)

参数说明:maxSize是要读取的最大字节数。实际读取的可能小于 maxSize,具体取决于设备中当前可用的数据量。

返回值

  • 返回一个 QByteArray,包含读取到的数据。
  • 如果没有数据可读,则返回空的 QByteArray(不是 nullptr)。
  • 如果读取失败(如设备未打开),返回空。

使用示例(基于 QTcpSocket)
示例 1:简单读取 socket 中的数据

connect(socket, &QTcpSocket::readyRead, this, [=]() {
    QByteArray data = socket->read(1024); // 最多读取 1024 字节
    qDebug() << "Received:" << data;
});

示例 2:持续读取直到没有更多数据(适合大数据)

connect(socket, &QTcpSocket::readyRead, this, [=]() {
    while (socket->bytesAvailable() > 0) {
        QByteArray chunk = socket->read(4096); // 每次读 4KB
        processData(chunk);
    }
});

注意事项
在这里插入图片描述
示例 3:读取带长度前缀的结构化数据
这种方式适合从 TCP 中接收完整消息(防止粘包问题)。

void MyReceiver::onReadyRead()
{
    QDataStream in(socket);
    in.setByteOrder(QDataStream::BigEndian);

    static quint32 expectedSize = 0;

    while (true) {
        if (expectedSize == 0) {
            if (socket->bytesAvailable() < sizeof(quint32))
                return;

            in >> expectedSize; // 先读长度
        }

        if (socket->bytesAvailable() < expectedSize)
            return;

        QByteArray payload = socket->read(expectedSize);
        processPayload(payload);
        expectedSize = 0; // 重置准备读下一包
    }
}

2.6 QByteArray QIODevice::readAll() const
功能说明:

  • 读取并返回 socket 缓冲区中当前已接收到的全部数据。
  • 不会阻塞,只能读取当前可用的部分数据(TCP 流可能分段到达)。
  • 回一个 QByteArray,包含已读数据;若没有数据,返回空。

使用示例:
示例 1:读取客户端发来的完整数据(非结构化协议)

connect(socket, &QTcpSocket::readyRead, this, [=]() {
    QByteArray data = socket->readAll();
    qDebug() << "Received:" << data;
});

2.7 bool QIODevice::isOpen() const

作用:判断 socket(或其他 IO 设备)是否被成功打开。对 QTcpSocket 来说,打开意味着 connectToHost() 已成功调用,并进入连接状态(ConnectedState)。

使用示例

QTcpSocket *socket = new QTcpSocket(this);
socket->connectToHost("127.0.0.1", 12345);

if (socket->waitForConnected(3000)) {
    if (socket->isOpen()) {
        qDebug() << "Socket is open.";
    }
}

2.8 bool QAbstractSocket::isValid() const

作用:判断 socket 的底层文件描述符是否有效。也就是它是否关联了一个系统资源(如 socket fd)。
即使 isOpen() 返回 true,isValid() 也可能是 false,如果底层 socket 描述符丢失或已关闭。

使用示例

if (socket->isValid()) {
    qDebug() << "Socket is valid (underlying descriptor exists).";
}

区别总结
在这里插入图片描述
建议使用方式

if (socket->isOpen() && socket->isValid()) {
    socket->write("ping");
}

检测当前 socket 是否仍处于有效连接状态

bool isSocketConnected(QTcpSocket *socket)
{
    if (!socket)
        return false;

    // 判断 socket 是否处于 ConnectedState
    if (socket->state() != QAbstractSocket::ConnectedState)
        return false;

    // 是否逻辑打开
    if (!socket->isOpen())
        return false;

    // 底层 socket 是否还有效
    if (!socket->isValid())
        return false;

    // 可选:检测是否最近有错误
    if (socket->error() != QAbstractSocket::UnknownSocketError)
        return false;

    return true;
}

使用方法:

if (isSocketConnected(socket)) {
    socket->write("Hello\n");
} else {
    qDebug() << "Socket disconnected or invalid!";
}

2.9 bool QAbstractSocket::waitForConnected(int msecs = 30000)

参数说明:msecs是最长等待时间,单位是毫秒(ms)默认是 30000ms(30秒)

返回值:true表示成功连接到服务器,false表示超时、错误,未连接成功

使用示例(客户端连接服务器)

QTcpSocket *socket = new QTcpSocket(this);
socket->connectToHost("127.0.0.1", 12345); // 发起连接

if (socket->waitForConnected(3000)) { // 最多等待 3 秒
    qDebug() << "Connected to server!";
    socket->write("hello");
} else {
    qWarning() << "Connection failed:" << socket->errorString();
}

2.10 bool QIODevice::waitForReadyRead(int msecs)
是 Qt 中 QIODevice 提供的一个阻塞函数,常用于 QTcpSocket 等设备,等待有数据可读。当你想同步读取数据时,这个函数非常实用。

参数说明:最多等待时间(单位:毫秒)默认由我们传入,例如 3000 表示最多等 3 秒

返回值:true表示已有数据可读,readyRead() 被触发,false表示超时或设备出错,无数据可读

使用示例(读取数据)

QTcpSocket socket;
socket.connectToHost("127.0.0.1", 12345);

if (socket.waitForConnected(3000)) {
    socket.write("hello");

    if (socket.waitForReadyRead(3000)) {
        QByteArray data = socket.readAll();
        qDebug() << "Received:" << data;
    } else {
        qWarning() << "No response from server, timeout!";
    }
} else {
    qWarning() << "Failed to connect to server!";
}

三、常用的信号函数

在这里插入图片描述
基本使用示例:

#include <QTcpSocket>
#include <QDebug>

class MyTcpClient : public QObject {
    Q_OBJECT
public:
    MyTcpClient(QObject* parent = nullptr) : QObject(parent) {
        socket = new QTcpSocket(this);

        connect(socket, &QTcpSocket::connected, this, &MyTcpClient::onConnected);
        connect(socket, &QTcpSocket::readyRead, this, &MyTcpClient::onReadyRead);
        connect(socket, &QTcpSocket::disconnected, this, &MyTcpClient::onDisconnected);
        connect(socket, &QTcpSocket::error, this, &MyTcpClient::onError);

        socket->connectToHost("127.0.0.1", 12345);  // 连接服务器
    }

private slots:
    void onConnected() {
        qDebug() << "Connected to server.";
        socket->write("Hello, Server!");  // 向服务器发送数据
    }

    void onReadyRead() {
        QByteArray data = socket->readAll();
        qDebug() << "Received from server:" << data;
    }

    void onDisconnected() {
        qDebug() << "Disconnected from server.";
    }

    void onError(QAbstractSocket::SocketError socketError) {
        qDebug() << "Socket error:" << socketError << socket->errorString();
    }

private:
    QTcpSocket* socket;
};

注意事项:

  • QTcpSocket 一般在主线程使用,不阻塞 UI,数据通过信号处理;
  • 使用 write() 后可能不会立刻发送,建议配合 flush() 或等待 bytesWritten;
  • 注意处理粘包和拆包问题,TCP 是面向流的协议;
  • 可与 QDataStream 配合使用实现结构化数据传输。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值