[ Java / Socket ] 접속자 제어 및 브로드캐스팅(그룹채팅 구현)

2024. 6. 29. 13:57· LANGUAGE/└ Java

환경 : Spring Tool Suite4

 

 

  • 창마다 새로운 사용자로 인식하게끔 설정하여, 그룹채팅의 효과를 이해해보자
  • 브로드캐스팅을 통해 모두에게 메세지가 전송되도록 구현! 

 

 

소켓 생성

   - GroupWebSocketServer.java

/**
 * 접속한 사용자들을 어떻게 관리할 것인가?
 * - 접속한 모든 사용자에게 메세지를 보내고 싶다면 사용자 정보를 저장
 * - 저장소를 Set<WebSocketSession> 형태로 생성
 * */
@Slf4j
@Service
public class GroupWebSocketServer extends TextWebSocketHandler{
	
	//사용자의 정보를 저장할 저장소 생성
	//private Set<WebSocketSession> users = new HashSet<>();//HashSet은 동기화 안 됨(자물쇠 없음)
	//private Set<WebSocketSession> users = Collections.synchronizedSet(new HashSet<>());//자물쇠 추가
	private Set<WebSocketSession> users = new CopyOnWriteArraySet<>();//동기화 됨(자물쇠 있음)
	
	
	@Override
	public void afterConnectionEstablished(WebSocketSession session) throws Exception {
		users.add(session);
		log.debug("사용자 접속! 현재 사용자 {}명", users.size());
	}
	
	@Override
	public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {
		users.remove(session);
		log.debug("사용자 접속 종료! 현재 사용자 {}명", users.size());
	}

	@Override
	protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
		//사용자가 메세지를 보내면 어떻게 할까?
		//- 사용자가 보낸 메세지를 저장소에 있는 모든 사용자에게 전송
		//- 메세지는 조작을 해도 되고 있는 그대로 전송해도 된다
		
		//전체에게 메세지를 전송(broadcast)
		for(WebSocketSession user : users) {
			user.sendMessage(message);
		}
	}
}

    (+) 사용자의 정보를 저장할 저장소를 생성하기 위해 Collection으로 받아야 한다.

         Set, Map, List 중 Set을 사용할 것임!

         HashSet의 경우 동기화가 되지 않으므로 사용할 수 없다. 

    (+) Collections.synchronizedSet(new HashSet<>()); 
        위의 토드는 동기화된 컬렉션을 생성하는 코드이다. 

        이 코드는 HashSet 컬렉션을 생성한 후 Collections 유틸리티 클래스의 synchronizedSet 메소드를 사용하여 HashSet을 동기화된 Set으로 래핑하는 것이다. 따라서 원래부터 동기화가 되는 것이 아닌 한 번 단계를 거쳐 동기화 기능을 부여했다고 볼 수 있다. 

    (+) CopyOnWriteArraySet<>(); 따라서 원래부터 동기화가 되는 이 메소드를 사용한다. 

 

    (+) 전체 사용자에게 메세지를 전송하기 위하여 for문을 활용하여 브로드캐스트 한다. 


 소켓 등록

   - WebSocketServerConfiguration.java

/**
 * 웹소켓과 관련된 설정을 작성하는 파일
 * 이미 등록해둔 웹소켓 서버들을 가져와서 추가적인 설정을 한 뒤 활성화
 * */

@EnableWebSocket//웹소켓을 사용할 것임을 표시(활성화)
@Configuration//설정파일임을 표시
public class WebSocketServerConfiguration implements WebSocketConfigurer {
	@Autowired
	private GroupWebSocketServer groupWebSocketServer;
	
	@Override
	public void registerWebSocketHandlers (WebSocketHandlerRegistry registry) {
		//SockJS를 사용하도록 설정하며 등록
		//[1] 웹소켓을 지원하지 않는 브라우저는 유사기술로 웹소켓처럼 구현해줌
		//(유사기술은 pulling, long-pulling과 같은 기술을 말함)
		//[2] 주소를 http로 사용 가능하며, 아무나 들어오지 못하도록 ws 주소가 변함
		//[3] 접속자에 대한 컴팩트한 관리가 가능하다(heartbeat 핑)
		registry.addHandler(groupWebSocketServer, "/ws/group")
				.withSockJS();
	}
}

페이지 등록

   - PageController.java

@Controller
@RequestMapping("/page")
public class PageController {
	@RequestMapping("/group")
	public String group() {
		return "group";
	}
}

 

 

   - group.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>

<!-- javascript toast library -->
<link rel="stylesheet" type="text/css" href="https://cdn.jsdelivr.net/npm/toastify-js/src/toastify.min.css">
<script type="text/javascript" src="https://cdn.jsdelivr.net/npm/toastify-js"></script>

<!-- sockjs cdn -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/sockjs-client/1.6.1/sockjs.min.js"></script>

<h1>그룹채팅 예제</h1>

<button class="btn-connect">연결</button>
<button class="btn-disconnect">종료</button>

<hr>

<input type="text" class="text-input" placeholder="메세지 작성">
<button class="btn-send">전송</button>

<div class="chat-wrapper"></div>

<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.7.1/jquery.min.js"></script>
<script type="text/javascript">
$(function(){
	$(".btn-connect").click(function(){
		window.socket = new SockJS("${pageContext.request.contextPath}/ws/group");
		
		//연결 완료 시 해야할 작업들을 "콜백 함수"로 설정
		window.socket.onmessage = function(e){
			var area = $("<div>").text(e.data);
			$(".chat-wrapper").append(area);
		}
	});	
	$(".btn-disconnect").click(function(){
		window.socket.close();
	});
	
	//전송버튼을 누르면 입력한 메세지가 있는 경우 서버에 전송
	$(".btn-send").click(function(){
		var text = $(".text-input").val();//입력창의 입력값을 불러온다
		if(text.length == 0) return;//입력값이 없으면 중지한다
		
		window.socket.send(text);//서버로 입력값을 전송한다
		$(".text-input").val("");//입력창의 입력값을 삭제한다
	});
});
</script>

    (+) 사용자가 작성한 내용이 chat-wrapper 클래스의 부분에 보여지고, input 창에 남은 내용은 지워주도록 구현. (이런 부분도 코드로 처리해줘야 한다는 사실......)

 

 

출력 결과

   - 왼쪽 사용자가 글 작성

 

 

   - 오른쪽 사용자가 글 작성

 

 

 

 

개인 공부 기록용입니다:)

728x90