사전 지식
소켓(Socket)이란
네트워크에서 동작하는 프로그램의 종착점이라고 주로 표현합니다.
IP주소와 포트 번호로 이루어져 있으며, 서버와 크라이언트가 양방향 통신을 할 수 있게 해주는 소프트웨어 장치입니다. 양방향으로 통신하려면 서로를 알아야 하므로 클라이언트와 서버 둘 다 소켓을 생성하여 연결해줘야 합니다.
자바에서는 소켓 기능을 클래스로 만들어 지원합니다
- 서버에서 서버용 소켓(ServerSocket)을 생성하고, 클라이언트가 접속하기를 기다립니다.
- 클라이언트가 소켓(Socket)을 생성하여 서버로 연결을 요청합니다.
- 서버가 접속을 허가(accept)합니다.
- 서버와 클라이언트는 각각 통신을 위한 I/O 스트림을 생성합니다.
- 스트림을 통해 서버와 클라이언트가 통신(wirte -> read)합니다.
- 클라이언트가 모든 작업을 마친 후 소켓을 종료(close)합니다.
- 서버는 새로운 클라이언트의 접속을 위해 대기(accept)하거나, 종료(close)할 수 있습니다.
웹소켓(WebSocket)이란
일반적인 웹 환경은 클라이언트의 요청을 받으면 응답 후 바로 연결을 종료하는 비연결 동기 소켓 방식을 사용합니다
하지만 웹소켓은 클라이언트의 요청에 응답한 후에도 연결을 그대로 유지하는 연결 지향 방식입니다
따라서 별도의 요청이 없어도 서버는 원하면 언제든 클라이언트로 데이터를 전송할 수 있습니다.
앞에서 소켓의 통신 절차가 꽤나 복잡한 것을 알 수 있는데, 웹소켓은 이 복잡한 절차를 간단히 구현할 수 있는 장치가 마련되어있습니다. 웹소캣 서버의 구현은 애너테이션을 이용됩니다
- @ServerEndpoint : 웹소캣 서버의 요청명을 설정합니다
- @OnOpen : 클라이언트가 접속했을 때 요청되는 메서드를 정의합니다
- @OnMessage : 클라이언트로부터 메시지가 전송되었을 때 실행되는 메서드를 정의합니다.
- @OnClose : 클라이언트의 접속이 종료되면 실행되는 메서드를 정의합니다.
- @OnError : 에러 발생 시 실행되는 메서드를 정의합니다.
위의 애너테이션을 이용해 클라이언트의 요청이 있을 때 적절한 메서드가 실행되어 응답하게 됩니다. 응답은 이벤트 객체를 통해 전달되고, 자바스크립트에서 리스너 메서드를 통해 전달받을 수 있습니다
구현하기
채팅 서버 구현하기
접속한 클라이언트들을 컬렉션으로 관리하면서 한 클라이언트가 메시지를 보내면 다른 모든 클라이언트에 전달하는 간단한 서버입니다.
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); // 세션 추가
}
@OnMessage // 메시지를 받으면 실행
public void onMessage(String message, Session session) throws IOException {
synchronized (clients) {
for (Session client : clients) { // 모든 클라이언트에 메시지 전달
if (!client.equals(session)) { // 단, 메시지를 보낸 클라이언트는 제외
client.getBasicRemote().sendText(message);
}
}
}
}
@OnClose // 클라이언트와의 연결이 끊기면 실행
public void onClose(Session session) {
clients.remove(session);
}
@OnError // 에러 발생 시 실행
public void onError(Throwable e) {
e.printStackTrace();
}
}
Point
1. @ServerEndpoint 애너테이션으로 해당 요청명으로 접속하는 클라이언트를 이 클래스가 처리하게 합니다.
웹소캣 접속 URL : ws://호스트:포트번호/컨텍스트루트/ChatingServer
2. Collections 클래스의 synchronizedSet() 메서드는 멀티 스레드 환경에서도 안전한(threed-safe) Set컬렉션을 생성해줍니다.
채팅 화면 구현하기
<%@ 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 != "") {
// 대화 출력
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>

Point
- "application.getInitParameter("CHAT_ADDR")" 부분에서 요청명은 서버의 환경에 따라 달라지는 정보이므로 컨텍스트 초기화 매개변수로 관리하는 것이 좋습니다. 실제로 배포할 때 web.xml만 수정하면 되기 때문입니다.
참고한 문헌
성낙현의 JSP 웹 프로그래밍 책
'JSP, Servlet' 카테고리의 다른 글
[JSP, Servlet] 네이버 검색 API를 활용한 검색 결과 출력하기 (1) | 2022.08.22 |
---|---|
[JSP, Servlet] SMTP를 활용한 이메일 전송하기 (0) | 2022.08.22 |