node.js脚本和可能的内存泄漏

时间:2012-10-25 22:20:05

标签: node.js memory-leaks

我正在编写一个node.js脚本,它从一个连接的套接字中获取数据并将其发送到另一个连接的套接字。在测试期间,我注意到如果我在服务器发送大量数据时反复断开连接并重新连接客户端,则会出现内存泄漏。以下是node.js代码。

var net = require('net');
var logServer = net.createServer();  
var clientList = [];
var clientIPList = [];
var serverList = [];
var serverIPList = [];
var port = 6451;

logServer.on('connection', function(client) {
    client.setEncoding('utf8');
    client.once('data', function(data) {
        if (data[0].toString() == 'S') {
            var server = client;
            client = undefined;
            serverList.push(server);
            serverIPList.push(server.remoteAddress + ":" + server.remotePort);
            console.log('Server connected: %s:%d', server.remoteAddress, server.remotePort);

            server.on('data', function(data) {
                for(var i=0;i<clientList.length;i+=1) {
                    try {
                        clientList[i].write(data);
                    } catch (err) {
                        console.log('Error writing to client "data event": ' + clientIPList[i] );
                        // close and null the socket on write error
                        try {
                            clientList[i] = null;
                            clientList[i].end();
                        } catch (err) {}
                        clientList.splice(i, 1);
                        clientIPList.splice(i, 1);
                    }
                }            
            })

            server.on('end', function() {
                try {
                    var d;
                    if( (d = serverList.indexOf( server )) != -1 ) {
                        console.log('Server disconnecting "end event": ' + serverIPList[d]);
                        try {
                            serverList[d] = null;
                            serverList[d].end();
                        } catch (err) {}
                        serverList.splice(d, 1);
                        serverIPList.splice(d, 1);
                    }
                    else {
                        console.log('Server disconnecting "end event": unknown server');                    
                    }
                } catch (err) {
                    console.log('Error cleaning up server socket list on "end event"');
                }
            })

            server.on('timeout', function() {
                try {
                    var d;
                    if( (d = serverList.indexOf( server )) != -1 ) {
                        console.log('Server disconnecting "timeout event": ' + serverIPList[d]);
                        try {
                            serverList[d] = null;
                            serverList[d].end();
                        } catch (err) {}
                        serverList.splice(d, 1);
                        serverIPList.splice(d, 1);
                    }
                    else {
                        console.log('Server disconnecting "timeout event": unknown server');                    
                    }
                } catch (err) {
                    console.log('Error cleaning up server socket list on "timeout event"');
                }
            })

            server.on('error', function(e) {
                try {
                    var d;
                    if( (d = serverList.indexOf( server )) != -1 ) {
                        console.log('Server disconnecting ' + e.code + ' "error event": ' + serverIPList[d]);
                        try {
                            serverList[d] = null;
                            serverList[d].end();
                        } catch (err) {}
                        serverList.splice(d, 1);
                        serverIPList.splice(d, 1);
                    }
                    else {
                        console.log('Server disconnecting "error event": unknown server');                  
                    }
                } catch (err) {
                    console.log('Error cleaning up server socket list on "error event"');
                }
            })

            server.on('close', function() {
                try {
                    var d;
                    if( (d = serverList.indexOf( server )) != -1 ) {
                        console.log('Server disconnecting "close event": ' + serverIPList[d]);
                        try {
                            serverList[d] = null;
                            serverList[d].end();
                        } catch (err) {}
                        serverList.splice(d, 1);
                        serverIPList.splice(d, 1);
                    }
                } catch (err) {
                    console.log('Error cleaning up server socket list on "close event"');
                }
            })
            server.on('drain', function() {
            })
        } 

        else {
            clientList.push(client);
            clientIPList.push(client.remoteAddress + ":" + client.remotePort);
            console.log('Client connected: %s:%d',client.remoteAddress, client.remotePort);

            client.on('data', function(data) {
                console.log('writing "%s" to %d servers', data.replace(/[\r\n]/g,''), serverList.length);
                for(var i=0;i<serverList.length;i+=1) {
                    try {
                        serverList[i].write(data);
                    } catch (err) {
                        console.log('Error writing to server "data event": ' + serverIPList[i] );
                        try {
                            serverList[i] = null;
                            serverList[i].end();
                        } catch (err) {}
                        serverList.splice(i, 1);
                        serverIPList.splice(i, 1);
                    }
                }
            })

            client.on('end', function() {
                try {
                    var d;
                    if( (d = clientList.indexOf( client )) != -1 ) {
                        console.log('Client disconnecting "end event": ' + clientIPList[d]);
                        // close and null the socket
                        try {
                            clientList[d] = null;
                            clientList[d].end();
                        } catch (err) {}
                        clientList.splice(d, 1);
                        clientIPList.splice(d, 1);
                    }
                    else {
                        console.log('Client disconnecting "end event": unknown client');
                    }               
                } catch (err) {
                    console.log('Error cleaning up socket client list on "end event"');
                }
            })

            client.on('timeout', function() {
                try {
                    client.end();
                } catch (err) {
                    var d;
                    if( (d = clientList.indexOf( client )) != -1 ) {
                        console.log('Error closing client connection "timeout event": ' + clientIPList[d]);
                    }
                    else {
                        console.log('Error closing client connection "timeout event": unknown client');                 
                    }
                }               
                try {
                    var d;
                    if( (d = clientList.indexOf( client )) != -1 ) {
                        console.log('Client disconnecting "timeout event": ' + clientIPList[d]);
                        try {
                            clientList[d] = null;
                            clientList[d].end();
                        } catch (err) {}
                        clientList.splice(d, 1);
                        clientIPList.splice(d, 1);
                    }
                    else {
                        console.log('Client disconnecting "timeout event": unknown client');
                    }               
                } catch (err) {
                    console.log('Error cleaning up client socket list on "timeout event"');
                }
            })

            client.on('error', function(e) {
                try {
                    var d;
                    if( (d = clientList.indexOf( client )) != -1 ) {
                        console.log('Client disconnecting ' + e.code + ' "error event": ' + clientIPList[d]);
                        try {
                            clientList[d] = null;
                            clientList[d].end();
                        } catch (err) {}
                        clientList.splice(d, 1);
                        clientIPList.splice(d, 1);
                    }
                    else {
                        console.log('Client disconnecting ' + e.code + ' "error event": unknown client');
                    }               
                } catch (err) {
                    console.log('Error cleaning up client socket list on "error event"');
                }
            })

            client.on('close', function() {
                try {
                    var d;
                    if( (d = clientList.indexOf( client )) != -1 ) {
                        console.log('Client disconnecting "close event": ' + clientIPList[d]);
                        try {
                            clientList[d] = null;
                            clientList[d].end();
                        } catch (err) {}
                        clientList.splice(d, 1);
                        clientIPList.splice(d, 1);
                    }
                } catch (err) {
                    console.log('Error cleaning up client socket list on "close event"');
                }
            })

            client.on('drain', function() {
                // nothing
            })
        }
    })
})
logServer.listen( port );

据我所知,我正在处理所有关键的“网络”事件,并且一旦检测到断开连接,我正在正确清理插座。以下是我用来测试的两个脚本。第一个只是作为客户端反复连接和断开连接,第二个作为服务器发送数据。我同时运行它们。

condiscon.rb:将自己注册为客户端后连接和断开连接“连接后发送换行符”。我使用'./condiscon.rb 1000'

运行
#!/usr/bin/ruby

require 'rubygems'
require 'socket'

def connectFlac
    host = '10.211.55.10'
    port = 6451

    sock = TCPSocket.open( host, port )
    sock.puts( "" )
    sock
end

sock = connectFlac()
data = []
user_agents = {}
instances_lat = {}

count = ARGV.shift.to_i

while( count > 0 )
    sock = connectFlac()
    sleep( 0.05 )
    sock.close()
    sleep( 0.05 )
    count-= 1
end

dataflood.rb:作为服务器连接,用计数器发送约2600字节的abcde数据包。我运行'dataflood.rb 30000'

#!/usr/bin/ruby

require 'socket'

def connectFlac
    host = '10.211.55.10'
    port = 6451

    sock = TCPSocket.open( host, port )
    sock.setsockopt(Socket::IPPROTO_TCP,Socket::TCP_NODELAY,1)
    sock.puts( "S" )
    sock
end

def syntax()
    print "./script number_of_packets\n"
    exit( 1 )
end

data = ""
(1..100).each {
    data+= "abcdefghijklmnopqrstuvwxyz"
}

sock = connectFlac()

numpackets = ARGV.shift.to_i || syntax()
counter = 1
byteswritten = 0

while( numpackets > 0 )
    r,w,e = IO.select( nil, [sock], nil, nil )
    w.each do |sock_write|
        print numpackets, "\n"
        sock.write( counter.to_s + "|" + data + "\n" )
        sock.flush()
        byteswritten+= counter.to_s.length + 1 + data.length + 1
        counter+= 1
        numpackets-= 1
    end
end
sock.close()

print "Wrote #{byteswritten} bytes\n"

以下是我看到的一些结果。在任何测试之前在logserver.js上运行内存配置文件时,它使用大约9兆字节的驻留内存。我要包含一个pmap来显示泄漏似乎占用的内存部分。

[root@localhost ~]# ps vwwwp 20658
  PID TTY      STAT   TIME  MAJFL   TRS   DRS   **RSS** %MEM COMMAND
20658 pts/4    Sl+    0:00      0  8100 581943 **8724**  0.8 /usr/local/node-v0.8.12/bin/node logserverdemo.js

[root@localhost ~]# pmap 20658
20658:   /usr/local/node-v0.8.12/bin/node logserverdemo.js    
0000000000400000   8104K r-x--  /usr/local/node-v0.8.12/bin/node    
0000000000de9000     76K rwx--  /usr/local/node-v0.8.12/bin/node    
0000000000dfc000     40K rwx--    [ anon ]    
**000000001408a000    960K rwx--    [ anon ]**    
0000000040622000      4K -----    [ anon ]

在ame时间对着logserver运行上面的两个ruby脚本之后,在流量停止后约30分钟内存是什么样的内存。 (我等待所有的gc发生)

[root@localhost ~]# ps vwwwp 20658
  PID TTY      STAT   TIME  MAJFL   TRS   DRS   RSS %MEM COMMAND
20658 pts/4    Sl+    0:01      0  8100 665839 **89368**  8.7 /usr/local/node-v0.8.12/bin/node logserverdemo.js

[root@localhost ~]# pmap 20658
20658:   /usr/local/node-v0.8.12/bin/node logserverdemo.js

0000000000400000   8104K r-x--  /usr/local/node-v0.8.12/bin/node
0000000000de9000     76K rwx--  /usr/local/node-v0.8.12/bin/node    
0000000000dfc000     40K rwx--    [ anon ]    
**000000001408a000  80760K rwx--    [ anon ]**
0000000040622000      4K -----    [ anon ]
0000000040623000     64K rwx--    [ anon ]

dataflood.rb共写了78198894字节的数据并且泄漏非常接近。我将内存转储到0x1408a000,我看到我从dataflood.rb发送的大部分数据包都被卡在内存中。

[root@localhost ~]# ./memoryprint 20658 0x1408a000 80760000 > 20658.txt
[root@localhost ~]# strings 20658.txt | grep '|abcde' | wc -l
30644
[root@localhost ~]# strings 20658.txt | grep '|abcde' | sort | uniq | wc -l
29638
等了24小时后,记忆仍然没有释放。任何人都可以给我的任何帮助将不胜感激。

2 个答案:

答案 0 :(得分:0)

可能不会导致泄漏,但是在将套接字设置为null后,您将结束()套接字:

clientList[i] = null;
clientList[i].end();

不应该是相反的吗?

答案 1 :(得分:0)

由于输入流和输出流之间的速度不平衡,可能会发生此问题。

尝试更改以下源代码。

<AS-IS>

server.on('data', function(data) {
                for(var i=0;i<clientList.length;i+=1) {
                    try {
                        clientList[i].write(data);
                    } catch (err) {
                        console.log('Error writing to client "data event": ' + clientIPList[i] );
                        // close and null the socket on write error
                        try {
                            clientList[i] = null;
                            clientList[i].end();
                        } catch (err) {}
                        clientList.splice(i, 1);
                        clientIPList.splice(i, 1);
                    }
                }            
            })

<TO-BE>

for(var i=0;i<clientList.length;i+=1) {
    try {
        server.pipe(clientList[i]);
    } catch (err) {
        console.log('Error writing to client "data event": ' + clientIPList[i] );
        // close and null the socket on write error
        try {
             clientList[i] = null;
             clientList[i].end();
        } catch (err) {}
             clientList.splice(i, 1);
             clientIPList.splice(i, 1);
        }
    }            
 }

此代码将调整您的内存问题。