2011-12-12 26 views
2

我有一個線程在獨立線程管理的套接字上選擇的情況。如何優雅地選擇()可以在另一個線程上關閉的套接字?

當套接字關閉時,select()可能會將該套接字作爲「可用」返回,直到我嘗試讀取它才意識到它已關閉。

但是我看到一個悖論:當插座從另一個線程關閉,系統無重新分配的文件描述符用於其他目的。 (我認爲)

如何通過我從插座(只是一個數字描述符)讀取時間系統尚未回收的那個描述,並用它爲一個新的套接字我得到保障?換句話說,就我所知,我可能正在從最近打開的某個其他套接字(可能是我不應該包含在我的select()中的套接字)讀取而不是剛剛關閉的套接字。

我可以保持最近關閉的描述符列表,但我不知道是否有更好的方法。

回答

4

答案很簡單:從另一個線程不要關閉插座,你是從在這一個閱讀!

FD可能會被重新分配。但你有問題,如果你正在閱讀從多個線程中的一個FD沒有某種方案之間進行通信。現在,如果共享內存中有一個「套接字描述」結構,它具有控制信號量和FD的某些指示以及其他狀態信息,這可能是可管理的,但我想你會發現最簡單的解決方案几乎是總是使FD的具體到單個線程...

+0

如果使用阻塞套接字,從另一個線程關閉套接字通常是解除阻塞阻塞操作的唯一方法,特別是如果連接的遠程端點沒有正常關閉,阻塞操作沒有檢測到關閉權限並且在本地端點關閉之前變爲僵局。 –

+0

我想(可能是盲目地)認爲OP由於引用了'select'而使用了非阻塞I/O,但是;在這種情況下,人們不得不使用信號量或類似的信號(可能是線程之間共享的變量)來表示關閉;在這種情況下,「閱讀器」線程應該立即檢查每次操作之後,如果設置了「關閉」標記/信號量,則「退出」...... – BRFennPocock

+0

@BRPocock,您說的我正在使用非阻塞I/O。我認爲你是正確的,我將不得不統一這些套接字操作,以便它們始終從單個線程發生。我正在考慮可能選擇除了套接字之外的控制消息。 – mpontillo

0

,如果你知道哪個線程正在訪問哪些插座,你可以終止,或至少從另一個線程關閉套接字之前,設置一個標誌,該線程。然後,當您仍在使用舊連接時,文件描述符無法重新用於新連接。

+0

只有兩個或三個線程;它不是每個套接字的線程。所以終止線程不是一個選項。設置標誌將不起作用,因爲能夠設置/讀取標誌的線程不是執行'select()'的線程,因此'select()'仍然可以從錯誤的文件描述符中讀取。 (無論如何感謝您的迴應。) – mpontillo

0

如果關閉一個套接字,然後在設置了任何fdsets的封閉套接字fd的情況下調用select,則select將立即返回EBADF,所以不要這樣做。

如果你想讓多個線程管理公共的套接字池並且乾淨地處理它們,你需要使用某種鎖來確保一個線程在另一個線程調用select時不關閉套接字。如果您有全局的fd_sets來跟蹤哪些套接字是「活的」,則可以使用讀寫器鎖來防止對該套接字的訪問。在複製集合並調用select之前獲取讀鎖定;選擇返回後釋放鎖定。在關閉套接字之前獲取寫鎖,然後在從fd_set移除現在關閉的套接字後釋放它。

另一種可能性是使用原子讀取 - 修改 - 寫入指令來操作全局fd_set,然後當您想要關閉套接字時,首先將其從全局fd_set中移除,然後等待足夠長的時間以使所有線程都能夠取得進展在實際關閉它之前。這給你一個競爭條件(另一個線程可能在你刪除關閉fd之前複製全局fd_set,而不是直到之後才調用select),所以你需要知道等待的時間足夠長,這取決於你的系統。

+0

關於'EBADF'的好處,但這不是問題。當我做'select()'的時候,我知道所有的FD都是有效的。使用鎖定機制,select()仍然會喚醒可能更改的套接字。我需要設置鎖定和數據結構以跟蹤關於是否關閉特定套接字的元數據。而且很難知道我的元數據是否過時,因爲套接字可以被重用。 (因此與'int'套接字相比,數據結構需要特別複雜。)基於單線程'read()/ close()'管理的基於pipe()的IPC方法更簡單。 – mpontillo

2

有許多方法可以做到這一點,這裏有一對夫婦的其他想法給你。然而,兩者的想法都是爲套接字的所有者(即線程完成關閉,而不是另一個線程)。

將控制套接字添加到選定呼叫中的讀取集,例如, unix插座。編寫一些控制數據以從其選擇呼叫中斷該線程。然後線程可以檢查套接字是否應該關閉。可以將其作爲套接字結構的一部分,甚至可以作爲控制數據包的實際數據。

例如

fd_set readSet; 
FD_ZERO(&readSet); 
FD_SET(sock, &readSet); 
FD_SET(controlSock, &readSet); 

int n = select(maxfd, &readSet, NULL, NULL); 
... 
if (FD_ISSET(controlSock, &readSet)) { 
    /* check if sock should be closed or not, also drain controlSock */ 
} 

然後只需寫入控制套接字來發出信號即可關閉。

你也可以調用信號,這通常會阻止阻塞IO調用。除非它們自動重啓。調用將返回-1,errno將被設置爲EINTR。你可以檢查一個標誌,看看你是否應該關閉和打破電話。

例如

if (n == -1 && errno == EINTR) 
    goto cleanup; 
+0

這實際上是我最終做的;我確實需要額外的計算來確保我的'maxfd'是正確的,因爲我不能確定我的控制fd是否會比我選擇的套接字大。 – mpontillo

0

更好的方法來這裏工作是:

與非阻塞套接字(O_NONBLOCK)工作。

製作您的API,以便您可以爲select()調用中添加的每個FD添加回調函數。現在只要套接字準備好了I/O,你的回調就會被調用,你可以做任何你想做的事情(甚至關閉套接字)。

因此,您可以在單個線程中以系統方式處理套接字。

示例代碼:

int f1() 
{ 
    MultiplexAdd(fd1, callbackFn1); 
    MultiplexAdd(fd2, callbackFn2); 

    MultiplexMainLoop(); /* This is where you will be blocked on select() */ 
} 


void callbackFn1(int fd) 
{ 
    /* Processing as desired */ 
    close(fd); 
    MultiplexRemove(fd); /* Remove fd from select, else select will return -1 
                    with EABDF */ 
} 

即使你堅持,你必須去,你必須從其他線程關閉套接字的情況,那麼你可以創建一個pipe並添加到select()以及。

你的write()關閉了套接字。在對管道pipeReadCb()的回調中,可以關閉該套接字。

+0

非常好的答案! – 2015-08-03 06:10:47