前一段時間通過調試Qt源碼,大致瞭解了Qt的事件機制、信號槽機制。畢竟能力和時間有限。有些地方理解的並不是很清楚。
開發環境:linux((fedora 17),Qt版本(qt-everywhere-opensource-src-4.7.3)。
Qt網絡編程比較常用的兩個類:QTcpServer和QTcpSocket。當然還有UDP的類(在這就不介紹了)。
這兩個類的操作比較簡單。
QTcpServer的基本操作:
1、調用listen監聽端口。
2、連接信號newConnection,在槽函數裏調用nextPendingConnection獲取連接進來的socket。
QTcpSocket的基本能操作:
1、調用connectToHost連接服務器。
2、調用waitForConnected判斷是否連接成功。
3、連接信號readyRead槽函數,異步讀取數據。
4、調用waitForReadyRead,阻塞讀取數據。
在main函數中調用 app.exec()之後會進入事件循環。在Qt的事件循環中回調用QEventLoop::processEvents。在這個函數中又會調用QAbstractEventDispatcher::processEvents。
Qt爲不同的平臺,提供了不同的EventDispatcher。本人用的是linux。接下來會調用QEventDispatcherUNIX::processEvents。doSelect,select。
通過調試源碼可以證實,函數調用的順序。其實Qt網絡編程,底層是通過select實現的。可能跟跨平臺有關係吧,採用select。linux有比select更好的API(poll,epoll)。
通過調試,可以發現newConnection和readyRead這兩個信號都和select有關係。
因爲調用的是select所以服務器端肯定會有監視文件描述符數量問題。select默認的是1024。Qt默認的也是1024。服務器端在調用nextPendingConnection。會創建一個QTcpSocket對象。這個對象在QTcpServer對象刪除的時候會自動刪除。也可以手動刪除,避免浪費內存,可以在該對象斷開的時候,刪除該對象。該對象在斷開的時候,select調用會清除該描述符,不會影響文件描述符的數量。如果內存足夠用的話,不需要關心該對象。
接下來介紹readyRead信號。該信號用的比較多。
該信號當有數據要讀的時候,會觸發該信號。不過在觸發該信號之前,Qt會嘗試讀取bytesToRead數據,存在內部緩衝區中。
下面是Qt源碼片段
- qint64 bytesToRead = socketEngine->bytesAvailable();
- #ifdef Q_OS_LINUX
- if (bytesToRead > 0) // ### See setSocketDescriptor()
- bytesToRead += addToBytesAvailable;
- #endif
- if (bytesToRead == 0) {
- // Under heavy load, certain conditions can trigger read notifications
- // for socket notifiers on which there is no activity. If we continue
- // to read 0 bytes from the socket, we will trigger behavior similar
- // to that which signals a remote close. When we hit this condition,
- // we try to read 4k of data from the socket, which will give us either
- // an EAGAIN/EWOULDBLOCK if the connection is alive (i.e., the remote
- // host has _not_ disappeared).
- bytesToRead = 4096;
- }
- if (readBufferMaxSize && bytesToRead > (readBufferMaxSize - readBuffer.size()))
- bytesToRead = readBufferMaxSize - readBuffer.size();
- // Read from the socket, store data in the read buffer.
- char *ptr = readBuffer.reserve(bytesToRead);
- qint64 readBytes = socketEngine->read(ptr, bytesToRead);
- if (readBytes == -2) {
- // No bytes currently available for reading.
- readBuffer.chop(bytesToRead);
- return true;
- }
- readBuffer.chop(int(bytesToRead - (readBytes < 0 ? qint64(0) : readBytes)));
- qint64 QNativeSocketEnginePrivate::nativeBytesAvailable() const
- {
- int nbytes = 0;
- // gives shorter than true amounts on Unix domain sockets.
- qint64 available = 0;
- #ifdef Q_OS_SYMBIAN
- if (::ioctl(socketDescriptor, FIONREAD, (char *) &nbytes) >= 0)
- #else
- if (qt_safe_ioctl(socketDescriptor, FIONREAD, (char *) &nbytes) >= 0)
- #endif
- available = (qint64) nbytes;
- return available;
- }
1、通過ioctl獲取系統能夠讀取的數據長度。
2、在linux系統中多加4K數據(跟linux域套接字有關係)。
3、如果調用setReadBufferSize()設置緩衝區大小的話。重新計算bytesToRead。從代碼中可以看出來,只有bytesToRead大的話,才重新計算。如果readBufferMaxSize設置過大,不會重新計算。
4、調用read函數嘗試讀取bytesToRead長度數據。返回實際讀取的數據長度readBytes。通過調試可以看出readBytes和bytesToRead相差並不是很多。
bytesAvailable()函數返回緩衝區長度。如果沒設定readBufferMaxSize,該函數返回值主要取決於ioctl系統調用。該系統調用跟系統當前運行的狀態有關係。
可能會出現的問題:
1、當觸發readyRead信號,但是緩衝區的長度小於另一端發送的數據。這樣就會觸發多次readyReady信號。如果一次槽函數裏面讀取緩衝區的長度,數據就會接受不全,進行數據處理肯定會出問題。Qt的例子中提供了一種方法。在發送數據的頭部加上數據的長度。只有當bytesAvailable大於數據的長度時,纔讀取數據。
2、系統API裏面調用read是從系統緩衝區裏面讀取數據。如果系統緩衝區滿的話。以前的就會被覆蓋。但是Qt裏面也存在緩衝區。如果一端發送數據。另一端並不從Qt緩衝區讀取數據。那麼Qt就會無限制的從系統緩衝區中讀出數據放置自己內部緩衝區。最後肯定會出現堆棧滿的情況,系統異常退出。
想進一步瞭解Qt網絡的東西,可以搭建調試環境。自己調試來分析源碼,查找原因。
轉載自:https://blog.csdn.net/ying_593254979/article/details/17006507