2013-03-12 71 views
2

我讀到它應該是安全的,從不同的線程併發,但我的程序有一些奇怪的行爲,我不知道什麼是錯的。發送和接收來自不同線程的相同套接字不起作用

我有併發線程與客戶端插座

  1. 一個通信做着發送到插座
  2. 做一個選擇,然後從同一插座

的recv因爲我仍然在發,客戶端已經收到數據並關閉了套接字。 同時,我在該套接字上做了select和recv,它返回0(因爲它是關閉的),所以我關閉了這個套接字。但是,發送還沒有返回...因爲我在這個套接字上調用close,所以發送調用失敗,EBADF。

我知道客戶端已經收到了正確的數據,因爲我關閉套接字後輸出它,它是正確的。但是,在我的最後,我的發送調用仍然返回一個錯誤(EBADF),所以我想修復它,所以它不會失敗。

這並非總是如此。它可能有40%的時間發生。我不會在任何地方使用睡眠。我應該暫停發送或recvs或任何東西?

下面是一些代碼:

發送:

while(true) 
{ 
    // keep sending until send returns 0 
    n = send(_sfd, bytesPtr, sentSize, 0); 

    if (n == 0) 
    { 
     break; 
    } 
    else if(n<0) 
    { 
     cerr << "ERROR: send returned an error "<<errno<< endl; // this case is triggered 
     return n; 
    } 

    sentSize -= n; 
    bytesPtr += n; 
} 

接收:

while(true) 
{ 
    memset(bufferPointer,0,sizeLeft); 
    n = recv(_sfd,bufferPointer,sizeLeft, 0); 
    if (debug) cerr << "Receiving..."<<sizeLeft<<endl; 
    if(n == 0) 
    { 
     cerr << "Connection closed"<<endl; // this case is triggered 
     return n; 
    } 
    else if (n < 0) 
    { 
     cerr << "ERROR reading from socket"<<endl; 
     return n; 
    } 
    bufferPointer += n; 
    sizeLeft -= n; 
    if(sizeLeft <= 0) break; 

} 

在客戶端,我用的是相同的接收代碼,然後我調用close()插槽上。 然後在我身邊,我從接收呼叫中得到0,並在套接字上調用close() 然後我的發送失敗。它還沒有完成?!但我的客戶已經收到了數據!

+0

請添加一些代碼。可能有錯誤與您懷疑的內容無關。 – user1952500 2013-03-12 23:22:25

+0

謝謝,我將添加代碼 – yuf 2013-03-12 23:22:50

+1

總是可以給套接字引用計數。 – 2013-03-12 23:36:44

回答

4

發現問題。這是我的循環。注意這是一個無限循環。當我沒有剩下的發送時,我的發送大小是0,但我仍然會循環嘗試發送更多。這時,另一個線程已經關閉了這個線程,所以我的發送0字節的調用返回一個錯誤。

我修正了它通過更改循環停止循環時sentSize爲0,它解決了問題!

+0

嘿,我現在只是發現自己。那會教我在睡覺前深夜回答問題!我在答案中談到的問題仍然有效,但正如我所說的,你不會期望經常看到他們。 – Cartroo 2013-03-13 08:04:51

5

我必須承認,我很驚訝你會像你一樣經常看到這個問題,但是當你處理線程時總是有可能的。當你打電話給send()時,你最終會進入內核將數據追加到那裏的套接字緩衝區中,因此很可能會出現上下文切換,或許是系統中的另一個進程。同時內核可能會很快緩衝並傳輸數據包。我猜你正在測試本地網絡,所以另一端接收數據並關閉連接,並將相應的FIN很快返回到您的端點。當發送機器仍在運行其他線程或進程時,這可能都會發生,因爲本地以太網網絡的延遲太低。

現在FIN到達 - 您的接收線程最近沒有做很多工作,因爲它一直在等待輸入。因此,許多調度系統會提高其優先級,並且很有可能會接下來運行(例如,您不指定使用哪種操作系統,但至少在Linux上可能會發生這種情況)。該線程由於其零讀取而關閉套接字。在此之後的某個時刻,發送線程將被重新喚醒,但大概內核會注意到該socket在從被阻塞的send()返回之前關閉並返回EBADF

現在,這只是關於確切原因的猜測 - 除此之外,它很大程度上取決於您的平臺。但是你可以看到這是如何發生的。

最簡單的解決方案可能也是在發送線程中使用poll(),但是等待套接字變爲寫入準備而不是準備就緒。顯然,你還需要等待,直到有任何緩衝數據發送 - 你如何做,取決於哪個線程緩衝數據。撥打電話poll()可讓您檢測連接何時關閉,方法是用POLLHUP標記該連接,在嘗試使用send()之前可檢測到該連接。

一般來說,在確定發送緩衝區已完全刷新之前,您不應該關閉套接字 - 只有在send()調用返回後才能確定這一點,並且指示所有其餘數據都具有出去。我在過去通過檢查發送緩衝區來處理這個問題,當我讀取的是零,如果它不是空的,我設置了一個「關閉」標誌。在你的情況下,發送線程將會使用這個提示來完成一次刷新的關閉。這很重要,因爲如果遠程端與shutdown()進行了半關,那麼即使它仍然可以讀取,您也會得到零讀數。但是,你可能不關心一半關閉,在這種情況下,你的策略是可以的。最後,我個人會避免發送和接收線程的麻煩,並且只有一個線程可以同時執行這兩個操作 - 這或多或少是select()poll()的要點,以允許單個執行線程處理一個線程或更多的文件句柄,而不用擔心執行阻止和捱餓其他連接的操作。

+0

謝謝你的回答。你描述的問題似乎很有可能,但我不認爲我正在經歷它,因爲它似乎需要非常特定的情況,並且我得到的錯誤經常發生。另外,我需要使用多個線程,因爲它是學校的分配規範。結束標誌評論聽起來非常有用,我會補充說,如果它是爲了一份工作,但這只是爲了標記哈哈。 – yuf 2013-03-13 01:32:10

+0

@cartroo - 當recv是阻塞呼叫時,如何recv和發送相同的套接字和相同的線程? poll()和select()聲音就像完全用於多個套接字一樣。 – JoeManiaci 2015-12-15 05:05:59

+0

@JoeManiaci'send()'和'recv()'不一定是阻塞操作。首先,如果它們無法取得進展,它們只會被阻止,否則它們會返回部分成功 - 「select()」和「poll()」的關鍵是要檢測何時可以獲得這樣的成功(即, t塊)。其次,你可以用'O_NONBLOCK'選項使套接字非阻塞。第三,'select()'和'poll()'可以用一個套接字正常工作 - 它們讓你等待多個描述符上的多個事件,甚至一個。它們旨在擴展到很多,但這並不能阻止你使用它們。 – Cartroo 2015-12-15 09:52:16