JavaScript >> Javascript 文檔 >  >> Tags >> web

如何使用 JavaScript 設置 Websocket 客戶端

如何創建一個可重用的函數來建立一個連接到現有 websocket 服務器以發送和接收消息的 websocket 客戶端。

開始使用

如果您還沒有 - 並且您沒有自己的現有 websocket 服務器可連接 - 建議您完成我們的配套教程,了解如何使用 Node.js 和 Express 設置 Websocket 服務器。

如果您已經完成了該教程,或者您有一個想要測試的 websocket 服務器,那麼對於本教程,我們將使用 CheatCode Next.js 樣板作為連接我們的 websocket 客戶端的起點:

終端

git clone https://github.com/cheatcode/nextjs-boilerplate.git

克隆項目副本後,01 進入其中並安裝其依賴項:

終端

cd nextjs-boilerplate && npm install

接下來,我們需要安裝一個額外的依賴,17 ,我們將使用它來解析來自我們的 URL 的查詢參數以與我們的 websocket 連接一起傳遞:

終端

npm i query-string

最後,啟動開發服務器:

終端

npm run dev

有了這個,我們就可以開始了。

構建 websocket 客戶端

對我們來說幸運的是,現代瀏覽器現在原生 支持網絡套接字。這意味著我們不需要依賴客戶端上的任何特殊庫來建立我們的連接。

/websockets/client.js

import queryString from "query-string";
import settings from "../settings";

const websocketClient = (options = {}, onConnect = null) => {
  // We'll write our code here...
};

export default websocketClient;

在這裡,我們開始規範我們的 websocket 客戶端。首先,請注意我們正在創建一個名為 20 的函數 我們打算在代碼中的其他地方導入。這裡的想法是,根據我們的應用程序,我們可能有多個 websocket 使用點;這種模式使我們能夠做到這一點沒有 不得不復制/粘貼大量代碼。

查看函數,我們將其設置為接受兩個參數:38 , 一個對象,包含 websocket 客戶端和 48 的一些基本設置 , 一個回調函數,我們可以在之後調用 我們已經與服務器建立了連接(如果您正在構建一個希望/需要在加載完整 UI 之前建立 websocket 連接的 UI,這很重要)。

/websockets/client.js

import queryString from "query-string";
import settings from "../settings";

const websocketClient = (options = {}, onConnect = null) => {
  let url = settings?.websockets?.url;
  let client = new WebSocket(url);

  client.addEventListener("open", () => {
    console.log(`[websockets] Connected to ${settings?.websockets?.url}`);
  });

  client.addEventListener("close", () => {
    console.log(`[websockets] Disconnected from ${settings?.websockets?.url}`);
    client = null;
  });
};

export default websocketClient;

構建我們函數的主體,我們需要建立我們的客戶端到 websocket 服務器的連接。為此,我們在這裡導入了 53 文件位於我們在教程開始時克隆的樣板的根目錄中。該文件包含一個函數,該函數從位於 61 的同一文件夾中的特定於環境的文件中提取前端的配置數據 從項目的根目錄。

如果您查看該文件夾,則會提供兩個示例文件 7189 .前者旨在包含開發 環境設置,而後者旨在包含生產 環境設置。這種區別很重要,因為您只想在開發環境中使用測試密鑰和 URL,以避免破壞生產環境。

/settings/settings-development.json

const settings = {
  [...]
  websockets: {
    url: "ws://localhost:5001/websockets",
  },
};

export default settings;

如果我們打開 93 文件,我們將向 102 添加一個新屬性 從名為 117 的文件中導出的對象 .我們將設置這個 屬性等於另一個對象,具有單個 121 屬性設置為我們的 websocket 服務器的 URL。在這裡,我們使用了我們期望在其他 CheatCode 教程中存在的 URL,該教程用於設置我們在本教程開始時鏈接到的 websocket 服務器。

如果您使用自己現有的 websocket 服務器,您將在此處進行設置。值得注意的是,當我們連接到 websocket 服務器時,我們在 URL 前面加上 136 而不是 147 (在生產中,我們會使用 156 就像我們使用 168 一樣的安全連接 )。這是因為 websockets 是一個獨立於 HTTP 協議的協議。如果我們用 179 作為前綴 ,我們的連接將因瀏覽器錯誤而失敗。

/websockets/client.js

import queryString from "query-string";
import settings from "../settings";

const websocketClient = (options = {}, onConnect = null) => {
  let url = settings?.websockets?.url;
  let client = new WebSocket(url);

  client.addEventListener("open", () => {
    console.log(`[websockets] Connected to ${settings?.websockets?.url}`);
  });

  client.addEventListener("close", () => {
    console.log(`[websockets] Disconnected from ${settings?.websockets?.url}`);
    client = null;

    if (options?.onDisconnect) {
      options.onDisconnect();
    }
  });
};

export default websocketClient;

回到我們的客戶端代碼,現在,我們從設置文件中提取我們的 websockets URL,將其存儲在變量 182 中 使用 196 聲明 (我們稍後會看到原因)。接下來,建立我們的連接 該 URL,在其下方的另一個變量中 206 (也使用 218 ),我們調用 220 傳入 239 對於我們的服務器。這裡,245本地人 瀏覽器 API。

您在這裡看不到它的導入,因為從技術上講,當我們的代碼在瀏覽器中加載時,全局 258 上下文已經有 261 定義為變量。

接下來,在我們的 275 下方 連接,我們為我們預期 289 的兩個事件添加一對 JavaScript 事件監聽器 發出:298306 .這些應該是不言自明的。第一個是當我們的 websocket 服務器連接打開時觸發的回調 , 而第二個會在我們的 websocket 服務器連接關閉時觸發 .

雖然從技術意義上說不是必需的,但這些對於向您自己(和其他開發人員)傳達連接成功或連接丟失非常重要。後一種情況發生在 websocket 服務器變得無法訪問或故意關閉與客戶端的連接時。通常,這發生在服務器重新啟動或內部代碼將特定客戶端踢出時(該踢出的“原因”取決於應用程序,並且沒有內置在 websockets 規範中)。

/websockets/client.js

import queryString from "query-string";
import settings from "../settings";

const websocketClient = (options = {}, onConnect = null) => {
  let url = settings?.websockets?.url;

  if (options.queryParams) {
    url = `${url}?${queryString.stringify(options.queryParams)}`;
  }

  let client = new WebSocket(url);

  client.addEventListener("open", () => {[...]});

  client.addEventListener("close", () => {[...]});

  const connection = {
    client,
    send: (message = {}) => {
      if (options.queryParams) {
        message = { ...message, ...options.queryParams };
      }

      return client.send(JSON.stringify(message));
    },
  };

  if (onConnect) onConnect(connection);

  return connection;
};

export default websocketClient;

我們在這裡添加了很多。回到頂部附近,請注意我們添加了對值 312 的期望 可能存在於 323 對像作為第一個參數傳遞給我們的 334 功能。

因為 websocket 連接不允許我們像使用 HTTP POST 請求那樣傳遞正文,所以我們僅限於傳遞連接參數(可以更好地識別連接的信息,如 343354 ) 作為 URL 安全的查詢字符串。在這裡,我們說“如果我們傳遞了一個 361 的對象 在選項中,我們希望將該對象轉換為 URL 安全的查詢字符串(類似於 370 )。

這就是 381 的用法 進來我們之前暗示過的。如果我們通過了 397 在我們的 400 ,我們想更新我們的 URL 以包含這些。在這種情況下,“更新”是 410 我們創建的變量。因為我們想將該變量的內容重新分配給包含查詢參數的字符串,所以我們必須使用 423 變量(或者,如果你想去老學校,433 )。原因是如果我們使用比較熟悉的444 (代表常數 ) 並嘗試運行 452 在這裡的代碼,JavaScript 會拋出一個錯誤,說我們不能重新分配一個常量。

以我們的 467 對象,我們導入 470 我們之前添加的包並使用它的 489 方法為我們生成字符串。所以,假設我們的基礎服務器 URL 是 496 我們傳遞一個 502 值等於 513 ,我們的 URL 將更新為等於 526 .

/websockets/client.js

import queryString from "query-string";
import settings from "../settings";

const websocketClient = (options = {}, onConnect = null) => {
  [...]

  let client = new WebSocket(url);

  client.addEventListener("open", () => {[...]});

  client.addEventListener("close", () => {[...]});

  const connection = {
    client,
    send: (message = {}) => {
      if (options.queryParams) {
        message = { ...message, ...options.queryParams };
      }

      return client.send(JSON.stringify(message));
    },
  };

  if (onConnect) onConnect(connection);

  return connection;
};

export default websocketClient;

回到我們函數的底部,我們添加了一個新對象 532 作為 543 其中包括兩個屬性:556 設置為 568 包含我們的 websocket 連接和 572 的變量 ,設置為我們定義的自定義函數以幫助我們發送消息。

websocket 服務器的核心概念之一是能夠在客戶端和服務器之間來回發送消息(將您的 websocket 連接想像成一根兩端連接著兩個罐子的字符串)。當我們從客戶端或服務器發送消息時,我們需要將它們轉換為字符串值584(意味著設置或轉換為不同類型的數據) .

在這裡,我們的 592 添加函數是為了方便我們將整個對像作為字符串傳遞。這裡的想法是,當我們使用我們的代碼時,在調用我們的 609 函數,我們會收到這個 614 目的。那麼,在我們的代碼中,我們將能夠調用 629 沒有 必須對我們手動傳入的對象進行字符串化。

此外,除了對我們的消息進行字符串化之外,此代碼還包括任何 638 這很有幫助,因為當我們在 websocket 服務器中處理客戶端連接時,或者每當我們從連接的客戶端接收消息時(例如,將 userId 與消息一起傳遞給確定是誰發送的)。

就在我們返回 644 之前 在我們函數的底部,請注意​​我們有條件地調用 650 (將在 之後調用的回調函數 我們的連接已建立)。從技術上講,在這裡,在調用此回調之前,我們不會等待實際連接建立。

一個 websocket 連接應該幾乎是即時建立的,所以在評估這段代碼時,我們可以預期一個客戶端連接存在。如果與服務器的連接速度很慢,我們會考慮將調用移至 664 671 的事件偵聽器回調內部 上面的事件。

/websockets/client.js

import queryString from "query-string";
import settings from "../settings";

const websocketClient = (options = {}, onConnect = null) => {
  let url = settings?.websockets?.url;

  if (options.queryParams) {
    url = `${url}?${queryString.stringify(options.queryParams)}`;
  }

  let client = new WebSocket(url);

  client.addEventListener("open", () => {
    console.log(`[websockets] Connected to ${settings?.websockets?.url}`);
  });

  client.addEventListener("close", () => {
    console.log(`[websockets] Disconnected from ${settings?.websockets?.url}`);
    client = null;
  });

  client.addEventListener("message", (event) => {
    if (event?.data && options.onMessage) {
      options.onMessage(JSON.parse(event.data));
    }
  });

  const connection = {
    client,
    send: (message = {}) => {
      if (options.queryParams) {
        message = { ...message, ...options.queryParams };
      }

      return client.send(JSON.stringify(message));
    },
  };

  return connection;
};

export default websocketClient;

還有一件事要潛入。雖然我們已經將 websocket 客戶端設置為 send 消息,我們尚未將其設置為接收 消息。

當消息被發送到連接的客戶端時(除非有意處理,websocket服務器發送的消息將被發送到所有 連接的客戶端),這些客戶端通過 682 接收該消息 他們的 697 上的事件 連接。

在這裡,我們為 702 添加了一個新的事件監聽器 事件。有條件地,假設發送了一條實際消息(在 714 字段)並且我們有一個 723 在我們的選項中回調函數,我們調用該函數,傳遞 736 'd 版本的消息。請記住,消息作為字符串來回發送。在這裡,我們假設我們從服務器接收到的消息是一個字符串化對象,我們希望將其轉換為 JavaScript 對象。

這就是我們的實施!現在,讓我們使用我們的客戶端並驗證一切是否按預期工作。

使用 websocket 客戶端

為了使用我們的客戶端,我們將在本教程開始時克隆的樣板文件中連接一個新的頁面組件。讓我們在 746 創建一個新頁面 現在看看我們需要做些什麼來集成我們的 websocket 客戶端。

/pages/index.js

import React from "react";
import PropTypes from "prop-types";
import websocketClient from "../websockets/client";

import StyledIndex from "./index.css";

class Index extends React.Component {
  state = {
    message: "",
    received: [],
    connected: false,
  };

  componentDidMount() {
    websocketClient(
      {
        queryParams: {
          favoritePizza: "supreme",
        },
        onMessage: (message) => {
          console.log(message);
          this.setState(({ received }) => {
            return {
              received: [...received, message],
            };
          });
        },
        onDisconnect: () => {
          this.setState({ connected: false });
        },
      },
      (websocketClient) => {
        this.setState({ connected: true }, () => {
          this.websocketClient = websocketClient;
        });
      }
    );
  }

  handleSendMessage = () => {
    const { message } = this.state;
    this.websocketClient.send({ message });
    this.setState({ message: "" });
  };

  render() {
    const { message, connected, received } = this.state;

    return (
      <StyledIndex>
        <div className="row">
          <div className="col-sm-6">
            <label className="form-label">Send a Message</label>
            <input
              className="form-control mb-3"
              type="text"
              name="message"
              placeholder="Type your message here..."
              value={message}
              onChange={(event) =>
                this.setState({ message: event.target.value })
              }
            />
            <button
              className="btn btn-primary"
              onClick={this.handleSendMessage}
            >
              Send Message
            </button>
          </div>
          <div className="row">
            <div className="col-sm-12">
              <div className="messages">
                <header>
                  <p>
                    <i
                      className={`fas ${connected ? "fa-circle" : "fa-times"}`}
                    />{" "}
                    {connected ? "Connected" : "Not Connected"}
                  </p>
                </header>
                <ul>
                  {received.map(({ message }, index) => {
                    return <li key={`${message}_${index}`}>{message}</li>;
                  })}
                  {connected && received.length === 0 && (
                    <li>No messages received yet.</li>
                  )}
                </ul>
              </div>
            </div>
          </div>
        </div>
      </StyledIndex>
    );
  }
}

Index.propTypes = {
  // prop: PropTypes.string.isRequired,
};

export default Index;

讓我們在這裡討論總體思路,然後關注 websocket 的東西。我們在這裡所做的是設置一個 React 組件,該組件呈現一個輸入、一個按鈕和一個從我們的 websocket 服務器接收到的消息列表。為了演示我們客戶端的用法,我們將連接到客戶端,然後將消息發送到服務器。我們期望(我們稍後會看到)我們的服務器以乒乓方式向我們發送一條消息,服務器確認 我們的 發回自己的消息。

750 在這裡,我們使用 Bootstrap(包含在我們為本教程克隆的樣板文件中)和使用 764 實現的少量自定義 CSS 的組合 通過 779 我們在組件文件頂部導入的組件。

CSS 的細節在這裡並不重要,但請確保在 787 處添加以下文件 (注意 .css.js 擴展名,因此導入仍然可以在您的組件中的 797 )。我們接下來展示的代碼在沒有它的情況下仍然可以工作,但它看起來不像我們下面展示的示例。

/pages/index.css.js

import styled from "styled-components";

export default styled.div`
  .messages {
    background: var(--gray-1);
    margin-top: 50px;

    header {
      padding: 20px;
      border-bottom: 1px solid #ddd;
    }

    header p {
      margin: 0;

      i {
        font-size: 11px;
        margin-right: 5px;
      }

      .fa-circle {
        color: lime;
      }
    }

    ul {
      padding: 20px;
      list-style: none;
      margin: 0;
    }
  }
`;

回到組件中,我們要關注兩個方法:我們的 809812

/pages/index.js

import React from "react";
import PropTypes from "prop-types";
import websocketClient from "../websockets/client";

import StyledIndex from "./index.css";

class Index extends React.Component {
  state = {
    message: "",
    received: [],
    connected: false,
  };

  componentDidMount() {
    websocketClient(
      {
        queryParams: {
          favoritePizza: "supreme",
        },
        onMessage: (message) => {
          console.log(message);
          this.setState(({ received }) => {
            return {
              received: [...received, message],
            };
          });
        },
        onDisconnect: () => {
          this.setState({ connected: false });
        },
      },
      (websocketClient) => {
        this.setState({ connected: true }, () => {
          this.websocketClient = websocketClient;
        });
      }
    );
  }

  handleSendMessage = () => {
    const { message } = this.state;
    this.websocketClient.send({ message });
    this.setState({ message: "" });
  };

  render() {
    const { message, connected, received } = this.state;

    return (
      <StyledIndex>
        [...]
      </StyledIndex>
    );
  }
}

Index.propTypes = {
  // prop: PropTypes.string.isRequired,
};

export default Index;

在這裡,在 825 函數,我們調用我們的 839 我們從 847 導入的函數 文件。當我們調用它時,我們傳遞了兩個預期的參數:首先,一個 859 包含一些 863 的對象 , 一個 872 回調函數和一個 888 回調,第二個,一個 898 回調函數,一旦它可用就會接收我們的 websocket 客戶端實例。

對於 904 這裡我們只是傳遞一些示例數據來展示它是如何工作的。

918 回調,我們接收消息(記住,這將是一個從我們從服務器接收到的消息字符串解析的 JavaScript 對象),然後通過將它與我們已有的消息連接來將其設置為組件的狀態 第929章 .在這裡,931 部分是說“將現有收到的消息添加到這個數組中”。實際上,我們得到了一個消息對像數組,其中包含先前接收到的消息和我們現在正在接收的消息。

最後,對於 949 ,我們還添加了一個955 設置 967 的回調 組件上的狀態(我們將使用它來確定連接是否成功)到 977 如果我們失去連接。

986中 回調(傳遞給 997 的第二個參數 ) 我們調用 1002 設置 1013 為真,然後——重要的部分——我們分配 1025 實例通過 1030 傳遞給我們 回調並將其設置在 React component 實例為 1043 .

我們想要這樣做的原因在於 1053 .每當我們的 1066 中的按鈕按下時,都會調用此消息 方法被點擊。點擊後,我們得到 1077 的當前值 (我們將狀態設置為 1087 每當輸入發生變化時),然後調用 1095 .請記住 1100 我們在這裡調用的函數與我們連接並分配給 1113 的函數相同 對象返回 1122 .

在這裡,我們將消息作為對象的一部分傳遞並期望 1139 在將其發送到服務器之前將其轉換為字符串。

這就是它的肉和土豆。在1145中 函數,一旦我們的 1156 數組有一些消息,我們將它們呈現為純 1160 1175 中的標記 塊。

這樣,當我們在瀏覽器中加載我們的應用程序並訪問 1186 ,我們應該看到我們的簡單表單和(假設我們的 websocket 服務器正在運行)輸入下方的“已連接”狀態!如果您發送消息,您應該會看到從服務器返回的響應。

注意 :同樣,如果您還沒有完成關於設置 websocket 服務器的 CheatCode 教程,請確保按照那裡的說明進行操作,以便您擁有一個工作服務器並確保啟動它。

總結

在本教程中,我們學習瞭如何使用瀏覽器內的原生 1199 設置 websocket 客戶端 班級。我們學習瞭如何編寫一個包裝函數來建立與我們的服務器的連接、處理查詢參數並處理所有基本的 websocket 事件,包括:1208 , 1215 , 和 1227 .

我們還學習瞭如何在 React 組件中連接我們的 websocket 客戶端,以及如何通過該客戶端從我們的組件中的表單發送消息。


Tutorial JavaScript 教程
  1. tsParticles 1.9.1

  2. JavaScript、異步編程和 Promise

  3. 同源策略和 CORS

  4. React 基礎:解釋 useContext 鉤子

  5. LeetCode 回文數

  6. 如果 Promise 中的“then”塊提供了“onRejected”函數,catch 塊不會被執行嗎?

  7. 如何在 create-react-app 應用程序中設置漂亮的導入路徑

  1. 如何在 5 分鐘內創建一個 React 應用程序?

  2. Javascript 創建 zip 文件

  3. Node.js 和 HashWick 漏洞

  4. C# 使用 JQuery 將 cshtml 加載到模態中

  5. 從頭開始創建 React-App

  6. React-select – 我不知道如何設置默認值

  7. 元素的 querySelectorAll 包含特定的類

  1. 使用 NodeJS 的基於文件的操作

  2. JavaScript 位運算符的 5 個簡單應用

  3. 5 個 jQuery 響應式全屏背景圖像插件

  4. 在 Reactjs 和 CSS 中保護您的博客內容