본문 바로가기
Framework/Spring Framework

[SpringFramework] WebSocket과 Stomp 연동하기

by 원동호 2018. 8. 1.
반응형

WebSocket은 웹 상에서 쉽게 소켓통신을 하게 해주는 라이브러리로 실시간 채팅 서비스등 여러 유용한 서비스의 기반이 된다.스프링 환경에서는 이러한 서비스를 구현하기 위해서 필요한 2가지가 있는데 WebSocket의 기능을 보완해주고 향상시켜주는 SockJS라이브러리와 메시징전송을 좀 더 효율적으로 지원해주기 위한 STOMP 프로토콜이 존재한다. 일반 WebSocket 환경에서는 핸들러만 구현해주고 직접 호출했지만 STOMP를 이용하면 핸들러와 브로커 라는 개념을 이용해서 서로간의 통신을 하게 된다.

 

  • STOMP

STOMP는 Simple/Streaming Text Oriented Messaging Protocol의 약자이며 텍스트 기반의 메시징 프로토콜 이다.

TCP나 WebSocket과 같은 신뢰성있는 양방향 streaming network protocol상에 사용될 수 있다. HTTP에 모델링된 frame 기반 프로토콜이다.

 

위의 구조에서 중요한 개념은 브로커Subscribe의 개념이다. STOMP는 구독이라는 개념을 통해 내가 통신 하고자 하는 주체(topic)를 판단하여 실시간 , 지속적으로 관심 을 가지며 해당 요청이 들어오면 처리하게 된다. 이러한 과정은 MessageHandler를 구현하여 처리한다.

 

1. Connect

 

<<<CONNECTED

version:1.1

heart-beat:0.0

user-name:user1

연결에 관한 구조이다. 버전정보와 현재의 세션정보를 가져온다.

 

2. Subscribe

 

>>> SUBSCRIBE

id : sub-0

destination:/topic/roomId

 

>>> SUBSCRIBE

id : sub-1

destination:/topic/out

 

>>> SUBSCRIBE

id : sub-2

destination:/topic/in

 

구독이라는 개념을 이용하여 현재 메세지에 대한 목적지를 설정한다. 구조를 보면 각각의 destination이 있다. connect 이후에 subscribe를 설정 하게 된다. 등록되지 않은 subscribe를 호출 시 찾을 수 없기에 정확한 통신이 되지 않는다.

 

3. Message

 

<<<MESSAGE

destination : /topic/roomId

content-type:application/json;charset=UTF-8

subscription:sub-0

message-id:2oy02l5d-71

content-length:43

{"senderName : "user1" , "sendMessage":"hi?"}

 

메시지 전송시 구조이다. 메시지의 전달지(destination)와 해당 메세지의 정보들이 출력 된다. 현재 위의 구조는 데이터의 타입은 JSON으로 전송하였고 목적지는 /topic/roomId이다. 메시지의 길이는 43이고 body부분은 데이터가 정의되어 있다. 데이터는 JSON구조로 key, value로 되어있다. 다양한 데이터 타입을 가질 수 있다.

 

4. Disconnect

 

>>>DISCONNECT

 

연결을 종료했을 시 구조이다.

 

Websocket설정에서는 bean생성과 handler 매핑을 xml파일로했지만

Stomp연동에서는 xml이아닌 java파일로 설정파일로 구성할것이다.

 

config파일인 WebSocketConfig.java와 핸들러를 담당할 MessageHandler.java를 생성해준다.

 

 

WebSocketConfig.java

package com.dongho.won; 
import org.springframework.context.annotation.Configuration; 
import org.springframework.messaging.simp.config.MessageBrokerRegistry; 
import org.springframework.web.socket.config.annotation.AbstractWebSocketMessageBrokerConfigurer; 
import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker; 
import org.springframework.web.socket.config.annotation.StompEndpointRegistry; 
import org.slf4j.Logger; import org.slf4j.LoggerFactory;  

@Configuration 
@EnableWebSocketMessageBroker 
public class WebSocketConfig extends AbstractWebSocketMessageBrokerConfigurer {  
	private static Logger logger = LoggerFactory.getLogger(WebSocketConfig.class);  	
	
	@Override 	
	public void configureMessageBroker(MessageBrokerRegistry config) { 		 
		//메시지 브로커가 /topic/이 들어가는 구독자들에게 메시지를 전달해준다. 		
		config.enableSimpleBroker("/topic/"); 		 		
		
		//클라이언트가 서버에게 /app 을 붙이고 메시지를 전달할 주소 		
		config.setApplicationDestinationPrefixes("/app"); 	
	}  	
	
	@Override 	
	public void registerStompEndpoints(StompEndpointRegistry registry) { 		
		//클라이언트가 서버에 접속할 Endpoint를설정한다.엔드포인트는 여러개 추가가능하다 . 		
		//client에서 Websocket대신 향상된 SockJS로 접속하려면 .withSockJS.()를 붙여준다.! 		
		registry.addEndpoint("/websocketHandler").withSockJS(); 		
		logger.info("{}","세션접속"); 	
	} 
} 

MessageHandler.java

package com.dongho.won;  
import org.slf4j.Logger; 
import org.slf4j.LoggerFactory; 
import org.springframework.beans.factory.annotation.Autowired; 
import org.springframework.messaging.handler.annotation.MessageMapping; 
import org.springframework.messaging.handler.annotation.SendTo; 
import org.springframework.web.bind.annotation.RestController; 
import org.springframework.web.client.RestTemplate;  
import com.dongho.vo.MemberVO; 

@RestController public class MessageHandler { 	
	@Autowired 	RestTemplate rest; 	
	private static Logger logger = LoggerFactory.getLogger(MessageHandler.class);  	 	
		//WebSocketConfig에서 설정한 destinationprefix + 클라이언트가 전송할 mapping주소  	
		//클라이언트가 /app/in 을 붙여 서버로 메시지를 전송한다. 	
		@MessageMapping("/in/tt")  	
		// /topic/in을 구독하고있는 클라이언트에게 데이터를 전송한다. 	
		@SendTo("/topic/in") 	
		public MemberVO in(MemberVO membervo) throws Exception{ 		
		//String test=rest.getForObject("http://localhost:8080/won/register", String.class); 	   
		rest.postForObject("http://localhost:8080/won/register2", membervo, String.class); 	
		logger.info("{}",membervo + "클라이언트에게 전송"); 		
		return membervo; 	
	} 
} 

chat.jsp

function connect() { 		
	var text="abc"; 	
	var text2="def"; 	
	var socket = new SockJS('/won/websocketHandler');  
	
	//websocket이아닌 SockJS로 접속한다. 			
	stompClient = Stomp.over(socket); 
	
	//stompClient에 socket을 넣어준다. 			
	stompClient.connect({}, function() { 
		//접속 				
		stompClient.send('/app/in/tt', {}, JSON.stringify({'id':text, 'pw':text2})); 
		stompClient.subscribe('/topic/in', function(msg) { 
			alert(msg); 				
		}); 			
	}); 		
} 		

connect(); 	
반응형

댓글