이번에는 채팅은 어떻게 구현되는지, 어떤 기술이 사용되는지 함께 알아보도록 하겠습니다.
🖐🏻 잠깐!
스프링+리액트 조합의 채팅을 구현하는 포스팅 중 일부입니다. (stomp, soket.js)
저는 프론트를 맡고 있고 스프링을 다룰 줄 몰라 스프링 코드는 제공하지 않습니다.
하지만 기본 개념을 같이 다루어보며 DB 구조와 채팅 구현이 어떻게 이루어지는지를 알아보겠습니다.
✍🏻 목록
기본적으로 클라이언트와 서버의 관계는 stateless 하게 이루어져 있습니다.
즉 클라이언트에서 Request 를 날리면 서버에서 Response 하는 과정으로 이루어져있습니다.
(그림이 구린데 이해 부탁드려요)
자 그런데 웹소켓은 Statefull protocol 입니다.
즉 요청을 매번 보내는게 아니라 connection을 유지해서 양방향 통신 또는 데이터 전송이 가능하도록 하는 기술입니다.
(그림이 구린데 이해 부탁드려요)
웹소켓은 handShake과정을 통해 Client와 Server 접속을 유지합니다.
웹소켓이 기존의 일반 TCP소켓과 다른점은 최초접속이 일반 http request를 통해 handshaking과정을 통해 이루어진다는 점입니다.
Client에서 랜덤하게 생성된 키값을 전송하고, Server는 이 키값을 바탕으로 토큰을 생성하여 Client에 Response를 보내어 Client와 Server간의 handShaking이 이루어 집니다!
그래서 채팅, 실시간화상통화 등 양방향 통신에 아주 유용하게 쓰인답니다!
인터넷 익스플로러 구버전의 사용자는 webcoket으로 작성된 웹페이지를 볼 수 없습니다. 이를 해결하기위해 soket.io는 웹페이지가 열리는 브라우저가 websoket을 지원하면 일반 websoket방식으로 동작하고 지원하지 않는 브라우저라면 http를 이용해 websoket을 흉내내는 방식으로 통신을 지원합니다. soket.io는 nodeJS에 종속적 입니다.
스프링에서 위와 같은 브라우저 문제를 해결하기 위한 방법으로 soketJS를 솔루션으로 제공합니다. 서버 개발시 일반 websoket으로 통신할지 SoketJS 호환으로 통신할지 결정할 수 있습니다. 그리고 클라이언트는 SoketJS client를 통해 서버랑 통신합니다.
stomp는 단순 (또는 스트리밍) 텍스트 지향 메시징 프로토콜입니다. spring에 종속적이며, 구독방식으로 사용하고 있습니다. 가벼워서 보통 많이들 사용합니다.
보통 Node.js 를 이용할땐 soket.io 를 주로 사용하고,
Spring을 사용할땐 soket.js, stomp 를 주로 사용합니다.
이번 파트에서는 soket.io말고 soket.js와 stomp에 대해 더 알아보겠습니다.
// 대략적인 구조만 보여드린 겁니다! Type과 Join은 무시해주세요!
저는 프론트 개발 파트를 맡았습니다. 라이브러리로 (Typescript + react) 리액트 내에서 웹소켓 클라이언트를 만드는 방법을 알아보겠습니다! 사실 어려운 부분은 서버쪽이지.. 클라이언트는 크게 어렵지 않습니다.
(근데 예전에 stomp는 리액트에서 바로 못쓴다는 이야기가 있더군요. 근데 지금은 쓰던데..왜지? 버전업하면서 쓸 수 있게 달라진건가요? 아시는 분은 댓글좀 달아주세요)
이번 포스팅에서는 stomp5버전을 기준으로 코드를 작성합니다.
좀 살펴보니 stomp가 5버전부터 크게 달라졌더라구요!
3, 4버전을 쓰시던 분은 아래 링크를 살펴보세요!
https://stomp-js.github.io/guide/stompjs/upgrading-stompjs.html
전체적인 stomp의 흐름은 다음과 같습니다.
서버와 연결할 클라이언트 객체 생성
→ 서버와 연결할 클라이언트 Connection
→ 메세지 전송 전 Subscriber와 Publisher를 지정
→ Subscribe를 하면 해당 URL로 나에게 메세지를 보낼 수 있는 경로가 생긴다
→ Publisher를 하면 Publishe한 URL로 메세지가 이동한다
Publishe와 Subscribe가 이해가 잘안돼요! 하시는 분들은 아래 그림을 봐주세요!
– app: WebSocket으로의 앱으로 접속을 위한 포인트가 되며 메시지를 실제로 보낼 때 사용된다
– topic: 일 대 다수의 커넥션에서 메시지를 전송한다
– queue: 일 대 일의 커넥션에서 메시지를 전송한다
– user: 메시지를 보내기 위한 사용자를 특정한다
우선 사전에 서버 개발자 분께 소켓 연결 endpoint를 받았습니다.
endpoint : "/api/ws"
웹소켓 클라이언트 구축전 soketjs-client와 stompjs를 install 해주겠습니다.
사실 최근 브라우저는 모두 소켓을 지원합니다. 다만 IE 9 이하에서는 soket을 지원하지 않는 걸로 알고 있습니다.
소켓을 지원하지 않는 브라우저 대응을 위해 soketjs-client를 추가로 설치했습니다.
npm i soketjs-client, @stomp/stompjs --save
그리고 저는 typescript를 사용하고 있었기에 .d.ts file을 install 해주었습니다.
(typescript를 사용하고 계신게 아니라면 따로 설치할 필요가 없습니다)
stompjs는 얼마전까진 타입을 지원안해줘서 따로 declare file을 직접 정의해 썼는데, 이번에 보니 생겼더라구요!
npm i @types/stompjs, @types/soketjs-client --save
Import는 다음과 같이 하면 됩니다.
import SockJS from 'sockjs-client';
import StompJs from '@stomp/stompjs';
우선 client객체를 만들어 주도록 하겠습니다.
const client = new StompJs.Client({
brokerURL: '/api/ws',
connectHeaders: {
login: 'user',
passcode: 'password',
},
debug: function (str) {
console.log(str);
},
reconnectDelay: 5000, //자동 재 연결
heartbeatIncoming: 4000,
heartbeatOutgoing: 4000,
});
연결됐을때 실행할 함수와 에러처리를 담당하는 함수를 만들어 주도록 합니다.
client.onConnect = function (frame) {
};
client.onStompError = function (frame) {
console.log('Broker reported error: ' + frame.headers['message']);
console.log('Additional details: ' + frame.body);
};
그 후 클라이언트를 활성화 시켜줍니다
client.activate();
클라이언트 비활성화를 해주고 싶을때는 아래와 같이 입력하면 됩니다. 활성화 연결이 있는 경우
다시연결 및 연결을 중지합니다.
client.deactivate();
기본적인 전체 구조는 다음과 같습니다.
brokerURL이 http 일경우 ws를 https일 경우 wss를 붙여서 사용하시면 됩니다!
const client = new StompJs.Client({
brokerURL: 'ws://local.corsmarket.ml/api/ws',
connectHeaders: {
login: 'user',
passcode: 'password',
},
debug: function (str) {
console.log(str);
},
reconnectDelay: 5000,
heartbeatIncoming: 4000,
heartbeatOutgoing: 4000,
});
client.onConnect = function (frame) {
// Do something, all subscribes must be done is this callback
// This is needed because this will be executed after a (re)connect
};
client.onStompError = function (frame) {
// Will be invoked in case of error encountered at Broker
// Bad login/passcode typically will cause an error
// Complaint brokers will set `message` header with a brief message. Body may contain details.
// Compliant brokers will terminate the connection after any error
console.log('Broker reported error: ' + frame.headers['message']);
console.log('Additional details: ' + frame.body);
};
client.activate();
SoketJS로 소켓 미지원 브라우저 대응하기
(필요에 따라 서버랑 잘 협의해서 선택적으로 사용하시길 권장드립니다.)
if (typeof WebSocket !== 'function') {
client.webSocketFactory = function () {
return new SockJS('http://localhost:15674/stomp');
};
}
클라이언트와 서버가 연결 되면 publish 메서드를 사용하여 메세지를 보낼 수 있습니다.
destination는 목적지라는 뜻입니다 어디로 메세지를 보낼지를 결정합니다.
body는 보낼 내용입니다.
client.publish({
destination: '/topic/general',
body: 'Hello world',
headers: { priority: '9' },
});
*v5부턴 바이너리 메세지 전송도 지원된다고 하네요! (header에 'content-type': 'application/octet-stream')로 contentType을 써줍니다.)
const binaryData = generateBinaryData();
client.publish({
destination: '/topic/special',
binaryBody: binaryData,
headers: { 'content-type': 'application/octet-stream' },
});
구독한 대상에 대해 메세지를 받기 위해 subscribe 메서드를 사용합니다!
const subscription = client.subscribe('/queue/test', callback);
이어서 다음시간에는 채팅을 직접 구현해 보도록 하겠습니다.
(다시말씀드리지만 서버쪽 코드는 제공 되지 않습니다. 좋은 강의를 참고자료에 남겨 두도록 하겠습니다.)
✔️✔️✔️(틀린 내용에 대한 피드백은 언제나 환영입니다)✔️✔️✔️
✔️ [stomp문서]
http://jmesnil.net/stomp-websocket/doc/
https://stomp-js.github.io/guide/stompjs/using-stompjs-v5.html
✔️ [MDN]
https://developer.mozilla.org/ko/docs/WebSockets/Writing_WebSocket_client_applications
✔️ [영상]
https://www.youtube.com/watch?v=gQyRxPjssWg&ab_channel=시니어코딩indiflex
https://yoon-dumbo.tistory.com/22
이거랑 내용이 같네요! 잘읽었습니다:)