QT分析之网络编程
2016-03-15 15:47
639 查看
原文地址:http://blog.163.com/net_worm/blog/static/127702419201002842553382/
首先对Windows下的网络编程总结一下:
如果是服务器,其WinSDK调用分别为:
如果是客户端程序,其调用序列为:
前面转贴的客户端(WinSocket温习)程序中,收到信息就在console打印出来然后退出了;在一般的应用中,通常是要一直等待收发消息的,直到程序确认退出才关闭socket。如果用一个轮询就会占用很多的CPU资源,所以很多嵌入式设计中会用一个WaitForMultiObject调用,等待退出命令或者超时,然后退出或进行下一轮信息接受。在Windows平台下也有一些比较高明的设计,使用异步socket,然后用异步选择的办法,实现多线程和事件的并发。在WinSocket中,同样也有一套异步Socket函数,那就是使用WSAAsyncSelect()及其配合函数。具体可以参考MSDN。QT在Windows平台上的实现,肯定也跟这些SDK调用有关。
按照这个思路,果然在QT代码里面找到了Qnativesocketengine_win.cpp,WSAStartup(),WSASocket()等序列WSA函数都有。QNativeSocketEnginePrivate类把这些SDK封装成:createNewSocket()、option()、setOption()、nativeConnect()、nativeBind()、nativeListen()、nativeAccept()、nativeWrite()、nativeRead()、nativeSelect()、nativeClose()等。按照QT的设计,QPrivate类是数据类;Q类应该是主类。接着看QNativeSocket类的继承:
QAbstractSocketEngine类是使用了大量纯虚函数的定义。继续深入查看,发现大量有关的类:QAbstractSocket,SocketAsyncHandler,QTcpSocket,QUdpSocket等,看来我要先了解下QT网络编程体系再进一步分析之
之前没有看QT自带的文档,看了doc之后对QT的网络体系有一个大致的了解:
QNatvieSocketEnginePrivate是OS相关的API封装,和QNativeSocketEngine一起构成具体平台SOCKET实现;
QTcpSocket、QUdpSocket、QTcpServer构成底层的应用API;QSslSocket是SSL加密相关API;
QHttp、QFtp构成高层次应该API;
QNetworkAccessManager、QNetworkRequest、QNetworkReply是高度抽象的网络层。
分析TCP的例子fortuneclient,运行起来按了[Get
Fortune]按钮之后,调用的是Client::requestNewFortune()。
具体看QTcpSocket::connectToHost()的代码
调用的是QAbstractSocket::connectToHostImplementation()。
继续调用QAbstractSocket::_q_startConnecting(),是QAbstractSocket的私有信号。简单来说,_q_startConnecting()就是调用了_q_connectToNextAddress()而已。
上面关键的三句,实际是把WinSocket编程中的简单过程分成三个阶段:socket初始化;connect到远程目标;设定Timer定时查看并处理Select的情况(收发数据或者关闭socket)。这里主要看前面两个:初始化和连接,select的处理放到明天分析。
1、初始化
上面可以知道socketEngine->initialize()实际调用的是QNativeSocketEngine::initialize()
至此,初始化过程完成,socket被设定为非阻塞模式(也就是Select会超时方式)。
2、connect到远程目标
连接相对简单。
3、读取信息
在QAbstractSocket中,有两个成员是收发数据用的:readData()、writeData()
readData()有两种读取方式:有缓冲和无缓冲方式。基本原理是一致的,简单其见只分析无缓冲直接读取方式。
从前面(二)可以知道,socketEngine->read()实际调用的是QNativeSocketEngine::read()
除了一些相关的检查,就是调用QNativeSocketPrivate::nativeRead()
至此,调用Windows API读取数据。
4、发送数据
同样分有缓存与无缓存方式,对无缓存方式:
查看QNativeSocketEngine::write():
至此分析完毕。
前面分析中,一个问题一直没有解决:新生成的SOCKET是什么时候加入WSASelect()的?另外还有一个不是很大的问题,close流程。
在
中WSAAsyncSelect()设置一个断点,观察call stack:
[下面的框架可能不正确和/或缺失,没有为 kernel32.dll 加载符号]
看QNativeSocketEngine::setWriteNotificationEnabled()的代码实现:
在QWriteNotifier对象新建的时候,引起其父类的构建:QSocketNotifier
原来是通过获取当前线程数据得到Dispatcher的指针(QEventDispatcherWin32),通过其注册QNativeSocketEngine对象自己本身。
在这里跟前面分析的QEventDispatcherWin32消息处理搭上关系了,把QWriteNotifier对象加入到系统的列表中;在QApplication::exec()的消息循环中,就能够获取目标对象了。
分析QNetworkAccessManager的时候,有一段设定HTTP的请求包的Header,当时没进行深入的分析。
如果想模拟IE浏览器,或者想修改成任何你希望的信息,就是在这里修改。
在设定了这些请求信息之后,发送的请求信息包是什么样子的呢?我把工具拦截的信息包具体情况贴出来
首先对Windows下的网络编程总结一下:
如果是服务器,其WinSDK调用分别为:
WSAStartup() -> socket() -> htons() / htonl() -> bind() -> listen() -> accept() -> recv() / send() -> closesocket() -> WSACleanup()
如果是客户端程序,其调用序列为:
WSAStartup() -> socket() -> htons() / htonl() -> connect() -> recv() / send() -> closesocket() -> WSACleanup()
前面转贴的客户端(WinSocket温习)程序中,收到信息就在console打印出来然后退出了;在一般的应用中,通常是要一直等待收发消息的,直到程序确认退出才关闭socket。如果用一个轮询就会占用很多的CPU资源,所以很多嵌入式设计中会用一个WaitForMultiObject调用,等待退出命令或者超时,然后退出或进行下一轮信息接受。在Windows平台下也有一些比较高明的设计,使用异步socket,然后用异步选择的办法,实现多线程和事件的并发。在WinSocket中,同样也有一套异步Socket函数,那就是使用WSAAsyncSelect()及其配合函数。具体可以参考MSDN。QT在Windows平台上的实现,肯定也跟这些SDK调用有关。
按照这个思路,果然在QT代码里面找到了Qnativesocketengine_win.cpp,WSAStartup(),WSASocket()等序列WSA函数都有。QNativeSocketEnginePrivate类把这些SDK封装成:createNewSocket()、option()、setOption()、nativeConnect()、nativeBind()、nativeListen()、nativeAccept()、nativeWrite()、nativeRead()、nativeSelect()、nativeClose()等。按照QT的设计,QPrivate类是数据类;Q类应该是主类。接着看QNativeSocket类的继承:
QNativeSocketEngine : public QAbstractSocketEngine : public QObject
QAbstractSocketEngine类是使用了大量纯虚函数的定义。继续深入查看,发现大量有关的类:QAbstractSocket,SocketAsyncHandler,QTcpSocket,QUdpSocket等,看来我要先了解下QT网络编程体系再进一步分析之
之前没有看QT自带的文档,看了doc之后对QT的网络体系有一个大致的了解:
QNatvieSocketEnginePrivate是OS相关的API封装,和QNativeSocketEngine一起构成具体平台SOCKET实现;
QTcpSocket、QUdpSocket、QTcpServer构成底层的应用API;QSslSocket是SSL加密相关API;
QHttp、QFtp构成高层次应该API;
QNetworkAccessManager、QNetworkRequest、QNetworkReply是高度抽象的网络层。
分析TCP的例子fortuneclient,运行起来按了[Get
Fortune]按钮之后,调用的是Client::requestNewFortune()。
void Client::requestNewFortune() { getFortuneButton->setEnabled(false); blockSize = 0; tcpSocket->abort(); tcpSocket->connectToHost(hostLineEdit->text(), portLineEdit->text().toInt()); }
具体看QTcpSocket::connectToHost()的代码
void QAbstractSocket::connectToHost(const QString &hostName, quint16 port, OpenMode openMode) { QMetaObject::invokeMethod(this, "connectToHostImplementation", Qt::DirectConnection, Q_ARG(QString, hostName), Q_ARG(quint16, port), Q_ARG(OpenMode, openMode)); }
调用的是QAbstractSocket::connectToHostImplementation()。
void QAbstractSocket::connectToHostImplementation(const QString &hostName, quint16 port, OpenMode openMode) { Q_D(QAbstractSocket); if (d->state == ConnectedState || d->state == ConnectingState || d->state == ClosingState) { qWarning("QAbstractSocket::connectToHost() called when already connecting/connected to \"%s\"", qPrintable(hostName)); return; } d->hostName = hostName; d->port = port; d->state = UnconnectedState; d->readBuffer.clear(); d->writeBuffer.clear(); d->abortCalled = false; d->closeCalled = false; d->pendingClose = false; d->localPort = 0; d->peerPort = 0; d->localAddress.clear(); d->peerAddress.clear(); d->peerName = hostName; if (d->hostLookupId != -1) { QHostInfo::abortHostLookup(d->hostLookupId); d->hostLookupId = -1; } #ifndef QT_NO_NETWORKPROXY // Get the proxy information d->resolveProxy(hostName, port); if (d->proxyInUse.type() == QNetworkProxy::DefaultProxy) { // failed to setup the proxy d->socketError = QAbstractSocket::UnsupportedSocketOperationError; setErrorString(QAbstractSocket::tr("Operation on socket is not supported")); emit error(d->socketError); return; } #endif if (!d_func()->isBuffered) openMode |= QAbstractSocket::Unbuffered; QIODevice::open(openMode); // ?? d->state = HostLookupState; emit stateChanged(d->state); QHostAddress temp; if (temp.setAddress(hostName)) { QHostInfo info; info.setAddresses(QList<QHostAddress>() << temp); d->_q_startConnecting(info); #ifndef QT_NO_NETWORKPROXY } else if (d->proxyInUse.capabilities() & QNetworkProxy::HostNameLookupCapability) { // the proxy supports connection by name, so use it d->startConnectingByName(hostName); return; #endif } else { if (d->threadData->eventDispatcher) d->hostLookupId = QHostInfo::lookupHost(hostName, this, SLOT(_q_startConnecting(QHostInfo))); } }
继续调用QAbstractSocket::_q_startConnecting(),是QAbstractSocket的私有信号。简单来说,_q_startConnecting()就是调用了_q_connectToNextAddress()而已。
void QAbstractSocketPrivate::_q_connectToNextAddress() { Q_Q(QAbstractSocket); do { // Check for more pending addresses if (addresses.isEmpty()) { state = QAbstractSocket::UnconnectedState; if (socketEngine) { if ((socketEngine->error() == QAbstractSocket::UnknownSocketError ) && socketEngine->state() == QAbstractSocket::ConnectingState) { socketError = QAbstractSocket::ConnectionRefusedError; q->setErrorString(QAbstractSocket::tr("Connection refused")); } else { socketError = socketEngine->error(); q->setErrorString(socketEngine->errorString()); } } else { // socketError = QAbstractSocket::ConnectionRefusedError; // q->setErrorString(QAbstractSocket::tr("Connection refused")); } emit q->stateChanged(state); emit q->error(socketError); return; } // Pick the first host address candidate host = addresses.takeFirst(); #if defined(QT_NO_IPV6) if (host.protocol() == QAbstractSocket::IPv6Protocol) { // If we have no IPv6 support, then we will not be able to // connect. So we just pretend we didn't see this address. continue; } #endif if (!initSocketLayer(host.protocol())) { // hope that the next address is better continue; } // Tries to connect to the address. If it succeeds immediately // (localhost address on BSD or any UDP connect), emit // connected() and return. if (socketEngine->connectToHost(host, port)) { //_q_testConnection(); fetchConnectionParameters(); return; } // cache the socket descriptor even if we're not fully connected yet cachedSocketDescriptor = socketEngine->socketDescriptor(); // Check that we're in delayed connection state. If not, try // the next address if (socketEngine->state() != QAbstractSocket::ConnectingState) { continue; } // Start the connect timer. if (threadData->eventDispatcher) { if (!connectTimer) { connectTimer = new QTimer(q); QObject::connect(connectTimer, SIGNAL(timeout()), 65 q, SLOT(_q_abortConnectionAttempt()), 66 Qt::DirectConnection); } connectTimer->start(QT_CONNECT_TIMEOUT); } // Wait for a write notification that will eventually call // _q_testConnection(). socketEngine->setWriteNotificationEnabled(true); break; } while (state != QAbstractSocket::ConnectedState); }
上面关键的三句,实际是把WinSocket编程中的简单过程分成三个阶段:socket初始化;connect到远程目标;设定Timer定时查看并处理Select的情况(收发数据或者关闭socket)。这里主要看前面两个:初始化和连接,select的处理放到明天分析。
1、初始化
bool QAbstractSocketPrivate::initSocketLayer(QAbstractSocket::NetworkLayerProtocol protocol) { #ifdef QT_NO_NETWORKPROXY // this is here to avoid a duplication of the call to createSocketEngine below static const QNetworkProxy &proxyInUse = *(QNetworkProxy *)0; #endif Q_Q(QAbstractSocket); resetSocketLayer(); socketEngine = QAbstractSocketEngine::createSocketEngine(q->socketType(), proxyInUse, q); if (!socketEngine) { socketError = QAbstractSocket::UnsupportedSocketOperationError; q->setErrorString(QAbstractSocket::tr("Operation on socket is not supported")); return false; } if (!socketEngine->initialize(q->socketType(), protocol)) { socketError = socketEngine->error(); q->setErrorString(socketEngine->errorString()); return false; } if (threadData->eventDispatcher) socketEngine->setReceiver(this); return true; } QAbstractSocketEngine *QAbstractSocketEngine::createSocketEngine(QAbstractSocket::SocketType socketType, const QNetworkProxy &proxy, QObject *parent) { #ifndef QT_NO_NETWORKPROXY // proxy type must have been resolved by now if (proxy.type() == QNetworkProxy::DefaultProxy) return 0; #endif QMutexLocker locker(&socketHandlers()->mutex); for (int i = 0; i < socketHandlers()->size(); i++) { if (QAbstractSocketEngine *ret = socketHandlers()->at(i)->createSocketEngine(socketType, proxy, parent)) return ret; } return new QNativeSocketEngine(parent); }
上面可以知道socketEngine->initialize()实际调用的是QNativeSocketEngine::initialize()
bool QNativeSocketEngine::initialize(QAbstractSocket::SocketType socketType, QAbstractSocket::NetworkLayerProtocol protocol) { Q_D(QNativeSocketEngine); if (isValid()) close(); #if defined(QT_NO_IPV6) if (protocol == QAbstractSocket::IPv6Protocol) { d->setError(QAbstractSocket::UnsupportedSocketOperationError, QNativeSocketEnginePrivate::NoIpV6ErrorString); return false; } #endif // Create the socket if (!d->createNewSocket(socketType, protocol)) { return false; } // Make the socket nonblocking. if (!setOption(NonBlockingSocketOption, 1)) { d->setError(QAbstractSocket::UnsupportedSocketOperationError, QNativeSocketEnginePrivate::NonBlockingInitFailedErrorString); close(); return false; } // Set the broadcasting flag if it's a UDP socket. if (socketType == QAbstractSocket::UdpSocket && !setOption(BroadcastSocketOption, 1)) { d->setError(QAbstractSocket::UnsupportedSocketOperationError, QNativeSocketEnginePrivate::BroadcastingInitFailedErrorString); close(); return false; } // Make sure we receive out-of-band data if (socketType == QAbstractSocket::TcpSocket && !setOption(ReceiveOutOfBandData, 1)) { qWarning("QNativeSocketEngine::initialize unable to inline out-of-band data"); } // Set the send and receive buffer sizes to a magic size, found // most optimal for our platforms. setReceiveBufferSize(49152); setSendBufferSize(49152); d->socketType = socketType; d->socketProtocol = protocol; return true; }
至此,初始化过程完成,socket被设定为非阻塞模式(也就是Select会超时方式)。
2、connect到远程目标
bool QNativeSocketEngine::connectToHost(const QHostAddress &address, quint16 port) { Q_D(QNativeSocketEngine); Q_CHECK_VALID_SOCKETLAYER(QNativeSocketEngine::connectToHost(), false); #if defined (QT_NO_IPV6) if (address.protocol() == QAbstractSocket::IPv6Protocol) { d->setError(QAbstractSocket::UnsupportedSocketOperationError, QNativeSocketEnginePrivate::NoIpV6ErrorString); return false; } #endif if (!d->checkProxy(address)) return false; Q_CHECK_STATES(QNativeSocketEngine::connectToHost(), QAbstractSocket::UnconnectedState, QAbstractSocket::ConnectingState, false); d->peerAddress = address; d->peerPort = port; bool connected = d->nativeConnect(address, port); if (connected) d->fetchConnectionParameters(); return connected; }
连接相对简单。
3、读取信息
在QAbstractSocket中,有两个成员是收发数据用的:readData()、writeData()
readData()有两种读取方式:有缓冲和无缓冲方式。基本原理是一致的,简单其见只分析无缓冲直接读取方式。
qint64 QAbstractSocket::readData(char *data, qint64 maxSize) { Q_D(QAbstractSocket); if (d->socketEngine && !d->socketEngine->isReadNotificationEnabled() && d->socketEngine->isValid()) d->socketEngine->setReadNotificationEnabled(true); if (!d->isBuffered) { if (!d->socketEngine) return -1; // no socket engine is probably EOF qint64 readBytes = d->socketEngine->read(data, maxSize); if (readBytes < 0) { d->socketError = d->socketEngine->error(); setErrorString(d->socketEngine->errorString()); } if (!d->socketEngine->isReadNotificationEnabled()) d->socketEngine->setReadNotificationEnabled(true); return readBytes; } if (d->readBuffer.isEmpty()) // if we're still connected, return 0 indicating there may be more data in the future // if we're not connected, return -1 indicating EOF return d->state == QAbstractSocket::ConnectedState ? qint64(0) : qint64(-1); // If readFromSocket() read data, copy it to its destination. if (maxSize == 1) { *data = d->readBuffer.getChar(); return 1; } qint64 bytesToRead = qMin(qint64(d->readBuffer.size()), maxSize); qint64 readSoFar = 0; while (readSoFar < bytesToRead) { const char *ptr = d->readBuffer.readPointer(); int bytesToReadFromThisBlock = qMin(int(bytesToRead - readSoFar), d->readBuffer.nextDataBlockSize()); memcpy(data + readSoFar, ptr, bytesToReadFromThisBlock); readSoFar += bytesToReadFromThisBlock; d->readBuffer.free(bytesToReadFromThisBlock); } return readSoFar; }
从前面(二)可以知道,socketEngine->read()实际调用的是QNativeSocketEngine::read()
qint64 QNativeSocketEngine::read(char *data, qint64 maxSize) { Q_D(QNativeSocketEngine); Q_CHECK_VALID_SOCKETLAYER(QNativeSocketEngine::read(), -1); Q_CHECK_STATES(QNativeSocketEngine::read(), QAbstractSocket::ConnectedState, QAbstractSocket::BoundState, -1); qint64 readBytes = d->nativeRead(data, maxSize); // Handle remote close if (readBytes == 0 && d->socketType == QAbstractSocket::TcpSocket) { d->setError(QAbstractSocket::RemoteHostClosedError, QNativeSocketEnginePrivate::RemoteHostClosedErrorString); close(); return -1; } return readBytes; }
除了一些相关的检查,就是调用QNativeSocketPrivate::nativeRead()
qint64 QNativeSocketEnginePrivate::nativeRead(char *data, qint64 maxLength) { qint64 ret = -1; WSABUF buf; buf.buf = data; buf.len = maxLength; DWORD flags = 0; DWORD bytesRead = 0; #if defined(Q_OS_WINCE) WSASetLastError(0); #endif if (::WSARecv(socketDescriptor, &buf, 1, &bytesRead, &flags, 0,0) == SOCKET_ERROR) { int err = WSAGetLastError(); WS_ERROR_DEBUG(err); switch (err) { case WSAEWOULDBLOCK: ret = -2; break; case WSAEBADF: case WSAEINVAL: setError(QAbstractSocket::NetworkError, ReadErrorString); break; case WSAECONNRESET: case WSAECONNABORTED: // for tcp sockets this will be handled in QNativeSocketEngine::read ret = 0; break; default: break; } } else { if (WSAGetLastError() == WSAEWOULDBLOCK) ret = -2; else ret = qint64(bytesRead); } return ret; }
至此,调用Windows API读取数据。
4、发送数据
同样分有缓存与无缓存方式,对无缓存方式:
qint64 QAbstractSocket::writeData(const char *data, qint64 size) { Q_D(QAbstractSocket); if (d->state == QAbstractSocket::UnconnectedState) { d->socketError = QAbstractSocket::UnknownSocketError; setErrorString(tr("Socket is not connected")); return -1; } if (!d->isBuffered) { qint64 written = d->socketEngine->write(data, size); if (written < 0) { d->socketError = d->socketEngine->error(); setErrorString(d->socketEngine->errorString()); } else if (!d->writeBuffer.isEmpty()) { d->socketEngine->setWriteNotificationEnabled(true); } if (written >= 0) emit bytesWritten(written); return written; } char *ptr = d->writeBuffer.reserve(size); if (size == 1) *ptr = *data; else memcpy(ptr, data, size); qint64 written = size; if (d->socketEngine && !d->writeBuffer.isEmpty()) d->socketEngine->setWriteNotificationEnabled(true); return written; }
查看QNativeSocketEngine::write():
qint64 QNativeSocketEngine::write(const char *data, qint64 size) { Q_D(QNativeSocketEngine); Q_CHECK_VALID_SOCKETLAYER(QNativeSocketEngine::write(), -1); Q_CHECK_STATE(QNativeSocketEngine::write(), QAbstractSocket::ConnectedState, -1); return d->nativeWrite(data, size); } qint64 QNativeSocketEnginePrivate::nativeWrite(const char *data, qint64 len) { Q_Q(QNativeSocketEngine); qint64 ret = 0; // don't send more than 49152 per call to WSASendTo to avoid getting a WSAENOBUFS for (;;) { qint64 bytesToSend = qMin<qint64>(49152, len - ret); WSABUF buf; buf.buf = (char*)data + ret; buf.len = bytesToSend; DWORD flags = 0; DWORD bytesWritten = 0; int socketRet = ::WSASend(socketDescriptor, &buf, 1, &bytesWritten, flags, 0,0); ret += qint64(bytesWritten); if (socketRet != SOCKET_ERROR) { if (ret == len) break; else continue; } else if (WSAGetLastError() == WSAEWOULDBLOCK) { break; } else { int err = WSAGetLastError(); WS_ERROR_DEBUG(err); switch (err) { case WSAECONNRESET: case WSAECONNABORTED: ret = -1; setError(QAbstractSocket::NetworkError, WriteErrorString); q->close(); break; default: break; } break; } } return ret; }
至此分析完毕。
前面分析中,一个问题一直没有解决:新生成的SOCKET是什么时候加入WSASelect()的?另外还有一个不是很大的问题,close流程。
在
QEventDispatcherWin32Private::doWsaAsyncSelect()
中WSAAsyncSelect()设置一个断点,观察call stack:
QtCored4.dll!QEventDispatcherWin32Private::doWsaAsyncSelect(int socket=0x00001628) 行633 C++ QtCored4.dll!QEventDispatcherWin32::registerSocketNotifier(QSocketNotifier * notifier=0x00c6f248) 行829 C++ QtCored4.dll!QSocketNotifier::QSocketNotifier(int socket=0x00001628, QSocketNotifier::Type type=Write, QObject * parent=0x00c66228) 行185 C++ QtNetworkd4.dll!QWriteNotifier::QWriteNotifier(int fd=0x00001628, QNativeSocketEngine * parent=0x00c66228) 行1053 + 0x1a 字节 C++ QtNetworkd4.dll!QNativeSocketEngine::setWriteNotificationEnabled(bool enable=true) 行1118 + 0x2d 字节 C++ QtNetworkd4.dll!QAbstractSocketPrivate::_q_connectToNextAddress() 行996 C++ QtNetworkd4.dll!QAbstractSocketPrivate::_q_startConnecting(const QHostInfo & hostInfo={...}) 行890 C++ QtNetworkd4.dll!QAbstractSocket::qt_metacall(QMetaObject::Call _c=InvokeMetaMethod, int _id=0x0000000a, void * * _a=0x00c6e510) 行104 + 0x16 字节 C++ QtNetworkd4.dll!QTcpSocket::qt_metacall(QMetaObject::Call _c=InvokeMetaMethod, int _id=0x00000012, void * * _a=0x00c6e510) 行58 + 0x14 字节 C++ QtCored4.dll!QMetaCallEvent::placeMetaCall(QObject * object=0x00c4f790) 行478 C++ QtCored4.dll!QObject::event(QEvent * e=0x00c4d8a0) 行1102 + 0x14 字节 C++ QtGuid4.dll!QApplicationPrivate::notify_helper(QObject * receiver=0x00c4f790, QEvent * e=0x00c4d8a0) 行4065 + 0x11 字节 C++ QtGuid4.dll!QApplication::notify(QObject * receiver=0x00c4f790, QEvent * e=0x00c4d8a0) 行3605 + 0x10 字节 C++ QtCored4.dll!QCoreApplication::notifyInternal(QObject * receiver=0x00c4f790, QEvent * event=0x00c4d8a0) 行610 + 0x15 字节 C++ QtCored4.dll!QCoreApplication::sendEvent(QObject * receiver=0x00c4f790, QEvent * event=0x00c4d8a0) 行213 + 0x39 字节 C++ QtCored4.dll!QCoreApplicationPrivate::sendPostedEvents(QObject * receiver=0x00000000, int event_type=0x00000000, QThreadData * data=0x00bc8890) 行1247 + 0xd 字节 C++ QtCored4.dll!QEventDispatcherWin32::processEvents(QFlags<enum QEventLoop::ProcessEventsFlag> flags={...}) 行679 + 0x10 字节 C++ QtGuid4.dll!QGuiEventDispatcherWin32::processEvents(QFlags<enum QEventLoop::ProcessEventsFlag> flags={...}) 行1182 + 0x15 字节 C++ QtCored4.dll!QEventLoop::processEvents(QFlags<enum QEventLoop::ProcessEventsFlag> flags={...}) 行150 C++ QtCored4.dll!QEventLoop::exec(QFlags<enum QEventLoop::ProcessEventsFlag> flags={...}) 行201 + 0x2d 字节 C++ QtGuid4.dll!QDialog::exec() 行499 C++ fortuneclient.exe!main(int argc=0x00000001, char * * argv=0x00bc8750) 行51 + 0x9 字节 C++ fortuneclient.exe!WinMain(HINSTANCE__ * instance=0x00400000, HINSTANCE__ * prevInstance=0x00000000, char * __formal=0x001520e2, int cmdShow=0x00000001) 行137 + 0x12 字节 C++ fortuneclient.exe!__tmainCRTStartup() 行574 + 0x35 字节 C fortuneclient.exe!WinMainCRTStartup() 行399 C kernel32.dll!7c82f23b()
[下面的框架可能不正确和/或缺失,没有为 kernel32.dll 加载符号]
看QNativeSocketEngine::setWriteNotificationEnabled()的代码实现:
void QNativeSocketEngine::setWriteNotificationEnabled(bool enable) { Q_D(QNativeSocketEngine); if (d->writeNotifier) { d->writeNotifier->setEnabled(enable); } else if (enable && d->threadData->eventDispatcher) { d->writeNotifier = new QWriteNotifier(d->socketDescriptor, this); d->writeNotifier->setEnabled(true); } }
在QWriteNotifier对象新建的时候,引起其父类的构建:QSocketNotifier
QSocketNotifier::QSocketNotifier(int socket, Type type, QObject *parent) : QObject(parent) { if (socket < 0) qWarning("QSocketNotifier: Invalid socket specified"); sockfd = socket; sntype = type; snenabled = true; Q_D(QObject); if (!d->threadData->eventDispatcher) { qWarning("QSocketNotifier: Can only be used with threads started with QThread"); } else { d->threadData->eventDispatcher->registerSocketNotifier(this); } }
原来是通过获取当前线程数据得到Dispatcher的指针(QEventDispatcherWin32),通过其注册QNativeSocketEngine对象自己本身。
void QEventDispatcherWin32::registerSocketNotifier(QSocketNotifier *notifier) { Q_ASSERT(notifier); int sockfd = notifier->socket(); int type = notifier->type(); Q_D(QEventDispatcherWin32); QSNDict *sn_vec[3] = { &d->sn_read, &d->sn_write, &d->sn_except }; QSNDict *dict = sn_vec[type]; if (QCoreApplication::closingDown()) // ### d->exitloop? return; // after sn_cleanup, don't reinitialize. if (dict->contains(sockfd)) { const char *t[] = { "Read", "Write", "Exception" }; /* Variable "socket" below is a function pointer. */ qWarning("QSocketNotifier: Multiple socket notifiers for " "same socket %d and type %s", sockfd, t[type]); } QSockNot *sn = new QSockNot; sn->obj = notifier; sn->fd = sockfd; dict->insert(sn->fd, sn); if (d->internalHwnd) d->doWsaAsyncSelect(sockfd); }
在这里跟前面分析的QEventDispatcherWin32消息处理搭上关系了,把QWriteNotifier对象加入到系统的列表中;在QApplication::exec()的消息循环中,就能够获取目标对象了。
分析QNetworkAccessManager的时候,有一段设定HTTP的请求包的Header,当时没进行深入的分析。
void QHttpNetworkConnectionPrivate::prepareRequest(HttpMessagePair &messagePair) { QHttpNetworkRequest &request = messagePair.first; QHttpNetworkReply *reply = messagePair.second; // add missing fields for the request QByteArray value; // check if Content-Length is provided QIODevice *data = request.data(); if (data && request.contentLength() == -1) { if (!data->isSequential()) request.setContentLength(data->size()); else bufferData(messagePair); // ### or do chunked upload } // set the Connection/Proxy-Connection: Keep-Alive headers #ifndef QT_NO_NETWORKPROXY if (networkProxy.type() == QNetworkProxy::HttpCachingProxy) { value = request.headerField("proxy-connection"); if (value.isEmpty()) request.setHeaderField("Proxy-Connection", "Keep-Alive"); } else { #endif value = request.headerField("connection"); if (value.isEmpty()) request.setHeaderField("Connection", "Keep-Alive"); #ifndef QT_NO_NETWORKPROXY } #endif // If the request had a accept-encoding set, we better not mess // with it. If it was not set, we announce that we understand gzip // and remember this fact in request.d->autoDecompress so that // we can later decompress the HTTP reply if it has such an // encoding. value = request.headerField("accept-encoding"); if (value.isEmpty()) { #ifndef QT_NO_COMPRESS request.setHeaderField("Accept-Encoding", "gzip"); request.d->autoDecompress = true; #else // if zlib is not available set this to false always request.d->autoDecompress = false; #endif } // set the User Agent value = request.headerField("user-agent"); if (value.isEmpty()) request.setHeaderField("User-Agent", "Mozilla/5.0"); // set the host value = request.headerField("host"); if (value.isEmpty()) { QByteArray host = QUrl::toAce(hostName); int port = request.url().port(); if (port != -1) { host += ':'; host += QByteArray::number(port); } request.setHeaderField("Host", host); } reply->d_func()->requestIsPrepared = true; }
如果想模拟IE浏览器,或者想修改成任何你希望的信息,就是在这里修改。
在设定了这些请求信息之后,发送的请求信息包是什么样子的呢?我把工具拦截的信息包具体情况贴出来
相关文章推荐
- Qt打包发布方法
- qt信号signal和槽slot机制
- QT分析之调试跟踪系统
- QT分析之消息事件机制
- Qt模拟键盘按键与组合键
- 更改Qt应用程序图标的方法
- QT分析之QPushButton的初始化
- QT分析之WebKit
- Qt 实时读串口数据,并将读到的数据从网口发送出去
- 发布Qt Widgets桌面应用程序的方法
- Qt中信号和槽的问题解析
- QML中的SmoothedAnimation平滑动画
- QML之PathAnimation路径动画
- QTsocket和标准C库socket混用遇到的问题
- QML之ColorAnimation颜色动画
- Qt中如何使用Sleep函数
- QML动画之NumberAnimation数字类动画
- QML中在信号处理器中处理动画
- Windows下QT程序中调用boost库
- QT异形图形的开发