generated at
WebSocket










Webにおける双方向通信を低コストで行うための規格
TCP上で行う
サーバーとクライアントは一対一の通信をする
コネクションが確立すれば、クライアント/サーバーのどちらからでもデータの送受信ができる
フレーム単位で送受信する
相手は決まっているので、送り先などの情報を含めない
プロトコルの名称なのかmrsekut
HTTPと異なり、ステートフルな接続
一回切れたら、接続し直さないといけない?



通信の仕組み
手順
HTTPでクライアントとサーバー間で情報をやり取りしてコネクションを確立
確立したコネクション上で、低コストな双方向通信
ここではHTTPを使わない?


利用例
チャットアプリを作る
push通知は?これ?

memo
今までは、クライアントがデータを取得するためには、クライアントからサーバーへリクエストを送ってレスポンスをもらわないといけなかったが、そうじゃなくて、サーバーから能動的にクライアントに何かをpushできるようになったってこと?
比較対象はHTTPになるのか
HTTPにもロングポーリングというのを使って無理やり同じようなことができたが、無駄が多すぎるらしい
無駄な通信が多いってことか?


用語
送信するデータの最小単位?
中身は、ビット列
このスライドのp.44-参照



クライアントの準備
WebSocket Opening Handshakeのための最初のリクエストさえ送ることができればいい
めっちゃかんたんmrsekut
WebSocket クラスを使う
js
var socket = new WebSocket('ws://game.example.com:12000/updates'); socket.onopen = () => { setInterval(() => { if (socket.bufferedAmount === 0) { socket.send(getUpdateData()); } }, 50); }
URIスキーム
ws はWebSocketのことだよmrsekut
wss というsecureなやつ?もある
メソッド
send([データ])
データをサーバーに送信
onmessage
イベントハンドラでデータを受信
clode([コード[,理由]])
ソケット切断
データに扱えるデータ型
文字列
Blob


サーバーの準備


従来の何かとはどう違う?
どんな技術を使っている?
利用したければ何を使う必要がある?
通信とか
ライブラリとか



関連
のhooks



参考
ざっくり紹介されていて、概要を掴むには良い記事
7章で理論の解説
9章でGoでの実装
よんでない
ガチめ
くわしい
仕様の非公式日本語訳



ChatGPTに聞いてとりあえずうごくものができた
全くコード読んでないので、何も理解してないけど
$ node srver.js
server.js
const WebSocket = require("ws"); const { v4: uuidv4 } = require("uuid"); const clients = []; const server = new WebSocket.Server({ port: 8080 }); server.on("connection", (socket) => { console.log("Connected"); const id = uuidv4(); const client = { id, socket }; clients.push(client); socket.on("message", (message) => { console.log(`Received: ${message}`); const parsedMessage = JSON.parse(message.toString()); const text = parsedMessage.text; clients.forEach((c) => { if (c !== client) { c.socket.send(JSON.stringify({ clientId: id, text })); } }); }); socket.on("close", () => { console.log("Disconnected"); const index = clients.findIndex((c) => c.id === id); clients.splice(index, 1); }); socket.send(JSON.stringify({ clientId: id, text: "" })); clients.forEach((c) => { if (c !== client) { socket.send(JSON.stringify({ clientId: c.id, text: "" })); } }); });
create-react-appなどをしたあと
ts
import React, { useEffect, useState } from "react"; class WebSocketClient { private socket: WebSocket | null = null; private onMessageCallbacks: ((message: MessageEvent) => void)[] = []; private onOpenCallbacks: (() => void)[] = []; public connect() { const socket = new WebSocket("ws://localhost:8080"); socket.addEventListener("open", () => { console.log("Connected"); this.socket = socket; this.onOpenCallbacks.forEach((callback) => callback()); }); socket.addEventListener("message", (event) => { this.onMessageCallbacks.forEach((callback) => callback(event)); }); socket.addEventListener("close", () => { console.log("Disconnected"); this.socket = null; }); } public close() { if (this.socket) { this.socket.close(); } } public getId(): string { return this.socket?.url ?? ""; } public send(message: string) { if (this.socket) { this.socket.send(message); } } public onMessage(callback: (message: MessageEvent) => void) { this.onMessageCallbacks.push(callback); } public onOpen(callback: () => void) { this.onOpenCallbacks.push(callback); } } const webSocketClient = new WebSocketClient(); interface Message { clientId: string; text: string; } interface Client { id: string; name: string; } function App() { const [text, setText] = useState<string>(""); const [clients, setClients] = useState<Client[]>([]); const [messages, setMessages] = useState<Message[]>([]); const handleTextChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => { const newText = e.target.value; setText(newText); webSocketClient.send( JSON.stringify({ clientId: webSocketClient.getId(), text: newText }) ); }; useEffect(() => { webSocketClient.onMessage((message) => { console.log(`Received message: ${message.data}`); try { const parsedMessage = JSON.parse(message.data.toString()) as Message; setMessages((messages) => { const existingMessageIndex = messages.findIndex( (m) => m.clientId === parsedMessage.clientId ); if (existingMessageIndex !== -1) { const newMessages = [...messages]; newMessages[existingMessageIndex] = parsedMessage; return newMessages; } else { return [...messages, parsedMessage]; } }); } catch (error) { console.error(error); } }); webSocketClient.onOpen(() => { const id = webSocketClient.getId(); webSocketClient.send(JSON.stringify({ clientId: id, text: "" })); }); webSocketClient.connect(); return () => { webSocketClient.close(); }; }, []); useEffect(() => { setClients((clients) => [ ...clients, { id: webSocketClient.getId(), name: "You" }, ...messages.map((m) => ({ id: m.clientId, name: `User ${m.clientId.substr(0, 4)}`, })), ]); }, [messages]); return ( <div> {/* <div> {clients.map((client) => ( <div key={client.id}>{client.name}</div> ))} </div> */} <div> <textarea value={text} onChange={handleTextChange} style={{ width: "100%", height: "400px" }} /> </div> <div> {messages.map((message) => ( <div key={message.clientId}> <span>{`[${ clients.find((client) => client.id === message.clientId)?.name }]`}</span> <span>{message.text}</span> </div> ))} </div> </div> ); } export default App;