개발자를 향해...

프로젝트 리팩토링하기(소켓채팅) - 1탄 본문

spring boot 강의 따라하기/spring boot 첫걸음

프로젝트 리팩토링하기(소켓채팅) - 1탄

eugeneHwang1124 2022. 4. 5. 00:01
728x90
반응형

작년 여름 동아리에서 써머 프로젝트로 했던 플젝이 대상 수상을 하며 꽤 만족스러운 결과를 얻었었다.

 

깃허브 링크를 함께 첨부한다.

https://github.com/SOLUX-web-team/0942_server

 

GitHub - SOLUX-web-team/0942_server: 0942 server

0942 server. Contribute to SOLUX-web-team/0942_server development by creating an account on GitHub.

github.com

 

이번엔 그 프로젝트의 코드를 리팩토링해보려고한다.

 

서버에서 내가 맡았던 채팅 부분을 리팩토링하는것이 일차적 목표이다.

 

전에 내가 짰던 코드는 채팅을 단순히 http 통신으로 구현함으로서 매우 비효율적인 구조였다. 심지어 프론트에서 실시간 채팅이 안되고 새로고침을 해야 채팅을 확인하는 구조였다... (그땐 빨리 플젝을 마무리하려고 대충짰었다ㅎㅎ 당시 새로고침으로 동작하는 채팅을 보고 당황해하던 팀원들에게 매우 미안했었다...)

 

앞으로 어떤 구조로 짤지 구상을 해보았다

 

보통 채팅을 구현하는데에는 소켓Socket을 많이 사용한다. Socket 통신에서는 서버와 클라이언트가 양방향으로 연결되어 통신이 이루어진다. 전에 썼던 http 통신은 클라이언트는 일방적으로 요청하고 서버는 일방적으로 응답하던 구조와 다르다.

 

Socket 통신(좌)과 http 통신(우)

실시간으로 채팅 데이터를 주고받아야하는 나의 경우 무조건 socket 통신으로 구현하는것이 맞다. 

 

그러면 이제 socket 통신을 spring에서 구현해보자.

 

spring에서는 socket을 위해

spring-boot-starter-websocket

을 사용할 수 있다.

 

본 프로젝트 같은 경우 gradle을 사용하기 때문에

implementation 'org.springframework.boot:spring-boot-starter-websocket'

이렇게 build.gralde에 추가해주었다.

 

아래 과정은 다음 spring 공식 문서를 참고하였다.

https://docs.spring.io/spring-framework/docs/current/reference/html/web.html#websocket

 

Web on Servlet Stack

Spring Web MVC is the original web framework built on the Servlet API and has been included in the Spring Framework from the very beginning. The formal name, “Spring Web MVC,” comes from the name of its source module (spring-webmvc), but it is more com

docs.spring.io

물론 영어로 쓰여있지만 나에게는 구글 번역기가 있다 ㅎㅎ

 

소켓통신은 TCP 연결을 통해 클라이언트와 서버 사이에 전이중 양방향 통신을 설정하는 표준화된 방법을 제공한다. HTTP와 다른 TCP 프로토콜이지만 포트 80 및 443을 사용하고 기존 방화벽 규칙을 재사용할 수 있도록 HTTP를 통해 작동하도록 설계되었습니다.

여기서 upgrade 헤더를 찾아보니 이미 정립된 클라이언트 서버 간의 연결을 다른 프로토콜로 바꿀때에 사용된다고 한다. 예를 들면 클라이언트가 http 1.1을 http 2.0으로 바꾸거나 혹은 http/https 를 WebSocket 연결로 변경하고 싶을 때  클라이언트가 사용할 수 있다.

UpgradeWebSocket 상호 작용은 HTTP 헤더를 사용하여 업그레이드하거나 이 경우 WebSocket 프로토콜로 전환 하는 HTTP 요청으로 시작됩니다 . 다음 예에서는 이러한 상호 작용을 보여줍니다.

일반적인 200 상태 코드 대신 WebSocket을 지원하는 서버는 다음과 유사한 출력을 반환합니다.

성공적인 핸드셰이크 후 HTTP 업그레이드 요청의 기반이 되는 TCP 소켓은 클라이언트와 서버 모두 계속해서 메시지를 보내고 받을 수 있도록 열려 있습니다.

Handshaking 

핸드셰이킹(handshaking), 주고받기는 정보기술과 전기통신 및 관련 분야에서 채널에 대한 정상적인 통신이 시작되기 전에 두 개의 실체 간에 확립된 통신 채널의 변수를 동적으로 설정하는 자동화된 협상 과정이다. 채널의 물리적인 확립이 잇따르며, 정상적인 정보 전송 이전에 이루어진다.

예시)
TCP 3방향 핸드셰이크
- 정상적인 TCP 연결을 수립하려면 3가지 단계가 필요하다:
1. 최초의 호스트(Alice)가 두 번째 호스트(Bob)에 SYN(동기화) 메시지를 보낸다. 이 메시지에는 자체 시퀀스 번호 x가 있으며, 이것을 Bob이 받는다.
2. Bob은 SYN-ACK 메시지와 함께 응답한다. 이 메시지는 자체 시퀀스 번호 y와 응답 번호 x+1을 포함하며, Alice가 이를 받는다.
3. Alice는 응답 메시지와 함께 응답한다. 이 메시지는 응답 번호 y+1이 포함되며 Bob이 이것을 받지만 이에 응답할 필요는 없다.

3방향 핸드셰이킹의 예.

통신을 위해 많은 URL이 필요한 다른 HTTP 통신과는 다르게 웹소켓에서는 초기 연결을 위한 URL하나만 있으면 된다.

 

구현하기

1. WebSocket API & Config

MyHandler.java

import org.springframework.web.socket.TextMessage;
import org.springframework.web.socket.WebSocketSession;
import org.springframework.web.socket.handler.TextWebSocketHandler;

public class MyHandler extends TextWebSocketHandler {

    @Override
    public void handleTextMessage(WebSocketSession session, TextMessage message) {
        
    }

}

WebSocketConfig.java (xml파일로도 가능함)

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.WebSocketHandler;
import org.springframework.web.socket.config.annotation.EnableWebSocket;
import org.springframework.web.socket.config.annotation.WebSocketConfigurer;
import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry;
import smwu._back.handler.MyHandler;

@Configuration
@EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer {
    @Override
    public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
        registry.addHandler(myHandler(), "/myHandler");
    }

    @Bean
    public WebSocketHandler myHandler() {
        return new MyHandler();
    }
}

 

2.  WebSocket Handshake

HTTP 웹소켓 핸드셰이크 초기화 요청을 사용자 정의하는 가장 쉬운 방법은 HandshakeInterceptor를 통해 핸드셰이크의 "before"와 "after"를 위한 메소드를 사용할 수 있게 하는 것이다. 이런 인터셉터를 사용해 핸드셰이커를 금지하거나 WebSocketSession에서 사용할 수 있는 속성을 만들 수 있다. 다음 예제는 내장형 인터셉터를 사용해 HTTP 세션 attribute를 WebSocket session에 전달한다.

@Configuration
@EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer {
    @Override
    public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
        registry.addHandler(myHandler(), "/myHandler") // 특정 URL에 웹소켓 핸들러를 매핑 
                .addInterceptors(new HttpSessionHandshakeInterceptor()) // 핸드셰이크 요청을 인터셉트할 인터셉터 허용

3.  SockJS 폴백

SockJS 클라이언트는 GET /info서버에서 기본 정보를 얻기 위해 전송을 시작합니다. 그런 다음 사용할 전송을 결정해야 합니다. 가능한 경우 WebSocket이 사용됩니다. 그렇지 않은 경우 대부분의 브라우저에는 적어도 하나의 HTTP 스트리밍 옵션이 있습니다. 그렇지 않은 경우 HTTP(긴) 폴링이 사용됩니다.

SockJS 특징으로는 최소한의 메세징 프레임을 사용한다는 것이다. 예를 들어,처음 서버는 o  문자("open" 프레임)를 보내고 이후 메시지 전송은 는 a["message1","message2"] (JSON 인코딩된 배열)로 전송하며,  25초(기본값) 동안 메시지가 흐르지 않으면 문자 h("heartbeat" 프레임)를, 세션을 닫을 때에는 문자는 c("닫기" 프레임)을 보낸다.

 

 

반응형