부족한 부분에 대한 보충설명 댓글은 언제나 환영입니다! ✨
내가 들어야하는 필수 과목들 중에는 캡스톤디자인1, 2가 있다.
이번 4학년 1학기에는 캡스톤디자인1을 수강했고, 한 학기를 팀 프로젝트 진행하는 데 사용하는만큼 팀원들과 상의하여 다같이 만들어보지 않았던 기능을 넣어보는 게 좋을 것 같다고 생각했다.
그게 바로 socket.io를 사용한 실기간 기능이었다!
기능을 구현했던 방법에 대해 정리하면서 나도 다시 한 번 복습하고, 이 기능이 필요한 사람들에게 도움이 되었으면 한다🤗 (물론, 내 코드가 정답은 아닐 것이다... 부족한 점이 많다ㅎㅎ)
React, TypeScript, Styled-component, socket.io-client를 사용하여 작성한 코드가 나올 예정이므로 게시글을 읽을 때 참고해주면 좋을 것 같다~
참고 : socket.io 공식문서 (client API 파트)
socket.io를 이야기하기 전, WebSocket이 무엇인지부터 간단하게 이야기해보고자 한다.
WebSocket은 브라우저와 서버 사이 동적인 양방향 연결 채널을 구성하는 HTML5 프로토콜이며 서버로 메세지를 보내서 요청 없이 응답을 받아오는 게 가능하다.
HTTP 통신과는 다르게 클라이언트가 특정 주기를 갖고 변경된 사항을 적절하게 전달할 수 있는 완전한 양방향 연결 스트림을 만들어주는 기술이라고 할 수 있다.
socket.io는 클라이언트와 서버 간의 짧은 대기 시간, 양방향 및 이벤트 기반 통신을 할 수 있도록 도와주는 라이브러리다. WebSocket 프로토콜 위에 구축된 라이브러리이고 HTTP의 긴 Polling이나 자동 재연결에 대한 Pollback을 제공해준다.
WebSocket은 오래된 브라우저의 경우 지원하지 않는 경우가 있기 때문에, 브라우저 간 호환이나 이전 버전 호환을 고려해서 Node.js와 강력한 시너지를 보여주는 Socket.io을 사용하는 게 권장된다!
이벤트를 받아서 콜백함수 진행하기
socket.on('이벤트 이름', (msg) => { 콜백함수 })
전송할 이벤트 명을 지정하고, 두 번째 파라미터에 전송할 메세지를 넣는다.
socket.emit('전송할 이벤트 이름', 메세지)
네임스페이스는 단일 공유 연결을 통해 애플리케이션 로직을 분할할 수 있는 통신 채널이다.
우리의 프로젝트 컨셉은 경매였기에 각 경매방 컴포넌트가 가지고 있는 고유 Id값을 네임스페이스라는 것을 사용하여 소켓 통신 구분 값으로 사용했다.
이렇게 글로만으로는 이해하기 어려울 수 있으니 작업했던 코드와 함께 살펴보도록 하자.
서버와의 socket.io 통신을 하기 위해서 클라이언트 단에는 socket.io-client 라이브러리를 설치해야 한다.
npm install socket.io-client
or
yarn add socket.io-client
import { io } from 'socket.io-client';
해당 모듈은 io 객체를 사용하는 곳에서 선언하면 된다.
계속 사용할 socket io 객체를 하나 만들어준다.
나는 파일을 socket.ts 파일을 따로 만들어서 필요한 컴포넌트에 불러와 사용했다.
[socket.ts 파일]
import { io } from 'socket.io-client';
import { API, getCookie } from './api';
export const socket = (roomId: string) => {
const token = getCookie();
return io(`${API}${roomId}`, {
extraHeaders: {
Authorization: `Bearer ${token}`,
}
})
}
socket io 객체를 사용하는 컴포넌트는 roomspace를 나누기 위해 string 타입의 경매방 Id 값을 전달해야 한다.
socket 서버를 연결할 땐, io('API 주소') 형태의 메소드를 사용한다.
인증된 사용자만이 socket 서버에 접근할 수 있도록 헤더에 인증 토큰을 추가하여 함께 통신했다.
이제 socket io 객체를 사용해야 하는 파일에 해당 객체를 불러올 것이다.
그리고 소켓을 선언하면서 내가 가장 큰 실수를 했던 것에 대해서도 이야기를 해볼까 한다!!
[RoomPage 컴포넌트 파일]
✔️ 🚨socket 인스턴스 값이 담길 변수는 컴포넌트 바깥에 설정하자🚨
이 부분이 내가 실수했던 부분이었다..
처음 소켓 인스턴스 값이 담길 변수를 컴포넌트 내부에 선언했었다.
그랬더니 웹페이지를 실행시키면 엄청난 수의 통신이 이루어지면서 같은 이름의 접속자수가 엄청나게 증가했고, 결국에는 못버틴 통신도 끊기게 되었다.
이것때문에 백엔드에서 다른 기능 로그 확인할 때 애먹었다고 한다... 입장이벤트 로그만 엄청나게 찍혀있어서...
리액트 렌더링 특성을 제대로 생각하지 않아 일어난 일이었던 것 같다😭
계속 오류났을 때는 진짜진짜 힘들었다. 앞으로는 주의할 예정이다...
import { socket } from '../../utils/socket';
import type { Socket } from 'socket.io-client';
// 이렇게 바깥에 소켓 값이 담길 인스턴스를 우선 선언해주기!!
const socketInstances: Record<string, Socket> = {};
// 컴포넌트 부분
const RoomPage = () => {
return (
.... 내용 ....
);
};
처음에 socketInstances는 단순히 type { Socket } 타입과 null이라는 default 값만을 가지고 있었다.
Record 타입으로 코드를 바꾼 이유는 작업을 하면서 소켓 중복 이슈가 있었고 해당 타입을 활용해보면 해결이 될 것 같아서 만지작하다보니 해결되었다.
✔️ 경매방 접속 후 socket 인스턴스에 값 들어가도록 하기
import { socket } from '../../utils/socket';
import type { Socket } from 'socket.io-client';
const socketInstances: Record<string, Socket> = {};
const RoomPage = () => {
useEffect(() => {
// 이때 id는 각 경매방이 가진 고유 Id
if (!socketInstances[id]) {
socketInstances[id] = socket(`${id}`)
...
// 이 아래부분은 소켓 이벤트응답(on)과 관련된 코드들이 쓰일 예정
...
}
}, [])
return (
... 내용 ...
);
};
useEffect를 사용해 경매방에 첫 접속 시, 만약 socketInstances 값을 가지지 않았다면 roomspace를 연결해주는 socket io 객체 값이 socketInstances에 들어갈 수 있도록 한다.
이렇게 들어간 socketInstances[id] 값을 가지고 우린 앞으로 on, emit 이벤트를 활용할 수 있게 된다!!
활용은 다음 게시글부터 진행하는 게 좋을 것 같다.
글이 길어질 것 같아서 우선 소켓 인스턴스 값을 담는 기본 세팅까지만 글을 작성했다.
다음 게시글에서는 그 인스턴스 값을 활용하여 어떻게 이벤트를 보내고, 받아오는지 살펴볼 예정이다!