wss WebSocket Connection

손찬호·2024년 7월 6일

네트워크

목록 보기
3/3

WS 사용

package.json

{
  "name": "hourglass_planner_front",
  "version": "0.1.0",
  "private": true,
  "scripts": {
    "dev": "next dev",
    "build": "next build",
    "lint": "next lint",
    "local": "node server-local.js",
    "start": "next start -p 3000"
  },
  "dependencies": {
    "chart.js": "^4.4.3",
    "dotenv": "^16.4.5",
    "env-cmd": "^10.1.0",
    "http-proxy-middleware": "^3.0.0",
    "js-cookie": "^3.0.5",
    "next": "14.2.4",
    "react": "^18",
    "react-dom": "^18",
    "simple-peer": "^9.11.1",
    "socket.io-client": "^4.7.5",
    "sockjs-client": "^1.6.1",
    "uuid": "^10.0.0",
    "webstomp-client": "^1.2.6",
    "yarn": "^1.22.22",
    "zustand": "^4.5.4"
  },
  "devDependencies": {
    "@types/js-cookie": "^3.0.6",
    "@types/node": "^20",
    "@types/react": "^18",
    "@types/react-dom": "^18",
    "@types/simple-peer": "^9.11.8",
    "@types/sockjs-client": "^1.5.4",
    "@types/uuid": "^10.0.0",
    "eslint": "^8",
    "eslint-config-next": "14.2.4",
    "postcss": "^8",
    "tailwindcss": "^3.4.1",
    "typescript": "^5"
  }
}

server-local.js

const { createServer } = require('https');
const { parse } = require('url');
const next = require('next');
const fs = require('fs');
const path = require('path');

require('dotenv').config({ path: '.env.localhost' }); // 환경 변수 로드

const dev = process.env.NODE_ENV !== 'production';
const app = next({ dev });
const handle = app.getRequestHandler();

// 인증서 파일 경로 설정
const httpsOptions = {
  key: fs.readFileSync(path.resolve(__dirname, 'dev.key')),
  cert: fs.readFileSync(path.resolve(__dirname, 'dev.cert')),
};

const port = process.env.PORT || 3000; // 로컬에서는 3000 포트 사용

app.prepare().then(() => {
  const server = createServer(httpsOptions, (req, res) => {
      res.setHeader('Access-Control-Allow-Origin', 'https://localhost:3000');
      res.setHeader('Access-Control-Allow-Methods', 'GET,POST,PUT,DELETE,OPTIONS');
      res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization');
      res.setHeader('Access-Control-Allow-Credentials', 'true');
      
      if (req.method === 'OPTIONS') {
          res.writeHead(204);
          res.end();
          return;
      }

      const parsedUrl = parse(req.url, true);
      handle(req, res, parsedUrl);
  });

  server.listen(port, (err) => {
      if (err) throw err;
      console.log(`> Ready on https://localhost:${port}/`);
  });
});

소켓 요청 page.tsx

숫자를 입력하고 방 생성 버튼을 누르면 세션으로 이동하게 된다.

'use client';
import { useState } from "react";

export default function TestPage() {
    const [room, setRoom] = useState("");
    const [rooms, setRooms] = useState<string[]>([]);
    const [socket, setSocket] = useState<WebSocket | null>(null);

    const createRoom = () => {
        if (room) {
            setRooms([...rooms, room]);
            const ws = new WebSocket('wss://localhost:8080/signal');
            ws.onopen = () => {
                console.log('WebSocket connection established');
                ws.send(JSON.stringify({ type: "create_room", room }));
            };
            ws.onmessage = (message) => {
                console.log("Message from server: ", message.data);
            };
            ws.onerror = (error) => {
                console.error('WebSocket error: ', error);
            };
            ws.onclose = (event) => {
                console.log('WebSocket connection closed', event);
            };
            setSocket(ws);
        }
    };

    const joinRoom = (room: string) => {
        console.log("Joining room: ", room);
        if (socket) {
            socket.send(JSON.stringify({ type: "join_room", room }));
        }
    };

    return (
        <div className="flex flex-col items-center">
            <div className="mb-4">
                <input
                    type="text"
                    value={room}
                    onChange={(e) => setRoom(e.target.value)}
                    placeholder="Enter room number"
                    className="border p-2"
                />
                <button onClick={createRoom} className="ml-2 p-2 bg-blue-500 text-white">
                    Create Room
                </button>
            </div>
            <div>
                {rooms.map((room, index) => (
                    <button
                        key={index}
                        onClick={() => joinRoom(room)}
                        className="m-2 p-2 bg-green-500 text-white"
                    >
                        Join Room {room}
                    </button>
                ))}
            </div>
        </div>
    );
}

Socket.io 사용

라이브러리 설치

npm install socket.io-client

server-local.js

const { createServer } = require('https');
const { parse } = require('url');
const next = require('next');
const fs = require('fs');
const path = require('path');
const socketIo = require('socket.io');

require('dotenv').config({ path: '.env.localhost' }); // 환경 변수 로드

const dev = process.env.NODE_ENV !== 'production';
const app = next({ dev });
const handle = app.getRequestHandler();

// 인증서 파일 경로 설정
const httpsOptions = {
  key: fs.readFileSync(path.resolve(__dirname, 'dev.key')),
  cert: fs.readFileSync(path.resolve(__dirname, 'dev.cert')),
};

const port = process.env.PORT || 3000; // 로컬에서는 3000 포트 사용

app.prepare().then(() => {
  const server = createServer(httpsOptions, (req, res) => {
      res.setHeader('Access-Control-Allow-Origin', 'https://localhost:3000');
      res.setHeader('Access-Control-Allow-Methods', 'GET,POST,PUT,DELETE,OPTIONS');
      res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization');
      res.setHeader('Access-Control-Allow-Credentials', 'true');
      
      if (req.method === 'OPTIONS') {
          res.writeHead(204);
          res.end();
          return;
      }

      const parsedUrl = parse(req.url, true);
      handle(req, res, parsedUrl);
  });

  const io = socketIo(server, {
    cors: {
      origin: "https://localhost:3000",
      methods: ["GET", "POST"],
      credentials: true
    }
  });

  io.on('connection', (socket) => {
    console.log('New client connected:', socket.id);

    socket.on('create_room', (data) => {
      const room = data.room;
      socket.join(room);
      console.log(`Room ${room} created by client ${socket.id}`);
      io.to(room).emit('room_created', { room });
    });

    socket.on('join_room', (data) => {
      const room = data.room;
      socket.join(room);
      console.log(`Client ${socket.id} joined room ${room}`);
      io.to(room).emit('room_joined', { room });
    });

    socket.on('disconnect', () => {
      console.log('Client disconnected:', socket.id);
    });
  });

  server.listen(port, (err) => {
      if (err) throw err;
      console.log(`> Ready on https://localhost:${port}/`);
  });
});

소켓요청 page.tsx

'use client';
import { useState, useEffect } from "react";
import io from 'socket.io-client';

const socket = io('https://localhost:3000', {
    withCredentials: true,
    transports: ['websocket'],
});

export default function TestPage() {
    const [room, setRoom] = useState("");
    const [rooms, setRooms] = useState<string[]>([]);

    useEffect(() => {
        socket.on('room_created', (data) => {
            console.log('Room created:', data.room);
        });

        socket.on('room_joined', (data) => {
            console.log('Room joined:', data.room);
        });

        return () => {
            socket.off('room_created');
            socket.off('room_joined');
        };
    }, []);

    const createRoom = () => {
        if (room) {
            setRooms([...rooms, room]);
            socket.emit('create_room', { room });
        }
    };

    const joinRoom = (room: string) => {
        console.log("Joining room: ", room);
        socket.emit('join_room', { room });
    };

    return (
        <div className="flex flex-col items-center">
            <div className="mb-4">
                <input
                    type="text"
                    value={room}
                    onChange={(e) => setRoom(e.target.value)}
                    placeholder="Enter room number"
                    className="border p-2"
                />
                <button onClick={createRoom} className="ml-2 p-2 bg-blue-500 text-white">
                    Create Room
                </button>
            </div>
            <div>
                {rooms.map((room, index) => (
                    <button
                        key={index}
                        onClick={() => joinRoom(room)}
                        className="m-2 p-2 bg-green-500 text-white"
                    >
                        Join Room {room}
                    </button>
                ))}
            </div>
        </div>
    );
}

Socket.io + WebRTC

server-local.js

const { createServer } = require('https');
const { parse } = require('url');
const next = require('next');
const fs = require('fs');
const path = require('path');
const socketIo = require('socket.io');

require('dotenv').config({ path: '.env.localhost' }); // 환경 변수 로드

const dev = process.env.NODE_ENV !== 'production';
const app = next({ dev });
const handle = app.getRequestHandler();

// 인증서 파일 경로 설정
const httpsOptions = {
  key: fs.readFileSync(path.resolve(__dirname, 'dev.key')),
  cert: fs.readFileSync(path.resolve(__dirname, 'dev.cert')),
};

const port = process.env.PORT || 3000; // 로컬에서는 3000 포트 사용

app.prepare().then(() => {
  const server = createServer(httpsOptions, (req, res) => {
      res.setHeader('Access-Control-Allow-Origin', 'https://localhost:3000');
      res.setHeader('Access-Control-Allow-Methods', 'GET,POST,PUT,DELETE,OPTIONS');
      res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization');
      res.setHeader('Access-Control-Allow-Credentials', 'true');
      
      if (req.method === 'OPTIONS') {
          res.writeHead(204);
          res.end();
          return;
      }

      const parsedUrl = parse(req.url, true);
      handle(req, res, parsedUrl);
  });

  const io = socketIo(server, {
    cors: {
      origin: "https://localhost:3000",
      methods: ["GET", "POST"],
      credentials: true
    }
  });

  io.on('connection', (socket) => {
    console.log('New client connected:', socket.id);

    socket.on('create_room', (data) => {
      const room = data.room;
      socket.join(room);
      console.log(`Room ${room} created by client ${socket.id}`);
      io.to(room).emit('room_created', { room });
    });

    socket.on('join_room', (data) => {
      const room = data.room;
      socket.join(room);
      console.log(`Client ${socket.id} joined room ${room}`);
      io.to(room).emit('room_joined', { room });
    });

    socket.on('signal', (data) => {
      const { room, signalData } = data;
      socket.to(room).emit('signal', { signalData, id: socket.id });
    });

    socket.on('disconnect', () => {
      console.log('Client disconnected:', socket.id);
    });
  });

  server.listen(port, (err) => {
      if (err) throw err;
      console.log(`> Ready on https://localhost:${port}/`);
  });
});

웹소켓 page.tsx

'use client';
import { useState, useEffect, useRef } from "react";
import io from 'socket.io-client';

const socket = io('https://localhost:3000', {
    withCredentials: true,
    transports: ['websocket'],
});

export default function TestPage() {
    const [room, setRoom] = useState("");
    const [rooms, setRooms] = useState<string[]>([]);
    const localVideoRef = useRef<HTMLVideoElement>(null);
    const remoteVideoRef = useRef<HTMLVideoElement>(null);
    const peerConnectionRef = useRef<RTCPeerConnection | null>(null);

    useEffect(() => {
        socket.on('room_created', (data) => {
            console.log('Room created:', data.room);
        });

        socket.on('room_joined', (data) => {
            console.log('Room joined:', data.room);
        });

        socket.on('signal', async (data) => {
            if (peerConnectionRef.current) {
                try {
                    await peerConnectionRef.current.setRemoteDescription(new RTCSessionDescription(data.signalData));
                    if (data.signalData.type === 'offer') {
                        const answer = await peerConnectionRef.current.createAnswer();
                        await peerConnectionRef.current.setLocalDescription(answer);
                        socket.emit('signal', { room, signalData: answer });
                    }
                } catch (error) {
                    console.error('Error handling signal:', error);
                }
            }
        });

        return () => {
            socket.off('room_created');
            socket.off('room_joined');
            socket.off('signal');
        };
    }, []);

    const createRoom = () => {
        if (room) {
            setRooms([...rooms, room]);
            socket.emit('create_room', { room });
            initializePeerConnection();
        }
    };

    const joinRoom = (room: string) => {
        console.log("Joining room: ", room);
        socket.emit('join_room', { room });
        initializePeerConnection();
    };

    const initializePeerConnection = async () => {
        const peerConnection = new RTCPeerConnection({
            iceServers: [
                {
                    urls: 'stun:stun.l.google.com:19302',
                },
            ],
        });

        peerConnection.onicecandidate = (event) => {
            if (event.candidate) {
                socket.emit('signal', { room, signalData: event.candidate });
            }
        };

        peerConnection.ontrack = (event) => {
            if (remoteVideoRef.current) {
                remoteVideoRef.current.srcObject = event.streams[0];
            }
        };

        try {
            const stream = await navigator.mediaDevices.getUserMedia({ video: true, audio: true });
            stream.getTracks().forEach((track) => peerConnection.addTrack(track, stream));
            if (localVideoRef.current) {
                localVideoRef.current.srcObject = stream;
            }

            const offer = await peerConnection.createOffer();
            await peerConnection.setLocalDescription(offer);
            socket.emit('signal', { room, signalData: offer });
        } catch (error) {
            console.error('Error accessing media devices:', error);
        }

        peerConnectionRef.current = peerConnection;
    };

    return (
        <div className="flex flex-col items-center">
            <div className="mb-4">
                <input
                    type="text"
                    value={room}
                    onChange={(e) => setRoom(e.target.value)}
                    placeholder="Enter room number"
                    className="border p-2"
                />
                <button onClick={createRoom} className="ml-2 p-2 bg-blue-500 text-white">
                    Create Room
                </button>
            </div>
            <div>
                {rooms.map((room, index) => (
                    <button
                        key={index}
                        onClick={() => joinRoom(room)}
                        className="m-2 p-2 bg-green-500 text-white"
                    >
                        Join Room {room}
                    </button>
                ))}
            </div>
            <div className="video-container">
                <video ref={localVideoRef} autoPlay muted className="local-video" />
                <video ref={remoteVideoRef} autoPlay className="remote-video" />
            </div>
        </div>
    );
}
profile
매일 1%씩 성장하려는 주니어 개발자입니다.

0개의 댓글