2016-04-22 36 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的值,如果该值等于或高于允许的服务器容量,则发送“忙”消息并关闭新连接。

该解决方案非常有效,并允许您通知客户有关服务器的状态。