2013-09-30 65 views
1

我有一个PHP脚本(我可以在线找到)无限期地监听一个端口,如果有连接到这个端口,它将建立一个TCP connection。然而,当我运行这个脚本并且有很多连接(大约500)时,连接的数量会增加。在此状态下连接的远程设备无法再次连接,因为CLOSE_WAIT未终止。PHP套接字脚本导致很多很长的CLOSE_WAIT套接字连接

// port info 
$host = "0.0.0.0"; 
$port = 10260; 
$pos = 1; 

// don't timeout! 
set_time_limit(0); 
record("START"); 

$sock = socket_create(AF_INET, SOCK_STREAM, 0); 
$timeout = array('sec'=>3000,'usec'=>0); 
$try = socket_set_option($sock,SOL_SOCKET,SO_RCVTIMEO,$timeout); 

// Bind the socket to the address/port 
if(!socket_bind($sock, $host, $port)) 
{ 
    echo socket_last_error() ; 
    die('Could not bind to address'); 
} 
record("SOCKET BIND OK"); 

// start listening for connections 
$result = socket_listen($sock, 1024) or die("Could not set up socket listener\n"); 
record("SOCKET LISTEN OK"); 
$clients = array($sock); 


// infinite while loop 
while(1) 
{ 

    // Setup clients listen socket for reading 
    $read = $clients; 

    $e = NULL; 

    if (socket_select($read, $write = NULL, $except = NULL, 0,0) < 1) 
    { 
     continue; 
    } 

    /* if a new read ready is being made add it to the client array */ 

    if (in_array($sock, $read)) { 
     record("NEW CONNECTION"); 
     $clients[$pos] = $newsock = socket_accept($sock); 
     $curpos = $pos; 
     $pos++; 
     socket_getpeername($newsock, $ip,$port); 
     record("Incoming IP: {$ip} PORT: {$port}"); 
     // remove the listening socket from the clients-with-data array 
     $key = array_search($sock, $read); 
     unset($read[$key]); 

    } // end if in_array 

    // loop through all the clients that have data to read from 
    foreach ($read as $read_key => $read_sock) { 
     // read until newline or 1024 bytes 
     // socket_read while show errors when the client is disconnected, so silence the error messages 
     $key = $read_key; 
     $fulldata = $data = @socket_read($read_sock, 1024); 

     // check if the client is disconnected 
     if ($data === false) { 
      // remove client for $clients array 
      $key = array_search($read_sock, $clients); 
      socket_close($read_sock); 
      unset($clients[$key]); 

      record("NO DATA"); 

      // continue to the next client to read from, if any 
      continue; 
     } 


     // .. do something with $data ... 

    } 


} 

socket_close($sock);  
record("END"); 
die("DONE"); 

我试图在代码中使用socket_close()但无济于事。 CLOSE_WAIT似乎需要更长时间才能转移到下一个状态。

+0

我也有类似的问题。在我的情况下,问题是单个客户端突然断开连接,当其缓冲区已满时,则无法连接到端口。曾经了解这个问题,但我已经修复了一个不太容忍的手工ping系统(只是一个信息,可能可以帮助你) –

+0

@chumkiu感谢您的信息。当你说客户端的缓冲区已满时,你是指应用程序还是连接到端口的远程设备?如果是应用程序,有没有办法确定缓冲区是否满了?谢谢! –

+0

我的意思是服务器上'netstat'中ghost客户端的'Recv-Q'。我没有发现任何东西在PHP中读取(但我的经验是在6年前) –

回答

3

我发现在代码两个问题:

首先,第四参数(tv_sec)socket_select的应而不是0 NULL作为无用的,无限循环在Manual,0原因高CPU负载描述,而NULL将阻塞,直到发生任何事情:

tv_sec可能为零,导致socket_select()立即返回。这对于轮询很有用。如果tv_sec为NULL(无超时),则socket_select()可以无限期地阻塞。

第二,我认为这是CLOSE_WAIT的原因是您的测试如果$ data为false。这只适用于一个优雅的关闭连接,但正如一条评论here所说,您还可以对en空字符串进行测试。 所以改变一个行:

if (socket_select($read, $write = NULL, $except = NULL, NULL, 0) < 1) 

和对方:

// check if the client is disconnected 
if ($data === false || $data === '') { 

,你应该罚款

+0

感谢您指出错误。我已经纠正了它们,它帮助了很多人。当连接大约100-200时,CLOSE_WAIT状态已经转移到了LAST_ACK状态。但是,当它达到500左右时,我仍然得到一些'CLOSE_WAIT'连接(比以前少得多),并且处于间歇性'LAST_ACK'状态。这是因为'socket_close'完成需要时间吗? –

+0

根据您的脚本对所有客户端所做的操作,可能需要一段时间,直到socket_close被调用为yes,此时它仍处于CLOSE_WAIT状态。据我所知,LAST_ACK意味着它等待客户端发送一个ACK到FIN数据包。如果其中一个数据包被防火墙或其他东西占用,则必须等到内核将超时清理连接。 – a4c8b