WebSocket vs SSE深度对比分析

还在纠结项目到底用WebSocket还是SSE?这不是一个"选哪个都行"的问题------选错了,会直接影响你的应用性能、服务器成本,甚至用户体验。

最近我看到一个现象:很多开发者(特别是在有些大厂)在做实时通信时,往往选择WebSocket作为"万能方案",结果反而用重了。而另一些团队完全不了解SSE,错过了大量简化架构的机会。

这篇文章的目的很简单:帮你真正理解这两种技术的本质差异,不是功能对比,而是思维模型的转变

核心概念:你的问题本质是什么?

在我们深入技术细节前,需要问自己一个问题:你的实时通信需求,方向是什么?

问题方向一:双向即时互动

  • 用户A的操作需要立即让用户B看到

  • 延迟必须极低(毫秒级)

  • 例子:在线协作编文档、实时对战游戏、直播弹幕互动

问题方向二:服务器主动推送

  • 只有服务器向客户端发数据

  • 客户端被动接收,很少反馈

  • 例子:库存变化通知、服务器监控告警、行情推送

这两个问题,需要的方案完全不同。

第一部分:WebSocket------双向高速通道

本质理解:它是什么

想象你和朋友在一条公路上:

  • 传统HTTP像是"你大喊一声,他才能回应"(请求-响应)

  • WebSocket像是"你们之间装了个对讲机,可以随时说话"(双向常连接)

go 复制代码
初始握手(HTTP Upgrade):
客户端    →  「我想升级到WebSocket」  →  服务器
客户端    ←  「好的,升级成功」        ←  服务器

升级后:
客户端  ⟷  「随时双向通信」  ⟷  服务器

为什么它对双向通信那么重要

  1. 一次握手,永久连接 --- 初始化成本只有一次,之后是纯数据流

  2. 双向流畅 --- 不用等对方先说话

  3. 低延迟 --- 没有HTTP请求-响应的开销

  4. 二进制支持 --- 不只能传文字

代码层面看:从HTTP升级的细节

客户端建立WebSocket

go 复制代码
// JavaScript版本
const socket = new WebSocket('ws://localhost:8080');

socket.addEventListener('open', () => {
console.log('连接建立');
  socket.send('Hello from client');
});

socket.addEventListener('message', (event) => {
console.log('收到消息:', event.data);
});

socket.addEventListener('close', () => {
console.log('连接断开');
});
go 复制代码
// TypeScript版本
class ChatClient {
private socket: WebSocket | null = null;

  connect(url: string): Promise<void> {
    returnnewPromise((resolve, reject) => {
      this.socket = new WebSocket(url);
      
      this.socket.addEventListener('open', () => {
        console.log('WebSocket已连接');
        resolve();
      });

      this.socket.addEventListener('error', (error) => {
        reject(error);
      });

      this.socket.addEventListener('message', (event: Event) => {
        const messageEvent = event as MessageEvent<string>;
        this.handleMessage(messageEvent.data);
      });
    });
  }

private handleMessage(data: string): void {
    try {
      const message = JSON.parse(data);
      console.log('收到消息:', message);
    } catch (e) {
      console.error('消息解析失败', e);
    }
  }

  send(message: Record<string, unknown>): void {
    if (this.socket?.readyState === WebSocket.OPEN) {
      this.socket.send(JSON.stringify(message));
    }
  }

  disconnect(): void {
    this.socket?.close();
  }
}

服务器端使用ws库

go 复制代码
// Node.js + ws库
const WebSocket = require('ws');
const http = require('http');

const server = http.createServer();
const wss = new WebSocket.Server({ server });

wss.on('connection', (ws) => {
console.log('客户端已连接');

  ws.on('message', (data) => {
    console.log('收到消息:', data);
    
    // 广播给所有客户端
    wss.clients.forEach((client) => {
      if (client.readyState === WebSocket.OPEN) {
        client.send(JSON.stringify({
          type: 'message',
          data: data,
          timestamp: Date.now()
        }));
      }
    });
  });

  ws.on('error', (error) => {
    console.error('WebSocket错误:', error);
  });

  ws.on('close', () => {
    console.log('客户端已断开');
  });
});

server.listen(8080, () => {
console.log('WebSocket服务运行在8080端口');
});
go 复制代码
// TypeScript版本
import WebSocket, { Server as WebSocketServer } from'ws';
import * as http from'http';

interface ClientMessage {
type: string;
  content: string;
  userId?: string;
}

interface BroadcastMessage {
type: string;
  data: string;
  timestamp: number;
from?: string;
}

class ChatServer {
private wss: WebSocketServer;
private clientCount: number = 0;

constructor(port: number) {
    const server = http.createServer();
    this.wss = new WebSocketServer({ server });
    
    this.wss.on('connection', (ws) =>this.handleConnection(ws));
    
    server.listen(port, () => {
      console.log(`WebSocket服务运行在${port}端口`);
    });
  }

private handleConnection(ws: WebSocket): void {
    this.clientCount++;
    console.log(`客户端已连接,当前在线数:${this.clientCount}`);

    ws.on('message', (data: WebSocket.Data) => {
      try {
        const message = JSON.parse(data.toString()) as ClientMessage;
        this.broadcastMessage({
          type: message.type,
          data: message.content,
          timestamp: Date.now(),
          from: message.userId || 'anonymous'
        });
      } catch (error) {
        console.error('消息解析失败:', error);
      }
    });

    ws.on('error', (error: Error) => {
      console.error('WebSocket错误:', error.message);
    });

    ws.on('close', () => {
      this.clientCount--;
      console.log(`客户端已断开,当前在线数:${this.clientCount}`);
    });
  }

private broadcastMessage(message: BroadcastMessage): void {
    this.wss.clients.forEach((client) => {
      if (client.readyState === WebSocket.OPEN) {
        client.send(JSON.stringify(message));
      }
    });
  }
}

new ChatServer(8080);

真实场景:你在用WebSocket

场景1:Alibaba的钉钉实时协作

  • 10个人同时编辑一个文档

  • 每个字符输入都要立即同步给其他9个人

  • WebSocket是唯一合理的选择

场景2:ByteDance的实时推荐互动

  • 用户点赞、评论、分享,需要实时更新数据

  • 主播和粉丝的实时互动(延迟要求非常严格)

  • WebSocket支撑高频双向消息

WebSocket的"税":你要付出什么代价

成本项 具体表现
内存占用 每个连接占用内存,1万个用户就是1万条常连接
复杂度 需要处理断线重连、消息去重、顺序保证等问题
水平扩展 多服务器时需要消息队列中转(Redis、RabbitMQ)
防火墙 某些企业网络可能限制WebSocket
状态管理 服务器要维护每个连接的状态

第二部分:Server-Sent Events(SSE)------单向推流管道

本质理解:它是什么

再用公路比喻:

  • SSE就是"你们之间装了个单向的水管,水只能从服务器流向客户端"
go 复制代码
建立连接:
客户端    →  「我要订阅你的消息」  →  服务器
客户端    ←  「好的,开始推送数据」  ←  服务器

推送过程:
客户端    ←  「数据1」  ←  服务器
客户端    ←  「数据2」  ←  服务器
客户端    ←  「数据3」  ←  服务器
(客户端想回复的话,得用独立的HTTP请求)

为什么它那么"轻"

SSE本质上就是一条HTTP连接,服务器把它当作一条永不关闭的数据流:

  1. 基于HTTP --- 不需要特殊的ws协议

  2. 自动重连 --- 浏览器原生支持自动断线重连

  3. 简化架构 --- 不用维护双向连接的复杂性

  4. 文本数据 --- 专为文本设计

代码层面看:看起来更简洁

客户端订阅

go 复制代码
// JavaScript版本
const eventSource = new EventSource('http://localhost:3000/subscribe');

eventSource.onmessage = (event) => {
  const data = JSON.parse(event.data);
  console.log('库存变化:', data);
  updateUI(data);
};

eventSource.onerror = (error) => {
  console.log('连接错误,浏览器会自动重连');
};
go 复制代码
// TypeScript版本
interface StockUpdate {
  symbol: string;
  price: number;
  change: number;
  timestamp: number;
}

class StockSubscriber {
private eventSource: EventSource | null = null;

  subscribe(url: string, onUpdate: (data: StockUpdate) =>void): void {
    this.eventSource = new EventSource(url);

    this.eventSource.onmessage = (event: MessageEvent<string>) => {
      try {
        const data = JSON.parse(event.data) as StockUpdate;
        onUpdate(data);
      } catch (error) {
        console.error('数据解析失败', error);
      }
    };

    this.eventSource.onerror = () => {
      console.log('连接错误,浏览器自动重连中...');
      // 浏览器会自动处理重连逻辑
    };
  }

  unsubscribe(): void {
    if (this.eventSource) {
      this.eventSource.close();
    }
  }
}

// 使用
const subscriber = new StockSubscriber();
subscriber.subscribe(
'http://localhost:3000/subscribe/stock',
(data) =>console.log('股票更新:', data)
);

服务器端推送

go 复制代码
// Node.js + Express
const express = require('express');
const app = express();

app.get('/subscribe', (req, res) => {
// 设置SSE相关头
  res.setHeader('Content-Type', 'text/event-stream');
  res.setHeader('Cache-Control', 'no-cache');
  res.setHeader('Connection', 'keep-alive');

// 模拟推送库存数据
let count = 0;
const interval = setInterval(() => {
    count++;
    const stockData = {
      symbol: 'TSLA',
      price: (100 + Math.random() * 50).toFixed(2),
      change: (Math.random() - 0.5) * 10,
      timestamp: Date.now()
    };

    // SSE格式:data: 内容\n\n
    res.write(`data: ${JSON.stringify(stockData)}\n\n`);

    if (count > 100) {
      clearInterval(interval);
      res.end();
    }
  }, 1000);

// 客户端断开连接时清理
  req.on('close', () => {
    clearInterval(interval);
  });
});

app.listen(3000, () => {
console.log('SSE服务运行在3000端口');
});
go 复制代码
// TypeScript + Express
import express, { Request, Response } from'express';

interface StockData {
  symbol: string;
  price: string;
  change: number;
  timestamp: number;
}

class StockFeedServer {
private app: express.Application;

constructor(port: number) {
    this.app = express();
    this.setupRoutes();
    
    this.app.listen(port, () => {
      console.log(`SSE服务运行在${port}端口`);
    });
  }

private setupRoutes(): void {
    this.app.get('/subscribe/stock', (req: Request, res: Response) => {
      this.handleSSEConnection(res);
    });
  }

private handleSSEConnection(res: Response): void {
    // 设置SSE响应头
    res.setHeader('Content-Type', 'text/event-stream');
    res.setHeader('Cache-Control', 'no-cache');
    res.setHeader('Connection', 'keep-alive');
    res.setHeader('Access-Control-Allow-Origin', '*');

    let messageCount = 0;
    
    const interval = setInterval(() => {
      const stockData: StockData = {
        symbol: 'TSLA',
        price: (100 + Math.random() * 50).toFixed(2),
        change: (Math.random() - 0.5) * 10,
        timestamp: Date.now()
      };

      // SSE消息格式
      res.write(`id: ${messageCount}\n`);
      res.write(`data: ${JSON.stringify(stockData)}\n\n`);
      messageCount++;

      if (messageCount > 100) {
        clearInterval(interval);
        res.end();
      }
    }, 1000);

    // 客户端断开连接时清理资源
    req.on('close', () => {
      clearInterval(interval);
      res.end();
    });

    req.on('error', (error: Error) => {
      console.error('SSE连接错误:', error.message);
      clearInterval(interval);
      res.end();
    });
  }
}

new StockFeedServer(3000);

真实场景:你可以用SSE

场景1:Alibaba商城的库存实时推送

  • 库存变化只需要从服务器推给用户

  • 用户的操作(下单)用独立HTTP请求

  • SSE就够了,不需要WebSocket

场景2:监控系统的告警推送

  • 服务器发现问题,需要实时通知用户

  • 用户不需要实时反馈

  • SSE + 独立的HTTP API完全够用

场景3:Tencent云的实时日志流

  • 云平台推送日志行

  • 用户只是被动查看

  • SSE是标准方案

SSE的优势:你能省什么

优势项 具体表现
服务器成本 基于HTTP,可用HTTP服务器直接支持
内存占用 比WebSocket少
跨域友好 基于HTTP的CORS,WebSocket有自己的跨域机制
自动重连 浏览器原生支持,代码省一堆
负载均衡 标准HTTP,任何负载均衡器都支持
调试 在浏览器DevTools里和HTTP一样

第三部分:对比与选择决策树

逐条对比:技术细节层面

go 复制代码
┌─────────────────────────────────────────────────────────┐
│  对比维度        │  WebSocket      │  SSE              │
├─────────────────────────────────────────────────────────┤
│ 通信方向        │  双向           │  单向(服务器→客户端)
│ 连接类型        │  持久化双向     │  持久化单向       │
│ 建立协议        │  需要Upgrade    │  标准HTTP         │
│ 消息格式        │  二进制或文本   │  纯文本           │
│ 自动重连        │  需手动实现     │  浏览器原生       │
│ 防火墙兼容      │  某些企业网络限制 │  无问题          │
│ 服务器成本      │  高             │  低               │
│ 实现复杂度      │  高             │  低               │
│ 延迟            │  极低(毫秒)   │  低(毫秒)       │
│ 浏览器支持      │  IE 10+         │  IE不支持         │
└─────────────────────────────────────────────────────────┘

决策流程图

go 复制代码
你的实时通信需求
│
├─ 需要「客户端」主动发送数据给「其他客户端」或「服务器」吗?
│  │
│  ├─ 是 → 需要「立即」看到反馈吗?
│  │      │
│  │      ├─ 是(毫秒级延迟)→ WebSocket
│  │      │
│  │      └─ 否(秒级以上)→ SSE + 独立POST请求
│  │
│  └─ 否 → 只需要「被动」接收服务器推送 → SSE ✓
│
└─ 总结:双向 + 低延迟 = WebSocket
        单向或低频反馈 = SSE

实际选择场景

应用类型 推荐方案 理由
在线协作编文档 WebSocket 需要实时同步每个字符
IM聊天应用 WebSocket 双向即时消息
直播弹幕 WebSocket 高频双向互动
股票行情推送 SSE 单向推送,客户端不反馈
系统通知 SSE 单向推送,用户被动接收
库存变化 SSE 单向推送,订单用独立HTTP
监控告警 SSE 单向推送
邮件推送 SSE 单向推送
实时对战游戏 WebSocket 极低延迟双向互动

第四部分:大厂是怎么用的

ByteDance的选择

  • 抖音直播 → WebSocket(弹幕、点赞、送礼需要实时双向)

  • 推荐流更新 → SSE(内容推送是单向的)

  • 实时通知 → SSE(通知推送)

Alibaba的选择

  • 钉钉实时协作 → WebSocket(文档编辑需要双向同步)

  • 淘宝消息中心 → SSE + 异步任务(消息推送)

  • 商城库存 → SSE(库存变化推送,下单用HTTP)

Tencent的选择

  • QQ即时通讯 → 自有协议(比WebSocket更优化的私有协议)

  • 微信支付通知 → WebHook(不是实时推送,是回调)

  • 腾讯云告警 → SSE(告警推送)

核心启发

大厂都在用"混合"方案

  • 需要低延迟互动 → WebSocket

  • 需要单向推送 → SSE

  • 需要异步通知 → 消息队列 + Webhook

第五部分:生产环境的隐藏坑

WebSocket的隐藏成本

坑1:多服务器部署

go 复制代码
问题:用户A连到服务器1,用户B连到服务器2
      服务器1上的消息怎样推给服务器2的用户B?

解决方案:用Redis/RabbitMQ中转
go 复制代码
// 使用Redis作为消息中转
import Redis from'redis';
import WebSocket from'ws';

const redis = Redis.createClient();
const subscriber = Redis.createClient();

class DistributedChat {
private wss: WebSocket.Server;
private userId: Map<WebSocket, string> = new Map();

constructor(port: number) {
    this.wss = new WebSocket.Server({ port });
    this.setupRedisSubscriber();
    
    this.wss.on('connection', (ws) => {
      const id = Date.now().toString();
      this.userId.set(ws, id);

      ws.on('message', async (data) => {
        const message = JSON.parse(data);
        // 发布到Redis,其他服务器可以订阅
        await redis.publish('chat-channel', JSON.stringify({
          from: id,
          content: message.content,
          timestamp: Date.now()
        }));
      });

      ws.on('close', () => {
        this.userId.delete(ws);
      });
    });
  }

private setupRedisSubscriber(): void {
    subscriber.subscribe('chat-channel');
    subscriber.on('message', (channel: string, message: string) => {
      // 广播给所有本服务器的连接
      this.wss.clients.forEach((client) => {
        if (client.readyState === WebSocket.OPEN) {
          client.send(message);
        }
      });
    });
  }
}

new DistributedChat(8080);

坑2:断线重连逻辑

go 复制代码
// 简单的重连策略
class RobustWebSocket {
private socket: WebSocket | null = null;
private reconnectAttempts = 0;
private maxReconnectAttempts = 5;
private reconnectDelay = 1000;

  connect(url: string): void {
    this.socket = new WebSocket(url);

    this.socket.onopen = () => {
      this.reconnectAttempts = 0;
      console.log('连接成功');
    };

    this.socket.onclose = () => {
      if (this.reconnectAttempts < this.maxReconnectAttempts) {
        setTimeout(
          () => {
            this.reconnectAttempts++;
            console.log(`第${this.reconnectAttempts}次重连...`);
            this.connect(url);
          },
          this.reconnectDelay * Math.pow(2, this.reconnectAttempts) // 指数退避
        );
      }
    };

    this.socket.onerror = (error) => {
      console.error('WebSocket错误:', error);
    };
  }

  send(data: Record<string, unknown>): void {
    if (this.socket?.readyState === WebSocket.OPEN) {
      this.socket.send(JSON.stringify(data));
    } else {
      console.warn('连接未打开,消息缓存待重连后发送');
      // 实际项目中应该缓存消息
    }
  }
}

SSE的注意事项

坑1:浏览器同时连接限制

go 复制代码
// 问题:浏览器对同域EventSource有连接数限制(通常是6个)
// 解决:用子域名或JSONP替代(较少见)

// 正确的做法:一个EventSource订阅多个事件类型
const eventSource = new EventSource('/events');

eventSource.addEventListener('stock-update', (event) => {
// 处理股票更新
});

eventSource.addEventListener('notification', (event) => {
// 处理通知
});

eventSource.addEventListener('alert', (event) => {
// 处理告警
});
go 复制代码
// 服务器端对应的实现
app.get('/events', (req, res) => {
  res.setHeader('Content-Type', 'text/event-stream');
  res.setHeader('Cache-Control', 'no-cache');
  res.setHeader('Connection', 'keep-alive');

const interval = setInterval(() => {
    // 使用event字段指定事件类型
    res.write('event: stock-update\n');
    res.write(`data: ${JSON.stringify(stockData)}\n\n`);

    res.write('event: notification\n');
    res.write(`data: ${JSON.stringify(notificationData)}\n\n`);
  }, 1000);

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

坑2:SSE不支持二进制数据

go 复制代码
// 错误的尝试:SSE只支持文本
res.write(binaryData); // ❌ 不行

// 正确的做法:Base64编码
const base64Data = Buffer.from(binaryData).toString('base64');
res.write(`data: ${base64Data}\n\n`);

// 客户端解码
eventSource.onmessage = (event) => {
  const binaryData = Buffer.from(event.data, 'base64');
  // 处理二进制数据
};

第六部分:性能数据对比

延迟对比(在相同网络条件下)

go 复制代码
建立连接延迟:
WebSocket: ~50-100ms(需要HTTP升级握手)
SSE:       ~30-50ms(标准HTTP连接)

消息往返延迟(1KB消息):
WebSocket: ~5-10ms(极低)
SSE:       ~8-15ms(稍高)

断线重连时间:
WebSocket: 取决于实现(通常秒级)
SSE:       浏览器自动,通常1-3秒内重连

服务器成本对比(以ByteDance规模估算)

go 复制代码
假设:100万同时在线用户

WebSocket方案:
- 内存占用:~500MB(单连接约500字节)
- CPU占用:中等(需要处理消息路由)
- 需要中间件:Redis/消息队列
- 估计成本:$$$$

SSE方案:
- 内存占用:~200MB
- CPU占用:低(服务器主推,客户端被动)
- 中间件需求:无(或仅用于数据源)
- 估计成本:$$

差异:SSE成本约为WebSocket的40-50%

常见问题(FAQ)

Q1:我应该同时用WebSocket和SSE吗?

A:完全可以。 最佳实践是混合方案:

  • WebSocket处理需要双向即时的部分(如编辑、弹幕)

  • SSE处理单向推送的部分(如通知、行情)

这样既降低成本,又满足需求。

Q2:如果我的SSE连接断了,未读的消息怎么办?

A:用idretry字段解决:

go 复制代码
// 服务器端
res.write(`id: ${messageId}\n`);
res.write(`retry: 5000\n`); // 断线5秒后自动重连
res.write(`data: ${JSON.stringify(data)}\n\n`);
go 复制代码
// 客户端
eventSource.onopen = () => {
  lastReceivedId = localStorage.getItem('lastMessageId');
  // 服务器可以根据lastReceivedId重发未读消息
};

eventSource.onmessage = (event) => {
  const messageId = event.lastEventId;
  localStorage.setItem('lastMessageId', messageId);
};

Q3:WebSocket vs 其他方案(Long Polling、WebRTC)怎么选?

A:不推荐其他方案了:

方案 为什么不推荐
Long Polling 浪费带宽,已被WebSocket/SSE淘汰
WebRTC 太复杂,除非需要P2P
GraphQL Subscriptions 这是应用层,下层还是WebSocket

现在的选择就是:WebSocket vs SSE

Q4:能在生产环境用SSE吗?大厂都用吗?

A:完全可以。 腾讯云、Alibaba Cloud都用SSE做通知系统。

关键是:

  • ✓ 选对场景(单向推送)

  • ✓ 处理好重连

  • ✓ 考虑好浏览器兼容性(IE不支持)

Q5:如何监控和调试WebSocket/SSE连接?

A:使用Chrome DevTools:

go 复制代码
// WebSocket调试
ws.addEventListener('open', () => console.log('WS open'));
ws.addEventListener('message', (e) => console.log('WS message:', e.data));
ws.addEventListener('close', () => console.log('WS close'));
ws.addEventListener('error', (e) => console.error('WS error:', e));

// SSE调试
eventSource.addEventListener('open', () => console.log('SSE open'));
eventSource.addEventListener('message', (e) => console.log('SSE message:', e.data));
eventSource.addEventListener('error', () => console.error('SSE error'));

// DevTools看法:
// - 打开Network标签
// - WebSocket会单独显示WS协议
// - SSE在XHR中显示为永不完成的请求

总结:做出正确的选择

这篇文章的核心观点

  1. 不存在"最好的方案",只有"最合适的方案"
  • 双向即时互动 → WebSocket

  • 单向推送 → SSE

  • 混合需求 → 两者结合

  • 大多数开发者用重了

    • 很多只需要SSE的场景,被架设成了WebSocket

    • 结果是多花了钱、多写了复杂代码

  • 生产环境用SSE很成熟

    • 不用自己实现重连逻辑

    • 完全可以应对百万级用户

    • 成本更低

  • 选择技术的标准是:

    • 理解本质(通信方向、延迟要求)

    • 评估成本(服务器、复杂度)

    • 考虑长期(扩展性、维护性)

    三个行动项

    1. 审视你现有的项目 --- 有没有用WebSocket做SSE的活儿的?可以优化。

    2. 新项目从问题出发 --- 先问"需要什么方向的通信",再选技术。

    3. 建立监控和告警 --- 无论用什么,都要监控连接数、消息延迟、错误率。

    深入学习资源

    • MDN: WebSocket API

    • MDN: Server-Sent Events

    • Node.js ws库

    • WebSocket vs SSE性能测试报告

    如果这篇文章对你有帮助,请点赞、分享、评论!

    你的支持是我持续输出高质量技术内容的动力。

相关推荐
电子_咸鱼2 小时前
Linux IPC 实战:管道与共享内存的使用场景 + 底层原理全剖析
linux·运维·服务器·开发语言·网络·vscode·qt
一尘之中2 小时前
InfiniBand多播组管理:从理论到实现的深度解析
网络·ai写作
悟道|养家2 小时前
微服务扇出:网络往返时间的影响与优化实践(5)
网络·微服务
funnycoffee1232 小时前
华为USG6555F 防火墙 ---华为6857交换机 光口对接无法UP故障
服务器·网络·华为·usg自协商
Tandy12356_2 小时前
手写TCP/IP协议栈——TCP数据接收
c语言·网络·网络协议·tcp/ip·计算机网络
wait_luky3 小时前
NFS服务器
linux·服务器·网络
2501_927773073 小时前
嵌入式——串口
网络
fy zs3 小时前
TCP/IP 协议栈深度解析
网络·网络协议·tcp/ip