Spring

Spring STEP 15 - WEB SOCKET

IT의 큰손 2023. 6. 23. 15:06
728x90

★ Socket

  • 네트워크상에서 호스트간의 통신하는 규약(약속) - 인터페이스
  • 여러 언어들이 Socket 구현 > 클래스 or 객체 제공

■ 웹통신, Ajax 통신 <-> 소켓 통신

  • 단방향 통신(편지) <-> 양방향 통신(전화)

■ WebSocket

  • HTML5
  • JavaScript에서 소켓 통신 지원
  • Spring -> WebSocket을 지원하는 API 제공

 

 

★ 새 프로젝트 생성

NEW > SPRING Legacy Project > Spring MVC Project > WebSocketTest > com.test.websocket > Finish

 

■ 기초 셋팅 작업

 

스프링 프로젝트 일괄 적용

수업. Contribute to pinnpublic/class development by creating an account on GitHub.

github.com

  • pom.xml -> 의존성 2개 더 추가
<dependency>
     <groupId>org.springframework</groupId>
     <artifactId>spring-websocket</artifactId>
     <version>${org.springframework-version}</version>
</dependency>
<dependency>
     <groupId>javax.websocket</groupId>
     <artifactId>javax.websocket-api</artifactId>
     <version>1.1</version>
</dependency>

 

■ 파일 생성

- com.test.controller > SocketController.java
- com.test.server > SocketServer.java

- views > test.jsp
  • 스캔 추가 -> servlet-context.xml
<context:component-scan base-package="com.test.controller" />
<context:component-scan base-package="com.test.server" />

 

■ 코드 작성

  • Controller 구성 -> SocketController.java
@Controller
public class SocketController {

	@GetMapping("/test.do")
	public String test() {
		
		return "test";
	}
	
}
  • test.jsp -> 페이지 구성 및 소켓 
<body>
	<!-- test.jsp -->
	<h1>WebSocket <small>연결 테스트</small></h1>
	
	<div>
		<button type="button" class="in" id="btnConnect">연결하기</button>
		<button type="button" class="out" id="btnDisconnect">종료하기</button>
	</div>
	
	<hr>
	
	<div>
		<input type="text" class="long" id="msg">
		<button type="button" class="add" id="btnEcho">에코 테스트</button>
	</div>
	
	<div class="message full"></div>
	

<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.6.4/jquery.min.js"></script>
<script>

	//1. 소켓 생성
	//2. 서버 접속(연결)
	//3. 통신
	//4. 서버 접속 종료

	const uri = 'ws://localhost/websocket/server.do';
	
	let ws;
	
	$('#btnConnect').click(function(){
		
		//1. 웹소켓 생성 > 자동으로 연결 요청
		ws = new WebSocket(uri);
		
		//웹소켓 서버에게 연결 요청이 성공하면 발생하는 이벤트 > 콜백 함수
		ws.onopen = function(evt) {
			log('서버와 연결되었습니다.');
		};
		
		ws.onclose = function(evt) {
			log('서버와 종료되었습니다.');
		};
		
		ws.onmessage = function(evt) {
			log(evt.data); //서버가 보낸 메시지
		};
		
		ws.onerror = function(evt) {
			log('에러가 발생했습니다.');
		}
		
	});
	
	$('#btnDisconnect').click(function() {
		
		//연결된 소켓을 중단
		ws.close();
		log('연결을 종료합니다.');
		
	});
	
	$('#btnEcho').click(function() {
		
		//연결된 소켓으로 서버에게 데이터 전송하기
		
		//ws.CONNECTING : 연결중
		//ws.OPEN : 열림 > 통신 가능 상태
		//ws.CLOSING : 닫는중
		//ws.CLOSED : 닫힘
		
		if(ws.readyState == ws.OPEN) {
			//ws.send('안녕하세요.');
			ws.send($('#msg').val());
			log('메시지를 전달했습니다.');
		}
		
	});
	
	function log(msg) {
		
		$('.message').prepend(`<div>[\${new Date().toLocaleTimeString()}] \${msg}</div>`);
		
	}

</script>
  • 소켓 서버 -> SocketServer.java
//프로그램 <- (통신) -> 프로그램
//카톡     <-        -> 카톡서버
//브라우저 <-        -> 톰캣 

//상대편의 고유한 주소 인식!! > IP 주소(NIC) + 포트번호 > EndPoint(종단점)

//클라이언트(WebSocket) 연결을 받아주는 역할 > 웹소켓 서버

//192.168.30.31:80/server.do
@ServerEndpoint("/server.do")
public class SocketServer {

	//1.클라이언트가 연결 요청을 하기를 기다림 > 자동 수락
	@OnOpen
	public void handleOpen() {
		
		System.out.println("클라이언트가 접속했습니다.");
	
	}
	


    //클라이언트가 연결을 종료하면 발생
    @OnClose
    public void handleClose() {

        System.out.println("클라이언트와 연결이 종료됐습니다.");

    }

    //클라이언트가 서버에게 메시지를 전송했을 때 발생
    @OnMessage
    public String handleMessage(String msg) { //클라이언트가 보낸 메시지

        System.out.println("클라이언트 메시지: " + msg);

        return "(응답)" + msg; //클라이언트에게 보내는 메시지

    }
    
    @OnError
	public void handleError(Throwable e) {
		
		System.out.println("에러 발생 : " + e.getMessage());
		
	}
    
}
  • 실행 결과

 

★ 알림 서비스

  • 관리자가 접속자에게 공지 > 알림 표시 구현
  • 파일 생성
- com.test.server > NoticeServer.java

- views > admin.jsp
        > user.jsp
  • 컨트롤러 추가 -> SocketController.java
@GetMapping("/admin.do")
public String admin() {

    return "admin";
}

@GetMapping("/user.do")
public String user() {

    return "user";
}
  • 소켓 서버 > NoticeServer.java
@ServerEndpoint("/noticeserver.do")
public class NoticeServer {
	
	private static List<Session> sessionList;
	
	static {
		sessionList = new ArrayList<Session>();
	}

	@OnOpen
	public void handleOpen(Session session) {
		
		System.out.println("연결 성공");
		sessionList.add(session);
		
	}
	
	@OnMessage
	public void handleMessage(String msg, Session session) {
		
		clearSession();
		
		System.out.println("메시지: " + msg); //공지사항
		
		//관리자 > (전송) > 공지사항 > (수신) > 서버 > (전송) > 모든 유저
		
		for(Session s : sessionList) {
			
			if(session != s)
				try {
					//관리자 제외한 나머지 유저들에게만 메시지 전달
					s.getBasicRemote().sendText(msg);
					
				} catch (IOException e) {
					e.printStackTrace();
				}
			
		}
		
	}
	
	@OnClose
	public void handleClose() {
		
	}
	
	@OnError
	public void handleError(Throwable e) {
		
	}
	
	public void clearSession() {
		
		Iterator<Session> iter = sessionList.iterator();
		
		while (iter.hasNext()) {
			if (!(iter.next()).isOpen()) {
				iter.remove(); //접속이 끊긴 클라이언트 소켓(세션)을 제거
			}
		}
		
	}
	
}
  • 관리자 페이지 -> admin.jsp
<body>
	<!-- admin.jsp -->
	<h1>알림 서비스 <small>관리자</small></h1>
	
	<div>
		<textarea id="msg"></textarea>
		<button type="button" id="btnSend" class="add">보내기</button>
	</div>
	

<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.6.4/jquery.min.js"></script>
<script>

	const uri = 'ws://localhost/websocket/noticeserver.do';
	
	let ws;
	
	$(document).ready(function() { //페이지 초기화
		
		ws = new WebSocket(uri);
	
		ws.onopen = function(evt) {
			console.log('서버와 연결됨');
		};
		
		ws.onmessage = function(evt) {
			
		};
		
		ws.onclose = function(evt) {
			
		};
		
		ws.onerror = function(evt) {
			
		};
		
	});
	
	$('#btnSend').click(function() {
		
		ws.send($('#msg').val());
		
	});

</script>
  • 사용자 페이지 -> user.jsp
<body>
	<!-- user.jsp -->
	<h1>알림 서비스 <small>유저</small></h1>
	
	<div id="msgBox"></div>
	

<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.6.4/jquery.min.js"></script>
<script>
	
	function showMsgBox(msg) {
		
		$('#msgBox').text(msg);
		
		$('#msgBox').css({
			bottom: '20px',
			opacity: 1
		});
		
		setTimeout(function() {
			
			$('#msgBox').css({
				bottom: '-200px',
				opacity: 0
			});
			
		}, 5000);
		
	}
	
	//showMsgBox();
	
	const uri = 'ws://localhost/websocket/noticeserver.do';
	
	let ws;
	
	$(document).ready(function() {
		
		ws = new WebSocket(uri);
		
		ws.onopen = function(evt) {
			console.log('연결 성공');
		};
		
		ws.onmessage = function(evt) {
			
			console.log(evt.data);
			
			showMsgBox(evt.data);
			
		};
		
		ws.onclose = function(evt) {
			console.log('연결 종료');
		};
		
		ws.onerror = function(evt) {
			
		};
		
	});
	
</script>
  • 실행 결과

 

★ 채팅 서비스 

  • 파일 생성
- com.test.server > ChatServer.java

- com.test.domain > ChatDTO.java

- views > index.jsp : 입장 페이지
        > chat.jsp : 대화 페이지
  • 컨트롤러 추가 -> SocketController.java
@GetMapping("/index.do")
public String index() {

    return "index";
}

@GetMapping("/chat.do")
public String chat() {

    return "chat";
}
  • 소켓 서버 -> ChatServer.java
@ServerEndpoint("/chatserver.do")
public class ChatServer {

private static List<Session> sessionList;
	
	static {
		sessionList = new ArrayList<Session>();
	}

	@OnOpen
	public void handleOpen(Session session) {
		
		System.out.println("연결 성공");
		sessionList.add(session);
		
	}
	
	@OnMessage
	public void handleMessage(String msg, Session session) {
		
		System.out.println(msg);
		
		//JSON <- (변환) -> Java Object
		//- GSON
		
		Gson gson = new Gson();
		
		ChatDTO dto = gson.fromJson(msg, ChatDTO.class);
		
		if (dto.getCode().equals("1")) {
			
			//새로운 유저가 접속했습니다. > 모든 사람에게 알림(방금 접속한 사람빼고)
			for (Session s : sessionList) {
				if(s != session) {
					try {
						s.getBasicRemote().sendText(msg);
					} catch (Exception e) {
						e.printStackTrace();
					}
				}
			}
			
		} else if (dto.getCode().equals("2")) {
			
			//누군가가 퇴장 > 나머지 사람들에게 알려줌
			sessionList.remove(session); //서버측에서의 퇴장
			
			for (Session s : sessionList) {
				try {
					
					s.getBasicRemote().sendText(msg);
					
				} catch (Exception e) {
					e.printStackTrace();
				}
			}
			
		} else if (dto.getCode().equals("3")) {
			
			//대화 메시지 > 모두에게 전달
			for (Session s : sessionList) {
				if(s != session) {
					try {
						s.getBasicRemote().sendText(msg);
					} catch (Exception e) {
						e.printStackTrace();
					}					
				}
			}
			
		} else if (dto.getCode().equals("4")) {
			
			//이모티콘 메시지 > 모두에게 전달
			for (Session s : sessionList) {
				if(s != session) {
					try {
						s.getBasicRemote().sendText(msg);
					} catch (Exception e) {
						e.printStackTrace();
					}					
				}
			}
			
		}
		
	}
	
	@OnClose
	public void handleClose() {
		
	}
	
	@OnError
	public void handleError(Throwable e) {
		
	}
	
	public void clearSession() {
		
		Iterator<Session> iter = sessionList.iterator();
		
		while (iter.hasNext()) {
			if (!(iter.next()).isOpen()) {
				iter.remove(); //접속이 끊긴 클라이언트 소켓(세션)을 제거
			}
		}
		
	}
	
}
  • DTO -> ChatDTO.java
@Data
public class ChatDTO {
	private String code;
	private String sender;
	private String receiver;
	private String content;
	private String regdate;
}
  • 입장 페이지 -> index.jsp
<body>
	<!-- index.jsp -->
	<h1>WebSocket <small>Chat</small></h1>
	
	<div>
		<div class="group">
			<label>대화명</label>
			<input type="text" name="name" id="name" class="short">
		</div>
	</div>
	<div>
		<button type="button" class="in">들어가기</button>
		
		<button type="button" class="in" data-name="강아지">강아지</button>
		<button type="button" class="in" data-name="고양이">고양이</button>
		<button type="button" class="in" data-name="사자">사자</button>
	</div>
	

<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.6.4/jquery.min.js"></script>
<script>

	$('.in').click(function() {
		
		let name = $(event.target).data('name');
		
		if(name==null || name==undefined) {
			name = $('#name').val();
		}
		
		let child = window.open('/websocket/chat.do', 'chat', 'width=360 height=500');
		
		$('.in').css('opacity', .5);
		$('.in').prop('disabled', true);
		
		child.addEventListener('load', function() {
		
			//child.connect($('#name').val());
			
			child.connect(name);
				
		});
		
		
		
	});

</script>
  • 채팅 페이지 -> chat.jsp
<body>
   <!-- chat.jsp -->
   <div id="main">
      <div id="header">
         <h2>
            WebSocket <small></small>
         </h2>
      </div>
      <div id="list"></div>
      <input type="text" id="msg" placeholder="대화 내용을 입력하세요.">
   </div>


   <script
      src="https://ajax.googleapis.com/ajax/libs/jquery/3.6.4/jquery.min.js"></script>
   <script src="https://cdn.jsdelivr.net/npm/dayjs@1.11.8/dayjs.min.js
"></script>
   <script>

   //창 닫을 때
   $(window).on('beforeunload', function() {
      
      $(opener.document).find('.in').css('opacity', 1);
      $(opener.document).find('.in').prop('disabled', false);
      
      //저 나가요~라고 서버에게 메시지 보내기
      let chat = {
         code: '2',
         sender: window.name,
         receiver: '',
         content: '',
         regdate: dayjs().format('YYYY-MM-DD HH:mm:ss')
      };
      
      ws.send(JSON.stringify(chat));
      
      
   });
   
   window.onkeydown = function() {
      if (event.keyCode == 116) {
         event.preventDefault();
         return false;
      }
   };
   
   /*
   
      서버 <- (대화) -> 클라이언트
      
      - code: 상태코드
         1: 새로운 유저가 들어옴
         2: 기존 유저가 나감
         3: 메시지 전달
         4: 이모티콘 전달
      - sender: 보내는 유저명
      - receiver: 받는 유저명
      - content: 메시지
      - regdate: 날짜/시간
   
   */
   
   //채팅 준비
   const url = 'ws://localhost/websocket/chatserver.do';
   
   let ws;
   
   var name;
   
   function connect(name) {
      
      window.name = name;
      $('#header small').text(name);
      
      //연결하기
      ws = new WebSocket(url);
      
      ws.onopen = function(evt) {
         log('서버 연결 성공');
         
         //내가 접속했다고 다른 사람들에게 알리기
         let chat = {
            code: '1',
            sender: window.name,
            receiver: '',
            content: '',
            regdate: dayjs().format('YYYY-MM-DD HH:mm:ss')
         };
         
         ws.send(JSON.stringify(chat));
         
         print('', '입장했습니다.', 'me', 'state', chat.regdate);
         
         $('#msg').focus();
         
      };
      
      ws.onmessage = function(evt) {
         
         log('메시지 수신');
         
         let chat = JSON.parse(evt.data);

         log(chat.code + ':' + chat.sender);
         
         if (chat.code == '1') {
            //다른 사람 입장
            print('',`[\${chat.sender}]님이 입장했습니다.`, 'other', 'state', chat.regdate);
         } else if (chat.code == '2') {
            //다른 사람 퇴장
            print('',`[\${chat.sender}]님이 퇴장했습니다.`, 'other', 'state', chat.regdate);
         } else if (chat.code == '3') {
            //대화 수신
            print(chat.sender, chat.content, 'other', 'msg', chat.regdate);
         } else if (chat.code == '4') {
             //대화 수신
             printEmoticon(chat.sender, chat.content, 'other', 'msg', chat.regdate);
         }
         
      };
      
      ws.onclose = function(evt) {
         
      };
      
      ws.onerror = function(evt) {
         
      };
      
   }
   
   function log(msg) {
      console.log(`[\${new Date().toLocaleTimeString()}]` + msg)
   }
    
   function print(name, msg, side, state, time) {
         let temp = `
            <div class="item \${state} \${side}">
               <div>
                  <div>\${name}</div>
                  <div>\${msg}</div>
               </div>
               <div>\${time}</div>
            </div>
         `;      
         $('#list').append(temp);
         scrollList();
   }
   
   function printEmoticon(name, msg, side, state, time) {
       let temp = `
          <div class="item \${state} \${side}">
             <div>
                <div>\${name}</div>
                <div style="background-color:#FFF;border:0;"><img src="/websocket/resources/emoticon/\${msg}.png"	</div>
             <div>\${time}</div>
          </div>
       `;      
       $('#list').append(temp);
       
       setTimeout(scrollList, 100);
 	}
   
   $('#msg').keydown(function(evt) {
      
      if (evt.keyCode == 13) {
         
         let chat = {
            code: '3',
            sender: window.name,
            receiver: '',
            content: $('#msg').val(),
            regdate: dayjs().format('YYYY-MM-DD HH:mm:ss')
         };
         
         //이모티콘 전송
         if($('#msg').val().startsWith('/')) {
        	chat.code = '4';
        	chat.content = chat.content.split(' ')[0];
         }
         
         ws.send(JSON.stringify(chat));
         
         if(chat.code == '3'){
         	print(window.name, chat.content, 'me', 'msg', chat.regdate);
         } else if (chat.code == '4') {
	         printEmoticon(window.name, chat.content, 'me', 'msg', chat.regdate);        	 
         }
         
         $('#msg').val('').focus();
         
      }
      
   });
   
   function scrollList() {
      $('#list').scrollTop($('#list')[0].scrollHeight + 500);
   }
   
</script>
  • 날짜 Library 사용 -> day.js 사용
moment.js
day.js
  • 실행 결과

728x90