자바(JAVA)/JSP 웹 프로그래밍 공부 (성낙현의 JSP 자바 웹 프로그래밍 참고)

JAVA/JSP 44. 웹소켓으로 채팅 프로그램 만들기 - 사전 지식, 프로젝트 구상, 채팅 서버/채팅 클라이언트 구현

개발학생 2025. 5. 12. 17:31
반응형

*JAVA/JSP 35번 글의 시점부터는 노트북이 고장나는 바람에.. 다른 분의 글과 이전에 올렸던 글들을 참고하여, 다른 데스크탑의 윈도우 환경에 환경설정을 다시 진행한 후 작성하였습니다. 환경설정 관련 글들은 아래를 참고해 주세요.

  • JDK11 설치 및 환경 변수(JAVA_HOME) 설정 - CLASSPATH는 추가하지 않음
 

자바(JAVA) 11 설치 및 환경설정 | 자바 11 환경 변수

자바로 코딩 공부를 하려면 개발 할 수 있는 환경을 만들어 주어야 한다. 그 순서를 먼저 작성하자면JAVA SE JDK 설치 -> JAVA 환경 변수 설정 -> IDE 설치 이렇게 볼 수 있겠다. 이 포스팅은 자바로 개

velog.io

  • 톰캣 설치, 이클립스 설치 - 이 글을 쓰는 시점에서는 Tomcat 8버전이 없어, Tomcat 9버전 설치
 

JSP) JDK, 이클립스(eclipse), 톰캣 설치 및 환경 설정

1. JDK 설치 → Path 설정 2.eclipse JAVA EE버전 다운로드 3.웹컨테이너 톰캣 설치 →톰캣 환경설정 1. JDK 설치 - JAVA) 1. JDK 참고하여 설치하고 설정한다. -https://storyblogger.tistory.com/15?category=984696 2. 구글에

storyblogger.tistory.com

  • 이클립스 JDK 버전 설정 - JDK11로 설정
 

[이클립스] JDK 버전 바꾸는 법

스프링을 사용하는 에러가 난다? 그러면 JRE 버전을 확인해보자! 프로젝트의 JRE System Library의 오른쪽을 살펴보면 [JavaSE-17]이라고 되어있다. 스프링이나 하이버네이트를 사용할 때, 자바 8이나 자

myvelop.tistory.com

  • JDK 설치 및 환경 변수(JAVA_HOME) 설정, 톰캣 설치, 이클립스 설치 및 JDK 버전 설정 이후~ ⇒ 톰캣 버전과 jsp 파일 경로는 글 내용과 다르게 아래 이미지와 같음

 

JAVA/JSP 1. 개발 환경 구축 - 이클립스 기본 설정

*JDK 설치 및 환경 변수(JAVA_HOME) 설정, 톰캣 설치, 이클립스 설치를 완료한 상태OpenJDK 11: 자바 프로그램을 컴파일하고 실행해주는 기본 도구*이클립스 JDK-17이 설치되어있어서, https://coding-house.tisto

keep-programming-study.tistory.com

 

 

JAVA/JSP 1. 개발 환경 구축 - JSP 예제 테스트

1. 프로젝트 생성1) [File] → [New] → [Dynamic Web Project] 선택 2) 프로젝트 설정 화면 → Project name: HelloJSP, Target runtime: Apache Tomcat v9.0, Dynamic web module version: 4.0으로 설정 후 Next 클릭 3) 자바 소스 파일

keep-programming-study.tistory.com

 

 

JAVA/JSP 1. 개발 환경 구축 - 마지막 추가 설정

1. 외부 웹 브라우저로 실행하기-크롬이클립스는 웹 애플리케이션 실행 시, 자체 브라우저를 통해 실행 결과 출력웹 애플리케이션 배포 시 사용자는 크롬/파이어폭스 같은 전용 웹 브라우저로

keep-programming-study.tistory.com

  • 오라클(Oracle Database 11gR2 Express Edition) 설치 및 설정 
 

JAVA/JSP 14. 데이터베이스 - 특징, 오라클 설치(Oracle Database 11gR2 Express Edition), 사용자 계정 생성 및

1. 데이터베이스의 특징우리가 매일 PC나 스마트폰을 통해 접하는 거의 모든 웹 애플리케이션에서 사용함매일 업데이트되는 뉴스나 날씨 등의 정보는 데이터베이스가 없다면 클라이언트에 전달

keep-programming-study.tistory.com

 

 

 

JAVA/JSP 15. 데이터베이스 - 테이블 및 시퀀스 생성, JDBC 설정 및 데이터베이스 연결

*회원제 게시판 만들기-회원 인증 필요4. 테이블 및 시퀀스 생성1) 테이블 생성(1) member 테이블 - 아이디, 패스워드, 이름, 가입 날짜테이블 정의컬럼명데이터 타입 null 허용 키 기본값 설명idvarchar2

keep-programming-study.tistory.com

 

JAVA/JSP 16. 데이터베이스 - 커넥션 풀로 성능 개선, 간단한 쿼리 작성 및 실행

6. 커넥션 풀로 성능 개선웹은 클라이언트의 요청에 서버가 응답하는 구조→ Connection 객체 생성 때마다 네트워크 통신이 이뤄지며, 시간이 걸리는 작업들이 수반됨 == 시스템 성능에 큰 영향을

keep-programming-study.tistory.com

  • 시퀀스 설정
 

JAVA/JSP 15. 데이터베이스 - 테이블 및 시퀀스 생성, JDBC 설정 및 데이터베이스 연결

*회원제 게시판 만들기-회원 인증 필요4. 테이블 및 시퀀스 생성1) 테이블 생성(1) member 테이블 - 아이디, 패스워드, 이름, 가입 날짜테이블 정의컬럼명데이터 타입 null 허용 키 기본값 설명idvarchar2

keep-programming-study.tistory.com

 

1. 사전 지식

1) 소켓(socket)

  • 네트워크에서 동작하는 프로그램의 종착점(endpoint)
  • IP 주소와 포트 번호로 이루어져 있으며, 서버-클라이언트 간 양방향 통신을 할 수 있게 해주는 소프트웨어 장치
    -> 클라이언트와 서버에서 모두 소켓을 생성하여 연결해줘야 함
  • 자바에서는 이 기능을 클래스로 만들어 지원
    1. 서버에서 서버용 소켓(ServerSocket)을 생성하고 클라이언트의 접속 대기
    2. 클라이언트가 소켓(Socket)을 생성하여 서버로 연결 요청
    3. 서버가 접속 허가(accept)
    4. 서버와 클라이언트에서 각각 통신을 위한 I/O 스트림 생성
    5. 스트림을 통해 서버와 클라이언트가 통신(write->read)
    6. 클라이언트가 모든 작업을 마친 후 소켓 종료(close)
    7. 서버는 새로운 클라이언트의 접속을 위해 대기(accept) or 종료(close)

자바에서의 소켓 통신 절차

2) 웹소켓(WebSocket)

  • 클라이언트의 요청에 응답한 후에도 연결을 그대로 유지하는 연결 지향 방식(connection oriented)
    -> 별도 요청 없이도 서버는 언제든 클라이언트로 데이터 전송 가능
  • 톰캣 9.0.x 버전 사용 시, 웹소켓 버전 1.1 
  • 소켓과 다르게 어노테이션을 사용하여 구현
@ServerEndpoint 웹소켓 서버의 요청명 설정
@OnOpen 클라이언트가 접속했을 때 요청되는 메서드 정의
@OnMessage 클라이언트로부터 메시지가 전송되었을 때 실행되는 메서드 정의
@OnClose 클라이언트 접속 종료 시 실행되는 메서드 정의
@OnError 에러 발생 시 실행되는 메서드 정의

 

2. 프로젝트 구상

  • 에코 클라이언트/서버 모델 적용
    -> 클라이언트가 서버로 보낸 메시지를, 접속해있는 모든 클라이언트로 그대로 전송
  • 실제 웹소켓 서버 구현 시, 메시지를 보낸 클라이언트(나)를 제외한 나머지 클라이언트에게만 메시지를 전송할 수 있도록 구현
    -> 특정 클라이언트에게만 전송할 수 있는 귓속말 기능도 추가

 

 

3. 채팅 서버/채팅 클라이언트 구현 

1) 채팅 서버 구현(ChatServer.java)

// Java Resources/src/websocket/ChatServer.java
package 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;

// 1. 웹소켓 서버의 요청명을 지정하여, 해당 요청명으로 접속하는 클라이언트를 이 클래스가 처리
// -> HTTP가 아닌 ws 프로토콜 사용
@ServerEndpoint("/ChatingServer")
public class ChatServer {
  // 2. 새로 접속한 클라이언트의 세션을 저장할 컬렉션 생성
  // -> syschronizedSet으로 여러 클라이언트가 동시에 접속해도 문제가 생기지 않도록 동기화
  private static Set<Session> clients
    = Collections.synchronizedSet(new HashSet<Session>());
  
  // 3. 클라이언트 접속 시 실행 - clients 컬렉션에 클라이언트의 세션 추가
  @OnOpen
  public void onOpen(Session session) {
    clients.add(session);  // 세션 추가
    System.out.println("웹소켓 연결: "+session.getId());
  }
  
  // 4. 메시지를 받으면 실행 - 클라이언트가 보낸 메시지와, 클라이언트와 연결된 세션이 매개변수
  @OnMessage
  public void onMessage(String message, Session session) throws IOException {
    System.out.println("메시지 전송: "+session.getId() + ":"+message);
    // 5. 동기화 블록
    synchronized (clients) {
      // 6. 모든 클라이언트에 메시지 전달
      for (Session client : clients) {
        // 7. 메시지를 보낸 클라이언트는 제외하고 메시지 전달
        if (!client.equals(session))  client.getBasicRemote().sendText(message);
      }
    }
  }
  
  // 8. 클라이언트와의 연결이 끊기면 실행 - 해당 클라이언트의 세션 삭제
  @OnClose
  public void onClose(Session session) {
    clients.remove(session);
    System.out.println("웹소켓 종료: "+session.getId());
  }
  
  // 9. 에러 발생 시 실행
  @OnError
  public void onError(Throwable e) {
    System.out.println("에러 발생");
    e.printStackTrace();
  }
}

 

2) 채팅 클라이언트 구현

(1) 채팅 참여 화면 작성(JSP 파일) - MultiChatMain.jsp 

<!-- src/main/webapp/websocket/MultiChatMain.jsp -->
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>웹소켓 채팅</title>
</head>
<body>
<script>
// 채팅창을 팝업창으로 열어주는 함수
function chatWinOpen() {
  var id = document.getElementById("chatId");
  if (id.value == "") {
    alert("대화명을 입력 후 채팅창을 열어주세요");
    id.focus();
    return;
  }
  // 대화명을 매개변수로 전달해 채팅창 띄움
  window.open("ChatWindow.jsp?chatId=" + id.value, "", "width=320, height=400");
  // 새로운 대화명 입력 가능하도록 기존 내용 삭제
  id.value = "";
}
</script>
  <h2>웹소켓 채팅 - 대화명 적용해서 채팅창 띄워주기</h2>
  대화명 : <input type="text" id="chatId" />
  <button onclick="chatWinOpen();">채팅 참여</button>
</body>
</html>

(2) 채팅창 작성(JSP 파일) - ChatWindow.jsp

<!-- src/main/webapp/websocket/ChatWindow.jsp -->
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>    
<html>
<head>
<title>웹소켓 채팅</title>
<script>
var webSocket = new WebSocket("<%= application.getInitParameter("CHAT_ADDR") %>/ChatingServer");
var 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) { 
  var message = event.data.split("|");  // 대화명과 메시지 분리
  var sender = message[0];   // 보낸 사람의 대화명
  var 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>
<style>  <!-- 대화창 스타일 지정 -->  
#chatWindow{border:1px solid black; width:270px; height:310px; overflow:scroll; padding:5px;}
#chatMessage{width:236px; height:30px;}
#sendBtn{height:30px; position:relative; top:2px; left:-2px;}
#closeBtn{margin-bottom:3px; position:relative; top:2px; left:-2px;}
#chatId{width:158px; height:24px; border:1px solid #AAAAAA; background-color:#EEEEEE;}
.myMsg{text-align:right;}
</style>
</head>

<body>  <!-- 대화창 UI 구조 정의 --> 
  대화명 : <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>

3) 동작 확인(MultiChatMain.jsp 우클릭 후 Run On Server)

undefined 오류 발생: 콘솔창에는 아무것도 찍히지 않으며, 채팅 자체는 됨 ..? 

깜빡 잊고 web.xml에 코드를 추가하지 않아서였음...

<!-- src/main/webapp/WEB-INF/web.xml -->
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://xmlns.jcp.org/xml/ns/javaee" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd" id="WebApp_ID" version="4.0">
  <display-name>HelloJSP</display-name>
  ... 생략 ...
  <context-param>
    <param-name>CHAT_ADDR</param-name>
    <param-value>ws://localhost:8081/HelloJSP</param-value>
  </context-param>
</web-app>

코드 추가 후 정상 동작

실시간 채팅 구현
실시간 채팅-귓속말 기능 구현

 

반응형