【问题标题】:node.js socket exception read ETIMEDOUT - how do I catch it properly? what about write timeouts?node.js 套接字异常读取 ETIMEDOUT - 我如何正确捕获它?写超时呢?
【发布时间】:2012-03-09 13:00:00
【问题描述】:

我有一个代理服务器,它管理着一堆客户端,还与另一个 http 服务器通信。消息来回发布并定向到客户。客户端可以并且确实超时,并且服务器具有心跳功能(每 n 秒重复一次),该功能向 clientId 到套接字连接映射中的所有客户端发送心跳。

当心跳尝试与不再连接但套接字仍处于活动状态的客户端通信时,我收到“读取 ETIMEDOUT”异常。我尝试暂时将套接字连接的超时设置为 2000 毫秒,理论是我的套接字超时事件处理程序会捕获这个(事件处理程序位于 tcp 服务器部分),但这并没有发生。死亡需要几次心跳。

部分问题肯定是我对如何构建 node.js 代码缺乏了解,所以如果您有任何建议,我将非常感激。

另一个问题是是否可以分别处理读取和写入超时,或者至少将它们分开。我真正想做的是让我的心跳功能成为 tcp 服务器的一部分,如果它在 n 秒内没有收到客户端的消息,则只发送心跳,并且只发送一次心跳。如果超时,则终止套接字,否则再次等待。

谢谢!

>>$ node --harmony-weakmaps server.js
Heartbeat: Sat Feb 18 2012 08:34:40 GMT+0000 (UTC)
{
    sending keep_alive to id:00:00:00:00:00:10 socket:[object Object]
}
socket:received data: {"id":"00:00:00:00:00:10","m":"keep_alive","success":"true"}

Heartbeat: Sat Feb 18 2012 08:35:40 GMT+0000 (UTC)
{
    sending keep_alive to id:00:00:00:00:00:10 socket:[object Object]
}


socket:received data: {"id":"00:00:00:00:00:10","m":"keep_alive","success":"true"}
node.js:201
        throw e; // process.nextTick error, or 'error' event on first tick
              ^
Error: read ETIMEDOUT
    at errnoException (net.js:642:11)
    at TCP.onread (net.js:375:20)

触发超时的心跳函数:

console.log("Starting heartbeat");
var beat_period = 60;
setInterval(function() {
    if(Object.keys(id2socket).length != 0){
        console.log("Heartbeat: " + new Date());
        //for (var key in id2socket) {
        //  console.log("\t"+key+"->"+id2socket[key]);
        //}
        console.log("{");
        for(var id in id2socket) {
            var socket = id2socket[id];
            // don't want sockets to time out
            socket.setTimeout(2000); // for heartbeat, set the timeout
            try {
                console.log("\tsending keep_alive to id:"+id+" socket:"+id2socket[id]);
                socket.write('{"m":"keep_alive"}\r\n');
            } catch(Error) {
                console.log("Heartbeat:Cannot find id:"+id);
                removeSocketFromMap(id,socket);
                // TODO: send message to API
            }
            socket.setTimeout(0); // no timeout
        }
        console.log("}");
    }
}, beat_period * 1000);

server.js:

// Launch Instructions
// node --harmony-weakmaps server.js

var net = require('net'); // tcp-server
var http = require("http"); // http-server
var querystring = require('querystring');

// Map of sockets to clients
var id2socket = new Object;
var socket2id = new WeakMap; // allows us to use object as key to hash

// Test for client:
// {"id":"123","m":"add"} // establishes connection and puts client into id2socket map
// {"id":"123","m":"test"} // sends a message through to API

// HTTP:POST outbound function
// http://www.theroamingcoder.com/node/111
function postOut(dataToPost){
    try{
        console.log("postOut msg:"+JSON.stringify(dataToPost));
    } catch (Error) {
        console.log("postOut error:"+Error);
    }

    var post_domain = '127.0.0.1';
    var post_port = 80;
    var post_path = '/cgi-bin/index3.py'; 

    var post_data = querystring.stringify({
            'act' : 'testjson',
            'json' : JSON.stringify(dataToPost)
    });
    console.log("post_data:"+post_data);

    var post_options = {
      host: post_domain,
      port: post_port,
      path: post_path,
      method: 'POST',
      headers: {
        'Content-Type': 'application/x-www-form-urlencoded',
        'Content-Length': post_data.length
      }
    };
    var post_req = http.request(post_options, function(res) {
      res.setEncoding('utf8');
      res.on('data', function (chunk) {
        console.log('Response:data: ' + chunk);
      });
    });

    // Handle various issues
    post_req.on('error', function(error) {
        console.log('ERROR' + error.message);
        // If you need to go on even if there is an error add below line
        //getSomething(i + 1);
    });
    post_req.on("response", function (response) {
        console.log("Response:response:"+response);
    });

    // write parameters to post body
    post_req.write(post_data);
    post_req.end();
}

function removeSocketFromMap(id,socket){
    console.log("removeSocketFromMap socket:"+socket+" id:"+id);
    delete id2socket[id];
    socket2id.delete(socket);
    //TODO: print map???
    console.log("socketmap {");
    for (var key in id2socket) {
        console.log("\t"+key+"->"+id2socket[key]);
    }
    console.log("}");
}

// Setup a tcp server
var server_plug = net.createServer(

    function(socket) {

        // Event handlers
        socket.addListener("connect", function(conn) {
            console.log("socket:connection from: " + socket.remoteAddress + ":" + socket.remotePort + " id:"+socket.id );   
        });

        socket.addListener("data", function(data) {
            console.log("socket:received data: " + data);
            var request = null;
            try {
                request = JSON.parse(data);
            } catch (SyntaxError) {
                console.log('Invalid JSON:' + data);
                socket.write('{"success":"false","response":"invalid JSON"}\r\n');
            }

            if(request!=null){
                response = request; // set up the response we send back to the client

                if(request.m=="keep_alive"){ // HACK for keep alive
                    // Do nothing
                } else if(request.m !== undefined && request['id'] !== undefined){ // hack on 'id', id is js obj property
                    if(request.m == 'connect_device' || request.m == 'add'){
                        console.log("associating uid " + request['id'] + " with socket " + socket);
                        id2socket[request['id']] = socket;
                        socket2id.set(socket, request['id']);
                    }
                    postOut(request);
                    socket.write(JSON.stringify(response)+"\r\n");
                } else if(request['id'] !== undefined){
                    postOut(request);
                    socket.write(JSON.stringify(response)+"\r\n");
                } else {
                    response['content'] = "JSON doesn't contain m or id params";
                    socket.write(JSON.stringify(response)+"\r\n");
                }
            } else {
                console.log("null request");
            }

        });

        socket.on('end', function() {
            id = socket2id.get(socket);

            console.log("socket:disconnect by id " + id);
            removeSocketFromMap(id,socket);
            socket.destroy();
        });

        socket.on('timeout', function() {
            id = socket2id.get(socket);

            console.log('socket:timeout by id ' + id);
            removeSocketFromMap(id,socket);
            socket.destroy();
        });

        // handle uncaught exceptions
        socket.on('uncaughtException', function(err) {
            id = socket2id.get(socket);

            console.log('socket:uncaughtException by id ' + id);
            removeSocketFromMap(id,socket);
            socket.destroy();
        });

    }
);
server_plug.on('error', function (error) {
    console.log('server_plug:Error: ' + error);
});

// Setup http server
var server_http = http.createServer(
    // Function to handle http:post requests, need two parts to it
    // http://jnjnjn.com/113/node-js-for-noobs-grabbing-post-content/
    function onRequest(request, response) {
        request.setEncoding("utf8");
        request.content = '';

        request.on('error', function(err){
            console.log("server_http:error: "+err);
        })

        request.addListener("data", function(chunk) {
            request.content += chunk;
        });

        request.addListener("end", function() {
            console.log("server_http:request_received");

            try {
                var json = querystring.parse(request.content);

                console.log("server_http:received_post {");
                for(var foo in json){
                    console.log("\t"+foo+"->"+json[foo]);
                }
                console.log("}");

                // Send json message content to socket
                if(json['json']!=null && json['id']!=null){
                    id = json['id'];
                    try {
                        var socket = id2socket[id];
                        socket.write(json['json']+"\r\n");
                    } catch (Error) {
                        console.log("Cannot find socket with id "+id);
                    } finally {
                        // respond to the incoming http request
                        response.end();
                        // TODO: This should really be in socket.read!
                    }
                }
            } catch(Error) {
                console.log("JSON parse error: "+Error)
            }
        });

        request.on('end', function () {
            console.log("http_request:end");
        });

        request.on('close', function () {
            console.log("http_request:close");
        });
    }
);
server_http.on('error', function (error) {
    console.log('server_http:Error: ' + error);
});

// Heartbeat function
console.log("Starting heartbeat");
var beat_period = 60;
setInterval(function() {
    if(Object.keys(id2socket).length != 0){
        console.log("Heartbeat: " + new Date());
        //for (var key in id2socket) {
        //  console.log("\t"+key+"->"+id2socket[key]);
        //}
        console.log("{");
        for(var id in id2socket) {
            var socket = id2socket[id];
            // don't want sockets to time out
            socket.setTimeout(2000); // for heartbeat, set the timeout
            try {
                console.log("\tsending keep_alive to id:"+id+" socket:"+id2socket[id]);
                socket.write('{"m":"keep_alive"}\r\n');
            } catch(Error) {
                console.log("Heartbeat:Cannot find id:"+id);
                removeSocketFromMap(id,socket);
                // TODO: send message to API
            }
            socket.setTimeout(0); // no timeout
        }
        console.log("}");
    }
}, beat_period * 1000);



// Fire up the servers
//var HOST = '127.0.0.1'; // just local incoming connections
var HOST = '0.0.0.0'; // allows access to all external IPs
var PORT = 5280;
var PORT2 = 9001;

// accept tcp-ip connections
server_plug.listen(PORT, HOST);
console.log("TCP server listening on "+HOST+":"+PORT);

// accept posts
server_http.listen(PORT2);
console.log("HTTP server listening on "+HOST+":"+PORT2);

编辑:

我应该使用 .on(event,callback) 还是 .onlistener(event,callback)?

更新:

这不起作用,我将 tcp_server 中的内容更改为心跳中的所有 add_listener 为 .on。还是没抓到错误就炸了,说我加了太多的监听器。

【问题讨论】:

  • 我也有同样的问题,无法捕捉到 ETIMEDOUT 异常。你解决了吗?

标签: sockets exception node.js


【解决方案1】:

对于 ETIMEDOUT 异常问题,您是否尝试在进程本身上侦听 uncaughtException?

process.on('uncaughtException', function (err) {
  console.log('Caught exception: ' + err);
});

在此处查看文档:http://nodejs.org/docs/latest/api/process.html#event_uncaughtException_

【讨论】:

  • 确保你有一个正确的 socket.on('error') 处理程序是最佳实践(参见 EdH 的回答),但这很高兴知道,并且可以防止被遗忘的错误处理程序崩溃整个连接了多个客户端的服务器。这是一个“使用风险自负”的功能。
【解决方案2】:

首先,如果不进一步了解代码的上下文,很难说您的结构是否正确...

尝试添加

socket.on('error', function() {
    id = socket2id.get(socket);

    console.log('socket:timeout by id ' + id);
    removeSocketFromMap(id,socket);
    socket.destroy();
}

到 net.CreateServer 中的匿名函数。 ETIMEDOUT 是系统调用错误,node.js 只是报告它。它可能不是由典型的“超时”引起的。你说它是由心跳写入引起的,但看起来 TCP.read 是起源。它可能是一个半封闭的套接字。

【讨论】:

    猜你喜欢
    • 2011-11-02
    • 2014-07-09
    • 2011-01-14
    • 2012-09-06
    • 2012-10-24
    • 2017-03-19
    • 1970-01-01
    • 2013-05-16
    • 1970-01-01
    相关资源
    最近更新 更多