websocket을 사용한 spirng 서버 개발하기

박해인·2023년 8월 22일
0

서버와 클라이언트가 통신하는 방법에는 여러가지가 있다.

1. HTTP 프로토콜
첫번째 HTTP 프로토콜은 client가 요청을 하면 server가 응답을 보낸다. 여기서 주의해야할점은 client가 "요청"을 해야만 server가 응답한다는것. 따라서 "실시간" 소통에는 적절치 않다.

2. Polling
HTTP 프로토콜과 같은 원리, 하지만 client가 일정주기로 server에 요청을 하기 때문에 HTTP보다는 실시간을 표방한다.

3. WebSocket
server와 client의 실시간 통신방법, server와 client가 1회의 연결이 성공한다면, client의 요청없이도 server가 client에 데이터를 보낼 수 있다.

우리팀은 "실시간" 도난의심상황 감지를 하는 기능을 만들어낼 필요가 있었기때문에, 도난의심상황을 나타내는 부분에서는 HTTP 통신방식이 아닌 WebSocket을 사용하여 구현했하였다.

  • 환경 : Spring Boot, Android Studio, MySQL

우리는 도난의심 상황을 분류하기 위하여 아래와 같은 알고리즘을 생각해냈다.

그리고 위와 같은 알고리즘을 구현하기 위하여 구체적으로 아래와 같은 절차를 거쳤다.

객체 수량 변화가 감지되면 controller 를 거치기 때문에,
객체 수량이 감지되어 호출된 controller에서 RestTemplate을 이용하여 RestAPI를 호출한다. 그래서 DB의 두개의 테이블에서 각각 수량을 가져와 비교하고, 일치하지 않으면 WebSocket을 호출하여 Client에게 신호를 보낸다. 그 후에 controller의 작동이 중단된다.
그렇다면, RestTemplate을 구현해야한다.

RestTemplate이란?

코드내에서 RestAPI를 호출해야할 필요가 있다. 이 때 사용하는게 RestTemplate이다.

  1. RestTemplateService 인터페이스를 작성한다.

RestTemplateService.java

package com.example.demo.service;

public interface RestTemplateService {

	public String getCam_stock(String item_id);
	public String getItem_price();
	public String getItem_stock();
	public int getMarket_id();
	public int getItem_id();
	public String getReal_stock(String item_id);
	
}
  1. RestTemlateService를 구현한 RestTemplateServiceImpl 클래스를 작성한다.

RestTemplateServiceImpl.java

package com.example.demo;

import java.net.URI;


import org.json.simple.JSONObject;
import org.json.simple.parser.ParseException;
import org.json.simple.parser.JSONParser;

import org.springframework.http.ResponseEntity;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.util.UriComponentsBuilder;
import com.example.demo.service.RestTemplateService;


public class RestTemplateServiceImpl implements RestTemplateService{

	JSONObject object;
	@Override
	public String getReal_stock(String item_id) {
		URI uri = UriComponentsBuilder
				.fromUriString("http://localhost:8080/real_stock/"+item_id)
				.encode()
				.build()
				.toUri();
		
		
		RestTemplate restTemplate = new RestTemplate();
		ResponseEntity<String> response = restTemplate.getForEntity(uri, String.class);

	 
		
		JSONParser parser = new JSONParser();
		try {
			JSONObject obj = (JSONObject) parser.parse(response.getBody());
			object = obj;
			
		} catch (ParseException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}

		String result = object.get("item_stock").toString();

		return result;
		
	}
	
	@Override
	public String getCam_stock(String item_id) {
		
		URI uri = UriComponentsBuilder
				.fromUriString("http://localhost:8080/stock/"+item_id)
				.encode()
				.build()
				.toUri();
		
		
		RestTemplate restTemplate = new RestTemplate();
	
		ResponseEntity<String> response = restTemplate.getForEntity(uri, String.class);
		
	 
		JSONParser parser = new JSONParser();
		try {
			JSONObject obj = (JSONObject) parser.parse(response.getBody());
			object = obj;
			
		} catch (ParseException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	
		String result = object.get("item_stock").toString();

		return result;
	}
	
	

	@Override
	public String getItem_price() {
		// TODO Auto-generated method stub
		return null;
	}

	@Override
	public String getItem_stock() {
		// TODO Auto-generated method stub
		return null;
	}

	@Override
	public int getMarket_id() {
		// TODO Auto-generated method stub
		return 0;
	}

	@Override
	public int getItem_id() {
		// TODO Auto-generated method stub
		return 0;
	}
	

}

자 이제 WebSocket을 사용할 준비가 다 되었다.
다음으로 WebSocket을 구현해보자.

  1. TextWebSocketHandler를 상속하는 SocketTextHandler 구현
  2. WebSocketConfigurer를 상속하는 WebSocketConfig 구현

[SocketTextHandler]

서버는 클라이언트가 보낸 메세지를 처리할 핸들러가 필요하다. 텍스트 기반의 소통이므로 'TextWebSocketHandler'를 상속받아서 작성한다.

[WebSocketConfigurer]

핸들러를 이용해 WebSocket을 활성화해준다.

SocketTextHandler.java

package com.example.demo.websocket;

import java.io.IOException;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;

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

public class SocketTextHandler extends TextWebSocketHandler {
	
	private Set<WebSocketSession> sessions = ConcurrentHashMap.newKeySet();
	
	public static WebSocketSession id;
	
	
	@Override
	public void afterConnectionEstablished(WebSocketSession session) throws Exception {
		
		sessions.add(session);
		
		// 클라이언트에게 메시지를 보내는 예시 코드
        //String responseMessage = "Hello, client!";
        
		System.out.print("세션 아이디는 " +session.getId());
        id = session;
        
        //extMessage textMessage = new TextMessage(responseMessage);
        
        //session.sendMessage(textMessage);
        
        System.out.print(id.toString());


	}
	
	
	public void sendmessage(WebSocketSession session, String message) {
	        try {
	        	
	            session.sendMessage(new TextMessage(message));
	        } catch (IOException e) {
	            e.printStackTrace();
	        }
	        
	    }
	

	@Override
	public void handleMessage(WebSocketSession session, WebSocketMessage<?> message) throws Exception {
		// TODO Auto-generated method stub
		
	}
	

	@Override
	public void handleTransportError(WebSocketSession session, Throwable exception) throws Exception {
		// TODO Auto-generated method stub
	}
	

	@Override
	public void afterConnectionClosed(WebSocketSession session, CloseStatus closeStatus) throws Exception {
		// TODO Auto-generated method stub
		sessions.remove(session);
	}
	

	@Override
	public boolean supportsPartialMessages() {
		// TODO Auto-generated method stub
		return false;
	}

}

WebSocketConfig.java

package com.example.demo.websocket;

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;

@Configuration
@EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer{
	@Override
	public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
		registry.addHandler(socketTextHandler(),"/text")
		.setAllowedOrigins("*");		
	}
	
	
	@Bean
	public WebSocketHandler socketTextHandler() {
		return new SocketTextHandler();
	}
	
	

}

위와 같이 구현하고 나면, 객체 수량이 일치하지 않을때 즉, 도난의심 상황이 발생했을때 Client의 별도의 요청(ex 새로고침)이 없을때도 Client에게 알림을 보낼 수 있다.

profile
갓생살고싶어라

0개의 댓글