器與 p5.js 客戶端)
教程文檔示例工程【免費(fèi)下載鏈接】website-archiveArchive of the Coding Train website (first version)項(xiàng)目地址https://gitcode.com/gh_mirrors/we/website-archive點(diǎn)擊查看免費(fèi)下載本篇對(duì)應(yīng) Coding Train《WebSockets》系列_Tutorials/12-websockets/index.md的第 3 期視頻核心任務(wù)是在已經(jīng)用 Express 搭好靜態(tài)服務(wù)器的 Node.js 應(yīng)用中引入 socket.io同時(shí)讓瀏覽器里的 p5.js 客戶端建立同一條實(shí)時(shí)雙向通道。讀完本文你將掌握 socket.io 在服務(wù)端與客戶端的接入方式、自定義事件收發(fā)emit/on、廣播broadcast機(jī)制并能直接運(yùn)行倉庫中配套的 Node/sockets 示例為下一步實(shí)現(xiàn)多人共享畫布打好基礎(chǔ)。一、系列定位本節(jié)在整個(gè)教程中的角色該系列圍繞一個(gè)明確目標(biāo)展開使用 Node.js、p5.js 與 socket.ioWebSockets在多個(gè)瀏覽器會(huì)話之間創(chuàng)建共享的 HTML5 畫布。整條學(xué)習(xí)路徑在倉庫中按視頻順序組織章節(jié)文檔內(nèi)容12.1_Tutorials/12-websockets/12.1-introduction-to-node.mdNode.js 基礎(chǔ)與運(yùn)行環(huán)境12.2_Tutorials/12-websockets/12.2-using-express-with-node.md用 Express 托管靜態(tài)文件12.3本篇_Tutorials/12-websockets/12.3-socket-io.md把 socket.io 同時(shí)接入服務(wù)器與客戶端建立連接12.4_Tutorials/12-websockets/12.4-shared-drawing-canvas.md整合全部組件實(shí)現(xiàn)共享畫布與消息廣播本節(jié)12.3處于通管道的關(guān)鍵位置12.1、12.2 解決了服務(wù)器能跑、靜態(tài)頁面能出的問題本節(jié)則引入實(shí)時(shí)雙向通信層12.4 再基于它完成業(yè)務(wù)功能。文檔標(biāo)題 Connecting Client to Server with Socket.io 直白點(diǎn)明了本節(jié)唯一的主題——把客戶端和服務(wù)器連起來。本節(jié)配套代碼目錄為 Node/sockets包含 4 個(gè)文件Node/sockets/ ├── package.json # 依賴聲明與入口 ├── server.js # Express socket.io 服務(wù)器 └── public/ ├── index.html # 引入 socket.io / p5.js 客戶端庫 └── sketch.js # p5.js 客戶端連接、收發(fā)事件、畫點(diǎn)二、服務(wù)端把 socket.io 掛到已有 HTTP 服務(wù)器上1. 依賴聲明與入口配置Node/sockets/package.json 聲明了兩個(gè)運(yùn)行時(shí)依賴express: ^4.13.4—— 提供 HTTP 服務(wù)與靜態(tài)文件托管socket.io: ^2.4.0—— 提供 WebSocket 實(shí)時(shí)通信能力。main字段指向server.js即服務(wù)器入口。安裝依賴只需在本地副本目錄執(zhí)行npm install2. 服務(wù)器骨架Express 先跑起來Node/sockets/server.js 的前半部分是 12.2 已經(jīng)講過的 Express 標(biāo)準(zhǔn)骨架// Using express: http://expressjs.com/ var express require(express); // Create the app var app express(); // Set up the server // process.env.PORT is related to deploying on heroku var server app.listen(process.env.PORT || 3000, listen); // This call back just tells us that the server has started function listen() { var host server.address().address; var port server.address().port; console.log(Example app listening at http:// host : port); } app.use(express.static(public));幾個(gè)值得注意的細(xì)節(jié)端口策略app.listen(process.env.PORT || 3000)優(yōu)先讀取環(huán)境變量process.env.PORT缺省回退到3000。源碼注釋明確說明這是為部署在 Heroku 等 PaaS 平臺(tái)準(zhǔn)備的——云平臺(tái)通常會(huì)注入一個(gè)動(dòng)態(tài)端口本地開發(fā)則固定用 3000。靜態(tài)托管app.use(express.static(public))把public目錄下的文件作為靜態(tài)資源直接提供給瀏覽器因此客戶端只需要index.html和sketch.js兩個(gè)文件無需任何構(gòu)建步驟。啟動(dòng)回調(diào)listen()在服務(wù)器就緒時(shí)打印實(shí)際監(jiān)聽地址與端口用于確認(rèn)進(jìn)程已正常運(yùn)行。3. 關(guān)鍵一步require(socket.io)(server)在 Express 服務(wù)器就緒之后第 25 行是本節(jié)的核心動(dòng)作Node/sockets/server.js// WebSocket Portion // WebSockets work with the HTTP server var io require(socket.io)(server);這里必須把已經(jīng)創(chuàng)建的 HTTPserver對(duì)象傳給 socket.io 工廠函數(shù)而不是新開一個(gè)獨(dú)立端口。原因在于WebSocket 連接以 HTTP 握手為前提通過Upgrade請(qǐng)求從同一端口升級(jí)為長(zhǎng)連接。這樣客戶端訪問http://localhost:3000拉取靜態(tài)頁面的同時(shí)也能在同一個(gè)端口上建立 WebSocket 通道服務(wù)端無需再維護(hù)第二套監(jiān)聽。源碼注釋 WebSockets work with the HTTP server 正是對(duì)這一設(shè)計(jì)意圖的說明。4.connection事件每個(gè)客戶端一個(gè)socket對(duì)象接下來注冊(cè)全局連接回調(diào)Node/sockets/server.jsio.sockets.on(connection, // We are given a websocket object in our function function (socket) { console.log(We have a new client: socket.id); socket.on(mouse, function(data) { // Data comes in as whatever was sent, including objects console.log(Received: mouse data.x data.y); // Send it to all other clients socket.broadcast.emit(mouse, data); } ); socket.on(disconnect, function() { console.log(Client has disconnected); }); } );逐點(diǎn)解讀io.sockets.on(connection, callback)每當(dāng)有客戶端建立連接時(shí)觸發(fā)?;卣{(diào)參數(shù)socket代表當(dāng)前這一個(gè)客戶端的專屬連接對(duì)象每個(gè)用戶都會(huì)拿到獨(dú)立的一份。socket.idsocket.io 為每個(gè)連接分配的唯一標(biāo)識(shí)服務(wù)端用它區(qū)分不同的客戶端。示例里每次新客戶端接入都會(huì)打印一行We have a new client: id。socket.on(mouse, function(data) {...})監(jiān)聽名為mouse的自定義事件。data是客戶端發(fā)來的任意數(shù)據(jù)——可以是字符串、數(shù)字也可以是如源碼注釋所說whatever was sent, including objects的完整 JavaScript 對(duì)象。這里收到的就是客戶端傳上來的{x, y}鼠標(biāo)坐標(biāo)對(duì)象。socket.broadcast.emit(mouse, data)這是本節(jié)最核心的廣播語義——把mouse事件連同數(shù)據(jù)轉(zhuǎn)發(fā)給除發(fā)送者之外的所有其他客戶端。發(fā)送者自己不需要這條回顯因?yàn)樗谋镜禺嫴家呀?jīng)畫過了。socket.on(disconnect, ...)監(jiān)聽該客戶端的斷開事件便于服務(wù)端做清理與日志記錄。三、客戶端在瀏覽器里建立 socket 連接1. 引入 socket.io 客戶端庫客戶端第一步是在 HTML 里加載 socket.io 的瀏覽器端庫Node/sockets/public/index.htmlscript typetext/javascript srchttps://cdn.socket.io/socket.io-1.4.5.js/script script typetext/javascript srchttps://cdn.jsdelivr.net/npm/p51.4.1/lib/p5.min.js/script script typetext/javascript srcsketch.js/script頁面依次加載socket.io 客戶端庫、p5.js 核心庫、本地sketch.js。注意 socket.io 客戶端庫必須先于sketch.js 加載否則io全局對(duì)象在setup()中不可用。2. 建立連接io.connect(url)在 Node/sockets/public/sketch.js 的setup()中建立連接第 11-13 行// Keep track of our socket connection var socket; function setup() { createCanvas(400, 400); background(0); // Start a socket connection to the server // Some day we would run this server somewhere else socket io.connect(http://localhost:3000); ... }io.connect(http://localhost:3000)顯式指定服務(wù)器地址。源碼注釋 Some day we would run this server somewhere else 提示一旦部署到遠(yuǎn)程主機(jī)這里應(yīng)替換為實(shí)際域名或 IP而非寫死本地地址。連接建立是異步的返回的socket對(duì)象即后續(xù)所有收發(fā)操作的門面。3. 接收事件socket.on(event, callback)緊接著注冊(cè)mouse事件的接收回調(diào)第 16-25 行socket.on(mouse, // When we receive data function(data) { console.log(Got: data.x data.y); // Draw a blue circle fill(0,0,255); noStroke(); ellipse(data.x, data.y, 20, 20); } );語義與服務(wù)端完全對(duì)稱監(jiān)聽名為mouse的事件收到數(shù)據(jù)后讀取data.x、data.y在對(duì)應(yīng)坐標(biāo)畫一個(gè)半徑為 20 的藍(lán)色圓點(diǎn)。藍(lán)色圓點(diǎn) 來自其他客戶端經(jīng)服務(wù)器轉(zhuǎn)發(fā)的鼠標(biāo)軌跡這是識(shí)別消息是否走了一圈回來的直觀視覺信號(hào)。4. 發(fā)送事件socket.emit(event, data)發(fā)送側(cè)由 p5.js 的mouseDragged()驅(qū)動(dòng)第 32-53 行function mouseDragged() { // Draw some white circles fill(255); noStroke(); ellipse(mouseX,mouseY,20,20); // Send the mouse coordinates sendmouse(mouseX,mouseY); } // Function for sending to the socket function sendmouse(xpos, ypos) { // We are sending! console.log(sendmouse: xpos ypos); // Make a little object with and y var data { x: xpos, y: ypos }; // Send that object to the socket socket.emit(mouse,data); }要點(diǎn)本地先畫mouseDragged()先在本機(jī)畫一個(gè)白色圓點(diǎn)再把坐標(biāo)發(fā)出去——白色圓點(diǎn) 自己的鼠標(biāo)軌跡。數(shù)據(jù)序列化坐標(biāo)被打包成{x, y}對(duì)象后通過socket.emit(mouse, data)發(fā)送。socket.io 在底層自動(dòng)完成 JSON 序列化服務(wù)端收到的data就是可直接訪問屬性的對(duì)象。事件名約定客戶端emit(mouse)與服務(wù)端socket.on(mouse)、以及服務(wù)端broadcast.emit(mouse)與客戶端socket.on(mouse)四處的字符串mouse必須完全一致事件名是雙方約定的頻道。四、完整消息鏈路一次拖拽發(fā)生了什么把服務(wù)端與客戶端串起來一次跨瀏覽器拖拽的完整鏈路是客戶端 A 拖拽 │ mouseDragged() 觸發(fā) │ 本地畫白色圓點(diǎn) │ sendmouse(mouseX, mouseY) 構(gòu)造 {x, y} ▼ socket.emit(mouse, data) ──────────? 服務(wù)器 socket.on(mouse, data) │ 打印 Received: mouse x y ▼ socket.broadcast.emit(mouse, data) │ ┌─────────────────────────────────────┘ ▼ 客戶端 B / C 的 socket.on(mouse, data) │ 打印 Got: x y ▼ 在 (data.x, data.y) 畫藍(lán)色圓點(diǎn)A 自己只看到白色軌跡本地繪制B、C 等所有其他客戶端看到來自 A 的藍(lán)色軌跡服務(wù)器轉(zhuǎn)發(fā)服務(wù)器全程只做接收與轉(zhuǎn)發(fā)同時(shí)打印日志用于調(diào)試。這一白點(diǎn)本地畫、藍(lán)點(diǎn)遠(yuǎn)端畫的雙色約定讓多標(biāo)簽頁測(cè)試時(shí)能一眼確認(rèn)實(shí)時(shí)鏈路是否打通。五、運(yùn)行與驗(yàn)證在本地副本中按以下步驟運(yùn)行倉庫本身是只讀歸檔需先將其克隆或復(fù)制到本地# 1. 進(jìn)入示例目錄 cd Node/sockets # 2. 安裝依賴 npm install # 3. 啟動(dòng)服務(wù)器 node server.js # 輸出類似Example app listening at http://:::3000 # 4. 瀏覽器訪問 http://localhost:3000驗(yàn)證要點(diǎn)單頁自測(cè)打開http://localhost:3000拖拽鼠標(biāo)應(yīng)看到白色圓點(diǎn)軌跡瀏覽器控制臺(tái)交替打印sendmouse: x y發(fā)送與Got: x y接收。跨頁驗(yàn)證再開一個(gè)新標(biāo)簽頁訪問同一地址或直接復(fù)制標(biāo)簽頁。在任意一個(gè)頁面拖拽另一個(gè)頁面應(yīng)實(shí)時(shí)出現(xiàn)藍(lán)色圓點(diǎn)軌跡——這就是多個(gè)瀏覽器會(huì)話共享畫布的最小雛形。服務(wù)端日志終端里能看到每次連接的We have a new client: id、每條消息的Received: mouse x y以及關(guān)閉頁面時(shí)的Client has disconnected。本地環(huán)境提示io.connect(http://localhost:3000)僅適用于本機(jī)開發(fā)若頁面通過其他域名/端口訪問需同步修改客戶端連接地址。六、進(jìn)階對(duì)比broadcast與發(fā)給所有人本節(jié)示例只用到了socket.broadcast.emit()發(fā)給除發(fā)送者外的所有客戶端。倉庫中另一個(gè)更完整的實(shí)戰(zhàn)示例 CodingChallenges/CC_032.2_agar.io_sockets/Nodeagar.io 風(fēng)格多人游戲展示了 socket.io 另外兩種消息模式的組合用法// 服務(wù)器維護(hù)全局狀態(tài) var blobs []; setInterval(heartbeat, 33); function heartbeat() { io.sockets.emit(heartbeat, blobs); // 發(fā)給所有人包括發(fā)送者 }對(duì)比三種發(fā)送方式參考 Node/sockets/server.js 與 CC_032.2 的 server.js調(diào)用方式語義適用場(chǎng)景socket.emit(...)只發(fā)給當(dāng)前這一個(gè)客戶端服務(wù)端對(duì)單客戶端的應(yīng)答socket.broadcast.emit(...)發(fā)給除發(fā)送者外的所有其他客戶端共享畫布中的轉(zhuǎn)發(fā)別人的軌跡io.sockets.emit(...)發(fā)給當(dāng)前連接的所有客戶端含發(fā)送者全局狀態(tài)同步、心跳廣播agario 示例還展示了服務(wù)端持有權(quán)威狀態(tài)blobs數(shù)組的做法客戶端通過socket.emit(start)注冊(cè)自己、socket.emit(update)持續(xù)上報(bào)位置服務(wù)器用setInterval(heartbeat, 33)約 30fps定時(shí)把全體狀態(tài)推給所有客戶端。這種客戶端上報(bào) 服務(wù)器廣播的模式正是從本節(jié)單事件轉(zhuǎn)發(fā)邁向多人在線同步的典型演進(jìn)路徑。七、版本與兼容性注意事項(xiàng)以倉庫實(shí)際內(nèi)容為準(zhǔn)本節(jié)示例存在兩處版本口徑需要特別留意客戶端 CDNNode/sockets/public/index.html 引入的是socket.io-1.4.5.jssocket.io 1.x 時(shí)代的瀏覽器端庫服務(wù)端依賴Node/sockets/package.json 聲明的是socket.io: ^2.4.0socket.io 2.x??蛻舳藥彀姹九c服務(wù)器依賴版本分屬兩個(gè)大版本區(qū)間實(shí)際運(yùn)行時(shí)可能存在協(xié)議握手兼容性差異。從源碼結(jié)構(gòu)看這是歸檔倉庫中示例代碼的原始狀態(tài)若要在新項(xiàng)目中復(fù)刻本教程建議將客戶端 CDN 與服務(wù)器端 socket.io 統(tǒng)一到同一大版本例如都使用 2.x 對(duì)應(yīng)的客戶端庫以保證連接穩(wěn)定。同理express ^4.13.4、p5.js 1.4.1 等版本也以倉庫當(dāng)前聲明為準(zhǔn)。八、后續(xù)延伸12.4 共享畫布本節(jié)的連接與事件機(jī)制將在 _Tutorials/12-websockets/12.4-shared-drawing-canvas.md 中被整合為完整的多人協(xié)作畫布并系統(tǒng)講解如何在客戶端之間廣播消息。前置基礎(chǔ)若對(duì) p5.js 還不熟悉可先學(xué)習(xí) beginners/p5js 入門系列數(shù)據(jù)與異步回調(diào)相關(guān)背景可參考 _Tutorials/10-working-with-data。實(shí)戰(zhàn)范本CodingChallenges/CC_032.2_agar.io_sockets/Node 展示了同一套Express socket.io p5.js技術(shù)棧在多人實(shí)時(shí)游戲中的應(yīng)用包含服務(wù)器狀態(tài)維護(hù)、心跳廣播與客戶端位置同步的完整實(shí)現(xiàn)。至此客戶端與服務(wù)器之間的實(shí)時(shí)通道已經(jīng)打通服務(wù)器用require(socket.io)(server)在同一端口上掛載 WebSocket 能力客戶端用io.connect()建立連接雙方以emit/on按自定義事件名收發(fā)數(shù)據(jù)并用broadcast實(shí)現(xiàn)跨客戶端轉(zhuǎn)發(fā)。下一步就可以在這個(gè)通道上裝載真正的業(yè)務(wù)邏輯了。贊分享教程文檔示例工程【免費(fèi)下載鏈接】website-archiveArchive of the Coding Train website (first version)項(xiàng)目地址https://gitcode.com/gh_mirrors/we/website-archive點(diǎn)擊查看免費(fèi)下載相關(guān)推薦基于 Node.js、Express、socket.io 與 p5.js 的多人共享協(xié)作畫布實(shí)戰(zhàn)指南Coding Train WebSockets 教程基于 Node.js、Express、socket.io 與 p5.js 的多人共享協(xié)作畫布實(shí)戰(zhàn)指南Coding Train WebSockets 教程 導(dǎo)教程文檔示例工程基于 Node.js、Express、Socket.io 與 p5.js 構(gòu)建多人共享繪圖畫布Coding Train WebSockets 系列實(shí)戰(zhàn)指南基于 Node.js、Express、Socket.io 與 p5.js 構(gòu)建多人共享繪圖畫布Coding Train WebSockets 系列實(shí)戰(zhàn)指南 本教程文檔示例工程BepisPlugins解鎖Illusion游戲無限可能的7大必備插件集合 BepisPlugins解鎖Illusion游戲無限可能的7大必備插件集合 你是否厭倦了Illusion/Illgames旗下游戲的有限功能想要為你的上一篇uWebSockets.js中的Pub/Sub模式實(shí)現(xiàn)詳解下一篇技術(shù)視角OpenCore Legacy Patcher如何重塑舊Mac的生命周期管理創(chuàng)作聲明:本文部分內(nèi)容由AI輔助生成(AIGC),僅供參考