首頁 web前端 js教程 WebRTC WHIP 和 WHEP 教學:建立即時串流應用程式

WebRTC WHIP 和 WHEP 教學:建立即時串流應用程式

Nov 01, 2024 am 09:38 AM

本文最初發佈在計量部落格:WebRTC WHIP 和 WHEP 教學:建立即時串流應用程式

WHIP(WebRTC-HTTP 攝取協定)WHEP(WebRTC-HTTP 出口協定) 是旨在藉助標準 HTTP 方法簡化 WebRTC 中訊號傳送的協定

  • WHIP 的定義:WHIP 簡化了客戶端裝置將媒體串流傳送到伺服器的方式。

    • 它取代了簡單 HTTP GET 請求所需的複雜訊號機制,從而更容易將媒體攝取到伺服器
  • WHEP 的定義:WHEP 協定用於將媒體串流從伺服器傳送到客戶端。它使用 HTTP 協定來處理媒體消費的訊號,從而使客戶端設備無需複雜的設定即可接收媒體串流

簡化 WebRTC 訊號的作用

  • 易於實施: WHEP 和 WHIP 使用 HTTP 協議,因此這些協議降低了與

  • 相關的複雜性
  • 無狀態通訊:這是因為 HTTP 是無狀態協議,伺服器不需要維護請求之間持續的會話資訊。

  • 相容性提高:由於HTTP具有通用相容性,因此使用HTTP進行訊號是跨平台和裝置相容性的最佳選擇

  • 快速開發:開發人員可以更有效地實現 WebRTC 應用程序,因為他們不需要考慮傳統信號方法的複雜細節

WebRTC WHIP & WHEP Tutorial: Build a live Streaming App

WHIP 是如何運作的

WHIP 如何處理媒體串流攝取

WHIP 協定徹底改變如何使用 HTTP 訊號方法將媒體串流從客戶端裝置傳送到伺服器

傳統上,要設定 WebRTC,您需要使用 Web 套接字或其他協定來設定複雜的訊號機制。借助 WHIP,透過使用 HTTP 協定發送訊號並啟動 WebRTC 會話,此過程變得簡單

  • HTTP POST 請求: 此處,用戶端裝置將正文中包含 SDP 或會話描述協定的 HTTP POST 要求傳送至 WHIP 端點

  • 伺服器回應: 媒體伺服器然後處理 SDP Offer 並使用 200 狀態碼回應,包括請求正文中的 SDP 答案

  • ICE 候選交換: WHIP 協議支援 ICE 協議,允許客戶端在新的 ICE 候選可用時發送額外的 HTTP PATCH 請求

  • 連接建立: 一旦 SDP 交換完成,就會建立點對點連接,使客戶端能夠將媒體串流傳輸到伺服器

相對於傳統攝取方法的優勢

  • 簡單性: 透過使用 WHIP 方法,WHIP 減少了對持久連線和訊號伺服器的需求。

  • 易於實作: 開發者可以使用具有通用相容性的 HTTP 來加快開發流程

  • 可擴充性:無狀態 HTTP 請求允許伺服器同時處理多個連線請求,從而輕鬆管理大量連線。

  • 防火牆和代理友好: HTTP 是防火牆友好的,幾乎所有類型的防火牆都允許 HTTP 流量

  • 成本效益: 透過 HTTP 進行的簡化訊號降低了與新增訊號伺服器相關的成本

WebRTC WHIP & WHEP Tutorial: Build a live Streaming App

WHEP 是如何運作的

WHEP 協定簡化了將媒體從伺服器傳輸到客戶端裝置的過程。 

因此,WHEP 協定可讓您使用 HTTP 來設定從伺服器和用戶端裝置接收媒體的訊號。

WHEP 在媒體串流中如何運作

  • HTTP GET 請求: 客戶端透過傳送 HTTP GET 請求至伺服器 WHEP 請求來要求媒體串流

  • SDP Exchange: 伺服器在 HTTP 回應中以 SDP Offer 回應,然後用戶端在後續 POST 請求中發回 SDP 回應

  • 媒體接收: 建立連線後,將透過已建立的 WebRTC 連線接收媒體串流。注意:很多時候您需要TURN 伺服器來建立 WebRTC 連線

  • 對 ICE 的支援: WHEP 允許透過額外的 HTTP 修補程式要求交換 ICE 候選者,從而實現更好的連接

客戶端串流的優勢

  • 簡化的客戶實現: 使用 HTTP 請求,從而減少複雜信號機制的需求 

  • 改進的兼容性: 對 HTTP 協定的普遍支援確保了改進的跨裝置相容性

  • 增強的可擴展性: 由於 HTTP 是無狀態協議,這提高了伺服器的可擴展性,並且使用少量資源即可擴展到大量用戶

  • 更好的網路遍歷: 因為您可以使用 HTTP 進行訊號傳輸,並且不需要 Web 套接字或其他機制,所以這可以改善連接的 NAT 遍歷。建立連線後,您確實需要WebRTC 的 TURN 伺服器

  • 減少延遲: 使用 HTTP 發送訊號可以實現更快的連接,從而增強用戶體驗。

WebRTC WHIP & WHEP Tutorial: Build a live Streaming App

WHIP 與 WHEP 之間的協同作用

結合兩種協定以實現高效率的端到端通訊:

透過結合 WHIP 和 WHEP,開發人員可以為 WebRTC 建立全面的訊號解決方案

這種組合簡化了媒體串流的攝取和交付,確保 WebRTC 的實施更加順利

  • 統一訊號方法: 使用 HTTP 進行攝取與交付,建立一致的訊號方法

  • 降低複雜性: 開發者只需處理 HTTP 協議,從而減少程式碼的學習曲線和維護

  • 增強的效能:使用單一訊號協定簡化程式碼,可以在傳輸媒體時縮短連線時間並降低延遲

展示效能改進的用例

  • 直播平台: 

  • 互動式應用程式

  • 可擴充架構

建立直播應用程式

在 WebRTC 應用程式中實作 WHIP 和 WHEP 非常簡單。在本節中,我們將設定一個 WHIP 伺服器並使用節點和 docker 等現代技術將其整合到您的應用程式中

這裡我們將使用 Metered.ca TURN 伺服器服務進行 NAT 穿越

設定 WHIP 伺服器

先決條件與環境設定:

  • Node.js 和 NPM: 確保您安裝了最新的 Node 和 nvm

  • Metered.ca 帳戶: 在 Metered TURN 伺服器

  • 上建立免費帳戶
  • 公用 IP 位址: 這是可透過網際網路存取伺服器所必需的。如果您的應用程式使用任何雲端供應商,您將獲得一個免費的公用 IP 位址

  • WebRTC 媒體伺服器: 我們需要一個支援 WHIP 的媒體伺服器,例如 GStreamer 或 Janus

使用 Node.Js 和 GStreamer 加強步驟設定

  1. 安裝支援 WHIP 的 GStreamer

  2. 使用 GStreamer 設定 WHIP 伺服器

    1. 建立一個 GStreamer 管道來監聽 WHIP 連接,如下所示
gst-launch-1.0 whipserversrc name=whip \
  ! queue ! videoconvert ! autovideosink
登入後複製
登入後複製

上述指令啟動一個 WHIP 伺服器,該伺服器接受傳入的媒體流並顯示它們

  1. 設定伺服器以使用Metered.ca TURN 伺服器

    1. 修改 GStreamer 管道以使用 TURN 伺服器。這很重要,因為 NAT 路由器和防火牆會阻止連接
gst-launch-1.0 whipserversrc name=whip ice-server="turn://YOUR_USERNAME:YOUR_CREDENTIAL@relay.metered.ca:80" \
  ! queue ! videoconvert ! autovideosink
登入後複製
登入後複製
  1. 使用 Node.JS 設定反向代理(可選):

    1. 如果您想管理 HTTP 端點或新增任何其他附加邏輯,您可以設定一個簡單的 Express js 伺服器
const express = require('express');
const httpProxy = require('http-proxy');
const app = express();
const proxy = httpProxy.createProxyServer();

app.post('/whip', (req, res) => {
  proxy.web(req, res, { target: 'http://localhost:PORT_WHERE_GSTREAMER_IS_RUNNING' });
});

app.listen(3000, () => {
  console.log('Proxy server running on port 3000');
});
登入後複製
登入後複製

在您的應用程式中實作 WHIP

整合 WHIP 的程式碼片段:

在用戶端,您可以藉助 RTCPeerConnection 擷取媒體串流,並使用 HTTP 請求來處理建立連線所需的訊號

  1. 捕捉媒體串流:

    1. 您可以捕捉媒體串流,例如
const mediaStream = await navigator.mediaDevices.getUserMedia({ video: true, audio: true });
登入後複製
登入後複製
  1. 建立 RTCPeerConnection:

您可以在計量 TURN 伺服器的協助下建立 RTCPeerConnection

var myPeerConnection = new RTCPeerConnection({
  iceServers: [
      {
        urls: "stun:stun.relay.metered.ca:80",
      },
      {
        urls: "turn:global.relay.metered.ca:80",
        username: "your-username",
        credential: "your-credential",
      },
      {
        urls: "turn:global.relay.metered.ca:80?transport=tcp",
        username: "your-username",
        credential: "your-credential",
      },
      {
        urls: "turn:global.relay.metered.ca:443",
        username: "your-username",
        credential: "your-credential",
      },
      {
        urls: "turns:global.relay.metered.ca:443?transport=tcp",
        username: "your-username",
        credential: "your-credential",
      },
  ],
});
登入後複製
登入後複製
  1. 將媒體軌道加入連結:
mediaStream.getTracks().forEach((track) => {
  pc.addTrack(track, mediaStream);
});
登入後複製
登入後複製
  1. 建立 SDP 報價並將其發送到 WHIP 伺服器:
const offer = await pc.createOffer();
await pc.setLocalDescription(offer);

const response = await fetch('http://YOUR_SERVER_IP:3000/whip', {
  method: 'POST',
  headers: { 'Content-Type': 'application/sdp' },
  body: pc.localDescription.sdp,
});

const sdpAnswer = await response.text();
await pc.setRemoteDescription({ type: 'answer', sdp: sdpAnswer });
登入後複製
登入後複製

處理訊號的 HTTP 請求和回應

  • 客戶端:

    • HTTP POST 請求: 

      • 網址:http://YOUR_SERVER_IP:3000/whip
      • 標頭:{ 'Content-Type': 'application/sdp' }
      • 正文:純文字形式的 SDP 報價
    • 期待回覆:

      • 狀態:201 已創建
      • 標題:帶有資源 URL 的位置標題
      • 正文:純文字形式的 SDP 答案
  • 伺服器端:

    • 接收 SDP 優惠:

      • 從 req.body 讀取 SDP
      • 建立WebRTC端點並設定遠端描述
    • 產生 SDP 答案

      • 來自伺服器 WebRTC 端點的 SDP 應答
      • 將 SDP 答案傳回 res.body
      • 使用 Metered.ca TURN 伺服器服務

WebRTC WHIP & WHEP Tutorial: Build a live Streaming App

使用 Metered.ca TURN 伺服器服務

TURN 伺服器的用途

當無法進行直接點對點連線時,促進媒體穿越 NAT 和防火牆

將 TURN 伺服器合併到 ICE 伺服器設定

這是 TURN 伺服器憑證和 ICE 伺服器

gst-launch-1.0 whipserversrc name=whip \
  ! queue ! videoconvert ! autovideosink
登入後複製
登入後複製

部署 WHEP 用戶端

擁有 WHIP 用戶端可讓您的應用程式使用 HTTP 訊號從伺服器接收媒體串流。

在客戶端整合 WHEP

  • 基本上了解 Javascript 中的 WebRTC API

  • 支援 WHEP GStreamer Janus 或任何其他

  • 的媒體伺服器
  • Metered.ca TURN 伺服器憑證

逐步將 WHEP 整合到您的應用程式中。

  1. 初始化 RTCPeerConnection

    1. 使用具有metered.ca轉伺服器的ICE伺服器建立一個RTCPeerConnection實例
gst-launch-1.0 whipserversrc name=whip ice-server="turn://YOUR_USERNAME:YOUR_CREDENTIAL@relay.metered.ca:80" \
  ! queue ! videoconvert ! autovideosink
登入後複製
登入後複製
  1. 處理傳入的媒體軌道

設定事件偵聽器以從伺服器恢復遠端軌道

const express = require('express');
const httpProxy = require('http-proxy');
const app = express();
const proxy = httpProxy.createProxyServer();

app.post('/whip', (req, res) => {
  proxy.web(req, res, { target: 'http://localhost:PORT_WHERE_GSTREAMER_IS_RUNNING' });
});

app.listen(3000, () => {
  console.log('Proxy server running on port 3000');
});
登入後複製
登入後複製
  1. 傳送 HTTP GET 伺服器到 WHEP 伺服器:

向伺服器發送 GET 請求

const mediaStream = await navigator.mediaDevices.getUserMedia({ video: true, audio: true });
登入後複製
登入後複製
  1. 收到的 SDP 報價的遠端描述
var myPeerConnection = new RTCPeerConnection({
  iceServers: [
      {
        urls: "stun:stun.relay.metered.ca:80",
      },
      {
        urls: "turn:global.relay.metered.ca:80",
        username: "your-username",
        credential: "your-credential",
      },
      {
        urls: "turn:global.relay.metered.ca:80?transport=tcp",
        username: "your-username",
        credential: "your-credential",
      },
      {
        urls: "turn:global.relay.metered.ca:443",
        username: "your-username",
        credential: "your-credential",
      },
      {
        urls: "turns:global.relay.metered.ca:443?transport=tcp",
        username: "your-username",
        credential: "your-credential",
      },
  ],
});
登入後複製
登入後複製
  1. 建立並發送 SDP 答案

建立 SDP 應答並透過 HTTP POST 請求將其傳送至伺服器

mediaStream.getTracks().forEach((track) => {
  pc.addTrack(track, mediaStream);
});
登入後複製
登入後複製
  1. 處理 ICE 候選人交換(可選):

如果您需要單獨發送 ICE 候選人,請處理icecandidate 事件

const offer = await pc.createOffer();
await pc.setLocalDescription(offer);

const response = await fetch('http://YOUR_SERVER_IP:3000/whip', {
  method: 'POST',
  headers: { 'Content-Type': 'application/sdp' },
  body: pc.localDescription.sdp,
});

const sdpAnswer = await response.text();
await pc.setRemoteDescription({ type: 'answer', sdp: sdpAnswer });
登入後複製
登入後複製

在前端處理媒體串流

  1. 在 HTML 中建立影片元素
var myPeerConnection = new RTCPeerConnection({
  iceServers: [
      {
        urls: "stun:stun.relay.metered.ca:80",
      },
      {
        urls: "turn:global.relay.metered.ca:80",
        username: "e13b9bsdfdsfsdfb0676cc5b6",
        credential: "dedewdewfer+gq5iT",
      },
      {
        urls: "turn:global.relay.metered.ca:80?transport=tcp",
        username: "e13bdfdsfds6b0676cc5b6",
        credential: "dewfrefre+gq5iT",
      },
      {
        urls: "turn:global.relay.metered.ca:443",
        username: "e13b9fsdfdsfsd86b0676cc5b6",
        credential: "csdfwefeer+gq5iT",
      },
      {
        urls: "turns:global.relay.metered.ca:443?transport=tcp",
        username: "e13b9dsfsdfe6b0676cc5b6",
        credential: "sdfewtrererer+gq5iT",
      },
  ],
});
登入後複製
登入後複製
  1. 將遠端串流附加到視訊元素

當觸發追蹤事件時,將收到的串流附加到視訊元素

  1. 處理媒體串流事件

    1. 連線狀態改變
var myPeerConnection = new RTCPeerConnection({
  iceServers: [
      {
        urls: "stun:stun.relay.metered.ca:80",
      },
      {
        urls: "turn:global.relay.metered.ca:80",
        username: "e13b9bsdfdsfsdfb0676cc5b6",
        credential: "dedewdewfer+gq5iT",
      },
      {
        urls: "turn:global.relay.metered.ca:80?transport=tcp",
        username: "e13bdfdsfds6b0676cc5b6",
        credential: "dewfrefre+gq5iT",
      },
      {
        urls: "turn:global.relay.metered.ca:443",
        username: "e13b9fsdfdsfsd86b0676cc5b6",
        credential: "csdfwefeer+gq5iT",
      },
      {
        urls: "turns:global.relay.metered.ca:443?transport=tcp",
        username: "e13b9dsfsdfe6b0676cc5b6",
        credential: "sdfewtrererer+gq5iT",
      },
  ],
});
登入後複製
登入後複製

b.需要協商

pc.addEventListener('track', (event) => {
  const [remoteStream] = event.streams;
  // Attach the remote stream to a video element
  const remoteVideo = document.getElementById('remoteVideo');
  remoteVideo.srcObject = remoteStream;
});
登入後複製
  1. 完整範例程式碼
const whepServerEndpoint = 'http://YOUR_SERVER_IP:3000/whep'; // Replace with your server's WHEP endpoint

const response = await fetch(whepEndpoint, {
  method: 'GET',
  headers: {
    Accept: 'application/sdp',
  },
});

const sdpOffer = await response.text();
登入後複製

WebRTC WHIP & WHEP Tutorial: Build a live Streaming App

計量 TURN 伺服器

  1. API: 使用強大的 API 進行 TURN 伺服器管理。您可以執行以下操作:透過 API 新增/刪除憑證、透過 API 檢索每個使用者/憑證和使用者指標、透過 API 啟用/停用憑證、透過 API 按日期檢索使用資料。

  2. 全球地理位置定位:自動將流量定向到最近的伺服器,以實現盡可能低的延遲和最高的品質效能。全球任何地方的延遲均低於 50 毫秒

  3. 全球所有地區的伺服器:多倫多、邁阿密、舊金山、阿姆斯特丹、倫敦、法蘭克福、班加羅爾、新加坡、雪梨、首爾、達拉斯、紐約

  4. 低延遲: 低於 50 毫秒的延遲,在世界任何地方。

  5. 經濟高效:即用即付定價,並提供頻寬和批量折扣。

  6. 輕鬆管理: 取得使用日誌、帳戶達到門檻限制時的電子郵件、帳單記錄以及電子郵件和電話支援。

  7. 符合標準: 符合 UDP、TCP、TLS 和 DTLS 的 RFC 5389、5769、5780、5766、6062、6156、5245、5768、6336、925336、92536、92536、92536。

  8. 多租用戶: 建立多個憑證並依客戶或不同應用程式分開使用。取得使用日誌、計費記錄和閾值警報。

  9. 企業可靠性: SLA 正​​常運作時間達 99.999%。

  10. 企業規模: 不限制併發流量或總流量。計量 TURN 伺服器提供企業可擴充性

  11. 每月 5 GB 免費: 透過免費方案每月獲得 5 GB 免費 TURN 伺服器使用量

  12. 在連接埠 80 和 443 上運作

  13. 支援 TURNS SSL 以允許透過深度封包偵測防火牆進行連線。

  14. 同時支援 TCP 和 UDP

  15. 免費無限制 STUN


您可以考慮我們的其他一些文章:

  1. WebRTC 資料通道:指引

  2. 簡單對等教學:為影片、DataChannel 新增 TURN 伺服器

  3. 使用計量設定 WebRTC TURN 伺服器的指南

  4. WebRTC 與 HLS:哪個最適合您?

以上是WebRTC WHIP 和 WHEP 教學:建立即時串流應用程式的詳細內容。更多資訊請關注PHP中文網其他相關文章!

本網站聲明
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn

熱AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover

AI Clothes Remover

用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool

Undress AI Tool

免費脫衣圖片

Clothoff.io

Clothoff.io

AI脫衣器

Video Face Swap

Video Face Swap

使用我們完全免費的人工智慧換臉工具,輕鬆在任何影片中換臉!

熱門文章

<🎜>:泡泡膠模擬器無窮大 - 如何獲取和使用皇家鑰匙
4 週前 By 尊渡假赌尊渡假赌尊渡假赌
北端:融合系統,解釋
4 週前 By 尊渡假赌尊渡假赌尊渡假赌
Mandragora:巫婆樹的耳語 - 如何解鎖抓鉤
3 週前 By 尊渡假赌尊渡假赌尊渡假赌

熱工具

記事本++7.3.1

記事本++7.3.1

好用且免費的程式碼編輯器

SublimeText3漢化版

SublimeText3漢化版

中文版,非常好用

禪工作室 13.0.1

禪工作室 13.0.1

強大的PHP整合開發環境

Dreamweaver CS6

Dreamweaver CS6

視覺化網頁開發工具

SublimeText3 Mac版

SublimeText3 Mac版

神級程式碼編輯軟體(SublimeText3)

熱門話題

Java教學
1673
14
CakePHP 教程
1429
52
Laravel 教程
1333
25
PHP教程
1278
29
C# 教程
1257
24
Python vs. JavaScript:學習曲線和易用性 Python vs. JavaScript:學習曲線和易用性 Apr 16, 2025 am 12:12 AM

Python更適合初學者,學習曲線平緩,語法簡潔;JavaScript適合前端開發,學習曲線較陡,語法靈活。 1.Python語法直觀,適用於數據科學和後端開發。 2.JavaScript靈活,廣泛用於前端和服務器端編程。

JavaScript和Web:核心功能和用例 JavaScript和Web:核心功能和用例 Apr 18, 2025 am 12:19 AM

JavaScript在Web開發中的主要用途包括客戶端交互、表單驗證和異步通信。 1)通過DOM操作實現動態內容更新和用戶交互;2)在用戶提交數據前進行客戶端驗證,提高用戶體驗;3)通過AJAX技術實現與服務器的無刷新通信。

JavaScript在行動中:現實世界中的示例和項目 JavaScript在行動中:現實世界中的示例和項目 Apr 19, 2025 am 12:13 AM

JavaScript在現實世界中的應用包括前端和後端開發。 1)通過構建TODO列表應用展示前端應用,涉及DOM操作和事件處理。 2)通過Node.js和Express構建RESTfulAPI展示後端應用。

了解JavaScript引擎:實施詳細信息 了解JavaScript引擎:實施詳細信息 Apr 17, 2025 am 12:05 AM

理解JavaScript引擎內部工作原理對開發者重要,因為它能幫助編寫更高效的代碼並理解性能瓶頸和優化策略。 1)引擎的工作流程包括解析、編譯和執行三個階段;2)執行過程中,引擎會進行動態優化,如內聯緩存和隱藏類;3)最佳實踐包括避免全局變量、優化循環、使用const和let,以及避免過度使用閉包。

Python vs. JavaScript:社區,圖書館和資源 Python vs. JavaScript:社區,圖書館和資源 Apr 15, 2025 am 12:16 AM

Python和JavaScript在社區、庫和資源方面的對比各有優劣。 1)Python社區友好,適合初學者,但前端開發資源不如JavaScript豐富。 2)Python在數據科學和機器學習庫方面強大,JavaScript則在前端開發庫和框架上更勝一籌。 3)兩者的學習資源都豐富,但Python適合從官方文檔開始,JavaScript則以MDNWebDocs為佳。選擇應基於項目需求和個人興趣。

Python vs. JavaScript:開發環境和工具 Python vs. JavaScript:開發環境和工具 Apr 26, 2025 am 12:09 AM

Python和JavaScript在開發環境上的選擇都很重要。 1)Python的開發環境包括PyCharm、JupyterNotebook和Anaconda,適合數據科學和快速原型開發。 2)JavaScript的開發環境包括Node.js、VSCode和Webpack,適用於前端和後端開發。根據項目需求選擇合適的工具可以提高開發效率和項目成功率。

C/C在JavaScript口譯員和編譯器中的作用 C/C在JavaScript口譯員和編譯器中的作用 Apr 20, 2025 am 12:01 AM

C和C 在JavaScript引擎中扮演了至关重要的角色,主要用于实现解释器和JIT编译器。1)C 用于解析JavaScript源码并生成抽象语法树。2)C 负责生成和执行字节码。3)C 实现JIT编译器,在运行时优化和编译热点代码,显著提高JavaScript的执行效率。

Python vs. JavaScript:比較用例和應用程序 Python vs. JavaScript:比較用例和應用程序 Apr 21, 2025 am 12:01 AM

Python更適合數據科學和自動化,JavaScript更適合前端和全棧開發。 1.Python在數據科學和機器學習中表現出色,使用NumPy、Pandas等庫進行數據處理和建模。 2.Python在自動化和腳本編寫方面簡潔高效。 3.JavaScript在前端開發中不可或缺,用於構建動態網頁和單頁面應用。 4.JavaScript通過Node.js在後端開發中發揮作用,支持全棧開發。

See all articles