본문 바로가기
JavaScript/Node

[Node] Socket.io 활용 WebRTC

by kwh_coding 2024. 8. 24.

가장 최근 진행한 프로젝트 당시 1:1 비대면 화상진료 기능을 맡았다.

 

해당 기능을 구현하기 위해서 Socket.io와 WebRTC를 사용해 보고자 했다.

Socket.io는 클라이언트와 서버 간의 실시간 양방향 통신을 처리하는 데 사용되며, WebRTC는 브라우저 간 P2P(피어 투 피어) 연결을 통해 오디오, 비디오 및 데이터를 실시간으로 전송할 수 있게 해준다.

 

WebSocket이 아닌 Socket.io를 사용한 이유는 

Socket.io는 소켓 연결이 끊어지면 자동으로 재연결을 시도하는 기능과 WebSocket뿐만 아니라 폴링이나 언폴링 등의 다양한 연결 방식을 가지고 있어 해당 클라이언트에 가장 잘 맞는 연결 방식을 동적으로 네트워크 환경 지원 여부에 따라 선택을 해준다.

 

또한, 네임 스페이스와 방 기능을 내장하고 있어 편리하게 사용이 가능하다.

 


 

const wsServer = new Server(httpsServer, {
    cors: {
        origin: ["https://admin.socket.io"],
        credentials: true
    }
});

Socket.io를 사용하여 WebSocket 서버를 HTTPS 서버와 연결하고, CORS 설정을 통해 특정 도메인에서의 요청을 허용하고 있다.

 

socket.on("enter_room", (roomName, done) => {
    socket.join(roomName);
    done();
    socket.to(roomName).emit("welcome", socket.nickname, countRoom(roomName));
    wsServer.sockets.emit("room_change", publicRooms());
});

 

사용자가 특정 채팅방에 들어가면 enter_room 이벤트를 통해 방에 참여하고, 해당 방에 있는 다른 사용자에게 메시지를 보낸다. room_change 이벤트를 통해 방의 상태를 업데이트한다.

 

socket.on("join_room", (roomName) => {
    socket.join(roomName);
    socket.to(roomName).emit("welcome");
});
socket.on("offer", (offer, roomName) => {
    socket.to(roomName).emit("offer", offer);
});
socket.on("answer", (answer, roomName) => {
    socket.to(roomName).emit("answer", answer);
});
socket.on("ice", (ice, roomName) => {
    socket.to(roomName).emit("ice", ice);
});

 

 

join_room 이벤트를 통해 사용자가 방에 참여할 때, 그 사실을 방의 다른 사용자에게 알린다. (카카오톡 단체방에서 "**님이 들어오셨습니다." 를 생각하면 된다.)

offer, answer, ice 이벤트는 WebRTC 연결 설정에 사용된다. 이들은 각각 SDP offer/answer 교환과 ICE 후보 정보 교환에 사용된다.

 

function handleChatRoomSubmit(event) {
    event.preventDefault();
    const input = chatForm.querySelector("input");
    socket.emit("enter_room", input.value, showRoom );
    chatRoomName = input.value;
    input.value="";
}

function handleNicknameSubmit(event){
    event.preventDefault();
    const input = room.querySelector('#name input');
    const value = input.value;
    socket.emit("nickname", input.value);
    alert(input.value + " 닉네임 설정")
}

 

 

handleChatRoomSubmit: 사용자가 특정 채팅방에 들어갈 때 서버에 enter_room 이벤트를 전송한다.

handleNicknameSubmit: 사용자가 닉네임을 설정하면 서버에 nickname 이벤트를 전송한다.

 

async function getMedia(deviceId){
    const initialConstraints = {
        audio: true,
        video: { facingMode: "user" },
    };
    const cameraConstraints = {
        audio: true,
        video: { deviceId: { exact: deviceId } },
    };
    try{
        myStream = await navigator.mediaDevices.getUserMedia(
            deviceId ? cameraConstraints : initialConstraints
        );
        myFace.srcObject = myStream;
        if (!deviceId) {
            await getCameras();
        }
    } catch(e){
        console.log(e);
    }
}

function handleMuteClick(){
    myStream.getAudioTracks()
        .forEach((track) => (track.enabled = !track.enabled));
    if (!muted){
        muteBtn.innerText = "소리켜기";
        muted = true;
    } else {
        muteBtn.innerText = "음소거";
        muted = false;
    }
}

 

getMedia: 사용자의 카메라와 마이크 스트림을 가져온다. deviceId가 주어지면 특정 카메라를 선택하고, 그렇지 않으면 기본 카메라를 사용한다.

 

function makeConnection(){
    myPeerConnection = new RTCPeerConnection();
    myPeerConnection.addEventListener("icecandidate", handleIce);
    myPeerConnection.addEventListener("addstream", handleAddStream);
    myStream.getTracks().forEach((track) => myPeerConnection.addTrack(track, myStream));
}

 

makeConnection: WebRTC P2P 연결을 설정하고, ICE 후보와 스트림 이벤트를 처리할 핸들러를 추가한다.

 

socket.on("welcome", async () => {
    const offer = await myPeerConnection.createOffer();
    myPeerConnection.setLocalDescription(offer);
    socket.emit("offer", offer, roomName);
});

socket.on("offer", async (offer) => {
    myPeerConnection.setRemoteDescription(offer);
    const answer = await myPeerConnection.createAnswer();
    myPeerConnection.setLocalDescription(answer);
    socket.emit("answer", answer, roomName);
});

socket.on("answer", answer => {
    myPeerConnection.setRemoteDescription(answer);
});

socket.on("ice", ice => {
    myPeerConnection.addIceCandidate(ice);
});

서버로부터 들어오는 offer, answer, ice 이벤트를 처리한다. WebRTC 연결을 통해 P2P 연결을 설정한다.

'JavaScript > Node' 카테고리의 다른 글

[Node] 암호화(feat. bcrypt)  (0) 2024.07.25
[Node] session  (3) 2024.07.24
[Node] Node.js?  (2) 2024.07.17