iT邦幫忙

2024 iThome 鐵人賽

DAY 28
0
Modern Web

雙向奔赴的websocket與冰冷的react系列 第 28

[day28]WebSocket 與 React 使用者對話

  • 分享至 

  • xImage
  •  

ITHOM進入尾聲了,最後來玩下有趣的東西吧

這次我們要做的是實現網域內溝通,只要在同個 ws server下client端可相互傳遞訊息

主要內容為
1.Server 端:分發唯一 ID,並管理每個連接的 WebSocket 實例。
2.Client 端:每個客戶端顯示自己的 ID,並可以選擇發送消息到指定的目標 ID。
3.雙向通信:客戶端之間可以通過 WebSocket 實現點對點通信。

先看成品
https://ithelp.ithome.com.tw/upload/images/20241006/20162004J3dqXmv7nK.png

server.js

直接上code

const WebSocket = require("ws");
const wss = new WebSocket.Server({ port: 8080 });

let connectionCounter = 0; // 用於分配唯一ID
const clients = {}; // 存儲連接的客戶端 { id: WebSocket }

wss.on("connection", (ws) => {
    connectionCounter++;
    const clientId = connectionCounter;
    clients[clientId] = ws; // 記錄客戶端的連接

    // 發送給客戶端,告知其ID
    ws.send(JSON.stringify({ type: "welcome", id: clientId, message: `歡迎!你的客戶端ID是: ${clientId}` }));

    console.log(`客戶端 ${clientId} 已連接`);

    // 發送給所有客戶端,告知新客戶端連接
    for (let id in clients) {
        clients[id].send(JSON.stringify({ type: "connectedClients", clients: Object.keys(clients) }));
    }

    // 當收到客戶端消息時
    ws.on("message", (data) => {
        const message = JSON.parse(data);

        if (message.type === "sendMessage") {
            const targetId = message.targetId;
            const content = message.content;

            if (clients[targetId]) {
                // 發送消息給目標ID
                clients[targetId].send(JSON.stringify({ type: "message", from: clientId, content }));
            } else {
                ws.send(JSON.stringify({ type: "error", message: `ID為 ${targetId} 的客戶端不存在` }));
            }
        }
    });

    ws.on("close", () => {
        console.log(`客戶端 ${clientId} 已斷開連接`);
        delete clients[clientId]; // 移除客戶端記錄
        for (let id in clients) {
            clients[id].send(JSON.stringify({ type: "connectedClients", clients: Object.keys(clients) }));
        }
    });
});

console.log("WebSocket 服務器運行在 ws://localhost:8080");
  • clients[clientId] = ws
    這段代碼的作用是為每個新連接的客戶端分配一個唯一的 clientId,並且將該客戶端的 WebSocket 連接實例存入 clients 對象。這樣,伺服器能夠管理多個客戶端,並根據 clientId 向特定的客戶端發送消息。
  • 點對點通信
    當客戶端發送消息時,伺服器會檢查目標客戶端是否存在。如果存在,伺服器會將消息轉發給目標客戶端

APP.jsx

import React, { useState, useEffect, useRef } from 'react';

function App() {
  const [clientId, setClientId] = useState(null); // 保存自己的ID
  const [connectedClients, setConnectedClients] = useState([]); // 保存當前連接的客戶端ID
  const [messages, setMessages] = useState([]); // 保存接收到的消息
  const [targetId, setTargetId] = useState(''); // 目標ID
  const [messageContent, setMessageContent] = useState(''); // 發送的消息內容
  const ws = useRef(null);

  useEffect(() => {
    // 建立 WebSocket 連接
    ws.current = new WebSocket('ws://localhost:8080');

    ws.current.onopen = () => {
      console.log('WebSocket 連接成功');
    };

    // 當收到服務器的消息時
    ws.current.onmessage = (event) => {
      const data = JSON.parse(event.data);

      if (data.type === 'welcome') {
        setClientId(data.id); // 保存自己的ID
        setMessages((prevMessages) => [...prevMessages, data.message]);
      }
      if(data.type === 'connectedClients') {
        setConnectedClients(data.clients);
      }
      if (data.type === 'message') {
        setMessages((prevMessages) => [
          ...prevMessages,
          `來自客戶端 ${data.from} 的消息: ${data.content}`
        ]);
      }

      if (data.type === 'error') {
        setMessages((prevMessages) => [...prevMessages, `錯誤: ${data.message}`]);
      }
    };

    ws.current.onclose = () => {
      console.log('WebSocket 連接關閉');
    };

    ws.current.onerror = (error) => {
      console.error('WebSocket 錯誤:', error);
    };

    return () => {
      if (ws.current) {
        ws.current.close();
      }
    };
  }, []);

  // 發送消息到指定ID的客戶端
  const sendMessage = () => {
    if (ws.current && ws.current.readyState === WebSocket.OPEN) {
      const message = {
        type: 'sendMessage',
        targetId: targetId,
        content: messageContent
      };
      ws.current.send(JSON.stringify(message)); // 發送消息到服務器
    }
  };

  return (
    <div style={{ textAlign: 'center', marginTop: '50px' }}>
      <h1>WebSocket 點對點通信</h1>
      <h2>你的客戶端ID是: {clientId}</h2>
      <div>
        <h3>目前連接的客戶</h3>
        {connectedClients.map((client, index) => (
          <span key={index}>id:{client},</span>))}
      </div>
      <div style={{ maxHeight: '200px', overflowY: 'auto', border: '1px solid #ccc', padding: '10px' }}>
        <h3>消息紀錄</h3>
        {messages.map((msg, index) => (
          <p key={index}>{msg}</p>
        ))}
      </div>

      <input
        type="text"
        value={targetId}
        onChange={(e) => setTargetId(e.target.value)}
        placeholder="輸入目標ID"
        style={{ marginTop: '20px' }}
      />
      <br />
      <input
        type="text"
        value={messageContent}
        onChange={(e) => setMessageContent(e.target.value)}
        placeholder="輸入要發送的內容"
        style={{ marginTop: '10px' }}
      />
      <br />
      <button onClick={sendMessage} style={{ marginTop: '10px' }}>
        發送消息
      </button>
    </div>
  );
}

export default App;

主要幾點:

  • useRef 用於保持 WebSocket 連接的持久性,即使 React 組件重新渲染也不會導致 WebSocket 重新初始化。這樣可以確保 WebSocket 連接只建立一次。
  • 當收到 WebSocket 服務器的消息時,根據消息類型進行對應的處理。
  • ws.current是儲存 WebSocket 連接本身,最後確認連接狀態來決定是否發送訊息。
  • 其餘的便透過useState來快速渲染

這樣簡易的client端間的相互溝通就完成啦

今天就這樣結束囉


上一篇
[day27]WebSocket 與 React 的狀態管理:實時更新數據(時鐘)
下一篇
[day29]websocket與React : 獲取外部資料渲染
系列文
雙向奔赴的websocket與冰冷的react30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言