2016-04-22 65 views
0

我正在C中編寫一個TCP服務器,並發現一些不尋常的事情發生一旦聽力FD得到「太多打開的文件」錯誤。 accept調用不再阻止,並且始終返回-1。TCP服務器 - 從「太多打開的文件」恢復

我也嘗試關閉收聽fd並重新打開,重新綁定它,但似乎沒有工作。

我的問題是,爲什麼accept保持返回-1在這種情況下,我應該怎樣做才能阻止它,使服務器能夠接受新的連接任何老客戶關閉後? (插座當然能夠accept時再正確一些連接關閉)

====== UPDATE:澄清======

只是因爲有效客戶的數量是出現該問題超過了開放文件系統的限制,所以我並沒有close示例代碼中的任何公認fds,只是爲了讓它更快地複製。

我每次添加時間戳accept返回輸出,減緩connect頻率曾經在2秒鐘,然後我發現其實在最新的成功accept之後立即發生了「打開的文件太多」錯誤。所以我認爲這是因爲當maxium fds達到時,每個accept的調用都會立即返回,返回值是-1。 (我認爲accept仍然會阻止,但在下一次傳入connect時返回-1。在這種情況下accept的行爲是我自己的理論,而不是來自手冊頁,如果它是錯誤的,請讓我知道)。

因此,對於我的第二個問題,爲了讓它停止,我認爲這是一個解決方案,在任何連接是close d之前停止呼叫accept

也更新示例代碼。謝謝你的幫助。

======示例代碼======

以下是我如何測試它。首先將ulimit -n設置爲一個較低的值(如16)並運行由以下C源代碼編譯的服務器程序;然後使用Python腳本來創建幾個連接

/* TCP server; bind :5555 */ 

#include <stdio.h> 
#include <unistd.h> 
#include <time.h> 
#include <stdlib.h> 
#include <string.h> 
#include <netdb.h> 
#include <sys/types.h> 
#include <sys/socket.h> 
#include <netinet/in.h> 
#include <arpa/inet.h> 

#define BUFSIZE 1024 
#define PORT 5555 

void error(char const* msg) 
{ 
    perror(msg); 
    exit(1); 
} 

int listen_port(int port) 
{ 
    int parentfd; /* parent socket */ 
    struct sockaddr_in serveraddr; /* server's addr */ 
    int optval; /* flag value for setsockopt */ 
    parentfd = socket(AF_INET, SOCK_STREAM, 0); 
    if (parentfd < 0) { 
     error("ERROR opening socket"); 
    } 

    optval = 1; 
    setsockopt(parentfd, SOL_SOCKET, SO_REUSEADDR, 
      (const void *)&optval , sizeof(int)); 

    bzero((char *) &serveraddr, sizeof(serveraddr)); 

    serveraddr.sin_family = AF_INET; 
    serveraddr.sin_addr.s_addr = htonl(INADDR_ANY); 
    serveraddr.sin_port = htons((unsigned short)port); 

    if (bind(parentfd, (struct sockaddr *) &serveraddr, sizeof(serveraddr)) < 0) { 
     error("ERROR on binding"); 
    } 

    if (listen(parentfd, 5) < 0) { 
     error("ERROR on listen"); 
    } 
    printf("Listen :%d\n", port); 
    return parentfd; 
} 

int main(int argc, char **argv) 
{ 
    int parentfd; /* parent socket */ 
    int childfd; /* child socket */ 
    int clientlen; /* byte size of client's address */ 
    struct sockaddr_in clientaddr; /* client addr */ 
    int accept_count; /* times of accept called */ 

    accept_count = 0; 
    parentfd = listen_port(PORT); 

    clientlen = sizeof(clientaddr); 

    while (1) { 
     childfd = accept(parentfd, (struct sockaddr *) &clientaddr, (socklen_t*) &clientlen); 
     printf("accept returns ; count=%d ; time=%u ; fd=%d\n", accept_count++, (unsigned) time(NULL), childfd); 
     if (childfd < 0) { 
      perror("error on accept"); 

      /* the following 2 lines try to close the listening fd and re-open it */ 
      // close(parentfd); 
      // parentfd = listen_port(PORT); 

      // the following line let the program exit at the first error 
      error("--- error on accept"); 
     } 
    } 
} 

Python程序來創建連接

import time 
import socket 

def connect(host, port): 
    s = socket.socket() 
    s.connect((host, port)) 
    return s 

if __name__ == '__main__': 
    socks = [] 

    try: 
     try: 
      for i in xrange(100): 
       socks.append(connect('127.0.0.1', 5555)) 
       print ('connect count: ' + str(i)) 
       time.sleep(2) 
     except IOError as e: 
      print ('error: ' + str(e)) 
     print ('stop') 
     while True: 
      time.sleep(10) 
    except KeyboardInterrupt: 
     for s in socks: 
      s.close() 
+0

我注意到您的示例代碼根本不涉及客戶端。在評論中你說這是爲了可讀性......但這也可能隱藏了這個問題。例如,如果您調用'fork',則需要記住關閉** all **進程上的連接(只有當所有打開的句柄關閉時,連接纔會被複制並關閉)。使用您的示例代碼無法檢查這些問題。現在,您只需要在客戶端調用close,就像我之前的其他人所說的那樣。 – Myst

回答

4

爲什麼接受保留返回-1在這種情況下

因爲你用完文件描述符,就像錯誤消息所述。

我應該做些什麼來阻止它,並讓服務器能夠在任何老客戶關閉後接受新的連接?

關閉客戶端。 問題不是accept()返回-1,這是因爲一旦你完成了它們,你沒有關閉接受的套接字。

關閉監聽套接字不是解決方案。這只是另一個問題。

編輯通過「完成了他們」我的意思的幾件事情之一:

  1. 他們已經完成了你,這是由recv()返回零所示。
  2. 你已經完成了,例如在發送最終答覆之後。
  3. 當您發送或接收來自EAGAIN/EWOULDBLOCK以外的錯誤時。
  4. 當您遇到一些其他內部致命錯誤,從而導致您無法進一步處理該客戶端,例如收到無法解析的請求或其他導致連接或會話無效的致命應用程序錯誤或整個客戶端。

在所有這些情況下,您應關閉接受的套接字。

+0

我只是從示例代碼中刪除'close'調用,因爲在這種情況下當前客戶端可能仍然處於活動狀態。你能告訴我,我應該怎麼做才能至少阻止'accept'返回-1? – neuront

+1

我已經告訴過你了。完成後請關閉可接受的套接字。 – EJP

+0

@neuront那麼你打算什麼時候完成孩子的FD?無論何時,當它發生時,都需要關閉它們。 – immibis

0

EJP的答案是正確的,但它並沒有告訴你如何處理這種情況。你需要做的是實際上做一些你能夠接受的套接字。簡單地調用它們就不會收到任何東西,但它會處理資源耗盡問題。你必須做什麼才能獲得正確的實現,並開始接收已接受的套接字並繼續接收,直到你收到0字節。如果您收到0字節,則表示對等方已使用其插槽的一側完成。這也是您的套接字調用關閉的觸發器,並處理資源問題。

您不必停止收聽。這將阻止你的服務器能夠處理新的請求,這不是問題。

+0

這就是*他們*完成*你。*還有其他場合關閉插座。 *你*可以結束*他們*(例如保持活動超時或單次連接),或者可能發生了處理該客戶端的致命錯誤。 – EJP

+0

OP並沒有真正給出線索,因此我假設他將向服務器發送數據。無論如何,接收和注意接收到的0字節是解決方案的關鍵。而這個錯誤案例的頂部。正如您在更新後的答案中提到的那樣。 –

0

我實施的解決方案here是查看新(接受的)fd的值,如果該值等於或高於允許的服務器容量,則發送「忙」消息並關閉新連接。

該解決方案非常有效,並允許您通知客戶有關服務器的狀態。