저번 포스트에 이어, 생각보다 많은 부분들이 개발되었다.
우선, 서버가 개발되어 StompJs
와 SockJs
를 이용하여 소켓 통신 테스트를 해볼 수 있었다.
우선 진행하기 앞서, sockjs
를 지난 포스트에서 스프링과 소켓 통신을 자주 사용한다 하였는데, STOMP
는 무엇인가??
STOMP is the Simple (or Streaming) Text Oriented Messaging Protocol
즉, STOMP는 텍스트 기반의 메세징 프로토콜로, STOMP 브로커를 사용하는 다양한 언어와 플랫폼, 브로커들 사이의 메세지 전송을 간편하게 한다.
제작한 서버는 STOMP의 브로커를 바탕으로 만들어진 Spring 서버이다. 따라서 클라이언트 측도, StompJs를 활용하면 훨씬 수월하게 연결 할 것이다.
useEffect(()=>{
client.current = new StompJs.Client({
webSocketFactory: () => new SockJS(userInfo.current.SERVER_URI), // proxy를 통한 접속
debug: function (str) {
if(userInfo.current.DEBUG) console.log(str);
},
reconnectDelay: 5000,
heartbeatIncoming: 4000,
heartbeatOutgoing: 4000,
onConnect: () => {
setLive(true);
client.current.subscribe(userInfo.current.STOMP_PROP.subscribe, function(chat){
const recieved = JSON.parse(chat.body);
console.log(recieved)
if(recieved.writerId !== userInfo.current.USER_INFO.id) setReceivedData((prev)=> ({...prev, writerId: recieved.writerId, writerNickname: recieved.writerNickname ,message: recieved.message}));
});
client.current.publish({destination: userInfo.current.STOMP_PROP.enter, body: JSON.stringify({ roomId: userInfo.current.STOMP_PROP.roomId, writerId: userInfo.current.USER_INFO.id })})
},
onStompError: (frame) => {
console.error(frame);
},
});
client.current.activate();
return () => {client.current.deactivate();}
},[])
다음은 메세지 창이 렌더링 될 때, useEffect 훅을 사용하여 서버와 소켓 통신을 연결하는 부분이다.
소켓 정보를 담고있는 객체는 useRef
를 이용하여 client
변수에 담았다. 불필요한 리렌더링을 방지하고자 하였다.
사용된 StompJs.Client 함수의 인자값으로는 설정값들을 갖고 있는 객체가 들어갔다.
webSocketFactory
는 URL 프로토콜 컨벤션에 맞추어 설정 할 수 있다. SockJs(http:
or https:
)를 활용하거나, 혹은 WebSockets(ws:
/ wss:
)을 활용할 수 있다. 후자의 경우, webSocketFactory 가 아닌 brokerURL,
brokerURL: 'ws://localhost:15674/ws',
이런 식으로 입력하면 될 것이다.
공식사이트에서는 fallback으로 webSocket을 사용하는 방법 또한 소개하니 참고하면 좋을 것이다.
debug는 말 그대로 디버그를 위한 부분이다. STOMP를 통해 수신하고, 전송한 메세지들을 문자열 형식으로 받아 볼 수 있다.
가장 처음 세 문자인 <<<
는 수신자의 방향에 따라 나타난다. 이 프로젝트의 경우, 클라이언트가 디버깅 모드를 적용 할 수 있도록 userInfo.current.DEBUG
의 불리언 값을 받아 나타낼 수 있도록 조건문 처리 하였다.
heartbeat 공식 문서를 통해서는 건강한(healthiness) TCP 연결을 하고 있고, 계속 연결되어 있는지 확인하는 수단이라고 나와있지만, stack overflow의 한 답변에, 서버와 클라이언트 측 이점을 잘 설명해주어 인용하였다.
Heart-beats potentially flow both from the client to the server and from the server to the client so the "remote end" referenced in the spec here could be the client or the server.
For the server, heart-beating is useful to ensure that server-side resources are cleaned up in a timely manner to avoid excessive strain. Server-side resources are maintained for all client connections and it helps the broker to be able to detect quickly when those connections fail (i.e. heart-beats aren't received) so it can clean up those resources. If heart-beating is disabled then it's possible that a dead connection would not be detected and the server would have to maintain its resources for that dead connection in vain.
For a client, heart-beating is useful to avoid message loss when performing asynchronous sends. Messages are often sent asynchronously by clients (i.e. fire and forget). If there was no mechanism to detect connection loss the client could continue sending messages async on a dead connection. Those messages would be lost since they would never reach the broker. Heart-beating mitigates this situation.
쉽게 말해, 서버측에서는 서버-사이드 리소스들이 적시에 클린업 되고, 리소스들이 클라이언트 연결을 위해 지속되며, 클라이언트와 연결이 실패할때 즉각 대응 할 수 있도록 해준다.
클라이언트측에서는 비동기 통신에 대한 message loss를 피할 수 있게 해주며, 죽은 서버 혹은 통신에 대해 메세지를 보내지 않도록 방지하여준다.
onConnect
부분은 소켓 통신이 연결되었을 때 불리는 콜백이다. 사실 이 연결 중 가장 중요한 부분이기도 하다. 콜백으로 3개의 메소드가 실행되는데 첫번째 setLive(true)
는 메세지 창이 렌더링 될 수 있도록 boolean 값을 갖고 있는 state
이고,
client.current.subscribe
와 client.current.publish
는 각각 소켓 통신의 구독과 발행을 의미한다.
client.current.subscribe의 첫 번째 인자는 구독하는 소켓의 destination을 입력해준다. 위의 경우, 클라이언트가 정의를 해주어야 하기 때문에, 변수를 할당하였고 클라이언트 측에서 보내주는 예시는 다음과 같다.
let data = {
accessToken: accessToken,
userInfo: res.data,
serverUri: 'http://localhost:8080/stomp/chat',
stomp: {
subscribe: '/channel/chat/room/open',
enter: '/publish/chat/enter',
send: '/publish/chat/message',
roomId: 'open',
},
debugMode: true,
};
콜백으로 정의한 함수는 서버에서 보내는 메세지가 담긴다. 구독한 destination에서 보내는 소켓 통신 정보들이 콜백의 첫번째 인자에 담겨 들어오게 되고, 이때 stringfy 된 JSON 데이터가 넘어오기 때문에, JSON.parse()
를 이용하여 가용할 수 있게 넘겨받고 state에 저장하였다.
client.current.publish
는 이후의 코드에서도 볼 수 있지만, SEND의 역할을 한다. 즉, 원하는 데이터를 보낼 수 있다. destination과 body를 담은 객체를 인자로 담아주었는데, enter라는 destination은 사용자가 채팅방에 접속했다는 알림을 보내기 위한 부분이다. 즉, 채팅방에 접속한 모든 사용자는(subscribe한 사용자) enter가 publish로 들어오는 순간 메세지(알람)을 받게 될 것이다.
정의 된 StompJs 객체는 activate 시켰고, 언마운트(componentWillUnmount
) 시 통신을 종료 시킬 수 있도록 정의하였다.
'개발일지' 카테고리의 다른 글
[이미지 렌더링: 01] 무한스크롤과 이미지 렌더링 (0) | 2022.07.01 |
---|---|
[크롬 익스텐션: 06] StompJs & SockJs, 디자인 및 구현 - 2 (0) | 2022.06.23 |
[리펙토링: 나들서울] Debounce & Throttling (0) | 2022.06.14 |
[크롬 익스텐션: 04] 클라이언트 - 소셜 로그인 (0) | 2022.06.09 |
[크롬 익스텐션: 03] sockjs-client를 이용한 소켓 통신 (1) | 2022.06.05 |