https://www.daddyprogrammer.org/post/4731/spring-websocket-chatting-server-redis-pub-sub/
0.๋ชฉ์ฐจ
1. ๊ฐ์ |
2. ์ ๊ฐ๋, ์ ์ฒด์ ์ธ ํ๋ฆ ํ์ |
3. ์ฝ๋ ๋ถ์ |
4. ํ๋ก์ ํธ ์งํํ๋ฉด์ ์์๋ ์๋ฌ ํด๊ฒฐ |
1. ๊ฐ์
์ด๋ฒ ํ๋ก์ ํธ์์๋ Redis๋ผ๋ ์ธ๋ฉ๋ชจ๋ฆฌํ DB๋ฅผ ํ์ฉํ์ฌ 2๋ฒ๊น์ง ์งํํ๋ STOMP์ WebSocket๋ง์ผ๋ก ๊ตฌํํ Chatting Server ํ๋ก์ ํธ๋ฅผ ๋ ๊ณ ๋ํ ํ๋ ค๊ณ ํ๋ค.
์ด? ์ด๋ฏธ ํ๋์ ์๋ฒ์์ ์๋ก ๋ฉ์ธ์ง๋ฅผ ์ฃผ๊ณ ๋ฐ๋ ๊ฒ์ ์ถฉ๋ถํ ๋๋๋ฐ ์ Redis๋ฅผ ์ฅ์ฐฉํด์ผ ํ ๊น์?
๊ทธ๋ ๋ค๋ฉด, ๋จผ์ Redis๋ฅผ ์ ์ฅ์ฐฉํด์ผ ํ๋์ง์ ๋ํด์ ์ค๋ช ํ๊ฒ ๋ค.
โ STOMP์ WebSocket๋ง์ ์ฐ๋ Chatting Server์ ๋ฌธ์ ์
โด ์ง๊ธ๊น์ง ๋ง๋ ํ๋ก์ ํธ๋ ์๋ฒ๊ฐ ๊บผ์ง ๋๋ง๋ค ์ฑํ ๋ฐฉ ์ ๋ณด๋ค์ด ์ ๋ถ ์ฌ๋ผ์ง๋ค.
๋น์ฐํ ๋ง์ด๋ค. ์ง๊ธ๊น์ง์ ํ๋ก์ ํธ์์ ์ฐ๋ฆฌ๋ DB์ ์ฑํ ๋ฐฉ ์ ๋ณด๋ค์ ์ ์ฅํ์ง ์๊ณ , HashMap์ ์ ์ฅํ๋ค. ๋ฐ๋ผ์ ์๋ฒ๊ฐ ๊บผ์ง๋ฉด ํด๋น ์ ๋ณด๋ค์ ๋ชจ๋ ์ฌ๋ผ์ง๋ค. ์ด๋ฅผ ๋ฐฉ์ง ํ๊ธฐ ์ํด, Redis๋ผ๋ ์ธ๋ฉ๋ชจ๋ฆฌํ DB๋ฅผ ์ฌ์ฉํ ์์ ์ด๋ค.
โด - โ ์ด? ๊ทผ๋ฐ ์ธ๋ฉ๋ชจ๋ฆฌํ DB์ด๋ฉด ํด๋น ์ฑํ ๋ฐฉ ์ ๋ณด๋ DB ์๋ฒ๊ฐ ๊บผ์ง๋ฉด ๋ค ์ฌ๋ผ์ง์ง ์๋์??
๊ทธ๊ฒ๋ ๋ง๋ ๋ง์ด๋ค. ๊ทธ๋ฐ๋ฐ ์๊ฐํด๋ณด๋ฉด, ๋ํ๊ฐ ๋๋๊ณ ๋ชจ๋๊ฐ ๋ฐฉ์ ๋๊ฐ๋ฉด ์ฌ๋ผ์ง๋ ์ฑํ ๋ฐฉ์ ์ ๋ณด๋ฅผ DB์ ์ ์ฅํ ํ์๊ฐ ์์๊น? ์นด์นด์คํก ๋จํก๋ฐฉ์ด๋, ํ ํฌ์จ ๊ฐ์ ํ๋ซํผ์ ์๊ฐํด๋ณด์. ๋ง์ฝ ๋ชจ๋ ์ฑํ ๋ฐฉ์ ๋์คํฌ์ ์๋ DB์ ์ ์ฅํ๋ค๋ฉด ์ธ๋ชจ ์๋ ๋ฐ์ดํฐ๋ค์ด ๋์คํฌ์ ์์ฌ๊ฐ ๊ฒ์ด๋ค. ์ฌ๋๋ค์ด ๋ค ๋๊ฐ๊ฑฐ๋, ๋ ์ด์ ์ฌ์ฉํ์ง ์๋ ์ฑํ ๋ฐฉ๋ ํ๋ฃจ์ ์ ๋ฐฑ, ์ ์ฒ๊ฐ๊ฐ ์๊ธธ ๊ฒ์ด๊ธฐ ๋๋ฌธ์ด๋ค. ์ด๋ฌํ ๊ฒฝ์ฐ Redis์ฒ๋ผ ์ธ๋ฉ๋ชจ๋ฆฌํ DB๊ฐ ๋ ์ ์ฉํ ์ ์๋ค. Redis๋ฅผ ์ฌ์ฉํด์ผ ํ๋ ์ด์ ์ฒซ๋ฒ์งธ๋ ๋ฐฑ์๋ ์๋น์ค ์๋ฒ์ DB ์๋ฒ๊ฐ ๋ถ๋ฆฌ๋๋ค๋ ์ ์ด๋ค. ๋ฐ๋ผ์ ์๋น์ค ์๋ฒ๊ฐ ๊บผ์ ธ๋, Redis ์๋ฒ๊ฐ ์ด์์๋ค๋ฉด ์ฑํ ๋ฐฉ ์ ๋ณด๋ค์ ์ฌ๋ผ์ง์ง ์๋๋ค. ๋ฐ๋ผ์ ๋ฐ์ดํฐ๊ฐ ํ๋ฐ๋๋ ์ ๋๋ฅผ ๋ฎ์ถ ์ ์๋ค. ๋ ๋ฒ์งธ๋ก ๋ฐ์ดํฐ์ ์ค์๋๋ฅผ ๋๋ ์ MySQL๊ฐ์ ๋์คํฌ ๊ธฐ๋ฐ ์๋ฒ์ Redis๊ฐ์ ์ธ๋ฉ๋ชจ๋ฆฌํ ์๋ฒ์ ๋๋ ์ ์ฅํ๋ค๋ฉด, ๋ฐ์ดํฐ์ ๊ฒฝ์ค์ ๋ฐ๋ผ ํจ์จ์ ์ธ ์ ๋ณด ์ ์ฅ ๋ํ ๊ฐ๋ฅํ๋ค. (ํ์ง๋ง ๋ง์ฝ DB๋ฅผ Redis ํ๋๋ง ์ด๋ค๋ฉด, Redis ์๋ฒ๊ฐ ๊บผ์ก์ ๊ฒฝ์ฐ ๋ฐ์ดํฐ ์ ์ค์ ์ด๋ป๊ฒ ๋์ฒํ ๊ฒ์ธ์ง์ ๋ํ ํ๋B๋ฅผ ๊ผญ ์ธ์๋์ผ ํ๊ฒ ๋ค.)
โต ์ง๊ธ๊น์ง ๋ง๋ ํ๋ก์ ํธ๋ ์ฑํ ์๋ฒ๊ฐ ์ฌ๋ฌ ๊ฐ์ผ ๊ฒฝ์ฐ, ์๋ฒ ๊ฐ์ ์ฑํ ๋ฐฉ์ ๊ณต์ ํ ์ ์๋ค.
์ง๊ธ๊น์ง ๋ง๋ ํ๋ก์ ํธ์ Topic์ Topic์ด ๋ฐํ๋ ์๋ฒ์์๋ง ์ ํจํ๋ค. ๋ฐ๋ผ์ A๋ผ๋ ์๋ฒ๋ฅผ ํตํด ์๋น์ค๋ฅผ ์ฌ์ฉํ๋ ์ฌ์ฉ์๋ B๋ผ๋ ์๋ฒ์ ๋ฐํ๋ Topic(์ฐ๋ฆฌ๋ก ์น๋ฉด ์ฑํ ๋ฐฉ)์ ๋ณผ ์๋ ์๊ณ , ๊ตฌ๋ ํ ์๋ ์๋ค. ๋ฉ์ดํ์คํ ๋ฆฌ ์๋ฒ๋ฅผ ๋ ์ฌ๋ฆฌ๋ฉด ํธํ๊ฒ ๋ค. ์ ๋์ค ์๋ฒ ์บ๋ฆญํฐ๋ ์ ๋ ์ค์นด๋์ ์๋ฒ ์บ๋ฆญํฐ์ ๋ํํ ์ ์๋ค. ์์ ๋ค๋ฅธ ์ธ๊ณ์ด๊ธฐ ๋๋ฌธ์ด๋ค. ์ฐ๋ฆฌ๊ฐ ๋ง๋ ์ฑํ ์๋ฒ๋ ๋ง์ฐฌ๊ฐ์ง๋ค. ๋ฐ๋ผ์ ์ด ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ๋ ค๋ฉด, ๋ชจ๋ ์๋ฒ๊ฐ ๊ณตํต์ผ๋ก ์ฌ์ฉํ ์ ์๋ pub/sub ์์คํ ์ ๊ตฌ์ถํ๋ ๊ฒ์ด ํ์ํ๊ฒ ๋ค.
ํด๋น ๋ฌธ์ ๋ Redis๋ฅผ ์ฌ์ฉํ๋ฉด ํด๊ฒฐํ ์ ์๋ค. ์๊น 1๋ฒ์์ Redis๋ฅผ ์จ์ผํ๋ ์ด์ ์ค ํ๋๋ก ๋ฐฑ์ค๋ ์๋ฒ์ DB ์๋ฒ์ ๋ถ๋ฆฌ๋ฅผ ๋ค์๋ค. ์ด๋ ๊ฒ ๋ฐฑ์ค๋ ์๋ฒ์ DB ์๋ฒ๋ฅผ ๋ถ๋ฆฌํ๋ค๋ฉด, ์ฌ๋ฌ ๊ฐ์ ๋ฐฑ์๋ ์๋ฒ๊ฐ ํ๋์ DB ์๋ฒ๋ฅผ ๊ณต์ ์ฌ์ฉํ๋๋ก ํ ์ ์๋ค. ์ด๋ ๊ฒ ๋๋ฉด, ๊ณตํต PUB/SUB ์์คํ ๋ง ๊ตฌ์ถ๋๋ค๋ฉด ํ๋์ ์๋ฒ์์ ๋ง๋ค์ด์ง Topic์ ๋ค๋ฅธ ์๋ฒ๋ค์์๋ ์ ๊ทผํ์ฌ ๊ตฌ๋ ํ๋๊ฒ ๊ฐ๋ฅํด์ง๋ค!
๋ง์นจ Redis์ ๊ฒฝ์ฐ, ์ด๋ฌํ ๊ณตํต PUB/SUB ์์คํ ๋ํ ๊ตฌ์ถ๋์ด์๋ค. (๊ทธ๋์ ๋๋์ฑ redis ์ฌ์ฉ์ ์ถ์ฒํ๋ค.) ๋ฐ๋ผ์ Redis๋ฅผ ์ฌ์ฉํ ๋ฒ ์๋ฒ์ ์น ์์ผ ํต์ ์ ๋ค์๊ณผ ๊ฐ์ ๊ทธ๋ฆผ์ผ๋ก ์งํ๋๋ค.
2. ์ ๊ฐ๋ ์ ์ฒด์ ์ธ ํ๋ฆ ํ์
ChatRoomController๋ ์ฑํ
๋ฐฉ ์์ฑ ๋ฐ Redis์ ๋ฑ๋ก, ์ฑํ
๋ฐฉ ๋ฆฌ์คํธ์ ํน์ ๋ฐฉ ๋ฐํ, ์ฑํ
๋ฐฉ ๋ฆฌ์คํธ ํ๋ฉด๊ณผ ์ฑํ
๋ฐฉ ํ๋์ Detail ํ๋ฉด์ผ๋ก ์ด๋์ ๋ด๋นํ๋ค. ์ฌ๊ธฐ์๋ ์ฑํ
๋ฐฉ ์์ฑ ์ Redis์ Topic์ผ๋ก ๋ฑ๋กํ๋ ์ผ ์ธ์ ํน๋ณํ ๋ฐ๋ก ์ผ์ ํ์ง ์๋๋ค.
๋ฐ๋ฉด Chat Controller๋ ๋ณธ๊ฒฉ์ ์ผ๋ก Pub/Sub์ ๊ดํ ์ผ์ ํ๋ค. ์ฑํ
๋ฐฉ Detail์ ๋ค์ด์จ ์ฌ์ฉ์๊ฐ ๋ฉ์ธ์ง๋ฅผ ์ณ์ ์
๋ ฅํ๋ฉด, ๋น๋ก์ ํด๋น ์ฌ์ฉ์๋ฅผ ํด๋น Topic์ ๊ตฌ๋
์ ๋ฐ ๋ฐํ์๋ก์ ๋ฑ๋กํ๊ณ , ์์ ์ด ๋ณด๋ธ ๋ฉ์ธ์ง ๋ฐ ๊ฐ์ Topic (์ฑํ
๋ฐฉ)์ ๋ค์ด์จ ์ฌ๋๋ค์ ๋ฉ์ธ์ง๋ฅผ ๋ณผ ์ ์๋๋ก ํ๋ ๊ฒ์ด๋ค.
3. ์ฝ๋ ๋ถ์
โ EmbeddedRedisConfig
package org.websocket.demo.config;
import jakarta.annotation.PostConstruct;
import jakarta.annotation.PreDestroy;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;
import redis.embedded.RedisServer;
// ๋ก์ปฌ ํ๊ฒฝ์์๋ง ์คํํฉ๋๋ค.
@Profile("local")
@Configuration
public class EmbeddedRedisConfig {
@Value("${spring.data.redis.port}")
private int redisPort;
private RedisServer redisServer;
@PostConstruct
public void redisServer() {
redisServer = RedisServer.builder()
.port(redisPort)
.setting("maxmemory 128M")
.build();
}
@PreDestroy
public void stopRedis() {
if(redisServer != null){
redisServer.stop();
}
}
}
ํด๋น EmbeddedRedisConfig๋ SpringBoot์ ๋ด์ฅ๋ Redis๋ฅผ ์ฌ์ฉํ๊ธฐ ์ํ ์ค์ ํด๋์ค์ด๋ค. Redis์ ๋ํด ์ฝ๊ฒ ๋ฐฐ์ฐ๊ธฐ ์ํ ์ง๊ธ์๋ง ์ฌ์ฉํ๋ Config๋ก ๋ณธ๊ฒฉ์ ์ธ Redis๋ฅผ ์ฌ์ฉํ ๋๋ ์ฌ์ฉํ์ง ์์ ํด๋์ค์ด๋ค. ํด๋น ํด๋์ค์์๋ @PostConstruct์ @PreDestory๋ ์ด๋ ธํ ์ด์ ์ ์ฌ์ฉํ๋ค. @PostConstruct๋ ๊ฐ์ฒด ์ด๊ธฐํ ์ดํ ๋ฑ ํ๋ฒ๋ง ๋งค์๋๋ฅผ ์คํ ์ํฌ ๋ ์ฌ์ฉํ๋ ์ด๋ ธํ ์ด์ ์ด๋ค. ํด๋น ์ด๋ ธํ ์ด์ ์ด ์ฐ์ธ RedisServer()๋ผ๋ ํจ์๋ ๋ด์ฅ RedisServer๊ฐ ๋์๊ฐ ํฌํธ์ ์ต๋ ์ฉ๋์ ์ค์ ํ์ฌ RedisServer๋ฅผ ์คํ์ํค๋ ํจ์์ด๋ค. ๋ฐ๋๋ก @PreDestroy์ด๋ ํด๋น Bean ๊ฐ์ฒด๊ฐ ์๋ฉธ๋๊ธฐ์ ์ ํด์ผํ ์ผ์ ๋ํด ์ ์ํ ์ด๋ ธํ ์ด์ ์ด๋ค. ์ฐ๋ฆฌ๋ ์๋ฒ๊ฐ ๊บผ์ง๊ธฐ ์ ์ RedisServer๋ฅผ ์์ '์ญ์ 'ํ์ง ์๊ณ ์ ์ '๋ฉ์ถ'๋ค. ๋ฐ๋ผ์ ์ปดํจํฐ๊ฐ ๊บผ์ก๋ค๊ฐ ๋ค์ ํฌ ๊ฒฝ์ฐ์๋ RedisServer์ ์ ์ฅํ ๋ด์ฉ๋ค์ด ๋ณด์ธ๋ค.
โ RedisConfig
@Configuration
public class RedisConfig {
// redis ๋ด์ฅ pub/sub ๊ธฐ๋ฅ์ ์ฌ์ฉํ๊ธฐ ์ํด Message Listener ์ค์ ์ ์ถ๊ฐ
@Bean
public RedisMessageListenerContainer redisMessageListener(RedisConnectionFactory connectionFactory) {
RedisMessageListenerContainer container = new RedisMessageListenerContainer();
container.setConnectionFactory(connectionFactory);
return container;
}
// ์ฐ๋ฆฌ๊ฐ ๋ง๋ ์ดํ๋ฆฌ์ผ์ด์
์์ ์ฌ์ฉํ RedisTemplate ์ค์ , ์ง๋ ฌํ ์ด์ผ ํ ๊ฑด์ง, RestTemplate ์ฐ๊ฒฐ์๋ ์ด๋ค ํฉํ ๋ฆฌ๋ฅผ ์ฐ๋์ง
// RedisTemplate: Redis ๋ฐ์ดํฐ ๋ฒ ์ด์ค์ ์ํธ์์ฉํ๊ธฐ์ํ ์ค์ ์ ๋ด์ ํด๋์ค
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory connectionFactory){
RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
redisTemplate.setConnectionFactory(connectionFactory);
redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.setValueSerializer(new Jackson2JsonRedisSerializer<>(String.class));
return redisTemplate;
}
}
๋ณธ๊ฒฉ์ ์ธ Redis ์ค์ ํ์ผ์ด๋ค. Redis์ ๋ด์ฅ Pub/Sub ์์คํ
์ ์ฌ์ฉํ๊ธฐ ์ํด์๋ 'RedisMessageListenerContainer'์ ๊ฐ์ฒด๊ฐ ํ์ํ๋ค. ํด๋น ๊ฐ์ฒด๋ ํน์ Topic์ผ๋ก ๋ฐํ๋๋ ๋ฉ์ธ์ง๋ฅผ Topic ๊ตฌ๋
์๋ค์ด ์ฝ์ด์ ๋ณผ ์ ์๋๋ก ํ๋ ๊ฐ์ฒด์ด๋ค. ์ด๋ RedisMessageListenerContainer๋ connectionFactory๋ผ๋ ๋
์์ ์์
ํ์ฌ ์์ ์ ๋ฉค๋ฒ ๋ณ์๋ก ์ ์ฅํ๋ค. connectionFactory๋, Redis DB์์ ์ฐ๊ฒฐ์ ์ด๋ป๊ฒ ํ ๊ฒ์ธ์ง ์ค์ ํ๋ ํด๋์ค์ด๋ค. ํด๋น ํด๋์ค๋ ๋ ๊ฐ์ง ์์ ํด๋์ค๊ฐ ์๋๋ฐ, ํด๋น ํด๋์ค๋ค์ ๋น๋๊ธฐ ์ฐ๊ฒฐ์ ์ง์ํ๋์ง, ๋ฆฌ์กํฐ๋ธ ํ๋ก๊ทธ๋๋ฐ์ ์์ํ๊ฒ ํ๋ ๋งค์๋๋ฅผ ์ง์ํ๋์ง ๋ฑ์ ์ฐจ์ด๊ฐ ์๋ค๊ณ ํ๋ค. ๋ด์ฅRedis์์๋ ์ด๋ค ConnectionFactory๋ฅผ ์ฌ์ฉํ๋์ง๊ฐ ๋ฏธ๋ฆฌ ์ ํด์ ธ ์๋ ๋ฏ ํ๋ค. ์ด๊ฒ์ ๋์ค์ ๋ ์์ธํ ์์๋ณด๊ฒ ๋ค.
RedisTemplate๋ Redis DB์์ ์ํธ์์ฉ(CRUD)์ ์ํ ์ค์ ์ ๋ด์ ํด๋์ค์ด๋ค. ์ฌ๊ธฐ์๋ ์ด๋ค ConnectionFactory๋ฅผ ์ฌ์ฉํ ์ง, Key์ Value์ ์ญ์ง๋ ฌํ๋ ์ด๋ป๊ฒ ํ ๊ฒ์ธ์ง์ ๋ํ ์ค์ ์ ํด์ค์ผ ํ๋ค.
โ WebSockConfig
@Configuration
@EnableWebSocketMessageBroker // ์น์์ผ์์ STOMP๋ฅผ ํ์ฑํ ํ๊ธฐ ์ํ ์ด๋
ธํ
์ด์
public class WebSockConfig implements WebSocketMessageBrokerConfigurer {
// ๋ฉ์ธ์ง ์ก ์์ ์ ๋ํ ์ค์ ์ ๋ฑ๋กํ ๋งค์๋์ด๋ค.
// ๋ฉ์ธ์ง ์์ ์ sub/room/๋ฐฉ ๋ฒํธ๋ก ํด๋น ๋ฐฉ ๋ฒํธ๋ก ์จ ๋ฉ์ธ์ง๋ง ์์ ํ๋๋ก ์ค์
// ๋ฉ์ธ์ง ๋ฐ์ ์ ๊ฒฝ์ฐ์๋ pub/room ์ผ๋ก ๋ณด๋ด๊ณ ๋ฐฉ ๋ฒํธ๋ responseBody ์ ๋ฉํ๋ฐ์ดํฐ๋ก ์ ์ฅํ๋ค.
@Override
public void configureMessageBroker (MessageBrokerRegistry config){
//๋ฉ์ธ์ง๋ฅผ ๋ฐํํ๋ ์์ฒญ์ ์ ๋์ฌ๋ /pub๊ฐ ๋๋๋ก ์ค์
config.setApplicationDestinationPrefixes("/pub");
//๋ฉ์ธ์ง๋ฅผ ๊ตฌ๋
ํ๋ ์์ฒญ์ ์ ๋์ฌ๋ /sub๊ฐ ๋๋๋ก ์ค์
config.enableSimpleBroker("/sub");
}
// Stomp ์์ผ์ ์ธ ์ฃผ์๋ฅผ ํน์ ํ๋ค.
@Override
public void registerStompEndpoints(StompEndpointRegistry registry){
// stomp websocket์ผ๋ก ์ฐ๊ฒฐํ๋ ์ฃผ์์ endpoint๋ /ws-stomp๋ก ์ค์
// ๋ฐ๋ผ์ ์ ์ฒด ์ฃผ์๋ ws://localhost:8080/ws-stomp
registry.addEndpoint("/ws-stomp").setAllowedOriginPatterns("*")
.withSockJS();
}
}
configureMessageBroker๋ ๋ฉ์ธ์ง๋ฅผ '๋ฐํํ๋ ํต๋ก'์ '๊ตฌ๋
ํ๋ ํต๋ก'๊ฐ ๋ฌด์์ธ์ง ์ค์ ํ๋ ๋งค์๋์ด๋ค.
registerStompEndPoints๋ STOMP ์น์์ผ์ด ์กด์ฌํ ์ฃผ์๋ฅผ ํน์ ํ๋ ๋งค์๋์ด๋ค. ํด๋น ๋งค์๋์์ ์ฐ๋ฆฌ๋ setAllowedOriginPattern(*)๋ฅผ ํตํด CORS ๋ฌธ์ ๋ฅผ ํผํ๊ณ ์ด๋ค ๋ธ๋ผ์ฐ์ ๋ ํด๋น ์ฃผ์๋ก ์ ์ํ ์ ์๋๋ก ํ๋ค.
โ ChatController
@RequiredArgsConstructor
@Controller
public class ChatController {
private final RedisPublisher redisPublisher;
private final ChatRoomRepository chatRoomRepository;
// @MessageMapping == ๋ฉ์ธ์ง๊ฐ WebSocket์ผ๋ก ๋ฐํ๋๋ ๊ฒฝ์ฐ ๋ฐ์ ๋งค์๋๊ฐ ์คํ๋๋ค.
@MessageMapping("/chat/message")
public void message (ChatMessage message) {
// ํด๋ผ์ด์ธํธ๊ฐ ์ฑํ
๋ฐฉ ์
์ฅ ์ ์ฑํ
๋ฐฉ(TOPIC)์์ ๋ํ๊ฐ ๊ฐ๋ฅํ๋๋ก Topic ๋ฆฌ์ค๋๋ฅผ ์ค์ ํ๋ enterChatRoom ๋ฉ์๋๋ฅผ ์ธํ
if(ChatMessage.MessageType.ENTER.equals(message.getType())){
chatRoomRepository.enterChatRoom(message.getRoomId());
message.setMessage(message.getSender() + "๋์ด ์
์ฅํ์
จ์ต๋๋ค.");
}
//๋ฉ์ธ์ง๋ฅผ redis Publisher๋ฅผ ํตํด ๋ฐํ -> RedisDB์ ์ ์ฅ๋ ํด๋น TOPIC์ผ๋ก ๋ฉ์ธ์ง๋ฅผ ๋ณด๋
redisPublisher.publish(chatRoomRepository.getTopic(message.getRoomId()), message);
}
}
Chat ์ปจํธ๋กค๋ฌ๋ Publish์ SubScribe๋ผ๋ ๋ฉ์ธ์ง ์์คํ ์ ๋ํด์ ๋ค๋ฃจ๋ ์ปจํธ๋กค๋ฌ์ด๋ค. ๋จผ์ ๋ฉ์ธ์ง๊ฐ ๋ฐํ๋๋ฉด, ๊ทธ ๋ฉ์ธ์ง๊ฐ Enter ํ์ ์ผ ๊ฒฝ์ฐ, ํด๋น ๋ฐฉ์ผ๋ก ์ ์ฅํ๋ ์ ์ฐจ๋ฅผ ๋จผ์ ๊ฑฐ์น๊ณ , ๋ฉ์ธ์ง ๋ด์ฉ๋ฌผ์ '~~๋์ด ์ ์ฅํ์ จ์ต๋๋ค๋ก ์ฑ์ด๋ค.' ์ ์ฅ ์ ์ฐจ๋ chatRoomRepository์์ ๋ ์์ธํ ๋ค๋ฃจ๊ฒ ๋ค. ๊ทธ ํ ๋ฉ์ธ์ง์ ๋ฐฉ๋ฒํธ์ ํด๋นํ๋ Topic์ผ๋ก ๋ฉ์ธ์ง๊ฐ ๋ฐํ๋๋ค. TALK ํ์ ์ผ ๊ฒฝ์ฐ, ํด๋น ๋ฉ์ธ์ง๋ฅผ ๋ฐ๋ก ๋ฐํํ๋ค.
โ ChatRoomController
@RequiredArgsConstructor
@Controller
@RequestMapping ("/chat")
public class ChatRoomController {
private final ChatRoomRepository chatRoomRepository;
// 1) ์ฑํ
๋ฆฌ์คํธ ํ๋ฉด ๋ฐํ
@GetMapping("/room")
public String rooms (Model model) {
return "/chat/room";
}
// 2) ์ฑํ
๋ฐฉ ์์ฑ -> ํ๋์ Topic์ ์์ฑ (RestAPI )
@PostMapping("/room")
@ResponseBody
public ChatRoom createRoom(@RequestParam String name){
return chatRoomRepository.createChatRoom(name);
}
// 3) ๋ชจ๋ ์ฑํ
๋ฐฉ ๋ชฉ๋ก ๋ฐํ (RestAPI)
@GetMapping("/rooms")
@ResponseBody
public List<ChatRoom> room() {
return chatRoomRepository.findAllRoom();
}
// 4) ์ฑํ
๋ฐฉ์ ์
์ฅ -> ํด๋น ํ ํฝ์ ๊ตฌ๋
ํ๋ค๋ ๋ป
@GetMapping("/room/enter/{roomId}")
public String roomDetail (Model model, @PathVariable String roomId) {
model.addAttribute("roomId", roomId);
return "/chat/roomdetail";
}
// 5) ํน์ ์ฑํ
๋ฐฉ ์กฐํ
@GetMapping("/room/{roomId}")
@ResponseBody
public ChatRoom roomInfo(@PathVariable String roomId) {
return chatRoomRepository.findRoomById(roomId);
}
}
ChatRoom Controller๋ ์ฑํ ๋ฐฉ ํ๋ฉด ์ด๋, ํน์ ํน์ ์ ์ฒด ์ฑํ ๋ฐฉ ์กฐํ, ์ฑํ ๋ฐฉ ๊ฐ์ค ๋ฒํผ์ ๋๋ฅผ ์, ์ฑํ ๋ฐฉ ์์ฑ ๋ก์ง์ผ๋ก ์ด๋ํ๋ ๊ฒ์ ๋ด๋นํ๊ณ ์๋ค.
โ ChatMessage
import lombok.Getter;
import lombok.Setter;
// ์ฑํ
๋ฉ์ธ์ง๋ฅผ ์ฃผ๊ณ ๋ฐ๊ธฐ ์ํ DTO
@Getter
@Setter
public class ChatMessage {
// ๋ฉ์์ง ํ์
: ์
์ฅ, ์ฑํ
-> ์๋ฒ์์ ํด๋ผ์ด์ธํธ์ ํ๋์ ๋ฐ๋ผ ํด๋ผ์ด์ธํธ์๊ฒ ์ ๋ฌํ ๋ฉ์ธ์ง๋ค์ ์ ์ธํ ๊ฒ
public enum MessageType {
ENTER, TALK
}
private MessageType type; // ๋ฉ์ธ์ง ํ์
private String roomId; // ๋ฐฉ ๋ฒํธ
private String sender; // ๋ฉ์ธ์ง ๋ณด๋ธ ์ฌ๋
private String message; // ๋ฉ์ธ์ง
}
์ฑํ ๋ฉ์ธ์ง์ DTO์ด๋ค. ENUM ๊ฐ์ผ๋ก ์ ์ฅ์ธ์ง, ๋ํ์ธ์ง๊ฐ ๋๋์ด์ง๊ณ , ๋ฐฉ ๋ฒํธ, ๋ณด๋ธ ์ด, ๋ฉ์ธ์ง ๋ด์ฉ๋ฌผ์ ๋ด๊ณ ์๋ค.
โ ChatRoom
// Redis์ ์ ์ฅ๋๋ ๊ฐ์ฒด๋ค์ Serializable์ด ๊ฐ๋ฅํด์ผ ํ๋ฏ๋ก, Serializable์ ์ฐธ์กฐํ๋๋ก ์ ์ธํ๊ณ
// serialVersionUID๋ฅผ ์ธํ
ํด์ค๋ค.
@Getter
@Setter
public class ChatRoom implements Serializable {
private static final long serialVersionUID = 6494678977089006639L;
// ์ฑํ
๋ฐฉ์ ๋ฐฉ ๋ฒํธ์ ์ด๋ฆ
private String roomId;
private String name;
// ๋ฐ์ ๋ด์ฉ์ ํด๋์ค ๋งค์๋๋ก์ ํด๋์ค ์์ฑ๋ถํฐ ์กด์ฌํ๋ ๋งค์๋์ด๊ณ , ํ๋์ chatRoom์ ๋ง๋ค์ด์ ๋ฐํํ๋ค.
public static ChatRoom create(String name) {
ChatRoom chatRoom = new ChatRoom();
chatRoom.roomId = UUID.randomUUID().toString();
chatRoom.name = name;
return chatRoom;
}
}
์ฑํ ๋ฐฉ ์์ฒด์ ๋ํ DTO์ด๋ค. ์ฑํ ๋ฐฉ ๋ฒํธ์ ์ด๋ฆ์ ๊ฐ์ง๊ณ ์๋ค. create๋ ๋งค์๋๋ ์ฑํ ๋ฐฉ ์ด๋ฆ์ ๋ฐ์ผ๋ฉด, UUID๋ฅผ ์ด์ฉํด ์ฑํ ๋ฐฉ์ ์์ฑํ๋ ๋งค์๋์ด๋ค. ํ์ง๋ง ์ฌ๊ธฐ์ ์์ฑํ ์ฑํ ๋ฐฉ์ ์์ง Redis DB์ ์ ์ฅํ์ง ์์์์ผ๋ก ์๋ฏธ๊ฐ ์๋ค. ํ์ Redis DB์ ์ ์ฅํ๋ ์ ์ฐจ ๋ํ ๊ฑฐ์ณ์ค์ผ ํ๋ค.
โ RedisPublisher
@RequiredArgsConstructor
@Service
public class RedisPublisher {
private final RedisTemplate<String, Object> redisTemplate;
public void publish(ChannelTopic topic, ChatMessage message) {
redisTemplate.convertAndSend(topic.getTopic(), message);
}
}
์ฑํ ๋ฐฉ์ผ๋ก ๋ฉ์ธ์ง๋ฅผ ๋ฐํํ๋ ๋ก์ง์ ๋ด์ ํด๋์ค์ด๋ค. ์๊น ์์์ ๋ฐฐ์ ๋ค์ํผ, RedisTemplate ํด๋์ค๋ Redis DB์์ CRUD๋ฅผ ํ๊ธฐ ์ํ ์ค์ ์ ํ๋ ํด๋์ค์ด๋ค. ํด๋น ํด๋์ค๋ฅผ ์ด์ฉํด RedisDB์ ์ ์ฅ๋์ด์๋ Topic์ผ๋ก ๋ฉ์ธ์ง ๋ฉ์ธ์ง๋ฅผ ๋ณด๋ธ๋ค.
โ Redis Subscriber
@Slf4j
@RequiredArgsConstructor
@Service
public class RedisSubscriber implements MessageListener {
private final ObjectMapper objectMapper;
private final RedisTemplate redisTemplate;
private final SimpMessageSendingOperations messagingTemplate;
// Redis์์ ๋ฉ์ธ์ง๊ฐ ๋ฐํ๋๋ฉด, ๋๊ธฐํ๊ณ ์๋ onMessage๊ฐ ํด๋น ๋ฉ์ธ์ง๋ฅผ ๋์์ฑ์ ์ฒ๋ฆฌํ๋ค.
@Override
public void onMessage(Message message, byte[] pattern) {
try{
// redis์์ ๋ฐํ๋ ๋ฐ์ดํฐ๋ฅผ ๋ฐ์ ์ญ์ง๋ ฌํ (๋์์ฑ)
String publishMessage = (String) redisTemplate.getStringSerializer().deserialize(message.getBody());
// ChatMessage ๊ฐ์ฒด๋ก ๋งตํ
ChatMessage roomMessage = objectMapper.readValue(publishMessage, ChatMessage.class);
// Websocket ๊ตฌ๋
์์๊ฒ ์ฑํ
๋ฉ์ธ์ง๋ฅผ Send
messagingTemplate.convertAndSend("/sub/chat/room/" + roomMessage.getRoomId(), roomMessage);
} catch (Exception e) {
log.error(e.getMessage());
}
}
}
Subscriber๋ ๋ฐํ๋์ด์ TOPIC์ผ๋ก ์จ ๋ฉ์ธ์ง๋ฅผ ์ด๋ป๊ฒ ์ ์ฅํ ์ง์ ๋ํ์ฌ ๋ํ๋ธ ํด๋์ค์ด๋ค. ์ด๋ MessageListener๋ผ๋ ์ธํฐํ์ด์ค๋ฅผ ๊ตฌํํ๋ค. ํด๋น ์ธํฐํ์ด์ค์ OnMessage๋ผ๋ ๋งค์๋๋ ๋ฉ์ธ์ง๋ฅผ ๋ฐ์์ ์ด๋ป๊ฒ ์ฒ๋ฆฌํ ์ง์ ๋ํด์ ๋ํ๋ด๋ ํด๋์ค์ด๋ค. ํด๋น ๋งค์๋ ๊ตฌํ ์ฌํญ์ ์ดํด๋ณด๋ฉด, ๋ฉ์ธ์ง๋ฅผ RedisTemplate ๊ฐ์ฒด๋ก๋ถํฐ ๋ฐ๊ณ , ์ญ์ง๋ ฌํํ๋ค. ๋ค์ ์ญ์ง๋ ฌํํ ๋ด์ฉ์ ChatRoom DTO์ ๋ง๊ฒ ์ง๋ ฌํํ์ฌ ๊ฐ์ฒด๋ฅผ ์์ฑํ๋ค.
messagingTemplate.convertAndSend("/sub/chat/room/" + roomMessage.getRoomId(), roomMessage);
ํด๋น ๋ถ๋ถ์ ํด๋น ๋ฐฉ ๋ฒํธ๋ฅผ ๊ตฌ๋ ํ๊ณ ์๋ ์ฌ์ฉ์๋ค์๊ฒ ๋ฉ์ธ์ง๋ฅผ ๋ณด๋ด๋ ๋ถ๋ถ์ด๋ค. (ํด๋น ๋ฐฉ ๋ฒํธ์ ๋ฐฉ์ ์ ์ฅ ํ์ฌ ์๋ ์ํ๊ฐ ๊ตฌ๋ ํ๊ณ ์๋ ์ํ์ด๋ค.)
โ ChatRoomRepository
@RequiredArgsConstructor
@Repository
public class ChatRoomRepository {
// ์ฑํ
๋ฐฉ์ ๋ฐํ๋๋ ๋ฉ์ธ์ง๋ฅผ ์ฒ๋ฆฌํ Listener
private final RedisMessageListenerContainer redisMessageListener;
// ๊ตฌ๋
์ฒ๋ฆฌ ์๋น์ค
private final RedisSubscriber redisSubscriber;
// Redis
private static final String CHAT_ROOMS = "CHAT_ROOM";
private final RedisTemplate<String, Object> redisTemplate;
// chatRoom์ด๋ ์ด๋ฆ์ผ๋ก ์ ์ฅ๋ ๋ชจ๋ HashMap๋ค
private HashOperations<String, String, ChatRoom> opsHashChatRoom;
// ์ฑํ
๋ฐฉ์ ๋ํ ๋ฉ์ธ์ง๋ฅผ ๋ฐํํ๊ธฐ ์ํ redis topic์ ์ ๋ณด
// ์๋ฒ ๋ณ๋ก ์ฑํ
๋ฐฉ์ ๋งค์น๋๋ topic ์ ๋ณด๋ฅผ Map์ ๋ฃ์ด roomId๋ก ์ฐพ์ ์ ์๋๋ก ํ๋ค.
private Map<String, ChannelTopic> topics;
@PostConstruct
private void init() {
opsHashChatRoom = redisTemplate.opsForHash();
topics = new HashMap<>();
}
public List<ChatRoom> findAllRoom() {
return opsHashChatRoom.values(CHAT_ROOMS);
}
public ChatRoom findRoomById (String id) {
return opsHashChatRoom.get(CHAT_ROOMS, id);
}
// ์ฑํ
๋ฐฉ ์์ฑ: ์๋ฒ ๊ฐ ์ฑํ
๋ฐฉ ๊ณต์ ๋ฅผ ์ํด redis hash์ ์ ์ฅํ๋ค.
public ChatRoom createChatRoom(String name) {
// ์ฑํ
๋ฐฉ์ ๋ง๋ค๊ณ
ChatRoom chatRoom = ChatRoom.create(name);
//Redis Hash์ ์ ์ฅ
opsHashChatRoom.put(CHAT_ROOMS, chatRoom.getRoomId(), chatRoom);
// ๊ทธ ํ ๋ง๋ ChatRoom์ ๋ฐํ
return chatRoom;
}
// ์ฑํ
๋ฐฉ ์
์ฅ: redis์ topic์ ๋ง๋ค๊ณ pub/sub ํต์ ์ ํ๊ธฐ ์ํด ๋ฆฌ์ค๋๋ฅผ ์ค์ ํ๋ค.
public void enterChatRoom(String roomId) {
// ์
์ฅํด์ผํ ์ฑํ
๋ฐฉ (topic)์ ์ป์ด์จ๋ค.
ChannelTopic topic = topics.get(roomId);
if(topic == null)
topic = new ChannelTopic(roomId);
// ํด๋น Topic์ผ๋ก ๋ค์ด์จ ๋ฉ์ธ์ง๋ ์ด๋ป๊ฒ ์ฒ๋ฆฌํ ๊ฒ์ธ์ง์ ๋ํด ๋ช
์ธํ redisSubScriber๋ฅผ Listener์ ๋ฑ๋ก
redisMessageListener.addMessageListener(redisSubscriber, topic);
topics.put(roomId, topic);
}
public ChannelTopic getTopic(String roomId) {
return topics.get(roomId);
}
๋จผ์
private HashOperations<String, String, ChatRoom> opsHashChatRoom;
์ธ์๊ฐ 3๊ฐ๋ ๋ค์ด๊ฐ๋ HashOperations์ ๋ํด์ ๊ถ๊ธํ ๊ฒ์ด๋ค. ํด๋น ๋ถ๋ถ์ <ํด๋น HashMap์ ์ด๋ฆ, Key, Value>๋ก ์ด๋ฃจ์ด์ง Redis์ ์๋ฃ๊ตฌ์กฐ์ด๋ค.
์์ ๋์จ ์ฑํ
๋ฐฉ์ ์กฐํํ๋ ๋ก์ง์ ์ ๋ฒ ํฌ์คํ
๊ณผ ๋ด์ฉ์ด ๊ฐ์ผ๋ฏ๋ก ์ฑํ
๋ฐฉ์ ์์ฑํ๊ณ , ์ฑํ
๋ฐฉ์ ์
์ฅํ๋ ๋ก์ง์ ๋ํด ๋ค๋ฃจ์ด๋ณด๊ฒ ๋ค.
๋จผ์ ์ฑํ
๋ฐฉ ์์ฑ๋ก์ง์ ์ด๋ฆ์ ๋ฐ์์ ์ฑํ
๋ฐฉ์ ๋ง๋ค๊ณ HashOperations์ ๋ฑ๋กํ๋ค. ๊ทธ ๋ค์ ChatRoom์ ๋ฐํํ๋ค.
์ฑํ ๋ฐฉ ์ ์ฅ ๋ก์ง์ ๋จผ์ Topics๋ผ๋ ์๋ฒ ๋จ์์ ์ฑํ ๋ฐฉ์ ์ ์ฅํ๊ณ ์๋ HashMap์์ ํด๋น ๋ฐฉ๋ฒํธ์ ํด๋นํ๋ Topic ๊ฐ์ฒด๊ฐ ์๋์ง ๋จผ์ ํ์ธํ๋ค. ๋ง์ฝ ์๋ค๋ฉด, ChannelTopic์ด๋ผ๋ ์์ฑ์๋ฅผ ์ด์ฉํ์ฌ Topic์ ์์ฑํ๊ณ Topics๋ผ๋ ๊ณณ์ ๋ฃ๋๋ค. ๋ํ Redis Subscriber๋ฅผ ์ด์ฉํด ํด๋น Topic์ Listener๋ฅผ ๋ฑ๋กํ๋ค. ํด๋น Topic์ผ๋ก ๋ญ๊ฐ ๋ฐํ๋๋ฉด Topic ๊ตฌ๋ ์๋ ๊ทธ๊ฒ์ ์ฝ์ ์ ์๋๋ก ํ๋ ๊ฒ์ด๋ค.
โ ํ๋ก ํธ ์๋ ์ฝ๋ ๋ถ์
ํด๋น ํ๋ก์ ํธ์ ๋ํ ์๋ฒฝํ ์ดํด๋ฅผ ์ํด์๋ ํ๋ก๊ทธ๋จ์ ํ ์ถ์ธ ํ๋ก ํธ์๋ ๋ถ๋ถ์ ๋ํ ์ดํด๋ ํ์์ ์ธ ๊ฒ ๊ฐ๋ค. ํ๋ก ํธ์๋ ํ์ผ์ ๋ฑ ๋ ๊ฐ๋ก ์ฑํ ๋ฐฉ ๋ฆฌ์คํธ ํ๋ฉด์ ๋ด๋นํ๋ Room.ftl ํ๋ฉด๊ณผ ์ฑํ ๋ฐฉ ์์ ๋ค์ด๊ฐ์ ๋ ํ๋ฉด์ธ RoomDetail ํ๋ฉด์ด ์๋ค. ํ๋์ฉ ์ฒ์ฒํ ์์๋ณด์.
โด Room.ftl
์๋ vue๋ก ๋์ด์๋๋ฐ, HTML ๋ถ๋ถ ๋นผ๊ณ Method ๋ถ๋ถ๋ง ์ดํด๋ณด์. HTML์ ์ง๊ด์ ์ผ๋ก ์ดํด๊ฐ ๋๊ณ , Method ๋ถ๋ถ์ด ํต์ฌ์ด๊ธฐ ๋๋ฌธ์ด๋ค. ๋ค๋ฅธ ๋ด์ฉ๋ค์ ๋ฐ์ ๊นํ๋ธ๋ฅผ ์ฐธ๊ณ ํด์ฃผ๋ฉด ์ข๊ฒ ๋ค.
methods: {
findAllRoom: function() {
axios.get('/chat/rooms').then(response => { this.chatrooms = response.data; });
},
createRoom: function() {
if("" === this.room_name) {
alert("๋ฐฉ ์ ๋ชฉ์ ์
๋ ฅํด ์ฃผ์ญ์์.");
return;
} else {
var params = new URLSearchParams();
params.append("name",this.room_name);
axios.post('/chat/room', params)
.then(
response => {
alert(response.data.name+"๋ฐฉ ๊ฐ์ค์ ์ฑ๊ณตํ์์ต๋๋ค.")
this.room_name = '';
this.findAllRoom();
}
)
.catch( response => { alert("์ฑํ
๋ฐฉ ๊ฐ์ค์ ์คํจํ์์ต๋๋ค."); } );
}
},
enterRoom: function(roomId) {
var sender = prompt('๋ํ๋ช
์ ์
๋ ฅํด ์ฃผ์ธ์.');
localStorage.setItem('wschat.sender',sender);
localStorage.setItem('wschat.roomId',roomId);
location.href="/chat/room/enter/"+roomId;
}
}
1. findALLRoom์ Get ์์ฒญ์ ํตํด JSON ํํ๋ก ๋ชจ๋ ChatRoom ๊ฐ์ฒด๋ฅผ ๋ฐํ ๋ฐ๋๋ค. ๊ทธ๋ฆฌ๊ณ ๋ฐํ๋ฐ์ JSON ๊ฐ๋ค์ chatRooms๋ผ๋ ๋ฐฐ์ด์ ๋ฃ์ด์ ํ๋ฉด์ ๋ณด์ฌ์ค๋ค.
2. createRoom Function์ ์ฑํ ๋ฐฉ ์ ๋ชฉ์ ์ ๋ ฅํ์ง ์์๋ค๋ฉด ๊ฒฝ๊ณ ๋ฌธ์ ๋์ด์ ๋๋ ค๋ณด๋ด๊ณ , ์ ๋๋ก ์ ์๋ค๋ฉด, /chat/room์ผ๋ก ํด๋น ๋ฐฉ ์ ๋ชฉ์ ์ค์ด์ Post ์์ฒญ์ ํ๋ค. ๊ทธ๋ฆฌ๊ณ , ์ ๋๋ก POST ์์ฒญ์ ์ฑ๊ณตํ๋ค๋ฉด, ๋ค์ findAllRoom()์ ํด์ ์ฑํ ๋ฐฉ์ ๋ฆฌ์คํธ๋ฅผ ์๋ก ๋ฐ์์ ํ๋ฉด์ ๋์ด๋ค.
3. EnterRoom์ ์ฌ์ฉ์๊ฐ ๊ณ ๋ฅธ ๋ฐฉ์ ๋ฐฉ์ ๋ชฉ๊ณผ ๋ํ๋ช ์ผ๋ก ์ ๋ ฅํ ๋ด์ฉ์ ์ธ์๋ก ๋ฐ์์ localStroage์ ์ ์ฅํ๋ค. ๊ทธ๋ฆฌ๊ณ /chat/room/enter+ ๋ฐฉ ๋ฒํธ๋ก ํ๋ฉด ์ด๋์ ํ๋ค. location.href๋ฅผ ์ด ๊ฒ์ ๋ณด๋, SPA๋ฅผ ์ง์ผ์ ์ฝ๋ฉํ ๊ฒ์ ์๋ ๋ฏ ํ๋ค...
โต roomDetail.ftl
// ๋ณ์ ์์ฑ ๋ฐ ์ด๊ธฐํ-------------------------------------------------------------------
var vm = new Vue({
el: '#app',
data: {
roomId: '',
room: {},
sender: '',
message: '',
messages: []
},
created() {
this.roomId = localStorage.getItem('wschat.roomId');
this.sender = localStorage.getItem('wschat.sender');
this.findRoom();
},
// AXIOS -----------------------------------------------------------------
methods: {
findRoom: function() {
axios.get('/chat/room/'+this.roomId).then(response => { this.room = response.data; });
},
sendMessage: function() {
ws.send("/pub/chat/message", {}, JSON.stringify({type:'TALK', roomId:this.roomId, sender:this.sender, message:this.message}));
this.message = '';
},
recvMessage: function(recv) {
this.messages.unshift({"type":recv.type,"sender":recv.type=='ENTER'?'[์๋ฆผ]':recv.sender,"message":recv.message})
}
}
});
// pub/sub event ---------------------------------------------------------------------
ws.connect({}, function(frame) {
ws.subscribe("/sub/chat/room/"+vm.$data.roomId, function(message) {
var recv = JSON.parse(message.body);
vm.recvMessage(recv);
});
ws.send("/pub/chat/message", {}, JSON.stringify({type:'ENTER', roomId:vm.$data.roomId, sender:vm.$data.sender}));
}, function(error) {
alert("error "+error);
});
๋จผ์ ๋ค์ด์ค์ ๋ง์ create() ํจ์๋ฅผ ์งํํด์ localStroage์ ๋ฃ์๋ ID์ sender๋ฅผ ํ์ฌ ํ์ด์ง์ roomId์ sender์ ๋ฃ๋๋ค.
(1) Method ๋ถ๋ถ
- findRoom ํจ์๋ฅผ ํตํด์ ํ์ฌ ์ฌ์ฉ์๊ฐ ๋ค์ด๊ฐ ๋ฐฉ์ ์ ๋ณด๋ฅผ ๋ฐ๋๋ค. ์ ๋ณด๋ฅผ ๋ฐ์์ room์ด๋ ๋ณ์์ ์ ์ฅํ๋ค.
- sendMessage : ๋๋์ด pub/chat/message๋ผ๋ ์ฃผ์๋ฅผ ๋ง๋ฌ๋ค!! ์ฌ๊ธฐ์๋ Message์ ๋ํ ๋ฐํ์ ๋ด๋นํ๋ค. ์ฌ์ฉ์๊ฐ ์น ๋ฉ์ธ์ง ๋ด์ฉ, ๋ฐฉ๋ฒํธ, ๋ณด๋ธ ์ด, ๋ณด๋ธ ๋ฉ์ธ์ง ํ์ : TALK์ผ๋ก ํด์ ํด๋น ์ฃผ์๋ก ๋ณด๋ธ๋ค. ์ ๋์ฌ๊ฐ pub์ด๋ฏ๋ก ๋ฐฑ์๋๋ก์ง์์๋ ํด๋น ๋ด์ฉ์ ๋ฐํ์ผ๋ก ์์๋จน์ ๊ฒ์ด๋ค.
- recvMessage: .unshift ํจ์๋ ๋ฐฐ์ด ๋งจ ์์ ํด๋น ์์๋ฅผ ์ถ๊ฐํ๊ณ , ์๋ก์ด ๊ธธ์ด๋ฅผ ๋ฐํํ๋ ํจ์์ด๋ค. ๋๊ฐ ๋ค์ด์ค๋ฉด ๋ฐ๋ก EnterType์ ๋ฉ์ธ์ง๋ฅผ ๋ง๋ค์ด์ Messages๋ผ๋ ํ๋ก ํธ Level์ ๋ฉ์ธ์ง๋ฅผ ์ ๋ถ ์ ์ฅํ๋ ๋ฐฐ์ด์ ์ ์ฅํ๋ค. ๋ง์ฝ MessageType์ด Enter๊ฐ ์๋๋ฉด ๊ทธ๋ฅ ์๋ ํด๋น ๋ด์ฉ์ผ๋ก ๋ฉ์ธ์ง ๊ฐ์ฒด๋ฅผ ๋ง๋ค์ด์ ๋ฉ์ธ์ง ๋ฐฐ์ด์ ๋ฃ๋๋ค.
(2) WebSocket Connect ๋ถ๋ถ
- ํ๋ก ํธ์ WebSocket ํต์ ์ ๋ํด ์์ ๋ชจ๋ฅด๊ฒ ์ง๋ง, (๋จผ์ ๋ฐฑ์๋ ๊ณต๋ถ ๋๋ด๋ฉด ๊ณต๋ถํด์ผ๊ฒ ๋ค.) ํด๋น ํ์ด์ง ๋๋๋ง ์ ๋ฐ๋ก ์คํํ๋ ์ฝ๋์ธ ๊ฒ ๊ฐ๋ค. ๋จผ์
var sock = new SockJS("/ws-stomp");
var ws = Stomp.over(sock);
ํด๋น ์ฝ๋๋ฅผ ํตํด ๋ฐฑ์๋์์ ์ค์ ํ๋ ์์ผ ํต์ ์ฃผ์๋ก ๋ชฉํ ์ง์ ์ ์ง์ ํ๋ค. ๊ทธ ํ
์์ ws.connect ๋ถ๋ถ์ ์คํํ๋ค.
- ws.subscribe๋ฅผ ํตํด์ "/sub/chat/room" + ํ์ฌ ๋ค์ด์จ ๋ฐฉ์ ๋ฐฉ๋ฒํธ๋ก ๊ตฌ๋ ํ๋ค. ์ด๋ ๊ฒ ๋๋ฉด ํด๋น ๋ฐฉ์ TOPIC์ผ๋ก ๋ฐํ๋๋ ๋ฉ์ธ์ง๋ฅผ ๋ฐ์ ์ ์๊ฒ ๋๋ค. ์ฌ๊ธฐ์๋ ๋ฉ์ธ์ง๋ฅผ ๋ฐ์์ ๊ฐ์ฒด๋ก Parsing ํ๊ณ ์์ Message ๋ฐฐ์ด์ ๊ฐ์ ๋ฃ๊ณ ๊ด๋ฆฌํ๋ recvMessage ํจ์์ ๊ฐ์ฒด๋ฅผ ๋ฃ๋๋ค.
- ws.send๋ pub/chat/message๋ฅผ ์ด์ฉํด์ ๋ฐฉ์ ์ ์ฅํ ์ฌ๋์ ์ ๋ณด์ ์๋ฆผ์ ๋ฐ๋ก socket์ผ๋ก ๋ณด๋ด๋๋ก ํ๋ค. ENTER TYPE์ sendํ๋ ๊ฒ์ ๋๊ฐ ํด๋น ๋ฐฉ์ ๋ค์ด์์ roomDetail ํ์ด์ง๊ฐ ์คํ๋ ์์ ๋ง ํ๋ค. ๋๋จธ์ง TALK ํ์ ๋ฉ์ธ์ง๋ ์์ sendMessage ํจ์๋ก ๋ฒํผ ํด๋ฆญ ์ ws.send ๋๋๋ก ๋ง๋ ๋ค.
4. ํ๋ก์ ํธ ์งํํ๋ฉด์ ์๊ธด ์๋ฌ๋ค์ ๋ํ์ฌ
ํด๋น ๋ถ๋ถ์ ๊ฒ์์ผ๋ก๋ ๋ณผ ์ ์๋๋ก ๋ค๋ฅธ ํฌ์คํ ์์ ์์ฑํ์ฌ ๋งํฌ๋ฅผ ์ฌ๋ฆฌ๋๋ก ํ๊ฒ ๋ค.
EmbededRedis ์ค์ ์ค๋ฅ : https://dalcheonroadhead.tistory.com/373
ํด๋น ๋ถ๋ถ ์ฝ๋ฉํ ๊นํ๋ธ ๋ฐ๋ก ๊ฐ๊ธฐ : https://github.com/dalcheonroadhead/WebSocketBEPractice/tree/ThirdStep