[{"data":1,"prerenderedAt":1372},["ShallowReactive",2],{"navigation_docs":3,"-web-dev-network-websocket-introduction":352,"-web-dev-network-websocket-introduction-surround":1367},[4,54,83,116,125,161,179,200],{"title":5,"path":6,"stem":7,"children":8},"Ai","/ai","ai",[9,12,42,46,50],{"title":10,"path":6,"stem":11},"AI 技術探索","ai/index",{"title":13,"path":14,"stem":15,"children":16,"page":41},"Rag","/ai/rag","ai/RAG",[17,21,25,29,33,37],{"title":18,"path":19,"stem":20},"BM25 演算法深入解析：從 TF-IDF 到現代搜尋引擎的核心技術","/ai/rag/bm25-deep-dive-from-tf-idf-to-rag","ai/RAG/bm25-deep-dive-from-tf-idf-to-rag",{"title":22,"path":23,"stem":24},"Dense Retrieval（向量檢索）深入解析","/ai/rag/dense-retrieval-deep-dive","ai/RAG/dense-retrieval-deep-dive",{"title":26,"path":27,"stem":28},"RAG（Retrieval-Augmented Generation）原理與實作入門","/ai/rag/rag-intro-principles-and-implementation","ai/RAG/rag-intro-principles-and-implementation",{"title":30,"path":31,"stem":32},"RAG Retrieval Strategy 深入解析","/ai/rag/rag-retrieval-strategy-deep-dive","ai/RAG/rag-retrieval-strategy-deep-dive",{"title":34,"path":35,"stem":36},"Reciprocal Rank Fusion（RRF）教學：打造更穩定的檢索融合策略","/ai/rag/rrf-reciprocal-rank-fusion-guide","ai/RAG/rrf-reciprocal-rank-fusion-guide",{"title":38,"path":39,"stem":40},"TF-IDF（Term Frequency–Inverse Document Frequency）深入解析","/ai/rag/tf-idf-deep-dive","ai/RAG/tf-idf-deep-dive",false,{"title":43,"path":44,"stem":45},"Docus AI 功能完整實作指南","/ai/docus-ai-implementation","ai/docus-ai-implementation",{"title":47,"path":48,"stem":49},"llms.txt 是什麼？在 Nuxt 中控制 AI 與搜尋引擎爬蟲的新方式","/ai/llms-txt","ai/llms-txt",{"title":51,"path":52,"stem":53},"MCP（Model Context Protocol）是什麼？用「AI 的 USB-C」理解模型如何安全連接工具與資料","/ai/mcp_concept","ai/mcp_concept",{"title":55,"path":56,"stem":57,"children":58,"page":41},"Data Analysis","/data-analysis","data-analysis",[59,63,67,71,75,79],{"title":60,"path":61,"stem":62},"Exploratory Data Analysis（EDA）資料探索分析入門","/data-analysis/exploratory-data-analysis-eda","data-analysis/exploratory-data-analysis-eda",{"title":64,"path":65,"stem":66},"PR-AUC 深入解析：不平衡分類問題的重要評估指標","/data-analysis/pr-auc-for-imbalanced-classification","data-analysis/pr-auc-for-imbalanced-classification",{"title":68,"path":69,"stem":70},"ROC-AUC 深入解析：理解分類模型的判斷能力","/data-analysis/roc-auc-complete-guide","data-analysis/roc-auc-complete-guide",{"title":72,"path":73,"stem":74},"SHAP（SHapley Additive exPlanations）模型解釋方法深入解析","/data-analysis/shap-model-interpretation-complete-guide","data-analysis/shap-model-interpretation-complete-guide",{"title":76,"path":77,"stem":78},"Social Network Analysis（SNA）深入解析","/data-analysis/social-network-analysis-sna","data-analysis/social-network-analysis-sna",{"title":80,"path":81,"stem":82},"Time-based Split：為什麼時間序列資料不能使用 Random Split？","/data-analysis/time-based-split-vs-random-split","data-analysis/time-based-split-vs-random-split",{"title":84,"path":85,"stem":86,"children":87,"page":41},"MachineLearning","/machine_learning","machine_learning",[88,92,96,100,104,108,112],{"title":89,"path":90,"stem":91},"Confusion Matrix（混淆矩陣）完整解析","/machine_learning/confusion-matrix","machine_learning/confusion-matrix",{"title":93,"path":94,"stem":95},"Decision Tree 模型完整教學：從原理到 Python 實作","/machine_learning/decision-tree-complete-guide","machine_learning/decision-tree-complete-guide",{"title":97,"path":98,"stem":99},"為什麼 Decision Tree 在 Tabular Data 上常常勝過 Deep Learning？","/machine_learning/decision-tree-vs-deep-learning-on-tabular-data","machine_learning/decision-tree-vs-deep-learning-on-tabular-data",{"title":101,"path":102,"stem":103},"LightGBM 模型入門：理解高效能的 Gradient Boosting 演算法","/machine_learning/lightgbm-intro-for-tabular-data","machine_learning/lightgbm-intro-for-tabular-data",{"title":105,"path":106,"stem":107},"Logistic Regression（Logit 回歸）完整教學","/machine_learning/logistic-regression-complete-guide","machine_learning/logistic-regression-complete-guide",{"title":109,"path":110,"stem":111},"為什麼模型訓練需要 Train / Validation / Test Dataset？","/machine_learning/train-validation-test-dataset","machine_learning/train-validation-test-dataset",{"title":113,"path":114,"stem":115},"XGBoost 原理與實務教學","/machine_learning/xgboost-complete-guide","machine_learning/xgboost-complete-guide",{"title":117,"path":118,"stem":119,"children":120,"page":41},"Network","/network","network",[121],{"title":122,"path":123,"stem":124},"DNS 設定入門：CNAME vs A Record 完整解析","/network/dns-cname-vs-a-record-guide","network/dns-cname-vs-a-record-guide",{"title":126,"path":127,"stem":128,"children":129},"Nuxt","/nuxt","nuxt",[130,133,137,141,145,149,153,157],{"title":131,"path":127,"stem":132},"Nuxt 實戰指南","nuxt/index",{"title":134,"path":135,"stem":136},"Abstraction Layer 與 Adapter 的差別，一次搞懂設計角色","/nuxt/adapter_abstraction_layer","nuxt/adapter_abstraction_layer",{"title":138,"path":139,"stem":140},"Nuxt 4 全面理解 — 專案檔案結構解析","/nuxt/directory_structure","nuxt/directory_structure",{"title":142,"path":143,"stem":144},"Nuxt 前端 build 流程完整解析","/nuxt/frontend_build","nuxt/frontend_build",{"title":146,"path":147,"stem":148},"Nitro 是什麼？從 Nuxt 專案到 Server 與 Edge 的關鍵引擎","/nuxt/nitro","nuxt/nitro",{"title":150,"path":151,"stem":152},"深入理解 Nuxt 的 .nuxt 與 .output：為什麼部署時一定要分清楚？","/nuxt/nuxt_and_output","nuxt/nuxt_and_output",{"title":154,"path":155,"stem":156},"Nuxt 中 dev、build、preview 的差異一次搞懂","/nuxt/nuxt_dev_build_preview","nuxt/nuxt_dev_build_preview",{"title":158,"path":159,"stem":160},"Nuxt Image vs \u003Cimg>：為什麼 Nuxt 3 專案幾乎都該用 \u003CNuxtImg>？","/nuxt/nuxt_image","nuxt/nuxt_image",{"title":162,"path":163,"stem":164,"children":165},"Python 程式開發","/python","python/index",[166,167,171,175],{"title":162,"path":163,"stem":164},{"title":168,"path":169,"stem":170},"如何在 FastAPI 中調用記憶體 Buffer 回傳圖片 (附實例)","/python/memory_buffer","python/memory_buffer",{"title":172,"path":173,"stem":174},"Python 單元測試教學：如何為你的程式撰寫 Test","/python/python-unit-testing-unittest-pytest","python/python-unit-testing-unittest-pytest",{"title":176,"path":177,"stem":178},"如何使用 uv 取代 pip：改善 Python 專案的開發流程","/python/uv","python/uv",{"title":180,"path":181,"stem":182,"children":183,"page":41},"Statistic","/statistic","statistic",[184,188,192,196],{"title":185,"path":186,"stem":187},"卡方檢定（Chi-Square Test）入門教學","/statistic/chi-square-test-intro","statistic/chi-square-test-intro",{"title":189,"path":190,"stem":191},"常見抽樣方法（Sampling Methods）教學：Stratified、Quota、Convenience、Systematic、Simple Random","/statistic/common-sampling-methods-guide","statistic/common-sampling-methods-guide",{"title":193,"path":194,"stem":195},"Cramér’s V 指標介紹：如何衡量兩個類別變數之間的關聯","/statistic/cramers-v-for-categorical-association","statistic/cramers-v-for-categorical-association",{"title":197,"path":198,"stem":199},"Mann–Whitney U 與 Kolmogorov–Smirnov（KS）檢定：非參數統計檢定的入門指南","/statistic/mann-whitney-u-and-ks-test-intro","statistic/mann-whitney-u-and-ks-test-intro",{"title":201,"path":202,"stem":203,"children":204},"WebDev","/web_dev","web_dev",[205,208,234,248,282,312,334],{"title":206,"path":202,"stem":207},"Web 前端開發","web_dev/index",{"title":209,"path":210,"stem":211,"children":212},"瀏覽器與渲染","/web_dev/browser","web_dev/browser/index",[213,214,218,222,226,230],{"title":209,"path":210,"stem":211},{"title":215,"path":216,"stem":217},"瀏覽器儲存空間完整解析：Cookie、localStorage、IndexedDB 到 Cache Storage","/web_dev/browser/browser-storage-comprehensive-guide","web_dev/browser/browser-storage-comprehensive-guide",{"title":219,"path":220,"stem":221},"DOM (Document Object Model) 深入解析","/web_dev/browser/dom","web_dev/browser/dom",{"title":223,"path":224,"stem":225},"Service Worker Request Flow 深入解析","/web_dev/browser/service-worker-request-flow","web_dev/browser/service-worker-request-flow",{"title":227,"path":228,"stem":229},"CSR、SSR 與 SSG 是什麼？前端渲染策略完整比較","/web_dev/browser/ssr_csr_ssg","web_dev/browser/ssr_csr_ssg",{"title":231,"path":232,"stem":233},"Virtual DOM 深入解析：為什麼它能優化前端效能？","/web_dev/browser/virtual_dom","web_dev/browser/virtual_dom",{"title":235,"path":236,"stem":237,"children":238},"圖形技術","/web_dev/graphics","web_dev/graphics/index",[239,240,244],{"title":235,"path":236,"stem":237},{"title":241,"path":242,"stem":243},"為什麼 Three.js 專案幾乎都選擇 CSR？從 SSR 問題談起","/web_dev/graphics/threejs_csr","web_dev/graphics/threejs_csr",{"title":245,"path":246,"stem":247},"WebGL 是什麼？為什麼前端 3D 幾乎都靠它？","/web_dev/graphics/webgl","web_dev/graphics/webGL",{"title":249,"path":250,"stem":251,"children":252},"架構與配置","/web_dev/infrastructure","web_dev/infrastructure/index",[253,254,258,262,266,270,274,278],{"title":249,"path":250,"stem":251},{"title":255,"path":256,"stem":257},"Zeabur + K3s + Nuxt 部署架構完整解析","/web_dev/infrastructure/k3s-zeabur","web_dev/infrastructure/K3s-zeabur",{"title":259,"path":260,"stem":261},"ECS vs Docker vs Kubernetes：從部署堆疊理解三層架構","/web_dev/infrastructure/ecs-docker-kubernetes-stack","web_dev/infrastructure/ecs-docker-kubernetes-stack",{"title":263,"path":264,"stem":265},"ECS vs VPS 是什麼？從部署網站的角度一次搞懂差別","/web_dev/infrastructure/ecs-vs-vps","web_dev/infrastructure/ecs-vs-vps",{"title":267,"path":268,"stem":269},"Zeabur 內網服務連線完整入門指南","/web_dev/infrastructure/k3s-internal-networking","web_dev/infrastructure/k3s-internal-networking",{"title":271,"path":272,"stem":273},"使用 Microservices 架構設計系統的優勢解析","/web_dev/infrastructure/microservices-architecture-advantages","web_dev/infrastructure/microservices-architecture-advantages",{"title":275,"path":276,"stem":277},"Nginx 入門教學：從反向代理到與 Kubernetes 的架構比較","/web_dev/infrastructure/nginx-intro","web_dev/infrastructure/nginx-intro",{"title":279,"path":280,"stem":281},"YAML 配置是什麼？為什麼現代開發都在用它","/web_dev/infrastructure/yaml-configuration","web_dev/infrastructure/yaml-configuration",{"title":283,"path":284,"stem":285,"children":286},"網絡與通訊","/web_dev/network","web_dev/network/index",[287,288,292,296,300,304,308],{"title":283,"path":284,"stem":285},{"title":289,"path":290,"stem":291},"HTTP Request 結構完整解析：從 Request Line 到 Header 一次看懂","/web_dev/network/http-request-structure","web_dev/network/http-request-structure",{"title":293,"path":294,"stem":295},"OSI 模型（Open Systems Interconnection Model）完整解析","/web_dev/network/osi-model","web_dev/network/osi-model",{"title":297,"path":298,"stem":299},"RESTful API 設計原則與實務解析","/web_dev/network/restful-api","web_dev/network/restful-api",{"title":301,"path":302,"stem":303},"RESTful API 五大設計原則深入解析","/web_dev/network/restful-api-principles","web_dev/network/restful-api-principles",{"title":305,"path":306,"stem":307},"Server-Sent Events (SSE) 深入解析與實作教學","/web_dev/network/sse-introduction","web_dev/network/sse-introduction",{"title":309,"path":310,"stem":311},"WebSocket 入門教學：從概念到 Node.js 實作","/web_dev/network/websocket-introduction","web_dev/network/websocket-introduction",{"title":313,"path":314,"stem":315,"children":316},"認證與安全","/web_dev/security","web_dev/security/index",[317,318,322,326,330],{"title":313,"path":314,"stem":315},{"title":319,"path":320,"stem":321},"使用 Cookie 與 Session 建立使用者驗證（完整新手教學）","/web_dev/security/cookie-session-authentication","web_dev/security/cookie-session-authentication",{"title":323,"path":324,"stem":325},"JWT 驗證機制完整解析：從登入流程到實務應用","/web_dev/security/jwt-authentication","web_dev/security/jwt-authentication",{"title":327,"path":328,"stem":329},"Secret Key 簽名是什麼？從零理解資料簽名的本質","/web_dev/security/secret-key-signing","web_dev/security/secret-key-signing",{"title":331,"path":332,"stem":333},"SSH（Secure Shell）是什麼？從遠端登入到安全通道的核心概念","/web_dev/security/ssh","web_dev/security/ssh",{"title":335,"path":336,"stem":337,"children":338},"SEO 與規範","/web_dev/seo","web_dev/seo/index",[339,340,344,348],{"title":335,"path":336,"stem":337},{"title":341,"path":342,"stem":343},"Canonical URL 是什麼？用生活化方式搞懂前端 SEO 的基本保命符","/web_dev/seo/canonical_link","web_dev/seo/canonical_link",{"title":345,"path":346,"stem":347},"robots.txt 是什麼？SEO 的第一道守門員","/web_dev/seo/robot_txt","web_dev/seo/robot_txt",{"title":349,"path":350,"stem":351},"Nuxt SEO 中的 Meta 與 SEO 工具是如何運作的？","/web_dev/seo/seo","web_dev/seo/seo",{"id":353,"title":309,"body":354,"description":1355,"extension":1356,"links":1357,"meta":1358,"navigation":622,"path":310,"seo":1365,"stem":311,"__hash__":1366},"docs/web_dev/network/websocket-introduction.md",{"type":355,"value":356,"toc":1340},"minimark",[357,361,366,370,373,376,384,387,390,394,397,406,409,412,428,435,437,441,444,447,465,484,486,490,497,502,505,542,547,551,558,812,815,828,831,850,854,857,1036,1039,1041,1044,1047,1058,1061,1133,1140,1142,1146,1149,1265,1271,1273,1277,1280,1293,1296,1298,1301,1304,1315,1318,1320,1323,1326,1333,1336],[358,359,309],"h1",{"id":360},"websocket-入門教學從概念到-nodejs-實作",[362,363,365],"h2",{"id":364},"為什麼我們需要-websocket","為什麼我們需要 WebSocket？",[367,368,369],"p",{},"在理解 WebSocket 之前，我們必須先問一個關鍵問題：為什麼 HTTP 不夠用？",[367,371,372],{},"在傳統的 Web 架構中，瀏覽器與伺服器之間採用的是 HTTP 協定。HTTP 是一種 request-response 模型，也就是說，必須由 client 主動發送請求，server 才會回應。每一次溝通都是獨立的請求與回應流程。",[367,374,375],{},"這種模式非常適合讀取文章、送出表單、登入系統或呼叫 API 等「單次互動」的場景。然而，一旦需求轉變為即時更新，例如聊天室訊息、股票價格變動、多人協作文件同步或線上遊戲狀態更新，就會遇到明顯限制。",[367,377,378,379,383],{},"因為在 HTTP 架構下，server 無法主動推送資料給 client。為了解決這個問題，前端通常會使用 polling（輪詢）技術，例如每秒呼叫一次 ",[380,381,382],"code",{},"fetch()","。這種方式雖然可行，但會造成大量不必要的請求，浪費頻寬與伺服器資源。",[367,385,386],{},"這正是 WebSocket 出現的背景。",[388,389],"hr",{},[362,391,393],{"id":392},"websocket-的核心概念","WebSocket 的核心概念",[367,395,396],{},"WebSocket 是一種建立在 TCP 之上的通訊協定，它的目標非常單純：",[398,399,400],"blockquote",{},[367,401,402],{},[403,404,405],"strong",{},"建立一條持續開啟的雙向連線。",[367,407,408],{},"與 HTTP 每次都建立新連線不同，WebSocket 在連線建立完成後，client 與 server 之間可以隨時互相傳送資料，而不需要重新握手。",[367,410,411],{},"簡單來說：",[413,414,415,422],"ul",{},[416,417,418,421],"li",{},[403,419,420],{},"HTTP"," 像是「說一句話就掛電話」",[416,423,424,427],{},[403,425,426],{},"WebSocket"," 像是「打電話後一直聊天」",[367,429,430,431,434],{},"當連線建立完成後，雙方可以 ",[380,432,433],{},"client ↔ server"," 隨時互傳資料。",[388,436],{},[362,438,440],{"id":439},"websocket-的運作流程","WebSocket 的運作流程",[367,442,443],{},"WebSocket 並不是完全取代 HTTP。事實上，它的連線建立過程仍然從 HTTP 開始。",[367,445,446],{},"整個流程大致如下：",[448,449,450,453,456,459,462],"ol",{},[416,451,452],{},"Client 發送 HTTP request",[416,454,455],{},"Server 回應 Upgrade header",[416,457,458],{},"連線升級為 WebSocket",[416,460,461],{},"保持連線狀態",[416,463,464],{},"雙向傳輸資料",[367,466,467,468,471,472,475,476,479,480,483],{},"因此你會看到 WebSocket 的網址格式為：",[380,469,470],{},"ws://"," 或 ",[380,473,474],{},"wss://","。其中 ",[380,477,478],{},"wss"," 對應的是加密版本，就像 ",[380,481,482],{},"https"," 一樣。",[388,485],{},[362,487,489],{"id":488},"在-nodejs-中使用-ws-實作-websocket","在 Node.js 中使用 ws 實作 WebSocket",[367,491,492,493,496],{},"接下來我們透過 Node.js 搭配 ",[380,494,495],{},"ws"," 套件，實作一個最小可運作的 WebSocket server。",[498,499,501],"h3",{"id":500},"step-1安裝-ws","Step 1：安裝 ws",[367,503,504],{},"首先建立專案並安裝套件：",[506,507,512],"pre",{"className":508,"code":509,"language":510,"meta":511,"style":511},"language-bash shiki shiki-themes github-dark","npm init -y\nnpm install ws\n","bash","",[380,513,514,531],{"__ignoreMap":511},[515,516,519,523,527],"span",{"class":517,"line":518},"line",1,[515,520,522],{"class":521},"svObZ","npm",[515,524,526],{"class":525},"sU2Wk"," init",[515,528,530],{"class":529},"sDLfK"," -y\n",[515,532,534,536,539],{"class":517,"line":533},2,[515,535,522],{"class":521},[515,537,538],{"class":525}," install",[515,540,541],{"class":525}," ws\n",[367,543,544,546],{},[380,545,495],{}," 是 Node.js 中非常輕量且廣泛使用的 WebSocket 實作套件。",[498,548,550],{"id":549},"step-2建立-websocket-server","Step 2：建立 WebSocket Server",[367,552,553,554,557],{},"建立 ",[380,555,556],{},"server.js","：",[506,559,563],{"className":560,"code":561,"language":562,"meta":511,"style":511},"language-js shiki shiki-themes github-dark","const WebSocket = require(\"ws\")\nconst wss = new WebSocket.Server({ port: 3000 })\n\nconsole.log(\"WebSocket server running on port 3000\")\n\nwss.on(\"connection\", (ws) => {\n  console.log(\"Client connected\")\n  \n  ws.on(\"message\", (message) => {\n    console.log(\"received:\", message.toString())\n    ws.send(\"Server received your message\")\n  })\n  \n  ws.on(\"close\", () => {\n    console.log(\"Client disconnected\")\n  })\n})\n","js",[380,564,565,590,617,624,640,645,674,689,695,719,741,757,763,768,787,801,806],{"__ignoreMap":511},[515,566,567,571,574,577,580,584,587],{"class":517,"line":518},[515,568,570],{"class":569},"snl16","const",[515,572,573],{"class":529}," WebSocket",[515,575,576],{"class":569}," =",[515,578,579],{"class":521}," require",[515,581,583],{"class":582},"s95oV","(",[515,585,586],{"class":525},"\"ws\"",[515,588,589],{"class":582},")\n",[515,591,592,594,597,599,602,605,608,611,614],{"class":517,"line":533},[515,593,570],{"class":569},[515,595,596],{"class":529}," wss",[515,598,576],{"class":569},[515,600,601],{"class":569}," new",[515,603,604],{"class":582}," WebSocket.",[515,606,607],{"class":521},"Server",[515,609,610],{"class":582},"({ port: ",[515,612,613],{"class":529},"3000",[515,615,616],{"class":582}," })\n",[515,618,620],{"class":517,"line":619},3,[515,621,623],{"emptyLinePlaceholder":622},true,"\n",[515,625,627,630,633,635,638],{"class":517,"line":626},4,[515,628,629],{"class":582},"console.",[515,631,632],{"class":521},"log",[515,634,583],{"class":582},[515,636,637],{"class":525},"\"WebSocket server running on port 3000\"",[515,639,589],{"class":582},[515,641,643],{"class":517,"line":642},5,[515,644,623],{"emptyLinePlaceholder":622},[515,646,648,651,654,656,659,662,665,668,671],{"class":517,"line":647},6,[515,649,650],{"class":582},"wss.",[515,652,653],{"class":521},"on",[515,655,583],{"class":582},[515,657,658],{"class":525},"\"connection\"",[515,660,661],{"class":582},", (",[515,663,495],{"class":664},"s9osk",[515,666,667],{"class":582},") ",[515,669,670],{"class":569},"=>",[515,672,673],{"class":582}," {\n",[515,675,677,680,682,684,687],{"class":517,"line":676},7,[515,678,679],{"class":582},"  console.",[515,681,632],{"class":521},[515,683,583],{"class":582},[515,685,686],{"class":525},"\"Client connected\"",[515,688,589],{"class":582},[515,690,692],{"class":517,"line":691},8,[515,693,694],{"class":582},"  \n",[515,696,698,701,703,705,708,710,713,715,717],{"class":517,"line":697},9,[515,699,700],{"class":582},"  ws.",[515,702,653],{"class":521},[515,704,583],{"class":582},[515,706,707],{"class":525},"\"message\"",[515,709,661],{"class":582},[515,711,712],{"class":664},"message",[515,714,667],{"class":582},[515,716,670],{"class":569},[515,718,673],{"class":582},[515,720,722,725,727,729,732,735,738],{"class":517,"line":721},10,[515,723,724],{"class":582},"    console.",[515,726,632],{"class":521},[515,728,583],{"class":582},[515,730,731],{"class":525},"\"received:\"",[515,733,734],{"class":582},", message.",[515,736,737],{"class":521},"toString",[515,739,740],{"class":582},"())\n",[515,742,744,747,750,752,755],{"class":517,"line":743},11,[515,745,746],{"class":582},"    ws.",[515,748,749],{"class":521},"send",[515,751,583],{"class":582},[515,753,754],{"class":525},"\"Server received your message\"",[515,756,589],{"class":582},[515,758,760],{"class":517,"line":759},12,[515,761,762],{"class":582},"  })\n",[515,764,766],{"class":517,"line":765},13,[515,767,694],{"class":582},[515,769,771,773,775,777,780,783,785],{"class":517,"line":770},14,[515,772,700],{"class":582},[515,774,653],{"class":521},[515,776,583],{"class":582},[515,778,779],{"class":525},"\"close\"",[515,781,782],{"class":582},", () ",[515,784,670],{"class":569},[515,786,673],{"class":582},[515,788,790,792,794,796,799],{"class":517,"line":789},15,[515,791,724],{"class":582},[515,793,632],{"class":521},[515,795,583],{"class":582},[515,797,798],{"class":525},"\"Client disconnected\"",[515,800,589],{"class":582},[515,802,804],{"class":517,"line":803},16,[515,805,762],{"class":582},[515,807,809],{"class":517,"line":808},17,[515,810,811],{"class":582},"})\n",[367,813,814],{},"接著啟動：",[506,816,818],{"className":508,"code":817,"language":510,"meta":511,"style":511},"node server.js\n",[380,819,820],{"__ignoreMap":511},[515,821,822,825],{"class":517,"line":518},[515,823,824],{"class":521},"node",[515,826,827],{"class":525}," server.js\n",[367,829,830],{},"當 server 啟動後，它會監聽 3000 port，等待 WebSocket 連線。這段程式碼的核心在於事件機制。WebSocket 幾乎完全是事件驅動模型，包括：",[413,832,833,839,844],{},[416,834,835,838],{},[380,836,837],{},"connection","：有 client 連入",[416,840,841,843],{},[380,842,712],{},"：收到資料",[416,845,846,849],{},[380,847,848],{},"close","：連線關閉",[498,851,853],{"id":852},"step-3建立瀏覽器-client-測試","Step 3：建立瀏覽器 Client 測試",[367,855,856],{},"在瀏覽器中測試：",[506,858,862],{"className":859,"code":860,"language":861,"meta":511,"style":511},"language-html shiki shiki-themes github-dark","\u003Cscript>\n  const ws = new WebSocket(\"ws://localhost:3000\")\n  \n  ws.onopen = () => {\n    console.log(\"connected\")\n    ws.send(\"Hello server!\")\n  }\n  \n  ws.onmessage = (event) => {\n    console.log(\"Server says:\", event.data)\n  }\n  \n  ws.onclose = () => {\n    console.log(\"connection closed\")\n  }\n\u003C/script>\n","html",[380,863,864,876,897,901,917,930,943,948,952,973,987,991,995,1010,1023,1027],{"__ignoreMap":511},[515,865,866,869,873],{"class":517,"line":518},[515,867,868],{"class":582},"\u003C",[515,870,872],{"class":871},"s4JwU","script",[515,874,875],{"class":582},">\n",[515,877,878,881,884,886,888,890,892,895],{"class":517,"line":533},[515,879,880],{"class":569},"  const",[515,882,883],{"class":529}," ws",[515,885,576],{"class":569},[515,887,601],{"class":569},[515,889,573],{"class":521},[515,891,583],{"class":582},[515,893,894],{"class":525},"\"ws://localhost:3000\"",[515,896,589],{"class":582},[515,898,899],{"class":517,"line":619},[515,900,694],{"class":582},[515,902,903,905,908,910,913,915],{"class":517,"line":626},[515,904,700],{"class":582},[515,906,907],{"class":521},"onopen",[515,909,576],{"class":569},[515,911,912],{"class":582}," () ",[515,914,670],{"class":569},[515,916,673],{"class":582},[515,918,919,921,923,925,928],{"class":517,"line":642},[515,920,724],{"class":582},[515,922,632],{"class":521},[515,924,583],{"class":582},[515,926,927],{"class":525},"\"connected\"",[515,929,589],{"class":582},[515,931,932,934,936,938,941],{"class":517,"line":647},[515,933,746],{"class":582},[515,935,749],{"class":521},[515,937,583],{"class":582},[515,939,940],{"class":525},"\"Hello server!\"",[515,942,589],{"class":582},[515,944,945],{"class":517,"line":676},[515,946,947],{"class":582},"  }\n",[515,949,950],{"class":517,"line":691},[515,951,694],{"class":582},[515,953,954,956,959,961,964,967,969,971],{"class":517,"line":697},[515,955,700],{"class":582},[515,957,958],{"class":521},"onmessage",[515,960,576],{"class":569},[515,962,963],{"class":582}," (",[515,965,966],{"class":664},"event",[515,968,667],{"class":582},[515,970,670],{"class":569},[515,972,673],{"class":582},[515,974,975,977,979,981,984],{"class":517,"line":721},[515,976,724],{"class":582},[515,978,632],{"class":521},[515,980,583],{"class":582},[515,982,983],{"class":525},"\"Server says:\"",[515,985,986],{"class":582},", event.data)\n",[515,988,989],{"class":517,"line":743},[515,990,947],{"class":582},[515,992,993],{"class":517,"line":759},[515,994,694],{"class":582},[515,996,997,999,1002,1004,1006,1008],{"class":517,"line":765},[515,998,700],{"class":582},[515,1000,1001],{"class":521},"onclose",[515,1003,576],{"class":569},[515,1005,912],{"class":582},[515,1007,670],{"class":569},[515,1009,673],{"class":582},[515,1011,1012,1014,1016,1018,1021],{"class":517,"line":770},[515,1013,724],{"class":582},[515,1015,632],{"class":521},[515,1017,583],{"class":582},[515,1019,1020],{"class":525},"\"connection closed\"",[515,1022,589],{"class":582},[515,1024,1025],{"class":517,"line":789},[515,1026,947],{"class":582},[515,1028,1029,1032,1034],{"class":517,"line":803},[515,1030,1031],{"class":582},"\u003C/",[515,1033,872],{"class":871},[515,1035,875],{"class":582},[367,1037,1038],{},"當頁面載入後，瀏覽器會建立 WebSocket 連線並發送訊息給 server。server 收到後回傳回應。這樣就完成了最基本的雙向通訊。",[388,1040],{},[362,1042,1043],{"id":1043},"傳輸資料的實務方式",[367,1045,1046],{},"WebSocket 傳送的資料本質上是：",[413,1048,1049,1052,1055],{},[416,1050,1051],{},"字串（string）",[416,1053,1054],{},"二進位資料（buffer）",[416,1056,1057],{},"JSON",[367,1059,1060],{},"實務上最常見的是 JSON 格式：",[506,1062,1064],{"className":560,"code":1063,"language":562,"meta":511,"style":511},"// 發送\nws.send(JSON.stringify({ type: \"chat\", text: \"hello\" }))\n\n// 接收\nconst data = JSON.parse(message)\n",[380,1065,1066,1072,1104,1108,1113],{"__ignoreMap":511},[515,1067,1068],{"class":517,"line":518},[515,1069,1071],{"class":1070},"sAwPA","// 發送\n",[515,1073,1074,1077,1079,1081,1083,1086,1089,1092,1095,1098,1101],{"class":517,"line":533},[515,1075,1076],{"class":582},"ws.",[515,1078,749],{"class":521},[515,1080,583],{"class":582},[515,1082,1057],{"class":529},[515,1084,1085],{"class":582},".",[515,1087,1088],{"class":521},"stringify",[515,1090,1091],{"class":582},"({ type: ",[515,1093,1094],{"class":525},"\"chat\"",[515,1096,1097],{"class":582},", text: ",[515,1099,1100],{"class":525},"\"hello\"",[515,1102,1103],{"class":582}," }))\n",[515,1105,1106],{"class":517,"line":619},[515,1107,623],{"emptyLinePlaceholder":622},[515,1109,1110],{"class":517,"line":626},[515,1111,1112],{"class":1070},"// 接收\n",[515,1114,1115,1117,1120,1122,1125,1127,1130],{"class":517,"line":642},[515,1116,570],{"class":569},[515,1118,1119],{"class":529}," data",[515,1121,576],{"class":569},[515,1123,1124],{"class":529}," JSON",[515,1126,1085],{"class":582},[515,1128,1129],{"class":521},"parse",[515,1131,1132],{"class":582},"(message)\n",[367,1134,1135,1136,1139],{},"這種設計讓你可以建立「訊息協定」，例如透過 ",[380,1137,1138],{},"type"," 區分 chat、notification 或 system message。",[388,1141],{},[362,1143,1145],{"id":1144},"實作一個簡易聊天室廣播功能","實作一個簡易聊天室（廣播功能）",[367,1147,1148],{},"在實際應用中，聊天室需要把一個人的訊息傳送給所有連線中的 client。我們可以這樣修改 server：",[506,1150,1152],{"className":560,"code":1151,"language":562,"meta":511,"style":511},"wss.on(\"connection\", (ws) => {\n  ws.on(\"message\", (message) => {\n    wss.clients.forEach((client) => {\n      if (client.readyState === WebSocket.OPEN) {\n        client.send(message.toString())\n      }\n    })\n  })\n})\n",[380,1153,1154,1174,1194,1214,1233,1247,1252,1257,1261],{"__ignoreMap":511},[515,1155,1156,1158,1160,1162,1164,1166,1168,1170,1172],{"class":517,"line":518},[515,1157,650],{"class":582},[515,1159,653],{"class":521},[515,1161,583],{"class":582},[515,1163,658],{"class":525},[515,1165,661],{"class":582},[515,1167,495],{"class":664},[515,1169,667],{"class":582},[515,1171,670],{"class":569},[515,1173,673],{"class":582},[515,1175,1176,1178,1180,1182,1184,1186,1188,1190,1192],{"class":517,"line":533},[515,1177,700],{"class":582},[515,1179,653],{"class":521},[515,1181,583],{"class":582},[515,1183,707],{"class":525},[515,1185,661],{"class":582},[515,1187,712],{"class":664},[515,1189,667],{"class":582},[515,1191,670],{"class":569},[515,1193,673],{"class":582},[515,1195,1196,1199,1202,1205,1208,1210,1212],{"class":517,"line":619},[515,1197,1198],{"class":582},"    wss.clients.",[515,1200,1201],{"class":521},"forEach",[515,1203,1204],{"class":582},"((",[515,1206,1207],{"class":664},"client",[515,1209,667],{"class":582},[515,1211,670],{"class":569},[515,1213,673],{"class":582},[515,1215,1216,1219,1222,1225,1227,1230],{"class":517,"line":626},[515,1217,1218],{"class":569},"      if",[515,1220,1221],{"class":582}," (client.readyState ",[515,1223,1224],{"class":569},"===",[515,1226,604],{"class":582},[515,1228,1229],{"class":529},"OPEN",[515,1231,1232],{"class":582},") {\n",[515,1234,1235,1238,1240,1243,1245],{"class":517,"line":642},[515,1236,1237],{"class":582},"        client.",[515,1239,749],{"class":521},[515,1241,1242],{"class":582},"(message.",[515,1244,737],{"class":521},[515,1246,740],{"class":582},[515,1248,1249],{"class":517,"line":647},[515,1250,1251],{"class":582},"      }\n",[515,1253,1254],{"class":517,"line":676},[515,1255,1256],{"class":582},"    })\n",[515,1258,1259],{"class":517,"line":691},[515,1260,762],{"class":582},[515,1262,1263],{"class":517,"line":697},[515,1264,811],{"class":582},[367,1266,1267,1270],{},[380,1268,1269],{},"wss.clients"," 會包含目前所有連線中的 client。只要遍歷並發送訊息，就可以完成最基本的聊天室廣播功能。",[388,1272],{},[362,1274,1276],{"id":1275},"websocket-與-rest-的差異","WebSocket 與 REST 的差異",[367,1278,1279],{},"許多初學者會誤以為 WebSocket 是 HTTP API 的替代品。事實上，它們解決的是不同問題。",[413,1281,1282,1288],{},[416,1283,1284,1287],{},[403,1285,1286],{},"REST API"," 適合資源導向操作，例如：取得使用者資料、建立文章、更新訂單狀態。",[416,1289,1290,1292],{},[403,1291,426],{}," 則適合：即時聊天、即時通知、即時數據流。",[367,1294,1295],{},"REST 是「一次請求一次回應」，WebSocket 是「持續雙向資料流」。兩者並不是競爭關係，而是互補關係。",[388,1297],{},[362,1299,1300],{"id":1300},"在現代框架中的應用",[367,1302,1303],{},"在實務專案中，你常會看到：",[413,1305,1306,1309,1312],{},[416,1307,1308],{},"Express + WebSocket",[416,1310,1311],{},"Fastify + WebSocket",[416,1313,1314],{},"或在 Nuxt/Nitro 中整合 WebSocket server",[367,1316,1317],{},"一般做法是：HTTP server 負責 API，WebSocket server 負責即時資料。這樣的架構可以讓系統同時具備 REST 與即時能力。",[388,1319],{},[362,1321,1322],{"id":1322},"最重要的理解",[367,1324,1325],{},"如果你只記住一件事，那就是：",[398,1327,1328],{},[367,1329,1330],{},[403,1331,1332],{},"WebSocket 是一條持續開著、可雙向傳資料的連線。",[367,1334,1335],{},"當你的系統需要即時更新，而不想讓 client 不斷輪詢 API 時，WebSocket 就是最自然的解法。掌握這些，你就真正跨進即時系統設計的世界了。",[1337,1338,1339],"style",{},"html pre.shiki code .svObZ, html code.shiki .svObZ{--shiki-default:#B392F0}html pre.shiki code .sU2Wk, html code.shiki .sU2Wk{--shiki-default:#9ECBFF}html pre.shiki code .sDLfK, html code.shiki .sDLfK{--shiki-default:#79B8FF}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html pre.shiki code .snl16, html code.shiki .snl16{--shiki-default:#F97583}html pre.shiki code .s95oV, html code.shiki .s95oV{--shiki-default:#E1E4E8}html pre.shiki code .s9osk, html code.shiki .s9osk{--shiki-default:#FFAB70}html pre.shiki code .s4JwU, html code.shiki .s4JwU{--shiki-default:#85E89D}html pre.shiki code .sAwPA, html code.shiki .sAwPA{--shiki-default:#6A737D}",{"title":511,"searchDepth":533,"depth":533,"links":1341},[1342,1343,1344,1345,1350,1351,1352,1353,1354],{"id":364,"depth":533,"text":365},{"id":392,"depth":533,"text":393},{"id":439,"depth":533,"text":440},{"id":488,"depth":533,"text":489,"children":1346},[1347,1348,1349],{"id":500,"depth":619,"text":501},{"id":549,"depth":619,"text":550},{"id":852,"depth":619,"text":853},{"id":1043,"depth":533,"text":1043},{"id":1144,"depth":533,"text":1145},{"id":1275,"depth":533,"text":1276},{"id":1300,"depth":533,"text":1300},{"id":1322,"depth":533,"text":1322},"深入理解 WebSocket 的核心概念，並透過 Node.js 與 ws 套件實作即時雙向通訊。","md",null,{"tags":1359,"category":203,"date":1364},[1360,1361,495,1362,1363],"websocket","nodejs","realtime","backend","2026-02-12",{"title":309,"description":1355},"lDcmjocEOADr1t0_TA-CN-oBndnFz-l96m7fSPtajvo",[1368,1370],{"title":305,"path":306,"stem":307,"description":1369,"children":-1},"從原理到實戰，完整理解 SSE 的運作機制、資料格式與 Node.js 實作方式，並學會何時選擇 SSE 而非 WebSocket。",{"title":313,"path":314,"stem":315,"description":1371,"children":-1},"探討身份驗證機制（JWT, Cookie/Session）、密鑰簽章以及 SSH 等安全技術。",1776690848046]