2011-03-23 169 views
2

我已經檢查了內存,同時通過一個連接發送和接收數據,並且我似乎正確地清除變量,因爲內存返回到先前的值。PHP Socket服務器內存泄漏

但由於某種原因,如果我建立新的連接,然後關閉連接,內存泄漏。我相信當插座被接受時,問題可能會發生。

我使用PHP 5.2.10

希望你可以發現有一個與源的播放和揣摩出其出了錯的時間。在此先感謝

<?php 
    Class SuperSocket 
     { 
      var $listen = array(); 
      var $status_listening = FALSE; 
      var $sockets = array(); 
      var $event_callbacks = array(); 
      var $recvq = 1; 
      var $parent; 
      var $delay = 100; // 10,000th of a second 
      var $data_buffer = array(); 

      function SuperSocket($listen = array('127.0.0.1:123')) 
       { 
        $listen = array_unique($listen); 
        foreach ($listen as $address) 
         { 
          list($address, $port) = explode(":", $address, 2); 
          $this->listen[] = array("ADDR" => trim($address), "PORT" => trim($port)); 
         }; 
       } 

      function start() 
       { 
        if ($this->status_listening) 
         { 
          return FALSE; 
         }; 
        $this->sockets = array(); 
        $cursocket = 0; 
        foreach ($this->listen as $listen) 
         { 
          if ($listen['ADDR'] == "*") 
           { 
            $this->sockets[$cursocket]['socket'] = socket_create_listen($listen['PORT']); 
            $listen['ADDR'] = FALSE; 
           } 
          else 
           { 
            $this->sockets[$cursocket]['socket'] = socket_create(AF_INET, SOCK_STREAM, SOL_TCP); 
           }; 
          if ($this->sockets[$cursocket]['socket'] < 0) 
           { 
            return FALSE; 
           }; 
          if (@socket_bind($this->sockets[$cursocket]['socket'], $listen['ADDR'], $listen['PORT']) < 0) 
           { 
            return FALSE; 
           }; 
          if (socket_listen($this->sockets[$cursocket]['socket']) < 0) 
           { 
            return FALSE; 
           }; 
          if (!socket_set_option($this->sockets[$cursocket]['socket'], SOL_SOCKET, SO_REUSEADDR, 1)) 
           { 
            return FALSE; 
           }; 
          if (!socket_set_nonblock($this->sockets[$cursocket]['socket'])) 
           { 
            return FALSE; 
           }; 
          $this->sockets[$cursocket]['info'] = array("ADDR" => $listen['ADDR'], "PORT" => $listen['PORT']); 
          $this->sockets[$cursocket]['channels'] = array(); 
          $this->sockets[$cursocket]['id'] = $cursocket; 
          $cursocket++; 
         }; 
        $this->status_listening = TRUE; 
       } 

      function new_socket_loop(&$socket) 
       { 
        $socket =& $this->sockets[$socket['id']]; 
        if ($newchannel = @stream_socket_accept($socket['socket'], 0));//@socket_accept($socket['socket'])) 
         { 
          socket_set_nonblock($newchannel); 
          $socket['channels'][]['socket'] = $newchannel; 
          $channel = array_pop(array_keys($socket['channels'])); 
          $this->remote_address($newchannel, $remote_addr, $remote_port); 
          $socket['channels'][$channel]['info'] = array('ADDR' => $remote_addr, 'PORT' => $remote_port); 
          $event = $this->event("NEW_SOCKET_CHANNEL"); 
          if ($event) 
          $event($socket['id'], $channel, $this); 
         }; 
       } 

    function endswith($string, $test) { 
     $strlen = strlen($string); 
     $testlen = strlen($test); 
     if ($testlen > $strlen) return false; 
     return substr_compare($string, $test, -$testlen) === 0; 
    } 

      function recv_socket_loop(&$socket) 
       { 
        $socket =& $this->sockets[$socket['id']]; 
        foreach ($socket['channels'] as $channel_id => $channel) 
         { 
          unset($buffer);#Flush buffer 
          $status = @socket_recv($channel['socket'], $buffer, $this->recvq, 0); 
          if ($status === 0 && $buffer === NULL) 
           { 
            $this->close($socket['id'], $channel_id); 
           } 
          elseif (!($status === FALSE && $buffer === NULL)) 
           { 
            $sockid = $socket['id']; 
            if(!isset($this->data_buffer[$sockid])) 
             $this->data_buffer[$sockid]=''; 

            if($buffer!="\r"&&$buffer!="\n") 
            { 
             //Putty ends with \r\n 
             $this->data_buffer[$sockid].=$buffer; 
            } 
            else if($buffer!="\n") //ignore the additional newline char \n 
            { 
             $event = $this->event("DATA_SOCKET_CHANNEL"); 
             if ($event) 
              $event($socket['id'], $channel_id, $this->data_buffer[$sockid], $this); 
             unset($this->data_buffer[$sockid]); 
            } 

           }; 
         } 
       } 

      function stop() 
       { 
        $this->closeall(); 
        $this->status_listening = FALSE; 
        foreach ($this->sockets as $socket_id => $socket) 
         { 
          socket_shutdown($socket['socket']); 
          socket_close($socket['socket']); 
         }; 
        $event = $this->event("SERVER_STOP"); 
        if ($event) 
        $event($this); 
       } 

      function closeall($socket_id = NULL) 
       { 
        if ($socket_id === NULL) 
         { 
          foreach ($this->sockets as $socket_id => $socket) 
           { 
            foreach ($socket['channels'] as $channel_id => $channel) 
             { 
              $this->close($socket_id, $channel_id); 
             } 
           } 
         } 
        else 
         { 
          foreach ($this->sockets[$socket_id]['channels'] as $channel_id => $channel) 
           { 
            $this->close($socket_id, $channel_id); 
           }; 
         }; 
       } 

      function close($socket_id, $channel_id) 
       { 
        unset($this->data_buffer[$socket_id]); //clear the sockets data buffer 
        $arrOpt = array('l_onoff' => 1, 'l_linger' => 1); 
        @socket_shutdown($this->sockets[$socket_id]['channels'][$channel_id]['socket']); 
        @socket_close($this->sockets[$socket_id]['channels'][$channel_id]['socket']); 
        $event = $this->event("LOST_SOCKET_CHANNEL"); 
        if ($event) 
        $event($socket_id, $channel_id, $this); 
       } 

      function loop() 
       { 
        while ($this->status_listening) 
         { 
          usleep($this->delay); 
          foreach ($this->sockets as $socket) 
           { 
            $this->new_socket_loop($socket); 
            $this->recv_socket_loop($socket); 
           }; 
          $event = $this->event("END_SOCKET_CHANNEL"); 
          if ($event) 
          $event($this); 
         }; 
       } 

      function write($socket_id, $channel_id, $buffer) 
       { 
        @socket_write($this->sockets[$socket_id]['channels'][$channel_id]['socket'], $buffer); 
        @socket_write($this->sockets[$socket_id]['channels'][$channel_id]['socket'], 'Server memory usage: '.memory_get_usage().'/'.memory_get_peak_usage(true)."\r\n"); 
       } 

      function get_channel_info($socket_id, $channel_id) 
       { 
        return $this->sockets[$socket_id]['channels'][$channel_id]['info']; 
       } 

      function get_socket_info($socket_id) 
       { 
        $socket_info = $this->sockets[$socket_id]['info']; 
        if (empty($socket_info['ADDR'])) 
         { 
          $socket_info['ADDR'] = "*"; 
         }; 
        return $socket_info; 
       } 

      function get_raw_channel_socket($socket_id, $channel_id) 
       { 
        return $this->sockets[$socket_id]['channels'][$channel_id]['socket']; 
       } 

      function remote_address($channel_socket, &$ipaddress, &$port) 
       { 
        socket_getpeername($channel_socket, $ipaddress, $port); 
       } 

      function event($name) 
       { 
        if (isset($this->event_callbacks[$name])) 
        return $this->event_callbacks[$name]; 
       } 

      function assign_callback($name, $function_name) 
       { 
        $this->event_callbacks[$name] = $function_name; 
       } 
     }; 

    ?> 

Server.php

include("supersocket.class.php"); 

function startswith($string, $test) { 
    return strpos($string, $test, 0) === 0; 
} 

function newdata($socket_id, $channel_id, $buffer, &$server) 
    { 
     //$server->write($socket_id, $channel_id, ">".$buffer."\r\n"); 
     if($buffer=="STOP") 
     { 
      $server->stop(); 
     } 
     else if($buffer=="DATETIME") 
     { 
      $server->write($socket_id, $channel_id, ">".date("dmYHis")."\r\n"); 
     } 
     else 
     { 
      $server->write($socket_id, $channel_id, ">BAD\r\n"); 
     } 

    }; 

function newclient($socket_id, $channel_id, &$server) 
{ 
    $server->write($socket_id, $channel_id, "HEADER\n\r"); 
} 

$socket = new SuperSocket(array('127.0.0.1:12345')); 
$socket->assign_callback("DATA_SOCKET_CHANNEL", "newdata"); 
$socket->assign_callback("NEW_SOCKET_CHANNEL", "newclient"); 
$socket->start(); 
//set_time_limit(60*2); 
set_time_limit(60*60*24*5); //5 days 
$socket->loop(); 

編輯:對不起,你可能需要更改插座接受​​回: 如果($ newchannel = @socket_accept($插座[ '插座'] ))

+0

那是很多代碼 – dynamic 2011-03-23 14:26:12

+2

看到2553字符44.如果你經常接受/關閉連接,是否有大量內存泄漏? – Quamis 2011-03-23 14:30:26

+0

?一些垃圾收集者沒有其他人那麼積極。 – lunixbochs 2011-03-23 14:40:50

回答

-1

關閉連接後,Channel數組從未被刪除,驚訝的是沒有人接受這個。內存使用現在超級緊張。

unset($ this-> sockets [$ socket_id] ['channels'] [$ channel_id]);

但它的確意味着任何LOST_SOCKET_CHANNEL事件暫時都沒用。

堆棧溢出允許時接受我自己的答案。感謝您的所有幫助脂肪酶..我想..

+6

沒有人發現它可能是因爲你包含了幾百行代碼;對於幫助站點,很少有(如果有的話)實際上將讀取和調試那些自定義編寫的代碼。這就是爲什麼你應該發佈最短的測試用例來證明這個問題。同樣,如果你已經用bugs.php.net報告了這麼多代碼,他們可能會解決這個問題,並建議你減少測試在這裏,你基本上只是把你所有的代碼扔在SO上,希望我們能夠爲你調試你的整個腳本 - 對不起,但我不認爲這就是爲什麼有人來到這裏。 – ken 2012-02-22 15:52:31

1

然後關閉連接,內存泄漏

這是一個棘手的一個 - 即使是標準的引用計數垃圾收集器僅在間隔踢這很難預測。調用gc_collect_cycles()應該會觸發gc。每當你關閉一個連接時,試着調用它,看看它是否有所作爲。

如果你仍然看到問題 - 那麼檢查你是否有循環引用計數器編譯 - 如果沒有,然後得到它。

+0

'gc_collect_cycles'是PHP的一部分5.3新的和改進的GC系統OP使用PHP 5.2.x,這當然是問題的一部分。 – Charles 2011-03-23 18:31:53