event-stream vs octet-stream

Jul 05, 2025 · 5472 字

TL;DR: text/event-stream 是结构化的事件流格式,适合实时事件推送;application/octet-stream 是原始二进制流,适合大数据传输和自定义格式。

在 HTTP 流式传输中,text/event-streamapplication/octet-stream 是两种不同的 Content-Type,它们代表了不同的数据组织方式和使用场景。

text/event-stream

text/event-stream 是专门用于服务器推送事件的 MIME 类型,定义了结构化的文本格式来表示事件流。每个事件都有固定的格式要求。

该格式具有以下特点:

  • 结构化数据:每个事件包含 data:event:id: 等字段
  • 文本格式:基于 UTF-8 编码的纯文本
  • 事件边界:使用双换行符 \n\n 分隔事件
  • 浏览器支持:可以用 EventSource API 或 ReadableStream 读取

使用 EventSource API(传统方式):

const eventSource = new EventSource("/api/events");

eventSource.onmessage = (event) => {
  const data = JSON.parse(event.data);
  console.log("收到数据:", data);
};

eventSource.onerror = (error) => {
  console.error("连接错误:", error);
};

使用 ReadableStream 读取(更灵活):

async function readEventStream() {
  const response = await fetch("/api/events");
  const reader = response.body.getReader();
  const decoder = new TextDecoder();

  while (true) {
    const { done, value } = await reader.read();
    if (done) break;

    const chunk = decoder.decode(value, { stream: true });
    const events = chunk.split("\n\n");

    events.forEach((event) => {
      if (event.startsWith("data: ")) {
        const data = event.substring(6);
        console.log("收到事件:", data);
      }
    });
  }
}

服务器端需要发送特定格式的响应:

app.get("/api/events", (req, res) => {
  res.writeHead(200, {
    "Content-Type": "text/event-stream",
    "Cache-Control": "no-cache",
    Connection: "keep-alive",
  });

  // 发送事件
  const sendEvent = (data, eventType = null, id = null) => {
    if (id) res.write(`id: ${id}\n`);
    if (eventType) res.write(`event: ${eventType}\n`);
    res.write(`data: ${JSON.stringify(data)}\n\n`);
  };

  // 定期推送数据
  const interval = setInterval(() => {
    sendEvent(
      {
        timestamp: Date.now(),
        message: "Hello",
      },
      "update",
      Date.now()
    );
  }, 1000);

  req.on("close", () => clearInterval(interval));
});

application/octet-stream

application/octet-stream 是通用的二进制流 MIME 类型,表示原始的字节流数据。它不预定义任何特定的数据结构,完全由应用程序决定数据格式。

该格式具有以下特点:

  • 原始数据:不对数据内容做任何假设或格式化
  • 二进制流:可以传输任意类型的数据
  • 自定义格式:数据格式完全由应用程序定义
  • 高效传输:没有格式开销,传输效率高

客户端使用 ReadableStream 读取:

async function readOctetStream() {
  const response = await fetch("/api/stream");
  const reader = response.body.getReader();

  while (true) {
    const { done, value } = await reader.read();
    if (done) break;

    // 处理原始字节数据
    console.log("接收到数据块:", value);

    // 根据应用协议解析数据
    const chunk = new TextDecoder().decode(value);
    console.log("解析后的数据:", chunk);
  }
}

服务器端可以发送原始数据流:

app.get("/api/stream", (req, res) => {
  res.writeHead(200, {
    "Content-Type": "application/octet-stream",
    "Transfer-Encoding": "chunked",
  });

  let count = 0;
  const interval = setInterval(() => {
    if (count >= 10) {
      res.end();
      clearInterval(interval);
      return;
    }

    // 发送自定义格式的数据
    const data = Buffer.from(`数据块 ${count++}\n`);
    res.write(data);
  }, 100);
});

传输方式与长度处理

两种格式都支持多种传输方式:

分块编码 (Chunked Encoding):服务器将响应分割成多个块,每个块包含大小信息和数据内容。这种方式不需要预先知道数据总长度,适合动态生成的内容。

已知内容长度:服务器预先知道数据总长度,在响应头中声明 Content-Length

对于不知道数据长度的场景,有几种处理方式:

  1. 使用分块编码:这是最推荐的方式,HTTP/1.1 标准支持,无需预先知道长度
  2. 设置较大的 Content-Length:设置一个足够大的值,传输完成后由服务器主动关闭连接
  3. 不设置 Content-Length:某些情况下可以省略该头部,依赖连接关闭来标识传输结束
// 方式1:分块编码(推荐)
app.get("/api/stream-chunked", (req, res) => {
  res.writeHead(200, {
    "Content-Type": "application/octet-stream",
    "Transfer-Encoding": "chunked",
  });

  // 动态生成数据,无需预知长度
  generateDataStream(
    (chunk) => {
      res.write(chunk);
    },
    () => {
      res.end(); // 数据生成完毕
    }
  );
});

// 方式2:设置较大长度
app.get("/api/stream-large", (req, res) => {
  res.writeHead(200, {
    "Content-Type": "text/event-stream",
    "Content-Length": "999999999", // 设置一个很大的值
  });

  // 传输数据
  streamData(
    (chunk) => {
      res.write(chunk);
    },
    () => {
      res.end(); // 服务器主动关闭
    }
  );
});

注意事项

  • 分块编码是处理未知长度数据的标准方式,几乎所有现代 HTTP 客户端都支持
  • 设置过大的 Content-Length 可能导致客户端等待超时,建议谨慎使用
  • 服务器主动关闭连接是终止流传输的可靠方式,客户端会收到相应的结束信号

格式对比

特性text/event-streamapplication/octet-stream
数据格式结构化事件格式原始字节流
数据类型文本(UTF-8)二进制
格式开销每个事件有固定格式开销无格式开销
解析复杂度需要解析事件边界应用程序自定义解析
浏览器支持EventSource + ReadableStreamReadableStream
重连机制EventSource 内置自动重连需要应用层实现
事件类型支持自定义事件类型无事件概念
数据效率相对较低(格式开销)高(无额外开销)

应用场景选择

场景类型text/event-streamapplication/octet-stream
实时通知消息推送、状态更新大量数据推送
金融行情股票价格、市场动态历史数据传输
协作应用多人编辑、在线会议文件同步、版本控制
监控面板系统状态、性能指标日志流、大数据分析
媒体传输轻量级事件推送视频下载、音频直播
AI 应用简单状态通知模型推理结果流式输出

选择建议

选择 text/event-stream 的情况:

  • 需要结构化的事件推送
  • 数据量较小且频繁
  • 希望使用浏览器内置的 EventSource API
  • 需要事件类型和 ID 管理
  • 主要面向 Web 客户端

选择 application/octet-stream 的情况:

  • 需要传输大量数据
  • 对传输效率有较高要求
  • 需要自定义数据格式
  • 需要支持多种客户端类型
  • 希望更灵活的数据组织方式

在实际项目中,这两种格式往往根据不同的业务场景来选择,甚至在同一个应用中同时使用两种格式。

粤公网安备44030002006951号 粤ICP备2025414119号

© 2025 Saurlax · Powered by Astro