QAbstractSocket 是QTcpSocket和QUdpSocket的基类,并且包含这两个类的所有通用功能。如果需要套接字,有两种选择:
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只是简单继承了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定义的成员变量包括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基类指针对象套接字引擎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;
};