본문 바로가기
Web/Java

Java(32) - WebSocket

by SeleniumBindingProtein 2022. 2. 9.
728x90
반응형

1. 소켓(Socket)
    1) 네트워크에서 동작하는 프로그램의 종착점(endpoint).
    2) ip 주소와 port 번호로 이루어짐
    3) 서버와 클라이언트가 양방향 통신할수 있게 해주는 소프트웨어 장치
        - 둘 다 소켓을 생성하여 연결해줘야 함

2. 소켓 통신 절차
    1) 서버에서 서버용 소켓(ServerSocket)을 생성, 클라이언트의 접속 대기함
    2) 클라이언트가 소켓(Socket) 생성하여 서버로 연결 요청함
    3) 서버가 접속을 허가(accept)함
    4) 서버와 클라이언트는 각각 통신을 위한 I/O 스트림 생성함
    5) 스트림을 통해 서버와 클라이언트가 통신함(write, read)
    6) 클라이언트가 모든 작업을 마친 후 소켓 종료(close)함
    7) 서버는 새로운 클라이언트 접속을 위해 대기(accept)하거나, 종료(close)할수 있음.

3. 웹 소켓
    1) 일반적인 웹 환경은 클라이언트의 요청을 받으면  응답 후 바로 연결을 종료하는 비연결(connectionless) 동기 소켓 방식을 사용함. 
    2) 웹소켓(WebSocket)은 클라이언트의 요청에 응답한 후에도 연결을 그대로 유지하는 연결 지향 방식(connection oriented)임.      
       - 별도의 요청이 없어도 서버는 원하면 언제든 클라이언트로 데이터를 전송 가능함.

4. 웹 소켓 서버 구현시 사용하는 애너테이션
    1) @ServerEndPoint : 웹소켓 서버의 요청명을 설정함    
    2) @OnOpen :  클라이언트 접속 시 실행되는 메서드를 정의
    3) @OnMessage : 클라이언트로부터 메시지가 전송되었을 때 실행되는 메서드 정의
    4) @OnClose : 클라이어트의 접속이 종료되면 실행되는 메서드 정의
    5) @OnError : 에러 발생시 실행되는 메서드 정의  

5. 채팅 서버 구현 
    https://tomcat.apache.org/whichversion.html

    ws://호스트:포트번호/컨텍스트루트/ChatingServer

6. 채팅 클라이언트 구현 
    1) 채팅 참여 화면 
    2) 채팅 창 화면

 


<multiChattingMain.jsp>

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>
<c:set var="contextPath" value="${pageContext.request.servletContext.contextPath}" />
<%
	request.setCharacterEncoding("UTF-8");
%>

<html>
<head>
	<title>웹소캣 채팅</title>
	<script type="text/javascript">
		function chatWindowOpen() {		// 채팅창을 팝업창으로 열어주는 함수
			let id = document.getElementById("chatId");
			if (id.value == "") {		// 프로필명이 입려되지 않았다면 alert 띄움
				alert("프로필명을 입력 후 채팅창을 열어주세요!");
				id.focus();
				return;
			}
			window.open("chattingWindow.jsp?chatId=" + id.value, "", "width=300, height=400");
			id.value = "";				// 새로운 프로필명 입력할수 있도록 기존 내용 제거
		}
	
	</script>
</head>

<body>
	<h2>웹소켓 채팅 - 채팅창 생성하기</h2>
	프로필명 : <input type="text" id="chatId">
	<button onclick="chatWindowOpen();">채팅 참여하기</button>
</body>
</html>

<chattingWindow.jsp>

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>
<c:set var="contextPath" value="${pageContext.request.servletContext.contextPath}" />
<%
	request.setCharacterEncoding("UTF-8");
%>
<html>
<head>
	<title>웹소켓 채팅창</title>
	<style type="text/css">
		#chatWindow {
			border: 1px solid black; width: 280px; height: 280px; overflow: scroll;padding: 5px;
		}
		#chatMessage {
			width: 240px; height: 30px;
		}
		#sendBtn {
			height: 30px; position: relative; top: 2px; left: -2px;
		}
		#closeBtn {
			margin-bottom: 3px; position: relative; top: 2px; left: -2px;
		}
		.myMsg {
			text-align: right;
		}
	</style>
	
	<script type="text/javascript">
		let webSocket = new WebSocket("<%=application.getInitParameter("CHATSERVER_ADDR") %>/ChatingServer");
		
		let chatWindow, chatMessage, chatId;
		
		// 채팅창이 열리면 대화창, 메세지 입력창, 대화명 표시란으로 사용할 DOM 객체 저장
		window.onload = function() {
			chatWindow = document.getElementById("chatWindow");
			chatMessage = document.getElementById("chatMessage");
			chatId = document.getElementById("chatId").value;
		}
		
		// 클라이언트의 메시지 전송하는 메서드임
		function sendMessage() {
			//대화창에 표시
			chatWindow.innerHTML += "<div class='myMsg'>" +chatMessage.value+ "</div>";
			webSocket.send(chatId +'|'+ chatMessage.value);		//서버로 전송 (대화명|메시지)
			chatMessage.value = "";								//메시지 입력창 내용 지움
			chatWindow.scrollTop = chatWindow.scrollHeight;		//스크롤바는 항상 아래로 내려줌
		}
		
		//서버 연결 종료
		function disconnect() {
			webSocket.close();
		}
		
		//엔터 키 입력 처리
		function enterKey() {
			if (window.event.keyCode == 13) {		//13은 'Enter' 키 코드값
				sendMessage();
			}
		}
		
		//웹소켓과 관련한 이벤트가 발생시 호출되는 함수들 정의
		//각 상황은 이벤트로 전달됨. => 리스너가 감지하여 이 메서들을 호출해줄것임.
		//호출시 인수로 이벤트 객체가 전달됨.
		
		//웹소켓 서버에 연결됐을 때 실행.
		webSocket.onopen = function(event) {
			chatWindow.innerHTML += "웹소켓 서버에 연결되었습니다. <br>";
		}
	
		//웹소켓이 닫혔을때 실행.
		webSocket.onclose = function(event) {
			chatWindow.innerHTML += "웹소켓 서버가 종료되었습니다. <br>";
		}
		
		//에러 발생시 실행
		webSocket.onerror = function(event) {
			alert(event.data);
			chatWindow.innerHTML += "채팅 중 에러가 발생했습니다. <br>";
		}
		
		//메시지를 받았을 때 실행
		webSocket.onmessage = function(event) {
			let message = event.data.split("|");		//프로필명과 메시지 분리
			let sender = message[0];					//보낸사람의 프로필명
			let content = message[1];					//메시지 내용
			
			if (content !="") {
				if (content.match("/")) {		//귓속말
					if (content.match(("/" + chatId))) {	
						var temp = content.replace(("/" + chatId), "[귓속말] : ");
						chatWindow.innerHTML += "<div>" +sender+ "" +temp+ "</div>";
					}	
				}
				else {									//일반대화
					chatWindow.innerHTML += "<div>" +sender+ " : " +content+ "</div>";
				}
			}
			chatWindow.scrollTop = chatWindow.scrollHeight;
		};
		
	</script>
</head>
	<body>
		프로필명 : <input type="text" id="chatId" value="${param.chatId }" readonly>
		<button id="closeBtn" onclick="disconnect();" >채팅 종료</button>
		<div id="chatWindow"></div>
		<div>
			<input type="text" id="chatMessage" onkeyup="enterKey();">
			<button id="sendBtn" onclick="sendMessage();">전송</button>
		</div>
	</body>
</html>

<ChatServer.java>

package kr.co.ezenac.websocket;

import java.io.IOException;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;

import javax.websocket.OnClose;
import javax.websocket.OnError;
import javax.websocket.OnMessage;
import javax.websocket.OnOpen;
import javax.websocket.Session;
import javax.websocket.server.ServerEndpoint;

/*
 * ws://호스트:포트번호/컨텍스트루트/ChatingServer
 * @ServerEndpoint : 위 요청명으로 접속하는 클라이언트를 아래 클래스가 처리하게 함
 */

@ServerEndpoint("/ChatingServer")
public class ChatServer {

	/*
	 * 새로 접속한 클라이언트의 세션을 저장할 컬렉션 생성.
	 * 멀티 스레드 환경에서 안전한 컬렉션 생성해줌. => 여러 클라이언트 동시 접속해도 문제가 않생김.
	 */
	private static Set<Session> clients = Collections.synchronizedSet(new HashSet<>());
	
	@OnOpen			// 클라이언트 접속 시 실행
	public void opOpen(Session session) {
		clients.add(session);			// clients 컬렉션에 클라이언트의 세션 추가함.
		System.out.println("웹소켓 연결 : " + session.getId());
	}
	
	@OnMessage		// 클라이언트로부터 메시지를 받으면 실행 -- 클라이언트가 보낸 메시지, 클라이언트와 연결된 세션
	public void onMessage(String message, Session session) throws IOException {
		System.out.println("메시지 전송 : " +session.getId()+ ":" + message);
		synchronized (clients) {
			for (Session client : clients) {		// 모든 클라이언트에게 메시지를 전송함.
				if (!client.equals(session)) {		// 단, 메시지를 보낸 클라이언트는 제외.
					client.getBasicRemote().sendText(message);
				}
			}
		}
	}
	
	@OnClose		//클라이언트와의 연결이 끊기면 실행
	public void onClose(Session session) {
		clients.remove(session);		//해당 클라이언트의 세션 삭제함
		System.out.println("웹 소켓 종료 : " + session.getId());
	}
	
	@OnError		//에러 발생 시 실행
	public void onError(Throwable e) {
		System.out.println("에러 발생");
		e.printStackTrace();
	}
	
}

javax.websocket-api-1.1.jar
0.04MB

728x90
반응형

'Web > Java' 카테고리의 다른 글

Java(31) - 멀티스레드2  (0) 2022.01.28
Java(30) - review4  (0) 2022.01.28
Java(28) - IO입출력  (0) 2022.01.28
Java(27) - 예외처리  (0) 2022.01.28
Java(26) - Stream  (0) 2022.01.28

댓글