4
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

JSL(日本システム技研) Advent Calendar 2024

Day 10

[メモ] Socket.IO使って無難にチャットアプリ作ってみるよ

Last updated at Posted at 2024-12-10

Socket.IOとは

サーバーとクライアントのリアルタイムな双方向通信を行うことができるライブラリです。
双方向通信の確立する際に様々なプロトコルを使用しサーバーとの通信を確立します。
Socket.IOを使うことでプロトコルの詳細を理解する必要なく双方向通信を実現することが可能です。
また、ルーム(特定のクライアント同士で通信できる部屋)などを作成する場合も簡単に実装することができます。

開発環境

今回はNextJSでSocket.IOを使用した双方向通信をざっくり実装してみたいと思います。
Pages Routerの場合のみ、API Routesを使用することでサーバーサイドの実装も可能となっていましたが、今回ご紹介する方法ではPages RouterApp Routerともに実装可能です。

インストール

npm install socket.io
npm install socket.io-client

サーバー側

プロジェクトルートにserver.jsを作成し以下を記述。

server.js
import { createServer } from "node:http";
import next from "next";
import { Server } from "socket.io";

const dev = process.env.NODE_ENV !== "production";
const hostname = "localhost";
const port = 3000;
// when using middleware `hostname` and `port` must be provided below
const app = next({ dev, hostname, port });
const handler = app.getRequestHandler();

app.prepare().then(() => {
  const httpServer = createServer(handler);

  const io = new Server(httpServer);

  io.on("connection", (socket) => {
    console.log('接続しました。')
  });

  httpServer
    .once("error", (err) => {
      console.error(err);
      process.exit(1);
    })
    .listen(port, () => {
      console.log(`> Ready on http://${hostname}:${port}`);
    });
});

package.jsonを編集し、実行時にWebSocketサーバを起動させる。

pachage.json
{
  "scripts": {
    "dev": "node server.js",
    "build": "next build",
    "start": "NODE_ENV=production node server.js",
    "lint": "next lint"
   }
}

クライアント側

サーバーへの接続

srcフォルダ配下にsocket.jsを作成し、以下を記述。

src/socket.js
"use client";

import { io } from "socket.io-client";

export const socket = io();

io()について

  • io()を呼び出すことにより現在のホストに対して接続を行います
  • io()に引数(URL)を渡すことで別ドメインのサーバーに接続することもできます
  • 戻り値としてsocketを受け取り以降記述するクライアントの処理でサーバーに対してイベントの送受信を行うことが可能です

接続状態を確認

page.tsx
"use client";

import { useEffect, useState } from "react";
import { socket } from "../socket";

const Home = () => {
  const [isConnected, setIsConnected] = useState(false);
  const [transport, setTransport] = useState("N/A");

  const onConnect = () => {
    setIsConnected(true);
    setTransport(socket.io.engine.transport.name);

    socket.io.engine.on("upgrade", (transport) => {
      setTransport(transport.name);
    });
  };

  const onDisconnect = () => {
    setIsConnected(false);
    setTransport("N/A");
  };

  useEffect(() => {
    if (socket.connected) {
      onConnect();
    }

    socket.on("connect", onConnect);
    socket.on("disconnect", onDisconnect);

    return () => {
      socket.off("connect", onConnect);
      socket.off("disconnect", onDisconnect);
    };
  }, []);

  return (
    <div>
      <p>Status: {isConnected ? "接続済み" : "接続中..."}</p>
      <p>Transport: {transport}</p>
    </div>
  );
};

export default Home;

接続後の流れ

  • 最初はpolingで通信を行う
    • クライアント側から一方通行の通信
  • クライアントとサーバー側でwebsocket通信をサポートしている場合、websocketのプロトコルへと切り替える
    • 双方向通信になる

onConnect()について

  • サーバーからupgradeイベントを受け取りwebsocketでの通信へと昇華する

実行してみると接続状態がN/Apolingwebsocketの順に変わることが確認できると思います。

npm run dev

メッセージ送信

サーバー側でメッセージ受信

server.ts
app.prepare().then(() => {
  ...
  io.on("connection", (socket) => {
    socket.on('send-message', (message) => {
      io.emit('receive-message', message)
    })
  });
  ...
}

socket.on()send-messageイベントでクライアント側からメッセージを受け取る。
io.emit()receive-messageで接続されている全てのクライアントへメッセージを送信。

クライアント側でメッセージ送信

page.tsx
const Home = () => {
  const [message, setMessage] = useState("");
  const [receivedMessages, setReceivedMessages] = useState<string[]>([]);
  ...
  // メッセージの送信
  const sendMessage = () => {
    if (message.trim() === "") return;
    socket.emit("send-message", message);
    setMessage("");
  };

  useEffect(() => {
    // メッセージの受信
    socket.on("receive-message", (receiveMessage) => {
      setReceivedMessages((currentReceivedMessages) => [
        ...currentReceivedMessages,
        receiveMessage,
      ]);
    });

    return () => {
      socket.off("receive-message");
    }
  }

  return (
    <>
      <div>
        <ul>
          {receivedMessages.map((receivedMessage, i) => (
            <li key={i} className="border-b border-dashed mb-2">
              <p className="whitespace-break-spaces">{receivedMessage}</p>
            </li>
          ))}
        </ul>
      </div>
      <div className="flex align-center">
          <textarea
            rows={1}
            value={message}
            onChange={(event) => setMessage(event.target.value)}
            className="grow text-stone-950 p-2 rounded-l-lg resize-none outline-none field-sizing-content"
            style={{ fieldSizing: "content" }}
          />
          <button
            onClick={() => sendMessage()}
            className="bg-blue-500 px-2 rounded-r-lg outline-none"
          >
            送信
          </button>
        </div>
    </>
  )
}

socket.emit()send-messageによりサーバーにメッセージ送信。
socket.on()"receive-message"によりメッセエージを受信。

まとめ

参考

4
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?