build.gradle

// 이메일 인증을 위한 설정
implementation 'org.springframework.boot:spring-boot-starter-mail'

 

EmailConfig

package com.cloneweek.hanghaebnb.util.email;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.mail.javamail.JavaMailSenderImpl;

import java.util.Properties;

@Configuration
@PropertySource("classpath:application-email.properties")
public class EmailConfig {

    @Value("${mail.smtp.port}")
    private int port;
    @Value("${mail.smtp.socketFactory.port}")
    private int socketPort;
    @Value("${mail.smtp.auth}")
    private boolean auth;
    @Value("${mail.smtp.starttls.enable}")
    private boolean starttls;
    @Value("${mail.smtp.starttls.required}")
    private boolean startlls_required;
    @Value("${mail.smtp.socketFactory.fallback}")
    private boolean fallback;
    @Value("${AdminMail.id}")
    private String id;
    @Value("${AdminMail.password}")
    private String password;

    @Bean
    public JavaMailSender javaMailService() {
        JavaMailSenderImpl javaMailSender = new JavaMailSenderImpl();
        javaMailSender.setHost("smtp.gmail.com");
        javaMailSender.setUsername(id);
        javaMailSender.setPassword(password);
        javaMailSender.setPort(port);
        javaMailSender.setJavaMailProperties(getMailProperties());
        javaMailSender.setDefaultEncoding("UTF-8");
        return javaMailSender;
    }
    private Properties getMailProperties()
    {
        Properties pt = new Properties();
        pt.put("mail.smtp.socketFactory.port", socketPort);
        pt.put("mail.smtp.auth", auth);
        pt.put("mail.smtp.starttls.enable", starttls);
        pt.put("mail.smtp.starttls.required", startlls_required);
        pt.put("mail.smtp.socketFactory.fallback",fallback);
        pt.put("mail.smtp.socketFactory.class", "javax.net.ssl.SSLSocketFactory");
        return pt;
    }
}

 

EmailController

package com.cloneweek.hanghaebnb.util.email;

import com.cloneweek.hanghaebnb.dto.RequestDto.DupliCheckDto;
import com.cloneweek.hanghaebnb.dto.ResponseDto.ResponseBoolDto;
import com.cloneweek.hanghaebnb.util.exception.StatusMsgCode;
import com.cloneweek.hanghaebnb.dto.ResponseDto.ResponseMsgDto;
import com.cloneweek.hanghaebnb.dto.RequestDto.SignupRequestDto;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

import javax.validation.Valid;
import java.util.Map;

import static com.cloneweek.hanghaebnb.util.exception.StatusMsgCode.EXIST_NICK;
import static com.cloneweek.hanghaebnb.util.exception.StatusMsgCode.NICKNAME;

@RestController
@RequiredArgsConstructor
@RequestMapping("/api/users/email")
public class EmailController {
    private final EmailService emailService;

    // 입력한 이메일로 인증코드 발송
    @PostMapping("/confirm")
    public ResponseEntity<?> emailConfirm(@RequestBody EmailConfirmDto emailConfirmDto) throws Exception {
        emailService.sendSimpleMessage(emailConfirmDto.getEmail());
        return ResponseEntity.ok(new ResponseMsgDto(StatusMsgCode.EMAIL_CONFIRM));
    }

    // 인증코드 발송받은 이메일과 인증번호 입력
    @PostMapping("/verifycode")
    public ResponseEntity<ResponseMsgDto> verifyCode(@RequestParam String issuedCode, String email) {
//        return emailService.verifyCode(issuedCode, email);
        return ResponseEntity.ok(emailService.verifyCode(issuedCode, email));
    }

    // 회원가입 로직
    @PostMapping("/signup")
    public ResponseEntity<ResponseMsgDto> emailSignup(@RequestBody @Valid SignupRequestDto dto) {
        emailService.emailSignup(dto);
        return ResponseEntity.ok(new ResponseMsgDto(StatusMsgCode.SIGN_UP));
    }
}

 

EmailEntity

package com.cloneweek.hanghaebnb.util.email;

import lombok.Getter;
import lombok.NoArgsConstructor;
import javax.persistence.*;

@Getter
@Entity
@NoArgsConstructor
public class Code {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(nullable = false)
    private String email;

    @Column(nullable = false)
    private String randomCode;

    public Code(String randomCode, String email) {
        this.randomCode = randomCode;
        this.email = email;
    }
}

 

EmailConfirmDto

package com.cloneweek.hanghaebnb.util.email;

import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;

@Getter
@NoArgsConstructor
@AllArgsConstructor
public class EmailConfirmDto {
    private String email;
}

 

EmailRepository

package com.cloneweek.hanghaebnb.util.email;

import org.springframework.data.jpa.repository.JpaRepository;

import java.util.List;

public interface CodeRepository extends JpaRepository<Code, Long> {
    List<Code> findByEmail(String email);
    Code findByEmailAndId(String email, Long id);
}

 

EmailService

package com.cloneweek.hanghaebnb.util.email;

import com.cloneweek.hanghaebnb.dto.ResponseDto.ResponseBoolDto;
import com.cloneweek.hanghaebnb.dto.ResponseDto.ResponseMsgDto;
import com.cloneweek.hanghaebnb.util.exception.CustomException;
import com.cloneweek.hanghaebnb.dto.RequestDto.SignupRequestDto;
import com.cloneweek.hanghaebnb.entity.User;
import com.cloneweek.hanghaebnb.repository.UserRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.mail.MailException;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;

import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeMessage;
import java.util.List;
import java.util.Random;

import static com.cloneweek.hanghaebnb.util.exception.StatusMsgCode.*;

@Service
@RequiredArgsConstructor
public class EmailService {
    private final JavaMailSender emailSender;
    private final PasswordEncoder passwordEncoder;
    private final UserRepository userRepository;
    private final CodeRepository codeRepository;


    // 랜덤하게 만든 인증코드 메일로 발송하는 메서드
    public String sendSimpleMessage(String email) throws Exception {
        if (userRepository.findByEmail(email).isPresent()) {
            throw new CustomException(EXIST_USER);
        }
        String randomCode = "";
        StringBuffer key = new StringBuffer();
        Random rnd = new Random();

        // 인증코드 랜덤하게 만들기. 인증 요청될 때마다 새롭게만들어 메일로 보냄
        for (int i = 0; i < 8; i++) { // 인증코드 8자리
            int index = rnd.nextInt(3); // 0~2 까지 랜덤

            switch (index) {
                case 0:
                    key.append((char) ((int) (rnd.nextInt(26)) + 97));
                    //  a~z  (ex. 1+97=98 => (char)98 = 'b')
                    break;
                case 1:
                    key.append((char) ((int) (rnd.nextInt(26)) + 65));
                    //  A~Z
                    break;
                case 2:
                    key.append((rnd.nextInt(10)));
                    // 0~9
                    break;
            }
        }
        randomCode = key.toString();

        // 메세지 내용 작성
        MimeMessage message = emailSender.createMimeMessage();

        message.addRecipients(MimeMessage.RecipientType.TO, email);
        message.setSubject("hanghaebnb 회원가입 이메일 인증");
        String msgg = "";
        msgg += "<div style='margin:100px;'>";
        msgg += "<h1> 안녕하세요 hanghaebnb입니다. </h1>";
        msgg += "<br>";
        msgg += "<p>아래 코드를 회원가입 창으로 돌아가 입력해주세요<p>";
        msgg += "<br>";
        msgg += "<p>감사합니다!<p>";
        msgg += "<br>";
        msgg += "<div align='center' style='border:1px solid black; font-family:verdana';>";
        msgg += "<h3 style='color:blue;'>회원가입 인증 코드입니다.</h3>";
        msgg += "<div style='font-size:130%'>";
        msgg += "CODE : <strong>";
        msgg += randomCode + "</strong><div><br/> ";
        msgg += "</div>";
        message.setText(msgg, "utf-8", "html");//내용
        message.setFrom(new InternetAddress("hanghaebnb@gmail.com", "test"));//보내는 사람

        // 메세지 발송
        try {
            emailSender.send(message);
        } catch (MailException es) {
            es.printStackTrace();
            throw new IllegalArgumentException();
        }

        // 만들었던 랜덤코드 DB에 저장하기
        Code code = new Code(randomCode, email);
        codeRepository.save(code);
        return randomCode;
    }

    // 회원가입
    public void emailSignup(SignupRequestDto dto) {
        String email = dto.getEmail();
        String password = passwordEncoder.encode(dto.getPassword());
        String nickname = dto.getNickname();

        // DB에 이메일과 닉네임 중복 있는지 체크
        if (userRepository.findByEmail(email).isPresent()) {
            throw new CustomException(EXIST_USER);
        }
        if (userRepository.findByNickname(nickname).isPresent()) {
            throw new CustomException(EXIST_NICK);
        }

        // DB에 회원가입할 데이터 저장. builder 패턴으로 적용해봄.
        // User user = new User(email, password, nickname);
        User user = User.builder().email(email)
                .kakaoNickname(nickname)
                .password(password)
                .build();                              // 생성자 호출

        userRepository.save(user);
    }

    // 이메일로 받은 랜덤코드 대조
    public ResponseMsgDto verifyCode(String issuedCode, String email) {

        // 리파지토리에서 해당 이메일로 저장된 모든 랜덤코드 불러오기
        // 리파지토리에서 불러온 랜덤코드 중 가장 최신순으로 발행된 코드 찾기
        List<Code> codeList = codeRepository.findByEmail(email);
        long id = 0L;
        for (Code code : codeList) {
            if (code.getId() > id) {
                id = code.getId();
            }
        }

        // 해당 email로 보낸 가장 최신 랜덤코드 불러오기
        Code code = codeRepository.findByEmailAndId(email, id);
        Boolean result = code.getRandomCode().equals(issuedCode);
        return new ResponseBoolDto(result? MATCH_CODE : UNMATCH_CODE, result);
    }
}

 

Email Properties

mail.smtp.auth=true
mail.smtp.starttls.required=true
mail.smtp.starttls.enable=true
mail.smtp.socketFactory.class=javax.net.ssl.SSLSocketFactory
mail.smtp.socketFactory.fallback=false
mail.smtp.port=465
mail.smtp.socketFactory.port=465

#admin ?? ??
AdminMail.id= 계정 이메일 주소
AdminMail.password= 계정 이메일 비밀번호

[ 참고 자료 ]

 

https://hyunmin1906.tistory.com/276

 

[Go] Google Gmail SMTP 설정 방법 및 메일 전송

■ SMTP 간이 우편 전송 프로토콜(Simple Mail Transfer Protocol)의 약자. 이메일 전송에 사용되는 네트워크 프로토콜이다. 인터넷에서 메일 전송에 사용되는 표준이다. 1982년 RFC821에서 표준화되어 현재

hyunmin1906.tistory.com

https://javaju.tistory.com/100

https://badstorage.tistory.com/38

 

1. 웹소켓 기술  

  • 전이중 통신 - 실시간성 보장하는 서비스 (채팅, 게임, 주식거래 사이트 등)에 주로 사용
  • HTTP에도 실시간성 보장하는 기법이 존재하지만, 서버에 부하가 많이 가기 때문에 웹소켓을 이용한다.
  • HTTP는 비연결성 프로토콜, 단방향성 통신이다. 따라서 매번 연결 맺고 끊는 과정의 비용이 든다.
  • 반면, 웹소켓은 연결지향이라 한번 연결 맺은 뒤 유지, 양방향 통신이다.
  • 보내야하는 메시지와 데이터 양에서도 차이가 난다. 웹소켓을 사용하면 HTTP와 비교했을 때 한번 통신한 후엔 훨씬 적은 양의 데이터로 통신해서 부담을 덜 수 있다. 

 

2. 웹소켓의 단점 : 모든 브라우저에서 허용되는 것은 아니다.

→ 이 문제를 SockJs 와 Socket.io 를 사용하여 해결 할 수 있다. (스프링은 SoketJs사용)

 

※ 스프링에서 웹소켓 설정은 아주 간단한다. 

  • 웹소켓 Config 파일 - addHandler 핸드쉐이크할 주소를 적어줌
  • 핸들러 부분 - 웹소켓 세션을 오버라이드해서 받아오는데, 웹소켓 연결정보를 담고 있는 세션이다.
  • Cors설정
  • .withSockJS() 를 통해 라이브러리 사용 가능.

 

3. Stomp 프로토콜

  • 메시지 브로커를 활용하여 쉽게 메시지를 주고 받을 수 있는 프로토콜
  • 웹소켓 위에 얹어 함께 사용할 수 있는 하위 프로토콜 
  • 메시지 브로커랑 발신자의 메시지를 받아와 수신자들에게 메시지를 전달하는 어떤 것 !

그렇다면 왜 이게 필요한가 ?

프레임 단위 ( 커맨드, 헤더, 바디 ) 의 형태로 메시지 형태를 정해줌. - 컨벤션을 따로 정의할 필요 없다.

 

 

https://www.youtube.com/watch?v=rvss-_t6gzg 

 

Message Broker란?

  • Publisher로부터 전달받은 메세지를 Subscriber로 전달해줄 때 중간에서 메세지를 주고 받게 해주는 중간 역할

https://docs.spring.io/spring-framework/docs/4.3.x/spring-framework-reference/html/websocket.html

 

26. WebSocket Support

This part of the reference documentation covers Spring Framework’s support for WebSocket-style messaging in web applications including use of STOMP as an application level WebSocket sub-protocol. Section 26.1, “Introduction” establishes a frame of m

docs.spring.io

 

In Memory Broker란?

  • Spring websocket에서 STOMP 프로토콜을 사용해서 웹소켓 기능을 구현할 때 STOMP는 Message Broker가 필요하다.
  • 이 때 아무 설정 없이 Spring 환경에서 STOMP 프로토콜을 사용한다면 메세지 브로커로 In Memory Broker를 사용하게 된다.

 

그렇다면 하나의 서비스를 만들 때 In Memory Broker를 메세지 브로커로 사용해도 상관 없을까?

상관있다... 아래를 보면, In Memory 브로커의 단점들이 존재한다.

  1. 세션을 수용할 수 있는 크기가 제한되어 있다.
  2. 장애 발생 시 메세지의 유실될 가능성이 높다.
  3. 따로 모니터링하는 것이 불편하다.

 

그렇다면 In Memory 브로커 대신 무슨 브로커를 사용할 수 있을까?

  • RabbitMQ, ActiveMQ 등에서는 STOMP 프로토콜의 Message Broker 기능을 제공해준다. 
  • 따라서 이들의 힘을 빌려 In Memory 브로커 대신 잘 만들어진 STOMP 전용 외부 브로커를 사용하는 것이 더 좋다.

 

STOMP 전용 외부 브로커를 사용하는 장점이 무엇이 있을까?

  1. 확장성 : 세션을 수용할 수 있는 크기가 크다.
  2. 결함 허용성 : 영구적이며 문제가 발생했을 시 재시도가 가능하여 복구가 가능하다.
  3. 모니터링 : 간편하게 모니터링을 할 수 있다.

 


[ 참고자료 ]

 

https://withseungryu.tistory.com/137

 

 RabbitMQ 와 Redis 의 차이

개발자들은 RabbbitMQ 를 "메세징 브로커(messagin broker: 전달 중개인, 역주: 마치 우체국과 같은 것)"라고 말한다.

RabbitMQ 는 응용 프로그램(applications)에게 메시지를 주고 받을 수 있으며, 메시지가 수신될 때까지 안전하게 있을 수 있도록 하는 공용 플래폼(common platform)을 제공한다.

 

반면, Redis 는 "디스크에 상주하는 인메모리 데이터베이스" 라고 설명한다.

Redis 는 BSD 라이센스된 진보된 키-값 보관용(advanced key-value store) 오픈소스다. 키가 문자열, 해쉬, 리스트 세트, 정렬된 세트를 포함할 수 있기 때문에 데이터 구조 서버(data structure server)라고 불린다.

 

RabbitMQ 와 Redis 는 주로 "메시지 큐" 와 "인메모리 데이터베이스" 툴로서 각각 분류된다.

 

개발자들이 경쟁 제품들에 비해 RabbitMQ 를 고려하는 주된 이유는 "그것이 빠르며(fast), 좋은 메트릭스(정량적 분석 기법)/감시(모니터링) 작업"이 가능하기 때문이며, Redis 를 고르는 주된 이유는 "성능(Performance)"을 말한다.

 

RabbitMQ 와 Redis 는 둘다 오픈 소스 툴이다. Redis 는 37.4K 깃허브 스타(GitHub stars)를, 14.4K 깃허브 포크(GitHub forks)를 나타내어 5.95K, 1.78K 를 가지는 RabbitMQ 보다 훨씬 인기있는 것으로 나타났다.

 

Redis와 RabbitMQ 언제 사용할까?

RabbitMQ — 메시지 브로커

  1. 메시지를 다른 대기열로 보낼 수 있는 라우팅 시스템을 갖춤
  2. 메시지 우선순위 지원
  3. 크고 복잡한 메시지에 적합
  4. 속도보다 지속성이 중요한 서비스에 적합

 

Redis — 인메모리 Cache 서버

  1. Key-Value를 이용해, Celery가 처리할 작업을 보낸 후 Cache에서 해당 Key 제거
  2. Database에 접근하기 전, 메모리에서 Cache를 가져다 쓴다는 점에서 속도가 빠름
  3. 지속성이 중요하지 않고, 약간의 손실을 견딜 수 있는 짧은 보존 메시지에 적합

 

RabbitMQ 와 Redis 를 대체할 수 있는 것들은?

  • Kafka
  • ActiveMQ
  • ZeroMQ
  • Amazon SNS
  • Gearman

[ 참고 자료]

 

WebSocket은 클라이언트 서버간 양방향 통신이 가능하지만, 다음과 같은 이슈가 있다.
1. websocket미지원 웹 브라우저가 있다는 점
2. 웹 브라우저 이외의 클라이언트 지원(서버 입장에서는 클라이언트는 웹 브라우저뿐만이 아님)


WebSocket

  • WebSocket 동작 순서

 

 

  • Http의 특징 중 하나는 단방향 통신이다.
    클라이언트와 서버간의 통신은 단건으로 이루어지며 하나의 요청(통신)이 끝나게 되면 클라이언트와 서버는 아무런 관계가 없어지게된다. 이 말은 서버입장에서 클라이언트로부터 요청이 있기전까지는 어떤 데이터도 전송할 수 없다는 것이다.
    이런 문제를 해결하기 위해 양방향 통신이 생겨나게 되었고, WebSocket으로 구현하게 되었다.
  • WebSocket은 Http를 통해 작동하도록 설계되었으며, Http Header에 특정 값("websocket")을 명시함으로써 WebSocket 프로토콜로 전환하는 요청으로 시작된다.

 

STOMP 

  • 스트리밍 텍스트 지향 메시지 프로토콜
  • STOMP는 Simple/Stream Text Oriented Message Protocol의 약자로, 메시지 브로커의 역할을 한다.
  • 메시지 전송을 효율적으로 처리하기 위한 프로토콜
  • STOMP는 WebSocket 기반으로 동작하며 pub/sub 구조로 되어 있다. pub/sub 구조는 쉽게 말해 편지를 쓰는 사람(Publisher)이 편지함에 편지를 넣어두면 그걸 기다리고 있던 편지를 받는 사람(Subscriber)이 편지를 받고 읽는 구조이다.
  • 즉, WebSocket 위에서 동작하는 프로토콜로써 클라이언트와 서버가 전송할 메시지의 유형, 형식, 내용들을 정의하는 메커니즘이다.

 

Stomp를 사용하는 이유

  • STOMP 를 사용하여 WebSocket만 사용할 때보다 더 다채로운 모델링을 할 수 있다.
  • Messaging Protocol을 만들고 메세지 형식을 커스터마이징 할 필요가 없다.
  • RabbitMQ, ActiveMQ 같은 Message Broker를 이용해, Subscription(구독)을 관리하고 메세지를 브로드캐스팅할 수 있다.
  • WebSocket 기반으로 각 Connection(연결)마다 WebSocketHandler를 구현하는 것 보다 @Controller 된 객체를 이용해 조직적으로 관리할 수 있다.
  • 즉, 메세지는 STOMP의 "destination" 헤더를 기반으로 @Controller 객체의 @MethodMapping 메서드로 라우팅 된다.
  • STOMP의 "destination" 및 Message Type을 기반으로 메세지를 보호하기 위해 Spring Security를 사용할 수 있다.

 

SockJS

  • WebSocket은 upgrade Header를 이용하여 Socket연결을 하고, 통신을 진행한다. 이 과정에서 여러가지 이유로 소켓연결이 불가능하게 될 경우가 있는데 이럴 경우 Http기반의 다른 기술로 전환하여 다시 연결을 시도해야할 필요가 있다. 이 때 사용하시는 것이 'WebSocket Emulation'이다. 그리고 Spring에서는 Emulation을 위해 'SockJS' 라이브러리를 지원한다.
  • SockJS는 다양한 기술을 이용해 웹소켓을 지원하지 않는 브라우저에서 정상 작동하도록 도와주는데.
  1. WebSocket
  2. HTTP Streaming
  3. HTTP Long Polling
    세가지 타입을 이용한다.

 

Redis 

레디스(Redis)는 Remote Dictionary Server의 약자로서, "키-값" 구조의 비정형 데이터를 저장하고 관리하기 위한 오픈 소스 기반의 비관계형 데이터베이스 관리 시스템(DBMS)이다.

 

redis를 사용하는 이유?

1.놀라울 정도로 빠른 성능

Redis와 같은 인 메모리 데이터베이스는 디스크에 액세스해야 할 필요를 없앰으로써 검색 시간으로 인한 지연을 방지하고 CPU 명령을 적게 사용하는 좀 더 간단한 알고리즘으로 데이터에 액세스할 수 있다.

 

2. 인메모리 데이터 구조

Redis를 사용하면 사용자가 다양한 데이터 유형에 매핑되는 키를 저장할 수 있다.

 

3. 다양성과 사용 편의성

Redis는 개발과 운영을 좀 더 쉽고 좀 더 빠르게 수행할 수 있는 여러 가지 도구를 제공한다. Pub/Sub는 메시지를 채널에 게시하며, 채널에서 구독자에게 전달된다. 채팅과 메시징 시스템에 매우 적합하다.

 

4. 복제 및 지속성

Redis는 마스터-슬레이브 아키텍처를 사용하며 비동기식 복제를 지원하여 데이터가 여러 슬레이브 서버에 복제될 수 있다. 이렇게 하면 주 서버에 장애가 발생하는 경우 요청이 여러 서버로 분산될 수 있으므로 향상된 읽기 성능과 복구 기능을 모두 제공할 수 있다.

 

5. 선호하는 개발 언어 지원

Redis 개발자는 백 개가 넘는 오픈 소스 클라이언트를 사용할 수 있으며, Java, Python, PHP, C, C++, C#, JavaScript, Node.js, Ruby, R, Go를 비롯한 다수의 언어가 지원된다.

 

※ Redis 다운로드

https://github.com/microsoftarchive/redis/releases

 


[ 정리 ]

  • Websoket을 사용하려면 메시지 브로커 역할을 해주는 Stomp와 웹소켓을 지원하지 않는 브라우저에서 작동하도록 도와주는 SockJS 라이브러리를 같이 사용해야 한다.
  • Stomp broker 설정으로만 채팅이 되는 것이 아니다. 클라이언트가 서로 다른 서버에 연결이 됬을때 채팅을 하려면 Redis가 필요하다.
  • 메시지 브로커 사용하는 이유는 3가지
    • 서비스(어플리케이션) 간의 의존성 제거
    • 메시지 처리 시점
    • 다양하고 유연한 통신

[ 참고자료 ]

 

https://fgh0296.tistory.com/24

 

sockJS와 stompJS

필자는 팀 프로젝트에서 채팅서비스를 도입하여 온라인에서 사용자끼리 상품 거래를 할 수 있도록 클라이언트에서 공부해야 할 기술 스택을 찾고 있다가 sockJS를 알게 되었다. 그리고 다음과 같

fgh0296.tistory.com

https://velog.io/@koseungbin/WebSocket

 

WebSocket

이 글은 Spring WebSocket(https://docs.spring.io/spring-framework/docs/current/spring-framework-reference/web.htmlWebSocket 프로토콜은 표준된 방법으로 서버-클라이언트 간

velog.io

https://thdwngus2.tistory.com/100

 

spring stomp, redis 적용하기

배운점 stomp 적용하기 stomp란? 메시지 전송을 효율적으로 하기 위해 나온 프로토콜이며 기본적으로 pub/sub 구조로 되어있어 메시지를 발송하고, 메시지를 받아 처리하는 부분이 확실히 정해져 있

thdwngus2.tistory.com

 

+ Recent posts