node.js 套接字异常读取 ETIMEDOUT - 如何正确捕获它?写超时呢?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/9339920/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-02 15:12:57  来源:igfitidea点击:

node.js socket exception read ETIMEDOUT - how do I catch it properly? what about write timeouts?

socketsexceptionnode.js

提问by nflacco

I have a proxy server that manages a bunch of clients and also talks with another http server. Messages get posted back and forth and directed to clients. Clients can and do timeout, and the server has a heartbeat function (that repeats every n seconds) that sends a heartbeat to all clients that are in a map of clientId to socket connection.

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

I get a 'read ETIMEDOUT' exception when the heartbeat tries to talk to a client that is no longer connected but who's socket is still active. I tried temporarily setting the timeout of the socket connection to 2000ms with the theory that my socket event handler for timeout would catch this (the event handler is in the tcp server portion), but this didn't happen. It takes several heartbeats to die.

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

Part of the problem is definitely my lack of understanding of how to structure node.js code, so if you have any suggestions, I'd very much appreciate them.

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

Another question is whether it is possible to handle read and write timeouts separately, or at least break them out. What I'd really like to do is have my heartbeat function be part of the tcp server and only send a heartbeat if it hasn't heard from the client in say n seconds, and only send this heartbeat once. If we get a timeout, then we kill the socket, otherwise we wait again.

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

Thanks!

谢谢!

>>$ 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)

Heartbeat function that triggers the timeout:

触发超时的心跳函数:

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:

服务器.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);

EDIT:

编辑:

Should I be using .on(event,callback) vs .onlistener(event,callback)?

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

UPDATE:

更新:

That didn't work, I changed the stuff in tcp_server to all add_listener in the heartbeat as .on. Still didn't catch the errors and blew up and said I added too many listeners.

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

回答by EdH

First, its a bit hard to say if your structure is right without some more understanding of the context of your code...

首先,如果没有更多地了解代码的上下文,很难说您的结构是否正确......

Try adding

尝试添加

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

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

to the anonymous function in net.CreateServer. ETIMEDOUT is a system call error, and node.js is just reporting it. It may not be caused by just a typical 'timeout'. You say its being caused by the Hearbeat write, but it looks like TCP.read is the origination. It may be a half-closed socket.

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

回答by Franck

For the ETIMEDOUT exception issue, did you try listening for an uncaughtException on the process itself?

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

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

See the documentation here : http://nodejs.org/docs/latest/api/process.html#event_uncaughtException_

请参阅此处的文档:http: //nodejs.org/docs/latest/api/process.html#event_uncaughtException_