우선 글을 작성하기전 첫글을 2월달에 작성하고 7월이 되서야 다음글을 작성하게 되어 미안하다.
분명 전시간에는 TextWebSocketHandler 인터페이스를 상속받는 단순한 구조의 채팅방이었다. 하지만 본글에는 갑자기 단계가 많이 발달된 채팅방일 수 있다. 그렇기에 이해가 더딜 수 있기 때문에 그전에 구현할 채팅방의 구조와 웹소켓 발전에 대해서 설명을 하도록 하겠다.
기본 HTTP통신의 경우 서버 측으로 요청(Request)를 보내면 응답(Response)을 해주는 방식이며 페이지를 요청할 시에 그 페이지를 구성하고 있는 모든 리소스를 요청을 보낸 클라이언트 쪽으로 보내게된다. 이런 HTTP 통신 기반으로 실시간 채팅을 구현한다고 해보자. 채팅은 상대방이 언제 메시지를 나에게 줄 것이라는 확실성이 없기 때문에 항상 대기상태여야하는데 HTTP에선 이를 위해 양쪽 모두가 매번 새로고침을 눌러 서버에 Request 와 Response를 수 없이 반복해야한다.
이러한 한계점이 있기 때문에 개발자들은 Polling이라는 기술을 도입하여서 일정 주기로 서버에 request를 보내는 방식으로 한계점을 해결하려했으나 불필요한 request와 커넥션이 생겨 문제가 되어 이벤트가 발생하는 경우에만 서버에서 response하는 Long Polling이라는 기술로 발달되었지만 이 또한 모든 이벤트에 response 되어 불필요한 커넥션이 많이 쌓이게된다. 이후 Streaming 이라는 기술이 나와서 서버로의 재요청이 사라지고 문제점이 해결되나했으나 지속적인 업데이트를 시키려고 무한정 요청을 대기시킨다. 이 기술의 경우 영상 스트리밍에 많이 사용된다.
웹 소켓이 등장하게 된 배경은 HTTP의 이러한 명확한 한계점을 극복하기 위해서 생겨났다. 기존의 Request - Response 방식을 버리고 브라우저(클라이언트)와 서버간에 커넥션을 열어 Open,Close인지 상태를 확인 하는 방식을 선택하여 서로가 Open인 경우에는 양방향 통신이 가능하고 Close를 직접하지 않는 이상 계속 Open상태이다.
이런 웹소켓의 메시지 프로토콜 방법에는 두가지가 존재하는데 하나가 내가 첫 블로그에 작성한 프로젝트의 형태가 TTMP(TCP Transaction Multiplexing Protocol)로 단순 텍스트 지향 메시지 프로토콜이다. 정말 단순하게 텍스트기반의 통신이 되는 용도이고 세션에서 모든 이벤트를 처리를 해야하기 때문에 대체제로 STOMP(Simple Text Oriented Messaging Protocol)가 등장하게 되었다.
TTMP의 경우 WebScoket을 통해 통신되는 메시지의 요청 타입,포맷,통신과정을 일일히 다 지정을 해주어야하지만 STOMP를 사용함에 따라서 클라이언트와 서버간의 메시지의 형식,유형,내용을 정의해주기 때문에 STOMP를 사용하는것이 개발자입장에서 편함과 동시에 더많은 트래픽을 효과적인 방법으로 처리할 수 있어 좋다.
또한 메세지 전송에 있어서 효율적으로 처리하기 위해 탄생한 프로토콜이고 /pub , /sub 구조로 되어있어 메세지를 전송하고 메시지를 받아 처리하는 부분이 확실하기 때문에 개발자 입장에서도 코드가 명확해진다. 사실 이렇게 저렇게 많은 말로 포장을 해주었지만 STOMP는 다음 한줄로 정의할 수 있다.
STOMP 프로토콜은 WebSocket위에서 동작하는 서브 프로토콜로서 클라이언트와 서버간의 전송할 메시지의 유형,형식,내용을 정의해주는 매커니즘이라고 생각하면된다.
STOMP 프로토콜의 특징
STOMP는 기존의 웹소켓 통신과는 다른 구조를 가진다. 위에서 말했듯이 STOMP도 결국 WebSocket위에서 작동하기 때문에 양방향 네트워크의 프로토콜을 기반으로 작동한다. 그리고 이름대로 Text or Binary 데이터형식의 Text지향 프로토콜이다.
STOMP는 pub/sub라는 메세지를 공급하는 주체와 소비하는 주체를 분리하여서 제공하는 메세징 방법으로 이를 사용하면 메시지를 보내는 주체와 받는자가 명확해진다. 예시를 들자면 각자 개인집(Topic)에 쿠팡기사(Publisher)가 택배를 배달해주는 행동과(퍼블리싱) 이를 택배를 시킨사람이 배달되는걸 기다렸다가 택배가 오면 받는 행동(Subscriber)행동으로 나뉜다. 웹소켓은 주식,채팅 등 실시간 통신에서 많이 사용되지만 가장 이해가 빠른 채팅으로 비유를 하자면 다음과같다.
채팅방 생성시에는 pub/sub 구현을 위한 Topic이 생성이되고 이 채팅방에 입장하는 것이 Topic을 구독하는것이 된다. 채팅방에서 메세지를 송수신하는것은 생성된 Topic을 통해 송신(pub)과 수신(sub) 하는것과 같다.
또한 클라이언트는 메세지를 전송하기 위한 명령어로 Send,Subscribe를 사용하고 이를 통해 메세지가 무엇이고 누가 받아서 처리하는지에 대한 정보가 각각 Frame과 Header에 담겨있다. 이때 Publisher와 Subscriber 관계에서 어디에 전송할지와 메시지 구독을 위하여 destination을 헤더로 요구한다. 하지만 Publisher와 Subscriber는 직접통신이 아닌 중간 Broker라는 개념을 통하여 사용자들에게 메세지를 보내거나 서버가 특정작업을 할 수 있는데 Spring의 경우 STOMP Broker가 동작하여 중간의 메세지의 size와 헤더등 다양한 정보를 담고 있는 Broker가 정확한 곳에 메세지를 보내주는 중간역할을 해주고있다. 이외에도 Subscribe중인 클라이언트들에게 메세지를 보낼때 클라이언트의 Subscribe정보를 자체적으로 메모리에 유지하여 다시 연결을 시도할필요가 없다. 이를 그림으로 표시하면 다음과 같다
우리는 WebSocket을 사용할때 Config 파일을 작성하게되고 그안에는 송신과 수신의 Url을 지정해야한다. 좀더 이해하기 쉽게 파이프를 하나 지정하여 송신을 할때와 수신을 할때 무조건 지나가야하는 파이프가 하나 있다고 생각하면 편하다.
순서대로 설명을 하자면 abc/123과 topic/123으로 Send 요청이 들어온다. 이를 서버의 Requst 에서 받아 처리하게 되는데 Config에 등록된 url이라면 Simple Broker로 보내져 바로 Response처리하여 클라이언트는 받을 수 있게된다.
하지만 abc로 오는 요청은 config에 등록되어있지 않기 때문에 Message Handler 쪽으로 가 찾게된다. 없을경우 반환되지 않고 있을경우 찾아서 반환해준다. 순서대로 보자면 다음과같다.
1. Config에 저장된 URL로 전송
2. 해당 메세지가 Message Broker(Simple Broker)에 전달
3. Message Broker는 해당 토픽과 대응되는 Response Channel로 내용과 헤더를 Route시켜준다.
4. 클라이언트가 Response Channel로 메세지를 받아올 수있다.
아 맞다 SockJS!
STOMP를 얘기하면서 여러 블로그에서 SockJS얘기도 빠지지 않는다.
대부분의 웹소켓으로 구현한 채팅들 불과 내가 전 포스팅에서도 작성한 웹소켓 프로젝트 또한 Chrome, Edge, FireFox 등 여러브라우저에서 호환이된다. 하지만 모바일 Chrome 과 IE(Internet Explorer)에서는 작동하지 않는 모습을 볼 수 있다.
간혹 이렇게 브라우저 자체에서 지원하지 않는 경우들이 있다. 정리를해서 좀더 깔끔히 말하자면 다음과 같다.
1. 모든 브라우저들이 WebSocket을 지원해주진않는다.
2. Server / Client 사이에 존재하는 Proxy가 에러를 일으키거나 Connection이 길어지게 되면 서버연결이 자동으로 끊기는 경우가 더러 존재한다.
이런 문제점들을 맞이하면 개발자는 상당히 억울해진다. 나는 코딩을 열심히 해놨는데 브라우저 때문에 작동하지않는다니?? (이거 완전억까네) 하지만 이런 문제점을 해결하기 위한 수단으로 WebSocket Emulation이라는게 존재한다.
위에서 말한 Polling과 Long-Polling 같은 Http기반의 기술들을 기억하는가? WebSocket Emulation은 우선적으로 WebSocket으로 연결을 시도하고 연결이 되지않는다면 위에 언급한 기술들을 사용하여 양방향 네트워크 구축을 이루어준다. 그중 Spring에서 대표적인것이 SockJS 이다. 그리고 Spring 프레임워크에서 지원해주기 때문에 Spring 개발자들은 이를 사용하면 대부분의 문제를 해결할 수 있다. 그리고 위에 설명을 보면 알겠지만 SockJS는 런타임에서 생기는 에러들을 대처하기 위해 생긴 라이브러리이다.
SockJS는 브라우저에서 정상적으로 통신이 작동하도록 해주게 하기위해 다음과 같은 전송순서 및 타입을 가진다.
- 1. WebSocket
- 2.HTTP Streaming
- 3.HTTP Long Polling
그렇다면 다음으로는 기본적으로 SockJS가 어떻게 동작하는지 보자.
SockJs는 서버의 기본정보 즉 서버가 WebSocket을 지원하는지 , 전송과정에서 필요한 Cookie, 헤더의 정보, Cors 정보가 담긴 Origin 정보를 전달받는다. 이는 우리가 웹소켓을 최초실행할때 Info라는 url을 Get으로 가져와 확인한다. 이를 바탕으로 SockJS는 어떠한 전송 타입으로 사용할지 결정하고 순차적으로 실행한다.
{entropy: 108028315, origins: ["*:*"], cookie_needed: true, websocket: true}
다음 코드를 보면 Origin은 개방된 상태이고 쿠키를 필요로 하며 WebSocket이 사용가능한 환경이란걸 알 수 있다.
그리고 모든 웹소켓 요청은 다음과 같은 구조를 가지게 된다. HandShaking을 위해서 최초 하나의 HTTP요청을 필요로 한다.
https://host:port/endPoint/Server Id/Seession Id/transport
이중 Endpoint는 Config파일에서 개발자가 지정하는 것이고 Server Id와 Session Id는 임의로 부여되고 transport는 현재 접속 타입에대해 나타낸다.
이정도면 STOMP에 대한 설명과 웹소켓에 대한 기본 설명은 끝난거 같다.
앞으로는 코드위주의 설명을 나아갈 것이다. 우선 프로젝트는 다음과 같이 구성될것이다.
1. JPA 환경에서 회원 엔티티를 생성하여 인증된 회원만 채팅 시스템을 사용한다.
2. 채팅방 또한 DB로 관리하며 회원과 채팅방은 다대다 (N:N) 관계이므로 중간테이블을 사용한다.
3. 채팅 내용은 Redis 라는 캐시 DB를 사용하여 저장한다.
4. 외부 메시지 시스템인 RabbitMQ 또는 Kafka를 사용하여 본다.
5. 기존의 Relation DB에 채팅내용을 저장하였을 때와 캐시 DB의 차이점을 알아본다.
6. 1:1 채팅과 1:N 채팅 구조를 알아본다.
7. WebSocketConfig파일 작성및 컨트롤러 작성
그렇다면 다음부터는 코드로 찾아오도록 하겠다.
'Spring-boot' 카테고리의 다른 글
@Getter와 @Setter는 왜 지양되어야 하는가? (0) | 2022.11.29 |
---|---|
[Spring] 패키지 구조 (0) | 2022.10.21 |
[JPA] 비즈니스 로직 디자인 패턴에 관한 정리 (0) | 2022.08.27 |
[JWT-Token] 스프링 시큐리티 JWT 토큰을 이용한 스프링부트 프로젝트 만들기 -(1) (0) | 2022.06.28 |
Web-Socket 공부 흔적(1) (0) | 2022.02.23 |