官术网_书友最值得收藏!

Listening for events

In the previous chapter we were introduced to the EventEmitter interface. This is the primary event interface we will be encountering as we move chapter to chapter, as it provides the prototype class for the many Node objects exposing evented interfaces, such as file and network streams. Various close, exit, data, and other events exposed by different module APIs signal the presence of an EventEmitter interface, and we will be learning about these modules and use cases as we progress.

Instead, the primary purpose of this section is to discuss some lesser-known event sources—signals, child process communication, filesystem change events, and deferred execution.

Signals

In many ways, evented programming is like hardware interrupt programming. Interrupts do exactly what their name suggests. They use their ability to interrupt whatever a controller or the CPU or any other device is doing, demanding that their particular need be serviced immediately.

In fact, the Node process object exposes standard Portable Operating System Interface (POSIX) signal names, such that a node process can subscribe to these system events.

 

A signal is a limited form of inter-process communication used in Unix, Unix-like, and other POSIX-compliant operating systems. It is an asynchronous notification sent to a process or to a specific thread within the same process in order to notify it of an event that occurred.

 
  --http://en.wikipedia.org/wiki/POSIX_signal

This is a very elegant and natural way to expose a Node process to operating system (OS) signal events. One might configure listeners to catch signals instructing a Node process to restart or update some configuration files or simply clean up and shut down.

For example, the SIGINT signal is sent to a process when its controlling terminal detects a Ctrl-C (or equivalent) keystroke. This signal tells a process that an interrupt has been requested. If a Node process has bound a callback to this event, that function might log the request prior to terminating, do some other cleanup work, or even ignore the request:

setInterval(function() {}, 1e6);
process.on('SIGINT', function() {
    console.log('SIGINT signal received');
    process.exit(1);
})

Here we have set up a far future interval such that the process does not immediately terminate, and a SIGINT listener. When a user sends a Ctrl-C interrupt to the terminal controlling this process, the message SIGINT signal received will be written on the terminal and the process will terminate.

Now consider a situation in which a Node process is doing some ongoing work, such as parsing logs. It might be useful to be able to send that process a signal, such as update your configuration files or restart the scan. You may want to send such signals from the command line. You might prefer to have another process do so—a practice known as Inter-Process Communication (IPC).

Create a file named ipc.js containing the following code:

setInterval(function() {}, 1e6);
process.on('SIGUSR1', function() {
    console.log('Got a signal!');
});

SIGUSR1 (and SIGUSR2) are user-defined signals (they are triggered by no specific action). This makes them ideal signals for custom functionality.

To send a command to a process you must determine its process ID (PID). With a PID in hand, processes can be addressed, and therefore communicated with. If the PID assigned to ipc.js after being run through Node is 123, then we can send that process a SIGUSR1 signal using the following command line:

kill –s SIGUSR1 123

Note

A simple way to find the PID for a given Node process in UNIX is to search the system process list for the name of the program that says the process is running. If ipc.js is currently executing, its PID is found by entering the following command line in the console/terminal:

ps aux | grep ipc.js

Try it.

Forks

A fundamental part of Node's design is to create or fork processes when parallelizing execution or scaling a system—as opposed to creating a thread pool, for instance. We will be using child processes in various ways throughout this book, and learn how to create and use them. Here the focus will be on understanding how communication events between child processes are to be handled.

To create a child process one need simply call the fork method of the child_process module, passing it the name of a program file to execute within the new process:

var cp = require('child_process');
var child = cp.fork(__dirname + '/lovechild.js');

In this way any number of subprocesses can be kept running. Additionally, on multicore machines, forked processes will be distributed (by the OS) to different cores. Spreading node processes across cores (even other machines) and managing IPC is (one) way to scale a Node application in a stable, understandable, and predictable way.

Extending the preceding, we can now have the forking process (parent) send, and listen for, messages from the forked process (child):

child.on('message', function(msg) {
    console.log('Child said: ', msg);
});
child.send("I love you");

Similarly, the child process (its program is defined in lovechild.js) can send and listen for messages:

// lovechild.js
process.on('message', function(msg) {
    console.log('Parent said: ', msg);
    process.send("I love you too");
});

Running parent.js should fork a child process and send that child a message. The child should respond in kind:

Parent said:  I love you
Child said:  I love you too

Another very powerful idea is to pass a network server an object to a child. This technique allows multiple processes, including the parent, to share the responsibility for servicing connection requests, spreading load across cores.

For example, the following program will start a network server, fork a child process, and pass this child the server reference:

var child = require('child_process').fork('./child.js');
var server = require('net').createServer();
server.on('connection', function(socket) {
    socket.end('Parent handled connection');
});
server.listen(8080, function() {
    child.send("The parent message", server);
});

In addition to passing a message to a child process as the first argument to send, the preceding code also sends the server handle to itself as a second argument. Our child server can now help out with the family's service business:

//    child.js
process.on('message', function(msg, server) {
    console.log(msg);
    server.on('connection', function(socket) {
        socket.end('Child handled connection');
    });
});

This child process should print out the sent message to your console, and begin listening for connections, sharing the sent server handle. Repeatedly connecting to this server at localhost:8080 will result in either Child handled connection or Parent handled connection being displayed; two separate processes are balancing the server load. It should be clear that this technique, when combined with the simple inter-process messaging protocol discussed previously, demonstrates how Ryan Dahl's creation succeeds in providing an easy way to build scalable network programs.

Note

We will discuss Node's new cluster module, which expands (and simplifies) the previously discussed technique in later chapters. If you are interested in how server handles are shared, visit the cluster documentation at the following link:

http://nodejs.org/api/cluster.html

For those who are truly curious, examine the cluster code itself at:

https://github.com/joyent/node/blob/c668185adde3a474585a11f172b8387e270ec23b/lib/cluster.js#L523-558

File events

Most applications make some use of the filesystem, in particular those that function as web services. As well, a professional application will likely log information about usage, cache pre-rendered data views, or make other regular changes to files and directory structures.

Node allows developers to register for notifications on file events through the fs.watch method. The watch method will broadcast changed events on both files and directories.

watch accepts three arguments, in order:

  1. The file or directory path being watched. If the file does not exist an ENOENT (no entity) error will be thrown, so using fs.exists at some prior useful point is encouraged.
  2. An optional options object:
    • persistent (Boolean): Node keeps processes alive as long as there is "something to do". An active file watcher will by default function as a persistence flag to Node. Setting this option to false flags not keeping the general process alive if the watcher is the only activity keeping it running.
  3. The listener function, which receives two arguments:
    • The name of the change event (one of rename or change).
    • The filename that was changed (important when watching directories).

    Note

    Some operating systems will not return this argument.

This example will set up a watcher on itself, change its own filename, and exit:

var fs = require('fs');

fs.watch(__filename, { persistent: false }, function(event, filename) {
    console.log(event);
    console.log(filename);
})

setImmediate(function() {
    fs.rename(__filename, __filename + '.new', function() {});
});

Two lines, rename and the name of the original file, should have been printed to the console.

Watcher channels can be closed at any time using the following code snippet:

var w = fs.watch('file', function(){})
w.close();

It should be noted that fs.watch depends a great deal on how the host OS handles file events, and according to the Node documentation:

"The fs.watch API is not 100% consistent across platforms, and is unavailable in some situations."

The author has had very good experiences with the module across many different systems, noting only that the filename argument is null in callbacks on OS X implementations. Nevertheless, be sure to run tests on your specific architecture—trust, but verify.

Deferred execution

One occasionally needs to defer the execution of a function. Traditional JavaScript uses timers for this purpose, the well-known setTimeout and setInterval functions. Node introduces another perspective on defers, primarily as means of controlling the order in which a callback executes in relation to I/O events, as well as timer events properly.

We'll learn more about this ordering in the event loop discussion that follows. For now we will examine two types of deferred event sources that give a developer the ability to schedule callback executions to occur either before, or after, the processing of queued I/O events.

process.nextTick

A method of the native Node process module, process.nextTick is similar to the familiar setTimeout method in which it delays execution of its callback function until some point in the future. However, the comparison is not exact; a list of all requested nextTick callbacks are placed at the head of the event queue and is processed, in its entirety and in order, before I/O or timer events and after execution of the current script (the JavaScript code executing synchronously on the V8 thread).

The primary use of nextTick in a function is to postpone the broadcast of result events to listeners on the current execution stack until the caller has had an opportunity to register event listeners—to give the currently executing program a chance to bind callbacks to EventEmitter.emit events. It may be thought of as a pattern used wherever asynchronous behavior should be emulated. For instance, imagine a lookup system that may either fetch from a cache or pull fresh data from a data store. The cache is fast and doesn't need callbacks, while the data I/O call would need them. The need for callbacks in the second case argues for emulation of the callback behavior with nextTick in the first case. This allows a consistent API, improving clarity of implementation without burdening the developer with the responsibility of determining whether or not to use a callback.

The following code seems to set up a simple transaction; when an instance of EventEmitter emits a start event, log "Started" to the console:

var events = require('events');

function getEmitter() {
    var emitter = new events.EventEmitter();
    emitter.emit('start');
    return emitter;
}

var myEmitter = getEmitter();

myEmitter.on("start", function() {
    console.log("Started");
});

However, the expected result will not occur. The event emitter instantiated within getEmitter emits "start" previous to being returned, wrong-footing the subsequent assignment of a listener, which arrives a step late, missing the event notification.

To solve this race condition we can use process.nextTick:

var events = require('events');

function getEmitter() {
    var emitter = new events.EventEmitter();
    process.nextTick(function() {
        emitter.emit('start');
    });
    return emitter;
}
var myEmitter = getEmitter();
myEmitter.on('start', function() {
    console.log('Started');
})

Here the attachment of the on(start handler is allowed to occur prior to the emission of the start event by the emitter instantiated in getEmitter.

Because it is possible to recursively call nextTick, which might lead to an infinite loop of recursive nextTick calls (starving the event loop, preventing I/O), there exists a failsafe mechanism in Node which limits the number of recursive nextTick calls evaluated prior to yielding the I/O: process.maxTickDepth. Set this value (which defaults to 1000) if such a construct becomes necessary—although what you probably want to use in such a case is setImmediate.

setImmediate

setImmediate is technically a member of the class of timers (setInterval, setTimeout). However, there is no sense of time associated with it—there is no number of milliseconds to wait argument to be sent. This method is really more of a sister to process.nextTick, differing in one very important way; while callbacks queued by nextTick will execute before I/O and timer events, callbacks queued by setImmediate will be called after I/O events.

Note

The naming of these two methods is confusing: nextTick occurs before setImmediate.

This method does reflect the standard behavior of timers in that its invocation will return an object which can be passed to cancelImmediate, cancelling setImmediate in the same way cancelTimeout cancels timers set with setTimeout.

主站蜘蛛池模板: 洪雅县| 公安县| 保山市| 宁明县| 东海县| 文化| 无为县| 定陶县| 达拉特旗| 长顺县| 新巴尔虎左旗| 辽阳县| 宁乡县| 台北市| 墨竹工卡县| 三明市| 广元市| 和硕县| 扶风县| 龙江县| 洞口县| 玛曲县| 包头市| 乌拉特后旗| 东阳市| 清水河县| 威海市| 得荣县| 江油市| 民县| 东莞市| 九寨沟县| 靖西县| 静宁县| 泽库县| 全州县| 崇明县| 涞水县| 正宁县| 汉川市| 龙海市|