Qt網絡編程QTcpServer和QTcpSocket的理解【轉載】

前一段時間通過調試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源碼片段

[cpp] view plain copy
  1.     qint64 bytesToRead = socketEngine->bytesAvailable();  
  2. #ifdef Q_OS_LINUX  
  3.     if (bytesToRead > 0) // ### See setSocketDescriptor()  
  4.         bytesToRead += addToBytesAvailable;  
  5. #endif  
  6.     if (bytesToRead == 0) {  
  7.         // Under heavy load, certain conditions can trigger read notifications  
  8.         // for socket notifiers on which there is no activity. If we continue  
  9.         // to read 0 bytes from the socket, we will trigger behavior similar  
  10.         // to that which signals a remote close. When we hit this condition,  
  11.         // we try to read 4k of data from the socket, which will give us either  
  12.         // an EAGAIN/EWOULDBLOCK if the connection is alive (i.e., the remote  
  13.         // host has _not_ disappeared).  
  14.         bytesToRead = 4096;  
  15.     }  
  16.     if (readBufferMaxSize && bytesToRead > (readBufferMaxSize - readBuffer.size()))  
  17.         bytesToRead = readBufferMaxSize - readBuffer.size();  
  18.   
  19.     // Read from the socket, store data in the read buffer.  
  20.     char *ptr = readBuffer.reserve(bytesToRead);  
  21.     qint64 readBytes = socketEngine->read(ptr, bytesToRead);  
  22.     if (readBytes == -2) {  
  23.         // No bytes currently available for reading.  
  24.         readBuffer.chop(bytesToRead);  
  25.         return true;  
  26.     }  
  27.     readBuffer.chop(int(bytesToRead - (readBytes < 0 ? qint64(0) : readBytes)));  
[cpp] view plain copy
  1. qint64 QNativeSocketEnginePrivate::nativeBytesAvailable() const  
  2. {  
  3.     int nbytes = 0;  
  4.     // gives shorter than true amounts on Unix domain sockets.  
  5.     qint64 available = 0;  
  6. #ifdef Q_OS_SYMBIAN  
  7.     if (::ioctl(socketDescriptor, FIONREAD, (char *) &nbytes) >= 0)  
  8. #else  
  9.     if (qt_safe_ioctl(socketDescriptor, FIONREAD, (char *) &nbytes) >= 0)  
  10. #endif  
  11.         available = (qint64) nbytes;  
  12.   
  13.     return available;  
  14. }  
讀取數據的步驟:

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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章