5

我正在编写一个 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 time 对 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 小时后,内存仍未释放。任何人都可以给我的任何帮助将不胜感激。

4

2 回答 2

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);
        }
    }            
 }

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

于 2013-01-19T17:50:06.347 回答
0

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

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

不应该反过来吗?

于 2012-10-30T09:48:02.037 回答