QT学习(20):QTcpSocket和QAbstractSocket

2023-12-15 04:44:12

QAbstractSocket

QAbstractSocket 是QTcpSocket和QUdpSocket的基类,并且包含这两个类的所有通用功能。如果需要套接字,有两种选择:

  • 实例化一个QTcpSocket或QUdpSocket对象
  • 创建一个本机套接字描述符,实例化 QAbstractSocket,然后调用setSocketDescriptor()来包装本机套接字。

TCP(传输控制协议)是一种可靠的、面向流的、面向连接的传输协议。UDP(用户数据报协议)是一种不可靠的、面向数据报的无连接协议。在实践中,这意味着 TCP 更适合连续传输数据,而当可靠性不重要时,可以使用更轻量级的 UDP。

建立连接的函数和信号

QAbstractSocket 的 API 统一了两种协议之间的大部分差异。例如,尽管 UDP 是无连接的,但connectToHost()会为 UDP 套接字建立虚拟连接,从而能够以或多或少相同的方式使用 QAbstractSocket,而不管底层协议如何。在内部,QAbstractSocket 会记住传递给connectToHost()的地址和端口,而read()write()等函数会使用这些值。

在任何时候,QAbstractSocket 都有一个状态(由state()返回)。初始状态为UnconnectedState。调用connectToHost()后,套接字首先进入HostLookupState。如果找到主机,QAbstractSocket 将进入ConnectingState并发出hostFound()信号。建立连接后,它会进入ConnectedState并发出connected()。如果在任何阶段发生错误,则会发出errorOccurred()。每当状态发生变化时,都会发出stateChanged()。为方便起见,如果套接字已准备好进行读取和写入,则返回isValid(),但请注意,在读取和写入发生之前套接字的状态必须是ConnectedState

// ### Qt6: make the first one virtual
    bool bind(const QHostAddress &address, quint16 port = 0, BindMode mode = DefaultForPlatform);
    bool bind(quint16 port = 0, BindMode mode = DefaultForPlatform);

    // ### Qt6: de-virtualize connectToHost(QHostAddress) overload
    virtual void connectToHost(const QString &hostName, quint16 port, OpenMode mode = ReadWrite, NetworkLayerProtocol protocol = AnyIPProtocol);
    virtual void connectToHost(const QHostAddress &address, quint16 port, OpenMode mode = ReadWrite);
    virtual void disconnectFromHost();
    void abort();

通过调用read()write()读取或写入数据,或使用便利函数readLine()和write()。QAbstractSocket 还继承了getChar()putChar()ungetChar(),它们适用于单个字节。当数据写入套接字时,会发出btyeWritten()信号。请注意,Qt不限制写入缓冲区的大小。您可以通过收听此信号来监控其大小。

每当有新的数据块到达时,都会发出readyRead()信号。bytesAvailable()然后返回可用于读取的字节数。通常,会将readyRead()信号连接到插槽并读取其中的所有可用数据。如果您没有一次读取所有数据,则剩余数据稍后仍将可用,并且任何新的传入数据都将附加到 QAbstractSocket 的内部读取缓冲区中。要限制读取缓冲区的大小,请调用setReadBufferSize()

要关闭套接字,请调用disconnectFromHost()。QAbstractSocket进入ClosingState。将所有挂起的数据写入套接字后,QAbstractSocket 实际上会关闭套接字,进入UnconnectedState,然后发出disConnected()。如果要立即中止连接,丢弃所有挂起的数据,请改为调用abort()。如果远程主机关闭连接,QAbstractSocket 将发出errorOccurred(),在此期间套接字状态仍为ConnectedState,然后发出disConnected()信号。

Q_SIGNALS:
    void hostFound();
    void connected();
    void disconnected();
    void stateChanged(QAbstractSocket::SocketState);
    void error(QAbstractSocket::SocketError);
#ifndef QT_NO_NETWORKPROXY
    void proxyAuthenticationRequired(const QNetworkProxy &proxy, QAuthenticator *authenticator);
#endif

通过调用peerPort()peerAddress()获取连接对象的端口和地址。peerName()返回传递给connectToHost()的连接对象的主机名。localPort()localAddress()返回本地套接字的端口和地址。

阻塞功能

QAbstractSocket 提供了一组函数,用于挂起调用线程,直到发出某些信号。这些函数可用于实现阻塞套接字:

  • waitForConnected()阻塞,直到建立连接。
  • waitForReadyRead()阻塞,直到有新数据可供读取。
  • waitForBytesWritten()阻塞,直到将一个数据有效负载写入套接字。
  • waitForDisconnected()阻塞,直到连接关闭。
// for synchronous access
    virtual bool waitForConnected(int msecs = 30000);
    bool waitForReadyRead(int msecs = 30000) override;
    bool waitForBytesWritten(int msecs = 30000) override;
    virtual bool waitForDisconnected(int msecs = 30000);

使用阻塞套接字进行编程与使用非阻塞套接字进行编程截然不同。阻塞套接字不需要事件循环,通常会导致代码更简单。但是,在 GUI 应用程序中,阻塞套接字应仅在非 GUI 线程中使用,以避免冻结用户界面。

注意:不鼓励将阻塞功能与信号一起使用。应使用其中之一。

class Q_NETWORK_EXPORT QAbstractSocket : public QIODevice
{
    Q_OBJECT
public:
    enum SocketType {TcpSocket,UdpSocket,SctpSocket,UnknownSocketType = -1 };
    //enum NetworkLayerProtocol,SocketError,SocketState,SocketOption,BindFlag,PauseMode
    QAbstractSocket(SocketType socketType, QObject *parent);
    virtual ~QAbstractSocket();
    //bind+connectToHost+disConnected
    //localPort\localAddress\peerPort\peerAddress\peerName\canReadLine\readBufferSize、bytesAvailable、bytesToWrite\setReadBufferSize
    //.................
protected:
    qint64 readData(char *data, qint64 maxlen) override;
    qint64 readLineData(char *data, qint64 maxlen) override;
    qint64 writeData(const char *data, qint64 len) override;
//setSocketState\setSocketError\setLocalPort\setLocalAddress\setPeerPort\setPeerAddress\setPeerName
    QAbstractSocket(SocketType socketType, QAbstractSocketPrivate &dd, QObject *parent = nullptr);
private:
//..............
};

QTcpSocket

从QTcpSocket和其私有类的定义中可以知道QTcpSocket只是简单继承了QAbstractSocket。实例化对象调用的方法都是父类版本的虚函数。

class Q_NETWORK_EXPORT QTcpSocket : public QAbstractSocket
{
    Q_OBJECT
public:
    explicit QTcpSocket(QObject *parent = nullptr);
    virtual ~QTcpSocket();
protected:
    QTcpSocket(QTcpSocketPrivate &dd, QObject *parent = nullptr);
    QTcpSocket(QAbstractSocket::SocketType socketType, QTcpSocketPrivate &dd,
               QObject *parent = nullptr);
private:
    Q_DISABLE_COPY(QTcpSocket)
    Q_DECLARE_PRIVATE(QTcpSocket)
};
class QTcpSocketPrivate : public QAbstractSocketPrivate
{
    Q_DECLARE_PUBLIC(QTcpSocket)
};
QTcpSocket socket;
socket.bind(address, port);

QTcpSocket绑定本地IP地址和端口时调用的是QAbstractSocket版本的虚函数bind(),在内部调用d指针的bind(),QAbstractSocketPrivate版本。

*/
bool QAbstractSocket::bind(const QHostAddress &address, quint16 port, BindMode mode)
{
    Q_D(QAbstractSocket);
    return d->bind(address, port, mode);
}

在d指针的bind()函数内部,调用了QAbstractSocketEngine类型成员变量socketEngine的bind(),将socketEngine中的描述符赋值给cachedSocketDescriptor。并更改连接状态state,将socketEngine中的地址和端口赋值给d指针,发出状态改变的信号。

bool QAbstractSocketPrivate::bind(const QHostAddress &address, quint16 port, QAbstractSocket::BindMode mode)
{//...............................
	bool result = socketEngine->bind(address, port);
    cachedSocketDescriptor = socketEngine->socketDescriptor();
//........................
    state = QAbstractSocket::BoundState;
    localAddress = socketEngine->localAddress();
    localPort = socketEngine->localPort();

    emit q->stateChanged(state);
    //.............
}
d->socketEngine = QAbstractSocketEngine::createSocketEngine(socketDescriptor, this);
QAbstractSocketEngine *QAbstractSocketEngine::createSocketEngine(qintptr socketDescripter, QObject *parent)
{
    //.......................................
    return new QNativeSocketEngine(parent);
}

QAbstractSocketEngine基类指针对象socketEngine实际上保存的是一个QNativeSocketEngine子类对象。socketEngine的bind()函数为QNativeSocketEngine::bind(),在该函数内调用d指针的nativeBind()函数,为QNativeSocketEnginePrivate::nativeBind()

class Q_AUTOTEST_EXPORT QNativeSocketEngine : public QAbstractSocketEngine{};
bool QNativeSocketEngine::bind(const QHostAddress &address, quint16 port)
{
//......................
    if (!d->nativeBind(d->adjustAddressProtocol(address), port))
        return false;
//.............................
}

QNativeSocketEnginePrivate::nativeBind()在windows系统和unix系统中有不同的定义。以windows系统为例,在该函数中调用windows的API:setsockoptbind

bool QNativeSocketEnginePrivate::nativeBind(const QHostAddress &a, quint16 port)
{
	setPortAndAddress(port, address, &aa, &sockAddrSize);
	//..................
	::setsockopt(socketDescriptor, IPPROTO_IPV6, IPV6_V6ONLY, (char*)&ipv6only, sizeof(ipv6only) );
	//.........................
	int bindResult = ::bind(socketDescriptor, &aa.a, sockAddrSize);
}

connectToHost()等函数的定义和bind类似。比如readData()是定义在QIODevice中的纯虚函数,在QAbstractSocket中实现,在QAbstractSocket::readData()d->socketEngine->read(data, maxSize)调用QNativeSocketEngine::read(),在该函数中通过d指针调用QNativeSocketEnginePrivate::nativeRead(),在windows环境下最终通过调用windows的API:WSARecv完成读操作。在unix环境中调用qt_safe_read()完成读操作,在该函数中通过doWhile循环调用::read()

QAbstractSocketPrivate

QAbstractSocketPrivate定义的成员变量包括IP地址、端口等信息,还有套接字描述符、连接定时器和套接字引擎。其中套接字引擎socketEngine负责完成端口和IP地址绑定、连接等核心功能。

class QAbstractSocketPrivate : public QIODevicePrivate, public QAbstractSocketEngineReceiver
{
    Q_DECLARE_PUBLIC(QAbstractSocket)
public:
    QAbstractSocketPrivate();
    virtual ~QAbstractSocketPrivate();
    //................
    QString hostName;
    quint16 port;
    QHostAddress host;
    QList<QHostAddress> addresses;
    quint16 localPort;
    quint16 peerPort;
    QHostAddress localAddress;
    QHostAddress peerAddress;
    QString peerName;

    QAbstractSocketEngine *socketEngine;
    qintptr cachedSocketDescriptor;
//........................................
    QTimer *connectTimer;
//.....................................................
};

QAbstractSocketEngine和QNativeSocketEngine

QAbstractSocketEngine基类指针对象套接字引擎socketEngine实际上保存的是一个QNativeSocketEngine子类对象。QAbstractSocketEngine是一个抽象类,定义了bind、connectToHost、listen等一系列纯虚函数。

class Q_AUTOTEST_EXPORT QAbstractSocketEngine : public QObject
{
    Q_OBJECT
public:
    static QAbstractSocketEngine *createSocketEngine(QAbstractSocket::SocketType socketType, const QNetworkProxy &, QObject *parent);
    static QAbstractSocketEngine *createSocketEngine(qintptr socketDescriptor, QObject *parent);
//有省略.......................
    virtual bool connectToHost(const QHostAddress &address, quint16 port) = 0;
    virtual bool connectToHostByName(const QString &name, quint16 port) = 0;
    virtual bool bind(const QHostAddress &address, quint16 port) = 0;
    virtual bool listen() = 0;
//有省略.......................
    virtual qint64 read(char *data, qint64 maxlen) = 0;
    virtual qint64 write(const char *data, qint64 len) = 0;
//有省略.......................
};

QAbstractSocketEngine中定义的纯虚函数在QNativeSocketEngine中实现,在函数内部通过d指针调用私有类QNativeSocketEnginePrivate中对应的函数。

class Q_AUTOTEST_EXPORT QNativeSocketEngine : public QAbstractSocketEngine
{
    Q_OBJECT
public://有省略.......................
    bool connectToHost(const QHostAddress &address, quint16 port) override;
    bool connectToHostByName(const QString &name, quint16 port) override;
    bool bind(const QHostAddress &address, quint16 port) override;
   //有省略.......................
    qint64 read(char *data, qint64 maxlen) override;
    qint64 write(const char *data, qint64 len) override;
//有省略.......................
};
class QNativeSocketEnginePrivate : public QAbstractSocketEnginePrivate
{
    Q_DECLARE_PUBLIC(QNativeSocketEngine)
public://有省略.......................
    bool nativeConnect(const QHostAddress &address, quint16 port);
    bool nativeBind(const QHostAddress &address, quint16 port);
    bool nativeListen(int backlog);
    int nativeAccept();
//有省略.......................
    qint64 nativeRead(char *data, qint64 maxLength);
    qint64 nativeWrite(const char *data, qint64 length);
//有省略.......................
};

QAbstractSocketEnginePrivate中只保存了IP地址等信息,套接字引擎的私有类继承了这些成员变量,赋值给QAbstractSocket的成员变量,返回给QAbstractSocket的函数。

class QAbstractSocketEnginePrivate : public QObjectPrivate
{
    Q_DECLARE_PUBLIC(QAbstractSocketEngine)
public:
    QAbstractSocketEnginePrivate();

    mutable QAbstractSocket::SocketError socketError;
    mutable bool hasSetSocketError;
    mutable QString socketErrorString;
    QAbstractSocket::SocketState socketState;
    QAbstractSocket::SocketType socketType;
    QAbstractSocket::NetworkLayerProtocol socketProtocol;
    QHostAddress localAddress;
    quint16 localPort;
    QHostAddress peerAddress;
    quint16 peerPort;
    int inboundStreamCount;
    int outboundStreamCount;
    QAbstractSocketEngineReceiver *receiver;
};

文章来源:https://blog.csdn.net/qq_45088122/article/details/134794518
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。