카테고리 없음

웹소켓

박수연_01 2023. 11. 25. 22:46

구현하려는 기능

팔로워들의 게시물을 인스타그램의 스토리 조회 기능처럼 게시물이 업로드된 순간 사용자에게 보내주도록 한다.

 

스토리 기능을 구현하기 위해서는 프론트의 호출 없이도 업데이트된 내용을 보내줘야 한다. 이를 위해서 우리는 한번 연결해두면 호출을 하지 않아도 프론트로 정보를 전송할 수 있도록 웹소켓을 사용했다.

 

사실 찾아보면서 느낀 것인데 프론트에서 서버로 보내는 내용이 없기 때문에 단방향으로 사용하는 SSE(server-sent-event)를 사용하는게 더 적합했다고 본다. 

 

 

먼저 해당 기능을 구현할 수 있는 4가지 방법에 대해 알아보겠다.

Short Polling

클라이언트가 주기적으로 서버로 요청을 보내는 방법

일정 시간마다 서버에 요청을 보내 데이터가 갱신되었는지 확인하고 만약 갱신되었다면 데이터를 응답 받는다.

실시간성을 보장하기는 힘들다고 한다.

Long Polling

요청을 보내고 서버에서 변경이 일어날 때까지 대기하는 방법

실시간 메시지 전달이 중요하지만 서버의 상태가 빈번하게 변하지 않는 경우에 적합

서버로부터 응답을 받고 나면 다시 연결 요청을 하기 때문에, 상태가 빈번하게 바뀐다면 연결 요청도 늘어나게 된다.

 

Server-Sent Events(SSE)

서버와 한번 연결을 맺고나면 일정 시간동안 서버에서 변경이 발생할 때마다 데이터를 전송받는 방법

응답마다 다시 요청을 해야하는 Long polling 방식보다 효율적

 

websocket

브라우저에서 소켓 통신을 이용하기 위해서는 소켓 통신이 가능한지 확인하는 핸드셰이크(Hand Shake) 과정이 필요하다. 이후 클라이언트에서 ws://에 api를 연결해서 사용한다.

양방향 통신과 실시간 네트워킹에 유용하다.

 

나는 메세지 프로토콜로 stomp를 선택했다. rabbitmq나 redis의 선택지도 있었지만 학습하는데 걸리는 시간과 우리는 서버 1대로 이용했기 때문에 서버 여러대를 이용해서 메세지간의 형식을 맞출 필요가 없었기 때문에 스프링에서 제공되는 stomp(Simple Text Oriented Messaging Protocol)를 선택했다.

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

@Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
    registry.addEndpoint("/ws-stomp").setAllowedOriginPatterns("*")
            .withSockJS();

"/sub" 주소로 게시물을 보내고 클라이언트에서 "sub"로 연결해서 그 게시물을 가져오도록 하였고 pub의 경우에는 클라이언트에서 서버로 보내는 정보가 없기때문에 현재 사용되지 않고 있다.  

SockJS는 어플리케이션이 WebSocket API를 사용하도록 허용하지만 브라우저에서 WebSocket을 지원하지 않는 경우에 대안으로 어플리케이션의 코드를 변경할 필요 없이 런타임에 필요할 때 대체하는 것이라고 한다.

 

@PostMapping
@PreAuthorize("hasAnyRole('USER', 'ADMIN')")
@Operation(summary = "게시물 생성", description = "오늘 날씨에 맞는 게시물을 생성한다.")
public ResponseEntity<ResultResponse> create(
        @Valid @RequestBody PostCreateRequestDto reqDto,
        @LoginUser User loginUser
) {
    PostCreateResponseDto resDto = postService.create(loginUser, reqDto);


    ChatMessage message = ChatMessage.builder()
            .roomId(loginUser.getEmail())  // 팔로워들의 기본키 id를 사용하도록 설정
            .sender(loginUser.getEmail())
            .message(resDto)
            .build();

    // 메시지 전송
    chatService.sendMessage(message, loginUser);


    return ResponseEntity.ok(ResultResponse.of(ResponseCode.POST_CREATE_SUCCESS, resDto));
}

 

@Service
@RequiredArgsConstructor
public class ChatService {

    private final SimpMessageSendingOperations messagingTemplate;
    private final FollowRepository followRepository;

    public void sendMessage(ChatMessage message, User sender) {
        List<Follow> followers = followRepository.findByToUser_Id(sender.getId());
        for (Follow follower : followers) {
            messagingTemplate.convertAndSend("/sub/chat/room/" + follower.getFromUser().getId(), message);
        }
    }
}

 

사용자가 게시글을 포스팅하면 그 사용자를 팔로우한 사람들에게 웹소켓을 통해 게시글이 클라이언트쪽으로 보내지도록 만들었다.

한 유저당 하나의 url로 생성해 그 url로 팔로우한 사람들의 게시물을 보내도록 했다.