目录
- QAbstractSocket
- 建立连接的函数和信号
- 阻塞功能
- QTcpSocket
- QAbstractSocketPrivate
- QAbstractSocketEngine和QNativeSocketEngine
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 virtualbool bind(const QHostAddress &address, quint16 port = 0, BindMode mode = DefaultForPlatform);bool bind(quint16 port = 0, BindMode mode = DefaultForPlatform);// ### Qt6: de-virtualize connectToHost(QHostAddress) overloadvirtual 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_NETWORKPROXYvoid proxyAuthenticationRequired(const QNetworkProxy &proxy, QAuthenticator *authenticator);
#endif
通过调用peerPort()
和peerAddress()
获取连接对象的端口和地址。peerName()返回传递给connectToHost()
的连接对象的主机名。localPort()
和localAddress()
返回本地套接字的端口和地址。
阻塞功能
QAbstractSocket 提供了一组函数,用于挂起调用线程,直到发出某些信号。这些函数可用于实现阻塞套接字:
waitForConnected()
阻塞,直到建立连接。waitForReadyRead()
阻塞,直到有新数据可供读取。waitForBytesWritten()
阻塞,直到将一个数据有效负载写入套接字。waitForDisconnected()
阻塞,直到连接关闭。
// for synchronous accessvirtual 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,PauseModeQAbstractSocket(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\setPeerNameQAbstractSocket(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:setsockopt
和bind
。
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;
};