Socket.IOとは
サーバーとクライアントのリアルタイムな双方向通信を行うことができるライブラリです。
双方向通信の確立する際に様々なプロトコルを使用しサーバーとの通信を確立します。
Socket.IOを使うことでプロトコルの詳細を理解する必要なく双方向通信を実現することが可能です。
また、ルーム(特定のクライアント同士で通信できる部屋)などを作成する場合も簡単に実装することができます。
開発環境
今回はNextJSでSocket.IOを使用した双方向通信をざっくり実装してみたいと思います。
Pages Router
の場合のみ、API Routes
を使用することでサーバーサイドの実装も可能となっていましたが、今回ご紹介する方法ではPages Router
、App Router
ともに実装可能です。
インストール
npm install socket.io
npm install socket.io-client
サーバー側
プロジェクトルートに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サーバを起動させる。
{
"scripts": {
"dev": "node server.js",
"build": "next build",
"start": "NODE_ENV=production node server.js",
"lint": "next lint"
}
}
クライアント側
サーバーへの接続
srcフォルダ配下にsocket.jsを作成し、以下を記述。
"use client";
import { io } from "socket.io-client";
export const socket = io();
io()
について
-
io()
を呼び出すことにより現在のホストに対して接続を行います -
io()
に引数(URL)を渡すことで別ドメインのサーバーに接続することもできます - 戻り値として
socket
を受け取り以降記述するクライアントの処理でサーバーに対してイベントの送受信を行うことが可能です
接続状態を確認
"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/A
→poling
→websocket
の順に変わることが確認できると思います。
npm run dev
メッセージ送信
サーバー側でメッセージ受信
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
で接続されている全てのクライアントへメッセージを送信。
クライアント側でメッセージ送信
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"
によりメッセエージを受信。
まとめ
- リアルタイム通信はやっぱりいいね!別タブ開いて送信した内容が反映されてるの見るだけでテンション上がる
- VercelだとNextJSのカスタムサーバーが非対応らしいので公開する時は注意が必要かも
- 色々調べているうちにAWSのAPI GatewayでWebsocket API作れるらしいので気が向いたら見てみようかな