Skip to content

Commit 0822bc4

Browse files
committed
feat: close seqpacket socket automatically when both read and write ends are closed
1 parent 28f3b80 commit 0822bc4

14 files changed

+401
-36
lines changed

README.md

Lines changed: 129 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,152 @@
11
# nix-socket
22

33
`nix-socket` allows you to use some nonblocking sockets that are not supported by Node.js native modules, including:
4+
- Using `SO_REUSEPORT` enabled TCP [net.Server](https://nodejs.org/dist/latest-v16.x/docs/api/net.html#class-netserver)
45
- unix seqpacket(`SOCK_SEQPACKET`) sockets
56
- unix datagram(`SOCK_DGRAM`) sockets
6-
- Using `SO_REUSEPORT` for TCP [net.Server](https://nodejs.org/dist/latest-v16.x/docs/api/net.html#class-netserver)
77

8-
`nix-socket` is a [napi-rs](https://napi.rs/) based [Node.js addons](https://nodejs.org/docs/latest-v16.x/api/addons.html). This lib uses [libuv](https://libuv.org/) inside Node.js so that it won't introduce other asynchronous runtimes.
8+
`nix-socket` is a [napi-rs](https://napi.rs/) based [Node.js addons](https://nodejs.org/docs/latest-v16.x/api/addons.html). This lib uses [libuv](https://libuv.org/) inside Node.js so that it won't introduce any other asynchronous runtimes.
99

10-
## Examples
10+
## API Documents
11+
12+
[API Documents](./docs/modules.md)
13+
14+
## `SO_REUSEPORT` enabled TCP net.Server
15+
16+
The [cluster](https://nodejs.org/dist/latest-v18.x/docs/api/cluster.html) module share server ports by accepting new connections in the primary process and distributing them to worker processes.
17+
18+
With `SO_REUSEPORT`, sockets will be distributed by kernel instead, and which should be more performant especially for scenario of having a lot of short-lived connections.
19+
20+
For example, the arrow in the image below shows cpu usage of a PM2 primary process which we found in our environment.
21+
22+
![cpu_usage](./resource/cpu_usage.png)
23+
24+
Note that `SO_REUSEPORT` might behave much differently across operating systems. See this [post](https://stackoverflow.com/questions/14388706/how-do-so-reuseaddr-and-so-reuseport-differ) for more information.
25+
26+
### Example
27+
28+
```js
29+
const { createReuseportFd } = require('nix-socket')
30+
const { Server, Socket } = require('net')
31+
32+
const port = 8080
33+
const host = '0.0.0.0'
34+
35+
// create multple servers listening to a same host, port.
36+
for (let i = 0; i < 2; i += 1) {
37+
const fd = createReuseportFd(port, host)
38+
const server = new Server((socket) => {
39+
socket.on('data', (buf) => {
40+
console.log(`server ${i} received:`, buf)
41+
// echo
42+
socket.write(buf)
43+
})
44+
})
45+
46+
server.listen({
47+
fd,
48+
}, () => {
49+
console.log(`server ${i} is listening on ${port}`)
50+
})
51+
}
52+
53+
setInterval(() => {
54+
const client = new Socket()
55+
client.on('data', (buf) => {
56+
console.log('client received:', buf)
57+
client.destroy()
58+
})
59+
client.connect(port, host, () => {
60+
client.write(Buffer.from('hello'))
61+
})
62+
}, 1000)
63+
```
64+
65+
## Seqpacket Sockets
66+
67+
`SOCK_SEQPACKET` sockets are like `SOCK_DGRAM` sockets and they will keep message boundaries.
1168

12-
### Seqpacket Sockets
69+
Note that `SOCK_SEQPACKET` sockets don't work on MacOS.
70+
71+
### Example
1372

1473
```js
1574
const { SeqpacketServer, SeqpacketSocket } = require('nix-socket')
1675
const os = require('os')
1776
const path = require('path')
77+
const fs = require('fs')
1878

19-
const bindPath = path.resolve(os.tmp(), './my.sock')
79+
const bindPath = path.resolve(os.tmpdir(), './my_seqpacket.sock')
2080

21-
const server = new SeqpacketServer()
81+
try { fs.unlinkSync(bindPath) } catch (e) {}
2282

83+
const server = new SeqpacketServer()
2384
server.listen(bindPath)
85+
server.on('connection', socket => {
86+
socket.on('data', buf => {
87+
console.log('received', buf.toString())
88+
})
89+
});
2490

2591
const client = new SeqpacketSocket()
92+
client.connect(bindPath, () => {
93+
const data = [
94+
'hello, ',
95+
'w',
96+
'o',
97+
'r',
98+
'l',
99+
'd'
100+
]
26101

27-
// TODO
28-
client.write()
102+
for (const str of data) {
103+
client.write(Buffer.from(str))
104+
}
105+
client.end()
106+
})
29107
```
30108

31-
## API Documents
109+
## Dgram Sockets
32110

33-
[API Documents](./docs/modules.md)
111+
### Example
112+
113+
```js
114+
const { DgramSocket } = require('nix-socket')
115+
const os = require('os')
116+
const path = require('path')
117+
const fs = require('fs')
118+
119+
const path1 = path.resolve(os.tmpdir(), './my_dgram_1.sock')
120+
const path2 = path.resolve(os.tmpdir(), './my_dgram_2.sock')
121+
122+
try {
123+
fs.unlinkSync(path1);
124+
fs.unlinkSync(path2);
125+
} catch (err) {}
126+
127+
const socket1 = new DgramSocket()
128+
const socket2 = new DgramSocket()
129+
130+
socket1.bind(path1)
131+
socket2.bind(path2)
132+
133+
socket2.on('data', (data, remoteAddr) => {
134+
console.log(`socket2 received: ${data.toString()}`)
135+
// echo
136+
socket2.sendTo(data, 0, data.length, remoteAddr);
137+
})
138+
139+
socket1.on('data', (data) => {
140+
console.log(`socket1 received: ${data.toString()}`)
141+
})
142+
143+
setInterval(() => {
144+
const buf = Buffer.from('hello')
145+
socket1.sendTo(buf, 0, buf.length, path2)
146+
}, 1000)
147+
```
148+
149+
## LICENSE
150+
151+
## TODO
152+
- seqpacket sockets should be closed automatically

__test__/seqpacket.spec.ts

Lines changed: 47 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,10 @@ async function createTestPair(
1111
client: SeqpacketSocket;
1212
server: SeqpacketServer;
1313
socket: SeqpacketSocket;
14-
}) => Promise<any>
14+
}) => Promise<any>,
15+
options: { autoClose: boolean } = { autoClose: true }
1516
) {
17+
const { autoClose } = options
1618
const server = new SeqpacketServer();
1719
const client = new SeqpacketSocket();
1820
server.listen(kServerpath);
@@ -33,6 +35,9 @@ async function createTestPair(
3335
socket,
3436
});
3537

38+
if (!autoClose) {
39+
return
40+
}
3641
socket.destroy();
3742
client.destroy();
3843
server.close();
@@ -552,6 +557,47 @@ if (!kIsDarwin) {
552557
client.unref();
553558
});
554559
});
560+
561+
it('should write whole buffer if "offset" and "length" are missed', async () => {
562+
await createTestPair(async (args) => {
563+
const { client, socket } = args;
564+
const buf = Buffer.from('hello, world');
565+
client.write(buf)
566+
567+
const {p, resolve } = createDefer();
568+
socket.on('data', received => {
569+
expect(received.toString('hex')).toBe(buf.toString('hex'))
570+
resolve()
571+
})
572+
await p
573+
});
574+
});
575+
576+
it('should emit "close" in sockets automatically when both read and write side of sockets are end', async () => {
577+
await createTestPair(async (args) => {
578+
const { client, socket, server } = args;
579+
580+
const { p: p1, resolve: r1 } = createDefer();
581+
const { p: p2, resolve: r2 } = createDefer();
582+
583+
client.on('close', () => {
584+
r1()
585+
});
586+
socket.on('close', () => {
587+
r2()
588+
})
589+
590+
client.write(Buffer.alloc(1024 * 64))
591+
client.end()
592+
socket.write(Buffer.alloc(1024 * 64))
593+
socket.end()
594+
await Promise.all([p1, p2])
595+
596+
server.close();
597+
}, {
598+
autoClose: false,
599+
})
600+
});
555601
});
556602
} else {
557603
describe('seqpacket', () => {

docs/README.md

Lines changed: 81 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,86 @@
1-
unix-socket / [Exports](modules.md)
1+
nix-socket / [Exports](modules.md)
22

3-
# unix-socket
3+
# nix-socket
4+
5+
`nix-socket` allows you to use some nonblocking sockets that are not supported by Node.js native modules, including:
6+
- Using `SO_REUSEPORT` enabled TCP [net.Server](https://nodejs.org/dist/latest-v16.x/docs/api/net.html#class-netserver)
7+
- unix seqpacket(`SOCK_SEQPACKET`) sockets
8+
- unix datagram(`SOCK_DGRAM`) sockets
9+
10+
`nix-socket` is a [napi-rs](https://napi.rs/) based [Node.js addons](https://nodejs.org/docs/latest-v16.x/api/addons.html). This lib uses [libuv](https://libuv.org/) inside Node.js so that it won't introduce any other asynchronous runtimes.
411

512
## API Documents
613

714
[API Documents](./docs/modules.md)
15+
16+
## `SO_REUSEPORT` enabled TCP net.Server
17+
18+
The [cluster](https://nodejs.org/dist/latest-v18.x/docs/api/cluster.html) module share server ports by accepting new connections in the primary process and distributing them to worker processes.
19+
With `SO_REUSEPORT`, sockets will be distributed by kernel instead, and which should be more performant especially for scenario of having a lot of short-lived connections.
20+
21+
Note that `SO_REUSEPORT` might behave much differently across operating systems. See this informative [post](https://stackoverflow.com/questions/14388706/how-do-so-reuseaddr-and-so-reuseport-differ) for more information.
22+
23+
### Example
24+
25+
```js
26+
const { createReuseportFd } = require('nix-socket')
27+
const { Server, Socket } = require('net')
28+
29+
const port = 8080
30+
const host = '0.0.0.0'
31+
32+
// create multple servers listening to a same host, port.
33+
for (let i = 0; i < 2; i += 1) {
34+
const fd = createReuseportFd(port, host)
35+
const server = new Server((socket) => {
36+
socket.on('data', (buf) => {
37+
console.log(`server ${i} received:`, buf)
38+
// echo
39+
socket.write(buf)
40+
})
41+
})
42+
43+
server.listen({
44+
fd,
45+
}, () => {
46+
console.log(`server ${i} is listening on ${port}`)
47+
})
48+
}
49+
50+
setInterval(() => {
51+
const client = new Socket()
52+
client.on('data', (buf) => {
53+
console.log('client received:', buf)
54+
client.destroy()
55+
})
56+
client.connect(port, host, () => {
57+
client.write(Buffer.from('hello'))
58+
})
59+
}, 1000)
60+
```
61+
62+
## Seqpacket Sockets
63+
64+
```js
65+
const { SeqpacketServer, SeqpacketSocket } = require('nix-socket')
66+
const os = require('os')
67+
const path = require('path')
68+
69+
const bindPath = path.resolve(os.tmp(), './my.sock')
70+
71+
const server = new SeqpacketServer()
72+
73+
server.listen(bindPath)
74+
75+
const client = new SeqpacketSocket()
76+
77+
// TODO
78+
client.write()
79+
```
80+
81+
## Dgram Sockets
82+
83+
```js
84+
```
85+
86+
## LICENSE

docs/classes/DgramSocket.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
[unix-socket](../README.md) / [Exports](../modules.md) / DgramSocket
1+
[nix-socket](../README.md) / [Exports](../modules.md) / DgramSocket
22

33
# Class: DgramSocket
44

docs/classes/SeqpacketServer.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
[unix-socket](../README.md) / [Exports](../modules.md) / SeqpacketServer
1+
[nix-socket](../README.md) / [Exports](../modules.md) / SeqpacketServer
22

33
# Class: SeqpacketServer
44

docs/classes/SeqpacketSocket.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
[unix-socket](../README.md) / [Exports](../modules.md) / SeqpacketSocket
1+
[nix-socket](../README.md) / [Exports](../modules.md) / SeqpacketSocket
22

33
# Class: SeqpacketSocket
44

docs/modules.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
[unix-socket](README.md) / Exports
1+
[nix-socket](README.md) / Exports
22

3-
# unix-socket
3+
# nix-socket
44

55
## Table of contents
66

0 commit comments

Comments
 (0)