草庐IT

node.js - 如何计算 node.js 套接字缓冲区以避免分配内存并且从不使用它?

coder 2023-09-19 原文

我使用 node.js 作为客户端对之间的服务器,来处理我的在线游戏。 客户端之间发送短消息[一条消息不应超过200bytes]。 目前我希望单个客户端 [平均] 每秒发送 1 条消息 [请记住,可能有 5 秒没有任何消息,而 5 条消息一个接一个地发送]。

我已经使用“net”模块下载了一个示例服务器,并重写了它以按照我需要的方式处理消息。 基本上,对于每个连接的套接字,它都会创建一个大小为 1024*8 的缓冲区。 目前我正在用一些机器人测试我的游戏,它们只是连接,等待 3 秒然后断开连接。他们只发送 1 条消息。没有其他事情发生。

function sendMessage(socket, message) {
    socket.write(message);
}

server.on('connection', function(socket) {
    socket.setNoDelay(true);
    socket.connection_id = require('crypto').createHash('sha1').update( 'krystian'  + Date.now() + Math.random() ).digest('hex') ; // unique sha1 hash generation
    socket.channel = '';
    socket.matchInProgress = false
    socket.resultAnnounced = false;
    socket.buffer = new Buffer(cfg.buffer_size);
    socket.buffer.len = 0; // due to Buffer's nature we have to keep track of buffer contents ourself

    _log('New client: ' + socket.remoteAddress +':'+ socket.remotePort);

    socket.on('data', function(data_raw) { // data_raw is an instance of Buffer as well
        if (data_raw.length > (cfg.buffer_size - socket.buffer.len)) {
            _log("Message doesn't fit the buffer. Adjust the buffer size in configuration");
            socket.buffer.len = 0; // trimming buffer
            return false;
        }

        socket.buffer.len +=  data_raw.copy(socket.buffer, socket.buffer.len); // keeping track of how much data we have in buffer

        var str, start, end
            , conn_id = socket.connection_id;
        str = socket.buffer.slice(0,socket.buffer.len).toString();

        if ( (start = str.indexOf("<somthing>")) !=  -1   &&   (end = str.indexOf("</something>"))  !=  -1) {
            try {
                if (!<some check to see if the message format is right>) {
                        sendMessage(socket, "<error message to the client>");
                    return;
                }
                <storing info on the socket>

            } catch(err) {
                sendMessage(socket, "<error message to the client>");
                return;
            }
            socket.channel = <channel>;
            str = str.substr(end + 11);
            socket.buffer.len = socket.buffer.write(str, 0);
            sockets[socket.channel] = sockets[socket.channel] || {}; // hashmap of sockets  subscribed to the same channel
            sockets[socket.channel][conn_id] = socket;
            waiting[socket.channel] = waiting[socket.channel] || {};
            waiting[socket.channel][conn_id] = socket;
            sendMessage(socket, "<info message to the client>");
            for (var prop in waiting[socket.channel]) {
                if (waiting[socket.channel].hasOwnProperty(prop) && waiting[socket.channel][prop].connection_id != socket.connection_id) {
                   <here I'll try to advertise this client among other clients>
                    sendMessage(waiting[socket.channel][prop], "<info to other clients about new client>");
                }
            }
        }

        var time_to_exit = true;
        do{  // this is for a case when several messages arrived in buffer
            if ( (start = str.indexOf("<some other format>")) !=  -1   &&  (end = str.indexOf("</some other format>"))  !=  -1 ) {
                var json = str.substr( start+19,  end-(start+19) );
                var jsono;
                try {
                    jsono = JSON.parse(json);
                } catch(err) {
                    sendMessage(socket, "<parse error>");
                    return;
                }
                if (<message indicates two clients are going to play together>) {
                    if (waiting[socket.channel][jsono.other_client_id] && waiting[socket.channel][socket.connection_id]) {
                        delete waiting[socket.channel][jsono.other_client_id];
                        delete waiting[socket.channel][socket.connection_id];
                        var opponentSocket = sockets[socket.channel][jsono.other_client_id];
                        sendMessage(opponentSocket, "<start game with the other socket>");
                        opponentSocket.opponentConnectionId = socket.connection_id;
                        sendMessage(socket, "<start game with the other socket>");
                        socket.opponentConnectionId = jsono.other_client_id;
                    }
                } else if (<check if clients play together>) { 
                    var opponentSocket = sockets[socket.channel][socket.opponentConnectionId];
                    if (<some generic action between clients, just pass the message>) {
                        sendMessage(sockets[socket.channel][socket.opponentConnectionId], json);
                    } else if (<match is over>) {
                        if (<match still in progress>) {
                            <send some messages indicating who won, who lost>
                        } else {
                            <log an error>
                        }
                        delete sockets[socket.channel][opponentSocket.connection_id];
                        delete sockets[socket.channel][socket.connection_id];
                    }
                }
                str = str.substr(end + 20);  // cut the message and remove the precedant part of the buffer since it can't be processed
                socket.buffer.len = socket.buffer.write(str, 0);
                time_to_exit = false;
            } else {  time_to_exit = true; } // if no json data found in buffer - then it is time to exit this loop
        } while ( !time_to_exit );
    }); // end of  socket.on 'data'


    socket.on('close', function(){  // we need to cut out closed socket from array of client socket connections
        if  (!socket.channel   ||   !sockets[socket.channel])  return;
        if (waiting[socket.channel] && waiting[socket.channel][socket.connection_id]) {
            delete waiting[socket.channel][socket.connection_id];
        }

        var opponentSocket = sockets[socket.channel][socket.opponentConnectionId];
        if (opponentSocket) {
            sendMessage(opponentSocket, "<the other client has disconnected>");
            delete sockets[socket.channel][socket.opponentConnectionId];
        }

        delete sockets[socket.channel][socket.connection_id];
        _log(socket.connection_id + " has been disconnected from channel " + socket.channel);
    }); // end of socket.on 'close'

}); //  end of server.on 'connection'

server.on('listening', function(){ console.log('Listening on ' + server.address().address +':'+ server.address().port); });
server.listen(cfg.port);

我已经粘贴了上面的代码 [原始的非常精简的版本],让您了解服务器是多么简单。 我有一组套接字,谁加入了游戏,还有一组套接字在等待名单上,等待另一个客户端玩。 没有其他事情发生。

脚本仍然很耗内存 - 5 小时的连接和断开连接给了我这个:

  PID USER      PR  NI  VIRT  RES  SHR S %CPU %MEM    TIME+  COMMAND                                                                                                                                                                                                
31461 ec2-user  20   0  995m  91m 7188 S  0.7 15.4   1:29.07 node           

我觉得这太过分了。 我目前正在使用 nodetime.com 免费服务来监控脚本,但没有任何指标表明脚本获得了如此多的内存(它开始时只有 10-12MB)。 我相信这是由于缓冲区造成的,因为它们分配了太多内存。

我只是想知道,我对缓冲区大小的假设是否正确。 我是否应该调整缓冲区以反射(reflect)我期望从客户端获得的数据量? 如果我希望客户端在很短的时间内发送 5 条消息,每条消息最多 200 字节,我是否应该假设 1024*3 就足够了?

或者我应该根据我期望的消息大小调整缓冲区大小,所以如果我确定消息永远不会超过 300 字节,我应该可以使用 512 的缓冲区大小吗?

谢谢, 克里斯蒂安

编辑:

Node 版本:

$ node -v
v0.10.5
$ npm -v
1.2.19

编辑2:

我用 400 个连接连接和断开连接测试了脚本,内存使用量显着下降到 60MB 左右。将测试设置改回 4 个连接后,它再次上升。

最佳答案

内核有一个至少为 8k 的套接字接收缓冲区,它负责处理套接字上的多个传入消息。您不需要缓冲已阅读的消息,因此您的应用程序缓冲区不需要比最大的预期消息大。

关于node.js - 如何计算 node.js 套接字缓冲区以避免分配内存并且从不使用它?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/17463333/

有关node.js - 如何计算 node.js 套接字缓冲区以避免分配内存并且从不使用它?的更多相关文章

  1. ruby - 如何使用 Nokogiri 的 xpath 和 at_xpath 方法 - 2

    我正在学习如何使用Nokogiri,根据这段代码我遇到了一些问题:require'rubygems'require'mechanize'post_agent=WWW::Mechanize.newpost_page=post_agent.get('http://www.vbulletin.org/forum/showthread.php?t=230708')puts"\nabsolutepathwithtbodygivesnil"putspost_page.parser.xpath('/html/body/div/div/div/div/div/table/tbody/tr/td/div

  2. ruby - 如何从 ruby​​ 中的字符串运行任意对象方法? - 2

    总的来说,我对ruby​​还比较陌生,我正在为我正在创建的对象编写一些rspec测试用例。许多测试用例都非常基础,我只是想确保正确填充和返回值。我想知道是否有办法使用循环结构来执行此操作。不必为我要测试的每个方法都设置一个assertEquals。例如:describeitem,"TestingtheItem"doit"willhaveanullvaluetostart"doitem=Item.new#HereIcoulddotheitem.name.shouldbe_nil#thenIcoulddoitem.category.shouldbe_nilendend但我想要一些方法来使用

  3. ruby-on-rails - Ruby net/ldap 模块中的内存泄漏 - 2

    作为我的Rails应用程序的一部分,我编写了一个小导入程序,它从我们的LDAP系统中吸取数据并将其塞入一个用户表中。不幸的是,与LDAP相关的代码在遍历我们的32K用户时泄漏了大量内存,我一直无法弄清楚如何解决这个问题。这个问题似乎在某种程度上与LDAP库有关,因为当我删除对LDAP内容的调用时,内存使用情况会很好地稳定下来。此外,不断增加的对象是Net::BER::BerIdentifiedString和Net::BER::BerIdentifiedArray,它们都是LDAP库的一部分。当我运行导入时,内存使用量最终达到超过1GB的峰值。如果问题存在,我需要找到一些方法来更正我的代

  4. python - 如何使用 Ruby 或 Python 创建一系列高音调和低音调的蜂鸣声? - 2

    关闭。这个问题是opinion-based.它目前不接受答案。想要改进这个问题?更新问题,以便editingthispost可以用事实和引用来回答它.关闭4年前。Improvethisquestion我想在固定时间创建一系列低音和高音调的哔哔声。例如:在150毫秒时发出高音调的蜂鸣声在151毫秒时发出低音调的蜂鸣声200毫秒时发出低音调的蜂鸣声250毫秒的高音调蜂鸣声有没有办法在Ruby或Python中做到这一点?我真的不在乎输出编码是什么(.wav、.mp3、.ogg等等),但我确实想创建一个输出文件。

  5. ruby-on-rails - 如何验证 update_all 是否实际在 Rails 中更新 - 2

    给定这段代码defcreate@upgrades=User.update_all(["role=?","upgraded"],:id=>params[:upgrade])redirect_toadmin_upgrades_path,:notice=>"Successfullyupgradeduser."end我如何在该操作中实际验证它们是否已保存或未重定向到适当的页面和消息? 最佳答案 在Rails3中,update_all不返回任何有意义的信息,除了已更新的记录数(这可能取决于您的DBMS是否返回该信息)。http://ar.ru

  6. ruby-on-rails - 'compass watch' 是如何工作的/它是如何与 rails 一起使用的 - 2

    我在我的项目目录中完成了compasscreate.和compassinitrails。几个问题:我已将我的.sass文件放在public/stylesheets中。这是放置它们的正确位置吗?当我运行compasswatch时,它不会自动编译这些.sass文件。我必须手动指定文件:compasswatchpublic/stylesheets/myfile.sass等。如何让它自动运行?文件ie.css、print.css和screen.css已放在stylesheets/compiled。如何在编译后不让它们重新出现的情况下删除它们?我自己编译的.sass文件编译成compiled/t

  7. ruby - 如何将脚本文件的末尾读取为数据文件(Perl 或任何其他语言) - 2

    我正在寻找执行以下操作的正确语法(在Perl、Shell或Ruby中):#variabletoaccessthedatalinesappendedasafileEND_OF_SCRIPT_MARKERrawdatastartshereanditcontinues. 最佳答案 Perl用__DATA__做这个:#!/usr/bin/perlusestrict;usewarnings;while(){print;}__DATA__Texttoprintgoeshere 关于ruby-如何将脚

  8. ruby - 如何指定 Rack 处理程序 - 2

    Rackup通过Rack的默认处理程序成功运行任何Rack应用程序。例如:classRackAppdefcall(environment)['200',{'Content-Type'=>'text/html'},["Helloworld"]]endendrunRackApp.new但是当最后一行更改为使用Rack的内置CGI处理程序时,rackup给出“NoMethodErrorat/undefinedmethod`call'fornil:NilClass”:Rack::Handler::CGI.runRackApp.newRack的其他内置处理程序也提出了同样的反对意见。例如Rack

  9. ruby - 如何每月在 Heroku 运行一次 Scheduler 插件? - 2

    在选择我想要运行操作的频率时,唯一的选项是“每天”、“每小时”和“每10分钟”。谢谢!我想为我的Rails3.1应用程序运行调度程序。 最佳答案 这不是一个优雅的解决方案,但您可以安排它每天运行,并在实际开始工作之前检查日期是否为当月的第一天。 关于ruby-如何每月在Heroku运行一次Scheduler插件?,我们在StackOverflow上找到一个类似的问题: https://stackoverflow.com/questions/8692687/

  10. ruby-on-rails - 如何从 format.xml 中删除 <hash></hash> - 2

    我有一个对象has_many应呈现为xml的子对象。这不是问题。我的问题是我创建了一个Hash包含此数据,就像解析器需要它一样。但是rails自动将整个文件包含在.........我需要摆脱type="array"和我该如何处理?我没有在文档中找到任何内容。 最佳答案 我遇到了同样的问题;这是我的XML:我在用这个:entries.to_xml将散列数据转换为XML,但这会将条目的数据包装到中所以我修改了:entries.to_xml(root:"Contacts")但这仍然将转换后的XML包装在“联系人”中,将我的XML代码修改为

随机推荐