Nodejs 想封装一个网络连接,但跑着跑着就崩了

Nodejs 想封装一个网络连接,但跑着跑着就崩了

各位大神们,我想这样子封装一个网络连接,但在被拼命发包时进程内存涨到200M左右就崩掉了,是因为闭包的问题,还是Buffer拼接,还是因为一下子收到的数据包太多一次过用for处理,还是其他问题啊~~~ 我是一只小小小小鸟。。。。。。

var net = require('net');

function buildConnection(getPacks, buildPack) {
  return function(sock) {
    var self = this;
    this.buf = new Buffer(0);
    sock.on('data', function(data) {
	  self.buf = Buffer.concat([self.buf, data]);
	  var packs = getPacks(self);
	  for (var i = 0; i < packs.length; i++) {
	    // do something with pack
	  }
	}
  }
}
}

var Connection = buildConnection(aGetPacksfunction, aBuildPackfunction);

var server = net.createserver();
server.on('connection', function(sock) {
  var conn = new Connection(sock);
});
server.listen(8000);

7 回复

Node.js 想封装一个网络连接,但跑着跑着就崩了

各位大神们,我在尝试封装一个网络连接时遇到了一些问题。具体来说,在进行大量数据传输时(例如拼命发包),进程的内存占用会迅速增加到200MB左右,然后程序崩溃。这可能是由于闭包问题、Buffer拼接问题,或者是由于一次性处理过多数据包导致的。希望各位大神能给我一些建议。

以下是我目前的实现代码:

var net = require('net');

function buildConnection(getPacks, buildPack) {
  return function(sock) {
    var self = this;
    this.buf = new Buffer(0);
    
    sock.on('data', function(data) {
      self.buf = Buffer.concat([self.buf, data]);
      
      var packs = getPacks(self);
      for (var i = 0; i < packs.length; i++) {
        // do something with pack
      }
    });
  };
}

// 示例函数
function aGetPacksfunction(self) {
  // 解析数据包并返回
  return [];
}

function aBuildPackfunction() {
  // 构建数据包
}

var Connection = buildConnection(aGetPacksfunction, aBuildPackfunction);

var server = net.createServer();
server.on('connection', function(sock) {
  var conn = new Connection(sock);
});

server.listen(8000, () => {
  console.log('Server is listening on port 8000');
});

问题分析与解决方案

  1. Buffer 拼接问题

    • Buffer.concat 方法虽然方便,但如果频繁使用可能会导致内存泄漏。每次调用 Buffer.concat 都会产生一个新的 Buffer 对象,旧的 Buffer 对象如果没有被引用,则会被垃圾回收器回收。
    • 可以考虑在适当的时候释放 self.buf 的内容,或者使用流式处理来避免内存占用过高。
  2. 数据包处理问题

    • for 循环中处理数据包时,如果数据量过大,可能会导致性能瓶颈或内存溢出。可以考虑将数据包处理逻辑拆分到不同的事件处理器中,或者使用异步处理方法。
  3. 内存泄漏

    • 确保所有事件处理器和定时器都被正确地清理,以防止内存泄漏。可以使用 process.on('exit', callback) 来检查是否有未释放的资源。

示例改进代码

var net = require('net');

function buildConnection(getPacks, buildPack) {
  return function(sock) {
    var self = this;
    this.buf = new Buffer(0);
    
    sock.on('data', function(data) {
      self.buf = Buffer.concat([self.buf, data]);
      
      process.nextTick(() => {
        var packs = getPacks(self);
        for (var i = 0; i < packs.length; i++) {
          // do something with pack
        }
      });
    });

    sock.on('end', function() {
      self.buf = null; // 清理缓冲区
    });
  };
}

// 示例函数
function aGetPacksfunction(self) {
  // 解析数据包并返回
  return [];
}

function aBuildPackfunction() {
  // 构建数据包
}

var Connection = buildConnection(aGetPacksfunction, aBuildPackfunction);

var server = net.createServer();
server.on('connection', function(sock) {
  var conn = new Connection(sock);
});

server.listen(8000, () => {
  console.log('Server is listening on port 8000');
});

通过以上改进,可以有效减少内存占用,并提高程序的稳定性和性能。希望这些方法对大家有所帮助!


为啥没有错误信息呢?

有2个原因吧:

  • v8对gc不是很积极,因为gc很昂贵。 v8对小内存对象更不积极,buffer对象是小内存,但是它hold住的数据在v8的heap外,所以会造成内存暴涨,因为buffer释放缓慢 . 你可以使用node xxx --export-gc 然后在程序里定时执行gc()。
  • 你在for循环时,低层libuv一直在接收数据 这个是属于proactor模式吧。底层一直在收,你可以通过接口sock.pause() and sock.resume()去控制接收buffer。

参考: https://github.com/clowwindy/shadowsocks-nodejs 的readme,作者目前放弃了nodejs版本就是因为内存的问题

self.buf = Buffer.concat([self.buf, data]);

我很好奇,这样不是每次都在self.buf后append data,self.buf不是会越来越大吗?

除非处理完pack后,重置了self.buf。 但这样tcp上的粘包没办法处理。

这是我的demo:

Session.prototype.append = function(buf) {
    if (!buf)
        return
if (!this._buf) {
    this._buf = buf
    this._buf_len = buf.length
    return
}

var left = this._buf.length - this._buf_len
if (left&gt;=buf.length) {
    buf.copy(this._buf,this._buf_len)
    this._buf_len+=buf.length
    return
}
this._buf = Buffer.concat([this._buf.slice(0,this._buf_len),buf])
this._buf_len+=buf.length

}

楼主,将代码用 markdown 语法标记一下

200M就跳票,好像还是比较奇怪的,还没到buffer上限

根据你的描述和提供的代码片段,崩溃可能是因为内存泄漏或数据处理不当。以下是一些可能的原因和改进措施:

  1. 内存泄漏self.buf 不断增长而没有清理,可能会导致内存泄漏。
  2. 数据包处理:使用 Buffer.concat 和循环处理数据包的方式可能导致性能问题,尤其是在处理大量数据时。

示例代码改进

使用流式处理数据

你可以考虑使用流式处理来避免内存泄漏,并提高处理效率。

var net = require('net');
var util = require('util');

function buildConnection(getPacks, buildPack) {
  return function(sock) {
    let buf = Buffer.alloc(0);

    sock.on('data', function(data) {
      buf = Buffer.concat([buf, data]);

      let offset = 0;
      while (true) {
        const pack = getPacks(buf.slice(offset));
        if (!pack) break;

        offset += pack.length;
        buildPack(pack);
      }

      buf = buf.slice(offset);
    });

    sock.on('end', () => {
      if (buf.length > 0) {
        console.log('End of stream, remaining buffer:', buf);
      }
    });
  };
}

const aGetPacksFunction = (buffer) => {
  // 自定义逻辑来解析数据包
  return buffer.slice(0, 10); // 假设前10个字节为一个包
};

const aBuildPackFunction = (pack) => {
  // 处理数据包
  console.log('Processing pack:', pack.toString());
};

const Connection = buildConnection(aGetPacksFunction, aBuildPackFunction);

const server = net.createServer();
server.on('connection', function(sock) {
  new Connection(sock);
});

server.listen(8000, () => {
  console.log('Server listening on port 8000');
});

解释

  1. 流式处理:通过不断累加数据并分批处理,可以避免一次性加载大量数据导致内存问题。
  2. 缓冲区管理:使用 Buffer.alloc 初始化缓冲区,并在每次处理完一个包后更新缓冲区。
  3. 循环处理:使用 while 循环处理所有数据包,直到缓冲区中的数据无法再形成完整的包。

这些改进可以帮助减少内存泄漏的风险,并提高处理效率。希望这能解决你遇到的问题!

回到顶部