【问题标题】:Receiving data in Node.js in chunks - when is the transmission finished?在 Node.js 中分块接收数据 - 传输何时完成?
【发布时间】:2018-07-28 11:30:56
【问题描述】:

我有两个正在相互通信的进程 - 客户端是 C# 控制台应用程序。服务器是一个 Node.js 应用程序,它使用了“网络”模块 (TCP)。

我需要完成的过程是:

  1. C# 客户端连接到 Node.js 服务器并发送 HTML。
  2. Node.js 接收 HTML 数据,并生成 PDF(使用 Puppeteer)。
  3. Node.js 服务器将 PDF 作为字节数组发送到 C# 客户端。

这工作顺利直到步骤 1 中的有效负载达到一定大小。那时,数据开始被分成多个块。块的组合大小等于最初发送的有效负载,因此它们都到达了服务器。

这会导致问题,因为生成 PDF 的代码位于 socket.on(data) 事件中。由于数据被多次接收,服务器正在创建多个 PDF,并且没有一个是完整的。

我读过的一些帖子建议使用 socket.on(end) 事件,但该事件没有触发,因为连接没有关闭 - 我需要保持连接打开,以便执行第 3 步。

我的第一个想法是“我需要增加缓冲区大小以便发送整个文件”,但经过一些研究后,我决定不这样做,因为拆分数据似乎是预期的行为。

我的问题是:我如何知道整个有效负载何时到达 Node.js 服务器?

  • 我可以利用 Node.js 中的套接字事件或其他事件吗?
  • 我可以检查发送的data 对象的属性吗?
  • 有没有办法让客户端“发出”它已完成发送数据的“信号”?

这似乎是一个常见的场景,所以我正在寻找一些最佳实践,因为我对 Node.js 比较陌生。

更新

这是导致创建多个 PDF 的(缩写)代码,因为数据被多次接收:

const net = require('net');
const PORT = 8080;
const ADDRESS = '127.0.0.1';

const server = net.createServer(onConnectionOpen);
server.listen(PORT, ADDRESS);

function onConnectionOpen(socket) {
    socket.on('data', (data) => {
        const folderName = 'reports';
        var fileName = functions.getRandomString(50) + '.pdf';
        var fullPath = process.cwd() + '\\' + folderName + '\\' + fileName;

    const puppeteer = require('puppeteer');

    (async () => {
        const browser = await puppeteer.launch({ headless: true });

        const page = await browser.newPage();

        await page.setContent(data.toString());
        const pdf = await page.pdf({ path: fullPath, format: 'Letter' });

        console.log('Total bytes returned: ' + pdf.length);
        socket.write(pdf);

        await browser.close();
    })();
})

socket.on('end', () => {
    socket.destroy();
})  

}

我已尝试将 Puppeteer 和 socket.write(...) 移动到 socket.on('end'),但该代码在生成 PDF 后无法运行,因为连接仍处于打开状态。

【问题讨论】:

  • 如果您展示用于接收数据的实际 node.js 代码,我们可以提供更好的帮助。
  • 超过一定大小的数据可能会以块的形式到达。您必须能够检测到最后一个块何时以某种方式到达。如果出于某种原因您不能使用 end 事件(我仍然不明白为什么,并且这是了解流何时完成向您发送数据的常用方法),那么您必须设计一些其他方式来您发送数据以了解最后一个块何时到达(可能发送一些您可以观察的分隔符)。那是你的责任。
  • 我已经更新了代码示例。我不能使用 end 事件的原因是因为根据我的测试和我在 nodejs.org 上阅读的内容,当连接关闭时会触发 end 事件。我保持连接打开,因为 node.js 服务接收到数据后,它需要在连接上发回数据。

标签: node.js sockets puppeteer


【解决方案1】:

将 socket.on(data) 处理程序中接收到的任何单个块的数据追加到匹配条件 data.toString().toLowerCase().endsWith("");

如果有效负载是 html,它应该可以工作。您只需在有效负载末尾有一个分隔符并使用它。

【讨论】:

  • data.toString().toLowerCase().endsWith("
【解决方案2】:

如果发送方在完成发送后不打算关闭套接字,那么您必须通过读取数据中的内容来判断它何时完成发送。有很多方法可以做到这一点。例如,您可以将其编码为 MIME 部分,该部分在其开头和结尾处具有独特的标记,您可以在阅读时查看这些标记。

既然您说内容是您控制另一端发送的 HTML,您可以在 HTML 的末尾使用结尾 </html> 作为分隔符。要以简单的方式执行此操作,您必须确保主文档中没有任何嵌入的 HTML 文档(例如具有本地内容的 iframe)。如果您可以做出这些假设,那么您可以使用对 </html> 的检测作为分隔符来告诉您何时完成接收 HTML。

通过这些简化的假设,您可以像这样检测:

const net = require('net');
const PORT = 8080;
const ADDRESS = '127.0.0.1';
const puppeteer = require('puppeteer');

const server = net.createServer(onConnectionOpen);
server.listen(PORT, ADDRESS);

function onConnectionOpen(socket) {
    let receivedData = "";
    socket.on('data', (data) => {
        receivedData += data.toString();
        // if we have the ending tag of our HTML, then process it
        let html = receivedData;
        if (html.indexOf("</html>") !== -1) {
            // reset receivedData so if any more data arrives (after the </html>)
            // it won't affect the html string we now have locally
            receivedData = "";
            const folderName = 'reports';
            const fileName = functions.getRandomString(50) + '.pdf';
            const fullPath = process.cwd() + '\\' + folderName + '\\' + fileName;

            (async () => {
                const browser = await puppeteer.launch({ headless: true });

                const page = await browser.newPage();

                await page.setContent(html);
                const pdf = await page.pdf({ path: fullPath, format: 'Letter' });

                console.log('Total bytes returned: ' + pdf.length);
                socket.write(pdf);

                await browser.close();
            })().catch(err => {
                // handle error here
            });
        }
    });

    socket.on('end', () => {
        socket.destroy();
    });  
}

如果可以嵌入 HTML 文档,则需要更多代码才能知道何时获得外部 &lt;/html&gt;。而且,如果您不能确定 &lt;/html&gt; 是否为小写,您也必须进行不区分大小写的搜索。

如果您甚至不能保证在 HTML 内容的末尾会有 &lt;/html&gt;,那么您需要以某种方式确保末尾有一些已知的分隔符,或者您必须先发送内容的长度所以你知道什么时候你得到了所有的数据。没有其他方法可以知道内容的结尾在哪里。

【讨论】:

  • 我将研究 MIME 编码,但至少,我总是可以在每条消息中附加类似 的内容,并在服务端检查该字符串。但是,这种经历让我想知道其他 TCP 服务如何处理这种情况。接收消息并仅在收到整个消息后才发送回复的服务似乎很常见(例如,您向 SQL Server 发送查询并获得回复)。这些服务如何处理这个问题?
  • 另外,如果我决定在客户端完成发送后关闭连接,有没有办法利用 socket.on(end)?有没有办法让 node.js 服务重新打开连接,即使它最初是由客户端打开的?
  • @ChadSC - 他们使用一种网络数据格式,可以明确数据的结束位置。这也是你必须做的。您可以提前发送数据长度,也可以添加实际内容中不可能出现的已知分隔符。
  • @ChadSC - 不可以。您无法重新打开已关闭的连接。客户端必须发送另一个请求才能获得响应。仅供参考,如果您的服务器是 HTTP 服务器而不是常规 TCP 服务器,则整个请求/响应部分都内置在该协议中,因此 HTTP 客户端和服务器代码将为您管理。您将数据发布到服务器,然后在收到后发送响应。 HTTP 将使用内容长度标头或 MIME 标记来了解内容的结尾在哪里。
猜你喜欢
  • 2011-08-19
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-03-13
  • 1970-01-01
  • 2023-03-20
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多