[Spring] SockJS,Stomp 실시간채팅방

JeaHyuck·2021년 8월 22일
1
post-thumbnail
post-custom-banner

하루동안 삽질하면서 독학 한 후기는...
우선 Spring boot가 아니고 Spring으로 구현된것들을 찾아보기 힘들었다.....

그래서 하나하나 직접 프로젝트에 공부한것들을 기반으로 하나 둘 넣어보다보니
시간이 좀 걸렸다..
하지만 결과는 대만족 ! 실시간 채팅이 어떻게 구현되는지 알고 직접 만들어보니
이렇게 뿌듯할수가 !

본론으로....

  1. 우선 Socket과 stomp dependency를 걸어준다.
		<!-- socket -->
		<dependency>
		 	<groupId>org.springframework</groupId>
		    <artifactId>spring-websocket</artifactId>
		    <version>${org.springframework-version}</version>
		</dependency>
		
		<dependency>
			<groupId>org.springframework</groupId>
		    <artifactId>spring-messaging</artifactId>
		    <version>${org.springframework-version}</version>
		</dependency>
		<!-- stomp -->
		<dependency>
		    <groupId>org.springframework.integration</groupId>
		    <artifactId>spring-integration-stomp</artifactId>
		    <version>5.3.2.RELEASE</version>
		</dependency>
  1. 그 후 Client에서 server로 send 또는 구독, 연결될때 받아주는 controller를 만들어준다 (stompController)

    필자의 프로젝트는 톰캣path가 /druwa이므로 client에서 연결할때는 /druwa/chat으로해야한다.

  2. 클라이언트가 server쪽으로 통신할때 그 다리를 연결해주는 WebSocketBroker안에
    레지스터 endPoints가필요한데 2번그림에 있는 registerStompEndpoints()메소드이다.
    연결이되면 Broker를 통해 (/topic : 암시적 1:N통신 /queue : 1:1 통신) 설정해주면된다.

  3. spring mvc 모델로 구현 할경우는 tomcat server에 있는 프로젝트 path에 맞게 설정해주면된다
    ex) Druwa프로잭트패스 = /druwa 니까 Endpoint에 나와있는 경로(/chat)다음 추가로
    SockJS(/druwa/chat); 이렇게 Client에 연결해주면된다

그 다음 var client = Stomp.over(sock); 코드를 작성해줌으로써 client와 server에 파이프통(연결)을 해준다고보면된다.

  1. 그 후 client.connect(); 로 connection이 맺어질때 실행되는메소드다.
    이 안에 server로 client.send();를 통해 값을보낼수있다. 그러면 StompController에 연결이되는데
    @MessageMapping("")을 통해 메소드를 실행해줄 수 있다.
<script>
	// 2. connection이 맺어지면 실행된다.
	    client.connect({}, function () {
	        // 3. send(path, header, message)로 메시지를 보낼 수 있다.
	        client.send('/druwa/chat/join', {}, JSON.stringify({
	        	auction_no: roomId,
	        	dw_member_no: member,
	        	message : member+"님 입장",
	        	sessionUrl : sock._transport.unloadRef
	        	})); 
	       
	        if(member != "0"){
	        	
		        // 4. subscribe(path, callback)로 메시지를 받을 수 있다. callback 첫번째 파라미터의 body로 메시지의 내용이 들어온다.
		        //'/topic/room/'+roomId 이 구독자들에게만 뿌려줌 
		        // 해당 방번호로 데이터가 들어올때마다 실행됨
		        client.subscribe('/topic/room/'+roomId, function (chat) {
		            var content = JSON.parse(chat.body);
		            console.log('회원이 채팅침');

		        	if(content.type =="user_JOIN"){
		        		chatBox.append('<li style="text-align:center;">(' + content.dw_member_nick + ')' + content.message +'<small style="font-size: 2px;">  '+content.date+'</small>' +'</li>');
		        	}else if(content.type =="user_SEND_MESSAGE"){
		        		if(content.dw_member_no == member){
		        			chatBox.append('<li style="margin-right: 9px; text-align: right;">' + content.dw_member_nick + ' : ' + content.message +'<small style="font-size: 2px;">  '+content.date+'</small>' + '</li>');
		        		}else{
		        			chatBox.append('<li style="margin-left: -26px;">' + content.dw_member_nick + ' : ' + content.message +'<small style="font-size: 2px;">  '+content.date+'</small>' + '</li>');
		        		}
		        	}else if(content.type =="user_DO_LOGIN"){
		        		$('#message').val("로그인 후 이용해주세요 !");
		        	}

		        });
	        }
</script>

위에코드를 보면 client.connect();는 서버가 연결될때 실행되는코드이다.
server와 client가 연결될때 수행할 로직을 작성할 수 있다. ex) 채팅방입장, 로그남기기 등등
코드상에선 채팅방 입장 로직을 작성했다.
코드를 잠깐 설명하자면 connection이 되면서 client.send('/druwa/chat/join', {},값); controller에 /druwa/chat/join 라는 매핑이 되어있는 메소드를 실행하기 위해 작성했다.
그 후 값을 보낼 수 있는데 메소드의 3번째에 값을 넣어주면 된다. 되도록 JSON을 보내주면 좋다. (JAVA쪽에선 객체또는 Map이 되겠다.)
그러면 server에선

	//채팅방입장
	@MessageMapping("/chat/join")
	public AuctionChatMessage sendMsg(String sessionUrl, AuctionChatMessage message) throws Exception {
		Date day = new Date();
	    SimpleDateFormat sDate2 = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss.SSS");
	    String now = sDate2.format(day);
	    System.out.println(message.getAuction_no()+" 번방 열음");
	   
	    MemberVO memberVO = memberService.getMember(message.getDw_member_no());
	    if(memberVO != null) {
	    	String user_nick = memberVO.getDw_member_nick();
	    	message.setDate(now);
		    message.setType("user_JOIN");
		    message.setDw_member_nick(user_nick);
		    message.setMessage("님이 입장하셨습니다.");
		    //해당 룸에 맞게 데이터를 뿌려줌
		    messageTemple.convertAndSend("/topic/room/"+message.getAuction_no(),message);
	    }else {
	    	System.out.println("비회원입장");
	    	message.setType("user_DO_LOGIN");
	    	//해당 룸에 맞게 데이터를 뿌려줌
//		    messageTemple.convertAndSend("/queue/"+message.getAuction_no()+"/"+message.getSessionUrl() ,message);
	    }
	    
	    
	    return message;
	}

이 메소드가 실행되는것이다. 그리고 server는 값을받아서 service 로직을 처리한뒤
messageTemple.convertAndSend("구독url", 보내줄값);
을 통해서 다시 client에 보내주게되는것이다.
여기서 코드를 작성하며 재밌던것이 있었는데

<script>
var sock = new SockJS("/");
var sessionId = sock._transport.unloadRef;
</script>

이 부분이다.
트위치나 아프리카TV같은곳을보면 " 로그인해야 채팅을 할수있다 ! " 라는 메세지를 하나의 client에 보내려면 유일한 주소가 필요한데 그 주소를 만들수있는것이 바로 저거다.
예를들어 "/queue/room/"+room_no+"/"+sessionId 를 해주면 해당 방 번호에 있는 client들중 특정한 client에게 메세지를 보낼 수 있다.

되짚어보자면....

  1. server가 client에 신호를 보내주려면 spring에서 제공해주는 SimpMessageSendingOperations객체가
    필요하다, 그 외에도있는데 Stomp로 구현하는 간단한 채팅이면 이 객체면 충분한것같다.
    SimpMessageSendingOperations 안에 convertAndSend(); 메소드를 통해 client로 구독된url(?)에 값을 보낸다.
    ex) convertAndSend("/topic/room/"+roomid, 보낼값);
    여기서 중요한게있는데 client와 server가 주고받는값은 JSON <ㅡㅡ> Map 형태가 되어야한다.
    String도 가능하다.

  2. server가 convertAndSend();로 client에 값을 보내주면 client는 connection할때 구독한 client.subscribe("구독url", 실행시킬메소드, 또는 어떤로직);
    이런식으로 client가 신호를 받을 수 있다.

즉, connection안에 subscribe메소드가 있다면 연결이 되어있는한 구독된url로 값,신호가 들어오면 client는 반응한다는 소리다.

profile
기억보단 기록을
post-custom-banner

0개의 댓글