웹소캣 활용 실시간 채팅(1)

InSeok·2023년 1월 31일
0

프로젝트

목록 보기
12/13

웹소캣

  • 기존의 단방향 HTTP 프로토콜과 호환되어 양방향 통신을 제공하기 위해 개발된 프로토콜.
  • 일반 socket 통신과 달리 HTTP 80포트를 이용하므로 방화벽에 제약이없다.
  • 접속까지는 HTTP 프로토콜을 이용하고 그 이후의 통신은 자체적이 Websocket 프로토콜로 통신한다.

실시간 채팅앱 구현을 위해 웹소캣을 사용하는 이유

  • 클라이언트A와 서버가 연결되고, 클라이언트B와 서버가 연결된 상태에서 클라이언트A가 채팅 메시지를 보내면 서버가 클라이언트B에게 보내주는 형태이다.
  • 이러한 방식은 클라이언트A와 B 모두 서버와 연결되어 있을 때만 사용 가능하다. 즉, 한 사람이 채팅 앱을 꺼뒀다면 메시지를 주고 받을 수 없는 것이다. 하지만 카카오톡을 비롯한 채팅 서비스는 그렇지 않다.
  • 실제 상용 채팅 서비스는 클라이언트의 채팅 메시지 전송을 수신한 뒤 데이터베이스에 저장하고, 만약 수신자가 서버와 연결되어 있다면 소켓 통신을 통해 메시지를 곧장 보내주고 그렇지 않다면(앱이나 채팅방을 꺼뒀다면) 앱에 메시지가 왔다는 푸쉬 알람을 보내는 형식으로 동작한다.

Websocket 접속 과정

출처: https://blog.naver.com/eztcpcom/220070508655

**Handshake 요청**

GET /chat HTTP/1.1
Host:server.example.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
Origin:http://example.com
Sec-WebSocket-Protocol: v10.stomp, v11.stomp, my-team-custom
Sec-WebSocket-Version: 13
  • Connection: Upgrade : HTTP 사용 방식을 변경하자.
  • Upgrade : websocket : WebSocket을 사용하자.
  • Sec-WebSocket-Protocol: xxx, yyy, zzz : WebSocket을 쓰면서 이 중에서 protocol을 골라서 쓰자.
  • Sec-WebSocket-Key : 보안을 위한 요청 키.

**Handshake 응답**

HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
  • 101 Switching Protocols : Handshake 요청 내용을 기반으로 다음부터 WebSocket으로 통신할 수 있다.
  • Sec-WebSocket-Accept : 보안을 위한 응답 키 - base64.encode(Sec-WebSocket-Key.concat(GUID))

WebSocket Server 특징

  • WebSocket은 하나의 URL을 통해 Connection이 맺어지고, 후에는 해당 Connection으로만 통신한다.
  • Handshake가 완료되고 Connection을 유지한다
  • 가용 Port 수만큼의 Client와 통신할 수 있다.
  • 자주 + 많은 양의 + 지연이 짧아야 하는 통신을 할 수록 WebSocket이 적합하다.

**SockJS**

  • SockJS는 어플리케이션이 WebSocket API를 사용하도록 허용하지만 브라우저에서 WebSocket을 지원하지 않는 경우에 대안으로 어플리케이션의 코드를 변경할 필요 없이 런타임에 필요할 때 대체하는 것이다.
  • 전송 타입
    1. WebSocket
    2. HTTP Streaming
    3. HTTP Long Polling

SockJS Client는 서버의 기본 정보를 얻기 위해 "GET /info"를 호출하는데, 이는 서버가 WebSocket을 지원하는지,  전송 과정에서 Cookies 지원이 필요한지 여부 그리고 CORS를 위한 Origin 정보 등의 정보를 응답으로 전달받는다.

**STOMP**

  • 메시지 전송을 효율적으로 하기 위한 프로토콜로, 기본적으로 Pub/Sub 구조로 되어있다.
  • 클라이언트 /서버 간 전송할 메시지의 유형, 형식, 내용들을 정의한 규칙
  • STOMP를 사용하게 되면, 사실상 Application에서 직접 session을 처리하는 것이 아니라, 오히려 proxy에 가까운 역할을 하게 된다.
  • handshake 과정에서 특정 sub-protocol을 사용하기로 합의할 수 있다. sub-protocol의 하나인 STOMP를 사용하게 되면 단순한 binary, text가 아닌 규격을 갖춘(format) message를 보낼 수 있다.
  • 클라이언트가 서버로 메시지를 보내는 것을 pub, 클라이언트가 서버로부터 메시지를 받는 것을 메시지를 구독한다는 개념으로 sub이 사용된다.
  • 또한 pub/sub을 중계 해줄 수 있는 브로커(broker)가 필요하다. 유명한 메시징 시스템인 RabbitMQ 같은걸 사용하거나 in-memory broker를 구축 해 사용할 수 있다.
    • SpringBoot에는 pub/sub 개념을 지닌 STOMP 프로토콜을 기반으로 in-memory broker를 함께 제공하는 라이브러리가 존재

pub /sub란 메시지를 공급하는 주체와 소비하는 주체를 분리하여 제공하는 메시징 방법이다.
예를 들면 우체통(topic)이 있으면 집배원(publisher)이 신문을 우체통에 배달하는 액션이 있고,
우체통에 신문이 배달되는 것을 기다렸다가 빼서 보는 구독자(subscriber)의 액션이 있다.
여기서 구독자는 여러명이 될 수 있다.

pub/sub 구조

출처: https://medium.com/frientrip/pub-sub-잘-알고-쓰자-de9dc1b9f739

COMMAND
header1:value1
header2:value2

Body^@
  • COMMAND : SEND, SUBSCRIBE를 지시할 수 있다.
  • header : 기존의 WebSocket으로는 표현이 불가능한 header를 작성할 수 있다.
    • destination : 이 헤더로 메시지를 보내거나(SEND), 구독(SUBSCRIBE)할 수 있다. 이를 통해 간단하게 PUB-SUB를 구현할 수 있다.
SUBSCRIBE
destination: /subscribe/chat/room/5

SEND
content-type: application/json
destination: /publish/chat

{"chatRoomId": 5, "type": "MESSAGE", "writer": "clientB"}

메시지 처리 흐름

MessageBroker

  • publisher로 부터 전달받은 메시지를 subscriber에게 메시지를 주고 받게 해주는 역할을 한다.
  • 브로커를 통해 연결된 다른 클라이언트로 메시지를 보내거나, 서버로 메시지를 보내 일부작업을 요청할 수있다.
@Configuration
@EnableWebSocketMessageBroker
public class WebSockConfig  implements WebSocketMessageBrokerConfigurer {

    @Override
    public void configureMessageBroker(MessageBrokerRegistry config) {
        config.enableSimpleBroker("/sub");
        config.setApplicationDestinationPrefixes("/pub");
    }

    @Override
    public void registerStompEndpoints(StompEndpointRegistry registry) {
        registry.addEndpoint("/ws-stomp").setAllowedOriginPatterns("*")
                .withSockJS();
    }
}
  • setApplicationDestinationPrefixes : client에서 SEND 요청을 처리한다.
    • Spring Reference에서는 /topic/queue가 주로 등장하는데 여기서는 이해를 돕기 위해 /publish로 지정하였다.
      • /topic : 암시적으로 1:N 전파를 의미한다.
      • /queue : 암시적으로 1:1 전파를 의미한다.
    • 메시지를 발행하는 요청의 prefix는 /pub로 시작하도록 설정
    • 메시지를 구독하는 요청의 prefix는 /sub로 시작하도록 설정
  • stomp websocket의 연결 end point 는 /ws-stomp로 설정
  • enableSimpleBroker : 해당 경로로 SimpleBroker를 등록한다. SimpleBroker는 해당하는 경로를 SUBSCRIBE하는 client에게 메시지를 전달하는 간단한 작업을 수행한다.
  • enableStompBrokerRelay : SimpleBroker의 기능과 외부 message broker(RabbitMQActiveMQ 등)에 메시지를 전달하는 기능을 가지고 있다.
@RequiredArgsConstructor
@Controller
public class ChatController {

    private final SimpMessageSendingOperations messagingTemplate;

    @MessageMapping("/chat/message")
    public void message(ChatMessage message) {
        if(ChatMessage.MessageType.ENTER.equals(message.getType()))
            message.setMessage(message.getSender() + "님이 입장하셨습니다.");
        messagingTemplate.convertAndSend("/sub/chat/room/" + message.getRoomId(), message);
    }
}
  • SimpleMessagingTemplate : @EnableWebSocketMessageBroker를 통해서 등록되는 bean이다. 특정 Broker로 메시지를 전달한다.

  • @MessageMapping : Client가 SEND를 할 수 있는 경로다. StompWebSocketConfig에서 등록한 applicationDestinationPrefixes와 @MessageMapping의 경로가 합쳐진다.(/pub/chat/message)

    • Websocket으로 들어오는 메시지 발행을 처리한다.
    • 메시지가 발행되면 /sub/chat/{roomId}로 메시지를 send 한다.
    • 클라이언트에선는 해당 주소를 구독하고 있다가 메시지가 전달되면 화면에 출력한다.
      • /sub/chat/{roomId}는 채팅룸을 구분하는 값으로 Topic의 역할
  • SimpAnnotationMethod : @MessageMapping 등 client의 SEND를 받아서 처리한다.

  • SimpleBroker : client의 정보를 메모리 상에 들고 있으며, client로 메시지를 내보낸다.

  • channel : 3종류의 channel이 등장한다

    • clientInboundChannel : WebSocket Client로부터 들어오는 요청을 전달하며, WebSocketMessageBrokerConfigurer를 통해 interceptor, taskExecutor를 설정할 수 있다.
    • clientOutboundChannel : WebSocket Client로 Server의 메시지를 내보내며, WebSocketMessageBrokerConfigurer를 통해 interceptor, taskExecutor를 설정할 수 있다.
    • brokerChannel : Server 내부에서 사용하는 channel이며, 이를 통해 SimpAnnotationMetho는 SimpleBroker의 존재를 직접 알지 못해도 메시지를 전달할 수 있다(결합도를 낮춤)

    참조 :https://dev-gorany.tistory.com/m/224
    https://www.daddyprogrammer.org/post/4731/spring-websocket-chatting-server-redis-pub-sub/

profile
백엔드 개발자

0개의 댓글