PHP Socket 服务器内存泄漏

发布于 2024-10-26 05:02:47 字数 12137 浏览 1 评论 0原文

我在通过一个连接发送和接收数据时检查了内存,并且当内存返回到其先前的值时,我似乎正确地清除了变量。

但由于某种原因,如果我建立一个新连接,然后关闭连接,内存就会泄漏。我相信当接受套接字时可能会出现问题。

我正在使用 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();

编辑:抱歉,您可能需要将套接字接受更改回: if ($newchannel = @socket_accept($socket['socket']))

I have checked the memory whilst sending and receiving data over one connection, and I appear to be correctly clearing variables, as the memory returns to its previous value.

But for some reason if I make a new connection, then close the connection, memory is leaked. I believe the problem may be occurring when a socket is accepted.

I am using PHP 5.2.10

Hopefully one of you can find the time to have a play with the source and figure out where its gone wrong. Thanks in advance

<?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();

Edit: sorry you might need to change the socket accept back to:
if ($newchannel = @socket_accept($socket['socket']))

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

岛歌少女 2024-11-02 05:02:47

然后关闭连接,内存泄漏

这是一个棘手的问题 - 即使标准引用计数垃圾收集器也只是以难以预测的时间间隔启动。调用 gc_collect_cycles() 应该会触发 gc。尝试在关闭连接时调用它,看看是否有影响。

如果您仍然遇到问题 - 然后检查是否已编译循环引用计数器 - 如果没有,则获取它。

then close the connection, memory is leaked

This is a tricky one - even the standard reference counting garbage collector only kicks in at intervals which are difficult to predict. Calling gc_collect_cycles() should trigger the gc though. Try calling that whenever you close a connection and see if it makes a difference.

If you're still seeing problems - then check if you've got the cyclic reference counter compiled in - if not, then get it.

你与清晨阳光 2024-11-02 05:02:47

关闭连接后,通道阵列从未被删除,令人惊讶的是没有人注意到这一点。内存使用现在非常紧张。

取消设置($this->sockets[$socket_id]['channels'][$channel_id]);

但这确实意味着 LOST_SOCKET_CHANNEL 的任何事件暂时都是毫无用处的。

当堆栈溢出允许时,将接受我自己的答案。感谢您的所有帮助。我想..

The Channel array was never removed upon closing the connection, surprised no one picked up on this. Memory usage is now super tight.

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

But it does mean that any event for LOST_SOCKET_CHANNEL is pretty useless for the time being.

Will accept my own answer when stack over flow allows. Thanks for all your help ppl .. i guess..

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文