[已解决]多个子进程监听同一端口时,为什么每个客户端连接会触发两个进程的响应?
发布于 2 个月前 作者 xvrzhao 680 次浏览 来自 问答

主进程 master.js 代码:

const { fork } = require('child_process')
const cpus = require('os').cpus()
const net = require('net')

// 根据 cpu 核数来创建子进程
const childProcesses = []
for (let i = 0; i < cpus.length; i++) {
    childProcesses.push(fork('./worker.js'))
}

// 创建 tcp 服务器,在监听端口之后把服务器句柄传递给每个子进程,并关闭服务。
// 通过传递服务器句柄的方式来达到多个子进程可以同时监听一个端口的效果。
const tcpServer = net.createServer()
tcpServer.listen(5800, () => {
    for (const childProcess of childProcesses) {
        childProcess.send('tcpServer', tcpServer)
    }
    tcpServer.close()
})

子进程 worker.js 代码:

const http = require('http')
const fs = require('fs')

// 创建 http 服务器,当有 tcp 连接时来手动触发 http 服务器的事件
const httpServer = http.createServer()
httpServer.on('request', (req, res) => {
    res.end(`Hello from child process (${process.pid})`)
})

// 子进程接收到主进程发来的 tcp 服务器句柄
process.on('message', (message, tcpServer) => {
    if (message === 'tcpServer') {
        tcpServer.on('connection', socket => {
            // 记录日志,用以证明当有 tcp 连接时,是只有一个子进程触发了事件,还是所有子进程同时触发了事件
            fs.writeFileSync('./log', `handled by child process (${process.pid}), ${new Date}\n`, {flag: 'a'})
            // 手动触发 http 服务器的 connection 事件,并将 socket(双工流)传入,http 服务器内部会
            // 根据协议将消息解析成 request(只读流)和 response(只写流)并触发自己的 request 事件
            httpServer.emit('connection', socket)
        })
    }
})

子进程接收到 tcp 连接事件,会打印日志,日志如下:

handled by child process (437), Wed Jun 26 2019 01:42:08 GMT+0800 (China Standard Time)
handled by child process (424), Wed Jun 26 2019 01:42:08 GMT+0800 (China Standard Time)
handled by child process (437), Wed Jun 26 2019 01:42:35 GMT+0800 (China Standard Time)
handled by child process (426), Wed Jun 26 2019 01:42:35 GMT+0800 (China Standard Time)
handled by child process (426), Wed Jun 26 2019 01:42:50 GMT+0800 (China Standard Time)
handled by child process (424), Wed Jun 26 2019 01:42:50 GMT+0800 (China Standard Time)
handled by child process (437), Wed Jun 26 2019 01:43:06 GMT+0800 (China Standard Time)
handled by child process (424), Wed Jun 26 2019 01:43:06 GMT+0800 (China Standard Time)
handled by child process (437), Wed Jun 26 2019 01:43:57 GMT+0800 (China Standard Time)
handled by child process (424), Wed Jun 26 2019 01:43:57 GMT+0800 (China Standard Time)
...

通过日志可以看到,每当有客户端连接到 5800 端口时,都触发了两个进程进行处理(日志时间相同),客户端不是使用的浏览器,所以排除浏览器请求 favicon.ico 的原因。 按说不同子进程共享同一个 socket 文件描述符 fd,客户端是只会触发其中一个进程执行的,因为是抢占式的。 求指点迷津!

4 回复

最新进展,将子进程代码改成:

const http = require('http')
const fs = require('fs')

// 创建 http 服务器,当有 tcp 连接时会触发 http 服务器的事件
const httpServer = http.createServer()
httpServer.on('request', (req, res) => {
    fs.writeFileSync('./log', `http handled by child process (${process.pid}), ${new Date}\n`, {flag: 'a'})
    res.end(`Hello from child process (${process.pid})`)
})

// 子进程接收到主进程发来的 tcp 服务器句柄
process.on('message', (message, tcpServer) => {
    if (message === 'tcpServer') {
        tcpServer.on('connection', socket => {
            // 记录日志,用以证明当有 tcp 连接时,是只有一个子进程触发了事件,还是所有子进程同时触发了事件
            fs.writeFileSync('./log', `tcp  handled by child process (${process.pid}), ${new Date}\n`, {flag: 'a'})
            // 手动触发 http 服务器的 connection 事件,并将 socket(双工流)传入,http 服务器内部会
            // 根据协议将消息解析成 request(只读流)和 response(只写流)并触发自己的 request 事件
            httpServer.emit('connection', socket)
        })
    }
})

即,在 http 请求事件回调中也打印日志,日志如下:

tcp  handled by child process (647), Wed Jun 26 2019 02:30:21 GMT+0800 (China Standard Time)
tcp  handled by child process (629), Wed Jun 26 2019 02:30:21 GMT+0800 (China Standard Time)
http handled by child process (629), Wed Jun 26 2019 02:30:21 GMT+0800 (China Standard Time)
tcp  handled by child process (637), Wed Jun 26 2019 02:31:32 GMT+0800 (China Standard Time)
tcp  handled by child process (630), Wed Jun 26 2019 02:31:32 GMT+0800 (China Standard Time)
http handled by child process (630), Wed Jun 26 2019 02:31:32 GMT+0800 (China Standard Time)

由此发现会有两个进程触发 tcp 连接事件,但这两个进程中只有一个会进行 http 请求的处理。

why?

另外说明,测试机器处理器为4核。

问题解决了,服务开启在docker中,测试是在外部,经过了一次tcp代理。

docker 内部测试没问题:

tcp  handled by child process (688), Wed Jun 26 2019 13:33:54 GMT+0800 (China Standard Time)
http handled by child process (688), Wed Jun 26 2019 13:33:54 GMT+0800 (China Standard Time)
tcp  handled by child process (691), Wed Jun 26 2019 13:34:16 GMT+0800 (China Standard Time)
http handled by child process (691), Wed Jun 26 2019 13:34:16 GMT+0800 (China Standard Time)

回到顶部