-
[Network] Socket, WebSocket, SockJS, STOMP프로젝트/당일 2024. 6. 5. 14:39
WebSocket, SockJS, STOMP 왜 궁금했을까❓
당일 서비스에서 Socket을 이용하여 Spring Boot와 FastAPI 서버간의 통신을 지원했다. 또한, 이전에 Share Your Trip에서도 채팅 서비스를 Socket을 이용하여 구현한 적이 있다. 시간적인 여유가 부족해서 개념적인 부분을 많이 놓쳤는데 이번 포스팅을 통해 확실히 개념을 잡아보려고 한다.
Socket과 WebSocket의 차이점
Socket과 WebSocket은 다른 개념으로 Socket의 경우 TCP/IP 레이어에서 작동하지만 WebSocket의 경우 HTTP 레이어에서 작동한다.
1. WebSocket
- HTTP 프로토콜과 호환되어 양방향 통신을 지원하기 위해 개발된 프로토콜
- HTTP 포트(80)을 사용하여 방화벽 제약이 없음
- 실시간 양방향 통신이 가능하고 CORS 적용이 가능
1.1. WebSocket 통신 과정
- WebSocket을 사용하기 위해서는 Client와 Server간의 HandShake 과정을 수행해야 한다.
- 하지만, WebSocket 역시 TCP/IP 위에서 동작하므로 TCP/IP 연결을 먼저 시도하게 된다.
- HandShake가 정상적으로 이루어지면 Client와 Server간의 통신 채널이 생겨 양방향 통신이 가능하게 된다.
1.2. HandShake 과정
HandShake Request 헤더
GET /ws HTTP/1.1 Host: dangil.store:8000 Upgrade: websocket Connection: Upgrade Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ== Sec-WebSocket-Version: 13
- GET을 사용하고 HTTP/1.1 이상의 버전을 사용해야 한다.
- WebSocket 서버의 주소를 입력한다.
- Websocket 프로토콜로 변환하기 위해 Upgrade에 websocket을 입력한다.
- Upgrade 필드에 명시가 되어 있다면 Connection 필드도 채워줘야 한다.
- 클라이언트에서 서버로부터 오는 handshake 응답을 검증하기 위해 임의의 값을 전송
HandShake Response 헤더
HTTP/1.1 101 Switching Protocols Upgrade: websocket Connection: Upgrade Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
- 101 상태 코드를 사용하여 클라이언트의 요청이 이상이 없다는 것을 나타낸다.
- Switching Protocols를 통해 프로토콜이 전환됬다는 것을 알린다.
- Sec-WebSocket-Accept에는 Sec-WebSocket-key를 통해 받은 임의의 값에 특정 값을 붙인 후 SHA-1로 해싱한 뒤 BASE64로 인코딩한 값을 넣는다. 이를 통해 클라이언트는 HandShake를 검증할 수 있다.
이 과정이 정상적으로 이뤄지고 나서 Frame 단위로 서로 간의 데이터를 송수신 할 수 있게 된다.
2. SockJS
WebSocket은 HTML5 이후에 나왔기에 HTML5 이전의 기술로 구현된 서비스에서는 사용할 수 없어 SockJS 기술 나오게 된다.
- SockJS는 WebSocket으로 연결을 시도하고 실패할 경우 HTTP Streaming, Long-Polling과 같은 HTTP 기반의 기술로 전환하여 연결을 시도한다.
- 또한, 프록시가 연결이 끊겼다는 결론을 내지 않도록 서버에서 주기적으로 Heartbeat 메시지를 전송하도록 한다. 이때, 주기를 정할 수 있는데 그 시간은 heartbeat_interval이라고 한다.
- 서버가 Heartbeat 메시지를 전송하고 클라이언트가 응답하지 않는다면 서버는 클라이언트가 죽은 것으로 판단한다.
- 클라이언트의 경우는 Heartbeat 메시지를 전송받고 heartbeat_timeout동안 메시지를 받지 못한다면 서버가 죽은 것으로 판단하고 연결을 끊는다.
- heartbeat_interval은 기본 값이 25초이며 heartbeat_timeout은 기본 값이 60초이다.
- 만약, STOMP를 이용한다면 SockJS의 Heartbeat는 비활성화 된다.
3. STOMP
- STOMP는 Simple Text Oriented Message Protocol의 약자로 메시지를 효율적으로 처리하기 위한 프로토콜이다.
- WebSocket을 이용하여 통신을 하게 된다면 해당 메시지가 어떤 요청이고 어떻게 처리해야 하는 지를 직접 구현해야 한다.
- STOMP를 사용한다면 클라이언트와 서버가 전송할 메시지의 유형, 형식, 내용들을 정의하여 보다 명확하게 처리가 가능하며 메시지의 헤더에 값을 부여하여 인증 처리도 할 수도 있다.
3.1. PUB / SUB 구조
- STOMP는 PUB/SUB 구조로 동작하여 메시지를 공급하는 주체와 소비하는 주체로 나뉜다.
- publisher가 특정 topic에 메시지를 보내면 해당 topic을 구독하고 있는 subscriber 모두에게 메시지가 전달된다.
3.2. STOMP의 Frame 구조
COMMAND header1:value1 header2:value2 Body^@
- COMMAND - 메시지의 타입을 나타내는 문자열로 SEND, SUBSCRIBE, UNSUBSCRIBE 등이 존재한다.
- header - 추가 정보를 제공하는 헤더로 기존의 WebSocket이 표현하지 못하는 헤더를 지원한다.
- destination - 해당 헤더를 통해 SEND, SUBSCRIBE를 할 수 있다.
- Body - 메시지의 내용이 담긴다.
- ^@ - NULL 문자로 Body의 끝을 나타낸다.
3.3. Frame 예시
SUBSCRIBE(구독)
SUBSCRIBE destination: /sub/chat/room/1 id: sub-1 ^@
SEND(전송)
SEND content-type: application/json destination: /pub/chat {"roomId": 1, "type": "MESSAGE", "writer": "chanhong9784"}^@
MESSAGE(브로드캐스트)
MESSAGE destination: /sub/chat/room/1 {"roomId": 1, "type": "MESSAGE", "writer": "chanhong"}
3.4. STOMP 통신 과정
- JunYeong이 채팅룸 1번을 구독한다는 메시지를 서버에게 전송한다.
- Chanhong이 채팅룸 1번에 메시지를 전송한다.
- 서버는 이를 받아 Broker에게 전달하고 Broker는 이를 받아 채팅룸 1번 구독자들에게 브로드캐스트 한다.
- 구독자들에게 메시지를 응답한다.
Spring Boot에서 STOMP 구현 과정을 알아보고 싶다면 여기를 클릭하면 된다!
'프로젝트 > 당일' 카테고리의 다른 글
[Infra] Apache Kafka (0) 2024.06.06 [Spring Boot / React Native] FCM을 활용한 알람 서비스 구축 (0) 2024.06.05 [Infra] Kafka를 통한 Spring Boot - FastAPI 통신 (0) 2024.06.03 [Infra] STOMP를 통한 Spring Boot - FastAPI 통신 (0) 2024.06.02 [FastAPI] AWS S3를 활용한 이미지 저장 (0) 2024.06.01