Source text: https://github.com/cool-firer/docs/blob/main/node.js_socket_stream.md
net socket and stream events
Test procedure
tcp_server.js
view codeconst net = require('net'); net.createServer(function(c) { console.log('conneceted'); c.on('finish', function() { console.log('finish 111'); }) c.on('close', function() { console.log('close'); }) c.on('finish', function() { console.log('finish 222'); }) c.on('end', function() { console.log('end'); }); }).listen(9988); console.log('listen on 9988', ' pid:', process.pid)
tcp_client.js
view codeconst net = require('net'); const c = net.createConnection({ port: 9988 }) c.on('finish', function() { console.log('finish 111'); }) c.on('close', function() { console.log('close'); }) c.on('finish', function() { console.log('finish 222'); })
Start the server, and then start cilent. ctrl + c will exit the client directly. The server will print out:
$ node tcp_server.js listen on 9988 pid: 27157 conneceted end finish 111 finish 222 close
You need to check the socket documents and stream documents, and then cooperate with the four waves of tcp to understand.
socket end event:
https://nodejs.org/docs/latest-v10.x/api/net.html#net_event_end
Emitted when the other end of the socket sends a FIN packet, thus ending the readable side of the socket.
By default (allowHalfOpen is false) the socket will send a FIN packet back and destroy its file descriptor once it has written out its pending write queue. However, if allowHalfOpen is set to true, the socket will not automatically end() its writable side, allowing the user to write arbitrary amounts of data. The user must call end() explicitly to close the connection (i.e. sending a FIN packet back).
It means that the 'end' event will be triggered when the FIN packet sent from the opposite end is received, indicating that it is no longer readable.
For a better understanding, take a look at the end event of stream:
https://nodejs.org/docs/latest-v10.x/api/stream.html#stream_event_end
The 'end' event is emitted when there is no more data to be consumed from the stream.
Note that only the Readable stream has an end event.
Socket is a Duplex stream. You can see that the end of socket corresponds to the end of Readable stream in the sense that there is no more data to read.
Therefore, when triggered at 1, end is printed first.
If there is no finish event in the socket, it can only be in the stream:
https://nodejs.org/docs/latest-v10.x/api/stream.html#stream_event_finish
The 'finish' event is emitted after the stream.end() method has been called, and all data has been flushed to the underlying system.
This means that all internal buffer data is brushed to the underlying system. At the same time, note that only Writable stream has the finish event. It can be guessed that it will be triggered only when the current socket is no longer writable, which is exactly after the current socket sends FIN to the opposite end.
Corresponding to 2, print out finish.
After that, the close event of socket:
https://nodejs.org/docs/latest-v10.x/api/net.html#net_event_close_1
Added in: v0.1.90
-
hadError true if the socket had a transmission error.
Emitted once the socket is fully closed. The argument hadError is a boolean which says if the socket was closed due to a transmission error.
Triggered when the socket is completely closed.
At the same time, both Readable stream and Writable stream have close events. Take a look:
Readable:
https://nodejs.org/docs/latest-v10.x/api/stream.html#stream_event_close_1
The 'close' event is emitted when the stream and any of its underlying resources (a file descriptor, for example) have been closed. The event indicates that no more events will be emitted, and no further computation will occur.
Writable:
https://nodejs.org/docs/latest-v10.x/api/stream.html#stream_event_close
The 'close' event is emitted when the stream and any of its underlying resources (a file descriptor, for example) have been closed. The event indicates that no more events will be emitted, and no further computation will occur.
Both Readable and Writable streams have highly consistent descriptions of the close event, which means that the underlying resource (file descriptor) of the stream is closed, which also corresponds to the close event of the socket.
Corresponding to 3, print close.
socket.end and consumption
If we change tcp_client.js code, replace ctrl + c with socket End () method, the server remains unchanged?
view code// tcp_client.js const net = require('net'); const c = net.createConnection({ port: 9988 }) c.on('end', function() { console.log('end'); }) c.on('finish', function() { console.log('finish 111'); }) c.on('close', function() { console.log('close'); }) c.on('finish', function() { console.log('finish 222'); }) setTimeout(function() { c.end('what the hell'); }, 3000)
After 3s, call the end() method to close the current connection.
Let's take a look at socket End() method description:
https://nodejs.org/docs/latest-v10.x/api/net.html#net_socket_end_data_encoding_callback
socket.end([data][, encoding][, callback])[src]#
Added in: v0.1.90
- data | |
- encoding Only used when data is string. Default: 'utf8'.
- callback Optional callback for when the socket is finished.
- Returns: <net.Socket> The socket itself.
Half-closes the socket. i.e., it sends a FIN packet. It is possible the server will still send some data.
If data is specified, it is equivalent to calling socket.write(data, encoding) followed by socket.end().
Half close the socket and send FIN packets to the opposite end.
Then, according to the newly modified code, will the server go through the wave process four times and print out 'end', 'finish' and 'close' in turn? First look at the output of the client:
$ node tcp_cilent.js finish 111 finish 222
Look at the output of the server:
$ node tcp_server.js listen on 9988 pid: 32405 conneceted
The end() method was called and the connection was not disconnected? And the server did not trigger the 'end' event? This...
The clue is in the end event description of stream:
https://nodejs.org/docs/latest-v10.x/api/stream.html#stream_event_end
Event: 'end'#
Added in: v0.9.4
The 'end' event is emitted when there is no more data to be consumed from the stream.
The 'end' event will not be emitted unless the data is completely consumed. This can be accomplished by switching the stream into flowing mode, or by calling stream.read() repeatedly until all data has been consumed.
'end' will not trigger unless data is fully consumed.
In addition, at the end of the document, it is also mentioned, and an example is given:
Look at the code on the server side. There is no 'data' event bound for the new socket, and there is no 'readable' + read() method to consume internal data. The socket is in pause mode. Or it can be understood that the FIN package is arranged at the end of the internal buffer. It can only be the FIN package after consuming the previous data.
Therefore, to make him wave after four normal walks, he needs to consume the socket on the server, like this:
view codeconst net = require('net'); net.createServer(function(c) { console.log('conneceted'); c.on('finish', function() { console.log('finish 111'); }) c.on('close', function() { console.log('close'); }) c.on('finish', function() { console.log('finish 222'); }) c.on('end', function() { console.log('end'); }); setTimeout(async () => { /** Choose one of several methods */ // Method 1: use flow mode c.on('data', (chunk) => { console.log(`Received ${chunk.length} bytes of data. chunkStr:${chunk.toString()}`); }); }, 5000); // Method 2: pause mode readable + read method c.on('readable', () => { let chunk; while (null !== (chunk = c.read())) { console.log(`Received ${chunk.length} bytes of data. chunkStr:${chunk.toString()}`); } }); // Method 3: pause mode direct read for(let i = 0; i < 16;i++) { const internelBuf = c.read(1); console.log(`${i} Received ${internelBuf ? internelBuf.length + ' bytes of data. chunkStr:' + internelBuf.toString() : null }`); await new Promise((r,j) => { setTimeout(() => { r(true); }, 2000) }) } // Method 4: flow mode resume method c.resume(); }).listen(9988); console.log('listen on 9988', ' pid:', process.pid)
In this way, the client and server can print normally.
$ node tcp_cilent.js finish 111 finish 222 end close
$ node tcp_server.js listen on 9988 pid: 32627 conneceted end finish 111 finish 222 close
Therefore, to trigger the socket 'end' event, a condition needs to be added: the current socket needs to be consumed and received the FIN packet before it can be triggered.
socket.destroy and finish
What if you change end to destroy?
// tcp_client.js const net = require('net'); const c = net.createConnection({ port: 9988 }) c.on('end', function() { console.log('end'); }) c.on('finish', function() { console.log('finish 111'); }) c.on('close', function() { console.log('close'); }) c.on('finish', function() { console.log('finish 222'); }) setTimeout(function() { // c.end('what the hell'); c.destroy(); }, 3000)
In the official document, the description of destroy is as follows:
socket part: https://nodejs.org/docs/latest-v10.x/api/net.html#net_socket_destroy_exception
socket.destroy([exception])
Added in: v0.1.90
Ensures that no more I/O activity happens on this socket. Only necessary in case of errors (parse error or so).
If exception is specified, an 'error' event will be emitted and any listeners for that event will receive exception as an argument.
stream part: https://nodejs.org/docs/latest-v10.x/api/stream.html#stream_writable_destroy_error
writable.destroy([error])#
Added in: v8.0.0
Destroy the stream, and emit the passed 'error' and a 'close' event. After this call, the writable stream has ended and subsequent calls to write() or end() will result in an ERR_STREAM_DESTROYED error. Implementors should not override this method, but instead implement writable._destroy().
The stream section says that the stream is destroyed and the 'close' event is triggered. The client is indeed like this:
$ node tcp_cilent.js close
On the server side, no matter whether there is a socket or not, it prints normally:
$ node tcp_server.js listen on 9988 pid: 32712 conneceted end finish 111 finish 222 close
As mentioned before, 'finish' will be triggered after sending the FIN package, but 'finish' is not triggered by destroy here. According to, whether it is end or destroy, FIN will be sent to the opposite end, but fd will be destroyed directly after destruction, not waiting for the ACK of the opposite end.
Event: 'finish'#
Added in: v0.9.4
The 'finish' event is emitted after the stream.end() method has been called, and all data has been flushed to the underlying system.
Therefore, after sending the FIN packet, 'finish' will not be triggered immediately, but the FIN packet will be sent, and the internal buffer will not be triggered until it is brushed to the bottom fd.
socket and Transform
Let's change again. What if we put socket pipe into a custom Transform? Many network NPM client libraries do this, such as mysql2, AMQ, etc.
Rewrite the server code:
// tcp_server.js const net = require('net'); class Incoming extends require('stream').Transform { _flush(callback) { console.log(' incoming _flush') this.push(null); callback(); } _transform(chunk, encoding, callback) { callback(null, chunk); } } const income = new Incoming(); net.createServer(function(c) { console.log('conneceted'); c.on('finish', function() { console.log('finish 111'); }) c.on('close', function() { console.log('close'); }) c.on('finish', function() { console.log('finish 222'); }) c.on('end', function() { console.log('end'); }) }, 5000) c.pipe(income); income.on('end' ,function() { console.log(' incoming end') }) income.on('finish' ,function() { console.log(' incoming finish') }) income.on('close' ,function() { console.log(' incoming close') }) }).listen(9988); console.log('listen on 9988', ' pid:', process.pid)
// tcp_client.js const net = require('net'); const c = net.createConnection({ port: 9988 }) c.on('end', function() { console.log('end'); }) c.on('finish', function() { console.log('finish 111'); }) c.on('close', function() { console.log('close'); }) c.on('finish', function() { console.log('finish 222'); }) setTimeout(function() { c.end('what the hell'); }, 3000)
In this scenario, the client outputs:
$ node tcp_cilent.js finish 111 finish 222 end close
Server output:
$ node tcp_server.js listen on 9988 pid: 34930 conneceted end incoming _flush incoming finish finish 111 finish 222 close
You can see that the socket s at both ends are closed normally.
socket. The pipe (income) implementation will bind the data event for the socket and forward the data read from the socket to the income.
For sockets, there is consumption data, so sockets can finish end, finish and close normally.
So, how to understand the finish of income?
As mentioned earlier, for the socket, the finish event is triggered after the current end sends FIN and flush to the bottom fd, which means that no data can be written to the current front end. Specifically, no data can be written to the internal writable buffer of the current end, which is reflected in the code, that is, the socket write('xxx'). The event that triggers finish can be represented by such pseudo code:
socket.write('xxx') ---> socket.write(FIN) ---> flushTo(fd) ---> emit('finish')
For income, there is no underlying fd. Its underlying fd is itself. The data transferred from socket is equivalent to income in code Write ('xxx '), which also means that you can't write data into income. Expressed in pseudo code:
income.write('xxx') ---> income.write(FIN) ---> flushTo(income buffer) ---> emit('finish')
As for_ The flush method prints before finish because:
https://nodejs.org/docs/latest-v10.x/api/stream.html#stream_transform_flush_callback
transform._flush(callback)#
- callback Function A callback function (optionally with an error argument and data) to be called when remaining data has been flushed.
Custom Transform implementations may implement the transform._flush() method. This will be called when there is no more written data to be consumed, but before the 'end' event is emitted signaling the end of the Readable stream.
This is the last method that will be called when writing the data chain. At this time, the data has not been flush ed, and it must be before the finish event.
If you want an income to be like a socket and finish, finish and close normally, it will be triggered only after consuming the internal data of the income. The method is the same as the previous method, binding the data event, calling resume and calling read multiple times.
const net = require('net'); class Incoming extends require('stream').Transform { _flush(callback) { console.log(' incoming _flush') this.push(null); callback(); } _transform(chunk, encoding, callback) { callback(null, chunk); } } const income = new Incoming(); net.createServer(function(c) { console.log('conneceted'); c.on('finish', function() { console.log('finish 111'); }) c.on('close', function() { console.log('close'); }) c.on('finish', function() { console.log('finish 222'); }) c.on('end', function() { console.log('end'); }); c.pipe(income); income.on('end' ,function() { console.log(' incoming end') }) income.on('finish' ,function() { console.log(' incoming finish') }) income.on('close' ,function() { console.log(' incoming close') }) setTimeout(async () => { // Method 1: use flow mode income.on('data', (chunk) => { console.log(`income Received ${chunk.length} bytes of data. chunkStr:${chunk.toString()}`); }) // Method 2: pause mode readable + read method income.on('readable', () => { let chunk; while (null !== (chunk = income.read())) { console.log(`income Received ${chunk.length} bytes of data. chunkStr:${chunk.toString()}`); } }); // Method 3: pause mode direct read for(let i = 0; i < 16;i++) { const internelBuf = income.read(1); console.log(`${i} income Received ${internelBuf ? internelBuf.length + ' bytes of data. chunkStr:' + internelBuf.toString() : null }`); await new Promise((r,j) => { setTimeout(() => { r(true); }, 2000) }) } // Method 4: flow mode resume method income.resume(); }, 5000) }).listen(9988); console.log('listen on 9988', ' pid:', process.pid)
At this time, the server can normally output end, finish and close:
$ node tcp_server.js listen on 9988 pid: 35495 conneceted end incoming _flush incoming finish finish 111 finish 222 close income Received 13 bytes of data. chunkStr:what the hell incoming end incoming close