가장 최근 진행한 프로젝트 당시 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 |