1. 程式人生 > >Qt網路程式設計QTcpServer和QTcpSocket的理解

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原始碼片段

    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網路的東西,可以搭建除錯環境。自己除錯來分析原始碼,查詢原因。