FS writeFile(Sync) 导致 ws.broadcast 无法工作

FS writeFile(Sync) causing ws.broadcast to not work

我试图通过在 NodeJS 中编写一个简单的聊天应用程序来学习 WebSocket API。我需要将消息保存在一个名为 messages.json 的文件中,该文件看起来像这样:

[
  [
    "username",
    "message"
  ],
  [
    "username",
    "message"
  ],
  ...
]

我已经完成了客​​户端部分。 HTML 和 Javascript 文件如下所示:

<!DOCTYPE html>
<html>
  <head>
    <title>Stuff</title>
    <link href="style.css" rel="stylesheet" />
  </head>
  <body>
    <input id="chatText" placeholder="Message" />
    <button
      id="sendBtn"
      onclick='if (document.getElementById("chatText").value.trim() !== "") ws.send(document.getElementById("chatText").value + " " + user); document.getElementById("chatText").value = ""'
    >
      <span style="margin-left: 5px"></span>Send<span
        style="margin-left: 5px"
      ></span>
    </button>
    <br />
    <script src="script.js"></script>
  </body>
</html>
var user = prompt('Username?').trim()

while (user.split(' ').length > 1 || user.trim() === '') {
  user = prompt('Username?').trim()
}

var ws = new WebSocket("ws://localhost:8080");

ws.onopen = function (event) {
  console.log('Connection successfully opened!');
};

ws.onerror = function (err) {
  console.log('err: ', err);
}

ws.onmessage = function (event) {
  var div = document.createElement('div')
  div.classList.add('chatMsg')
  div.innerText = event.data.split(/ (?=[^ ]+$)/)[1] + ': ' + event.data.split(/ (?=[^ ]+$)/)[0]
  document.body.appendChild(div)
};

我使用正则表达式在最后一个 space 字符处拆分数据,因为我发现我无法将数组或对象从客户端发送到服务器,反之亦然,并且用户名不能包含任何 space,尽管我可能不小心把事情弄得太复杂了。

然后,服务器端的NodeJS代码就得写了。当我想永久保存消息时,我就完成了它:

const express = require('express');
const bodyParser = require('body-parser');
const app = express();

app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: true }));

app.use(express.static('public'));

app.get('/', (req, res) => {
  res.sendFile('public/index.html');
});

app.listen(8000, () => console.log('server started'));

const WebSocketServer = require('ws').Server
const wss = new WebSocketServer({ port: 8080 });

wss.on('connection', ((ws) => {
  ws.on('message', (message) => {
    wss.broadcast(message);
  });

  ws.on('end', () => {
    console.log('Connection ended...');
  });
}));

wss.broadcast = function broadcast(msg) {
  wss.clients.forEach(function each(client) {
    client.send(msg);
  });
};

到目前为止,我能够从一个浏览器选项卡发送消息,而另一个选项卡能够正确接收消息并发送另一条消息,依此类推。我认为永久保存消息的最佳方式是 JSON 文件,所以我创建了一个名为 messages.json 的文件,上面提到了

在程序开始时,我通过包含 var messages = require('./messages.json')required 模块 fs 来读取文件以写入 messages.json.

const fs = require('fs');
const messages = require('./messages.json');

然后我将 ws.on('message', (message) => { ... 更改为如下所示:

ws.on('message', (message) => {
  messages.push(message.split(/ (?=[^ ]+$)/))
  fs.writeFile('./messages.json', JSON.stringify(messages), () => { })
  wss.broadcast(message);
});

然后,我在 file:// ... /public/index.html 的浏览器中打开了两个选项卡。我从第一个选项卡发送了一条消息。然后我切换到第二个选项卡并尝试发送消息。我在输入框中输入了一些内容并按下了“发送”按钮,输入框内的文本就如预期的那样消失了,但除此之外什么也没有发生。然后我切换回第一个选项卡并尝试了同样的事情,但它的行为方式与第二个选项卡相同。我切换回 VSCode window 并检查终端,但没有错误。我打开 messages.json 并在数组中只看到一条消息。该文件如下所示:

[
  [
    "user",
    "test"
  ]
]

我很困惑,因为我发送了三条消息,但文件中只记录了一条。我将 messages.json 重置为 [] 并使用 fs.readFileSync 而不是 fs.readFile。没有任何变化,然后,为了调试目的,我将行 fs.writeFile('./messages.json', JSON.stringify(messages), () => { }) 更改为 console.log('test')。我关闭了两个选项卡并再次打开它们。然后,我从第一个选项卡发送了一条消息,从第二个选项卡发送了一条消息,从第一个选项卡发送了另一条消息。一切都按预期进行,两个选项卡都显示了消息。终端显示输出:

test
test
test

它按预期工作。为什么它不适用于 fs.readFilefs.readFileSync

到 运行 并重新加载服务器,我正在使用名为 live 的 NPM 脚本和 nodemon:

"scripts": {
  "live": "nodemon index.js"
},

要启动服务器,我只需在终端中 运行 nodemon index.js

我的目录结构:

node_modules
public
  |-- index.html
  |-- script.js
index.js
messages.json
package.json

问题是我使用 require() 读取 JSON 文件,因为这是更短、更容易的方法。我能够通过使用 JSON.parse(fs.readFileSync('./messages.json', () => { }).toString()) 而不是 require('./messages.json').

来修复它
const express = require('express')
const bodyParser = require('body-parser')
const fs = require('fs')
const app = express()

var messages = JSON.parse(fs.readFileSync('./messages.json', () => { }).toString())

app.use(bodyParser.json())
app.use(bodyParser.urlencoded({ extended: true }))

app.use(express.static('public'))

app.get('/', (req, res) => {
  res.sendFile('public/index.html')
})

app.listen(8000, () => { })

const WebSocketServer = require('ws').Server
const wss = new WebSocketServer({ port: 8080 })

wss.on('connection', ((ws) => {
  ws.on('message', (message) => {
    messages.push(message.split(/ (?=[^ ]+$)/))
    fs.writeFile('./messages.json', JSON.stringify(messages), () => { })
    wss.broadcast(message)
  })

  messages.forEach((item) => {
    ws.send(item.join(' '))
  })

  ws.on('end', () => {
    console.log('Connection ended...')
  })
}))

wss.broadcast = function broadcast(msg) {
  wss.clients.forEach(function each(client) {
    client.send(msg)
  })
}

编辑:

正如@Bergi 在评论中告诉我的那样,这是因为 nodemon - 它正在将文件视为依赖项(懒惰地使用 require() 读取 JSON 文件)并在每次编辑 messages.json 时重新启动服务器,这就是为什么它在第一条聊天消息后不起作用的原因。我能够通过 运行 我的应用程序使用 node 而不是 nodemon 来确认这一点。