15 웹소켓으로 채팅 프로그램 만들기

알재·2023년 7월 24일
0

JSP & Servlet

목록 보기
15/16

15.1 사전 지식

15.1.1 소켓이란?

소켓은 네트워크에서 동작하는 프로그램의 종착점이다.
IP주소와 포트 번호로 이루어져 있고, 서버와 클라이언트가 양방향 통신을 할 수 있게 해주는 장치이다.
양방향으로 통신하려면 서로를 알아야 하므로 클라이언트와 서버 둘 다 소켓을 생성 하여 연결한다.

15.1.2 웹소켓이란?

일반적으로 클라이언트의 요청을 받으면 응답 후 바로 연결을 종료하는 비연결 동기 소켓 방식을 사용.
하지만 웹소켓은 응답 후에도 연결을 유지하는 연결지향 방식이다.

웹소켓 서버 구현 애너테이션은 다음과 같다.

  • @ServerEndpoint : 웹소켓 서버의 요청명을 설정
  • @OnOpen : 클라이언트가 접속했을 때 요청되는 매서드를 정의
  • @OnMessage : 클라이언트로부터 메세지가 전송되었을 때 실행되는 매서드를 정의
  • @OnClose : 클라이언트의 접속잉 종료되면 실행되는 매서드
  • @OnError : 에러 발생 시 실행되는 매서드를 정의

15.3 채팅 서버 구현

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;

@ServerEndpoint("/ChatingServer")
public class ChatServer {
    private static Set<Session> clients
            = Collections.synchronizedSet(new HashSet<Session>());

    @OnOpen  // 클라이언트 접속 시 실행
    public void onOpen(Session session) {
        clients.add(session);  // 세션 추가
        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();
    }
}

웹소켓에 접속하기위한 URL은 다음과 같은 형식이다.

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

15.4 채팅 클라이언트 구현

15.4.1 채팅 참여 화면 작성

MultiChatMain.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<html>
<head><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>


15.4.2 채팅창 작성

web.xml

  <context-param>
    <param-name>CHAT_ADDR</param-name>
    <param-value>ws://localhost:8081/MustHaveJSP</param-value>
  </context-param> 
</web-app>
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>

웹소켓 서버에 연결될 때, 연결이 종료될 때, 에러가 발생했을 때, 서버로부터 메세지를 받았을 때 각 이벤트별 리스너가 감지하여 이 메서드들을 호출해 준다.


15.5 동작 확인

profile
저장소

0개의 댓글

관련 채용 정보