2010-06-30 105 views
2

嘗試將阻塞套接字服務器轉換爲非阻塞套接字服務器時出現奇怪問題。雖然這個消息只有在使用阻塞套接字發送時纔會被接收,但是使用非阻塞套接字的消息似乎會被無數次接收。 這裏是已更改代碼:在C++中正確寫入非阻塞套接字

return ::write(client, message, size); 

// Nonblocking socket code 
int total_sent = 0, result = -1; 
while(total_sent < size) { 
    // Create a temporary set of flags for use with the select function 
    fd_set working_set; 
    memcpy(&working_set, &master_set, sizeof(master_set)); 

    // Check if data is available for the socket - wait 1 second for timeout 
    timeout.tv_sec = 1; 
    timeout.tv_usec = 0; 
    result = select(client + 1, NULL, &working_set, NULL, &timeout); 

    // We are able to write - do so 
    result = ::write(client, &message[total_sent], (size - total_sent)); 
    if (result == -1) { 
     std::cerr << "An error has occured while writing to the server." 
       << std::endl; 
     return result; 
    } 
    total_sent += result; 
} 

return 0; 

編輯:主集的初始化看起來是這樣的:

// Private member variables in header file 
fd_set master_set; 
int sock; 

...

// Creation of socket in class constructor 
sock = ::socket(PF_INET, socket_type, 0); 

// Makes the socket nonblocking 
fcntl(sock,F_GETFL,0); 

FD_ZERO(&master_set); 
FD_SET(sock, &master_set); 

...

// And then when accept is called on the socket 
result = ::accept(sock, NULL, NULL); 
if (result > 0) { 
    // A connection was made with a client - change the master file 
    // descriptor to note that 
    FD_SET(result, &master_set); 
} 

我已經證實,在這兩種情況下,代碼只會被調用一次用於違規消息。另外,客戶端代碼根本沒有改變 - 有沒有人有任何建議?

+1

注意,非阻塞套接字返回-1,並將errno == EWOULDBLOCK或errno你是不是檢查== EAGAIN代替的阻塞。 – ninjalj 2010-06-30 22:50:26

+0

master_set是什麼樣的? – Mau 2010-06-30 23:04:08

+0

你沒有使用fd_sets或正確選擇,閱讀它們...... – SoapBox 2010-06-30 23:27:22

回答

2

我不認爲這個代碼在「非阻塞」版本中只被調用過一次(引用是因爲它並非真正的非阻塞,正如Maister指出的,look here),請再次檢查。如果阻塞和非阻塞版本一致,則非阻塞版本應返回total_sent(或大小)。與return 0而不是調用者可能認爲沒有發送。這將導致無限的發送......是不是發生了什麼?

另外你的「非阻塞」代碼很奇怪。你似乎用select來阻止它......好吧,超時時間爲1秒,但你爲什麼不讓它真的阻塞?即:刪除所有select材料並在write()中測試錯誤情況,其中errno爲EWOULDBLOCKselectpoll用於複用。

此外,你應該檢查選擇錯誤,並使用FD_ISSET來檢查套接字是否真的準備好。如果1秒超時真的發生了怎麼辦?或者如果選擇是由於某種中斷而停止的?如果在寫入時發生錯誤,您還應該寫出哪個錯誤,這比您的通用消息更有用。但我猜這部分代碼還沒有完成。

據我理解你的代碼,它可能看起來應該有點像(如果代碼是在獨特的線程中運行或螺紋,或接受連接時會改變細節分叉):

// Creation of socket in class constructor 
sock = ::socket(PF_INET, socket_type, 0); 
fcntl(sock, F_SETFL, O_NONBLOCK); 

// And then when accept is called on the socket 
result = ::accept(sock, NULL, NULL); 
if (result > 0) { 
    // A connection was made with a client 
    client = result; 
    fcntl(client, F_SETFL, O_NONBLOCK); 
} 

// Nonblocking socket code 
result = ::write(client, &message[total_sent], (size - total_sent)); 
if (result == -1) { 
    if (errno == EWOULDBLOCK){ 
     return 0; 
    } 
    std::cerr << "An error has occured while writing to the server." 
      << std::endl; 
    return result; 
} 
return size; 
+0

每當他添加一個EWOULDBLOCK/EAGAIN檢查時,我們應該告訴他檢查EINTR; P – ninjalj 2010-06-30 23:50:47

+0

@ninjalj:是的。這就是爲什麼我在我的回答中寫道「如果選擇是由於某種干擾而停止的」。 – kriss 2010-07-01 00:01:20

+0

我已經更改了代碼,建議您使插槽實際非阻塞 - 非常感謝您的建議。事實證明,發送函數實際上只被調用一次,但消息參數的大小設置不正確,導致它不斷循環。但是,您的幫助對於實現這一目標至關重要 - 謝謝! – candrews 2010-07-01 16:32:14

4
fcntl(sock,F_GETFL,0); 

這是如何使插座無阻塞?

fcntl(sock, F_SETFL, O_NONBLOCK); 

另外,如果你實際上可以寫入套接字非阻塞風格

FD_ISSET(client, &working_set); 
+0

謝謝!我在我的代碼中解決了這兩個問題。 – candrews 2010-07-01 16:30:00