Preface
이번 장에선 웹 소켓을 이용해 간단한 채팅 프로그램을 구현해봤다.
"이것이 자바다" 책에서 소켓 클래스를 이용해 채팅 프로그램을 만드는 방식은 생각보다 꽤 복잡했던 것으로 기억하는데,
어노테이션을 사용해 코드를 작성하니 길이도 짧아지고 가독성도 좋아진 것 같다.
1. 소켓이란?
- 소켓(Socket): 서버와 클라이언트가 양방향 통신을 할 수 있게 해주는 소프트웨어
→ IP 주소와 포트 번호로 이루어진다.
- 자바에선 소켓 기능을 클래스로 만들어 지원한다.
1) 서버에서 서버용 소켓(ServerSocket)을 생성한 후, 클라이언트의 접속을 기다린다.
2) 클라이언트가 소켓을 생성하여 서버로 연결을 요청한다.
3) 서버가 접속을 허가(accept)한다.
4) 서버와 클라이언트가 각각 통신을 위한 I/O 스트림을 생성한다.
5) 스트림을 통해 서버와 클라이언트가 통신한다.
6) 클라이언트가 모든 작업을 마친 후 소켓을 종료한다.
7) 서버는 새로운 클라이언트의 접속을 위해 대기하거나 종료할 수 있다.
- 일반적인 웹 환경은 클라이언트의 요청을 받으면 응답 후 바로 연결을 종료하는 비연결 동기 소켓 방식을 사용한다.
- 웹소켓(WebSocket): 클라이언트의 요청에 응답한 후에도 연결을 그대로 유지하는 연결 지향 방식
→ 별도의 요청이 없어도 서버가 원할 때 언제든 클라이언트로 데이터를 전송할 수 있다.
- 웹 소켓에선 어노테이션을 이용하여 웹 소켓 서버를 간단히 구현할 수 있다.
→ 응답은 이벤트 객체를 통해 전달되고, JS에서 리스너 메서드를 통해 전달받을 수 있다.
1) @ServerEndpoint: 웹 소컷 서버의 요청명을 설정한다.
2) @OnOpen: 클라이언트가 접속했을 때 요청되는 메서드를 정의한다.
3) @OnMessage: 클라이언트로부터 메시지가 전송되었을 때 실행되는 메서드를 정의한다.
4) @OnClose: 클라이언트의 접속이 종료되면 실행되는 메서드를 정의한다.
5) @OnError: 에러 발생 시 실행되는 메서드를 정의한다.
2. 채팅 서버 구현
- 채팅 서버
→ Collection 클래스의 synchronuzedSet( ) 메서드: 멀티 스레드 환경에서 안전한 Set 컬렉션을 생성
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();
}
}
- 웹 소켓은 http가 아닌 ws 프로토콜을 사용한다.
→ 위의 웹 소켓에 접속하기 위한 전체 URL
ws://호스트:포트번호/컨텍스트루트/ChatingServer
3. 채팅 클라이언트 구현
- 채팅 참여 화면
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</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>
- 채팅창
→ "/chatId" 형태로 입력하면 귓속말을 보낼 수 있다.
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</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.srollHeight;
};
</script>
<style>
#chatWindow {
border: 1px solid black;
width: 270px;
height: 310px;
overflow: scroll;
padding: 5px;
}
#chatMessage {
margin-top: 10px;
width: 236px;
height: 30px;
}
#sendBtn {
margin-top: 10px;
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 #AAA;
background-color: #EEE;
}
.myMsg {
text-align: right;
}
</style>
</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>
- 같은 공유기를 사용하는 환경에선 내부 IP를 설정하여 다른 컴퓨터에서 접속해 채팅할 수 있다.
1) web.xml에서 CHAT_ADDR의 value값을 IP주소로 변경한 후 우베 서버를 재실행한다.
2) 해당 IP주소의 컴퓨터에서 채팅 서버를 실행한 상태로 다른 컴퓨터에서 채팅 참여 화면 코드를 실행시킨다.
'JSP > 성낙현의 JSP 자바 웹 프로그래밍' 카테고리의 다른 글
네이버 검색 API를 활용한 검색 결과 출력하기 (0) | 2023.07.06 |
---|---|
SMTP를 활용한 메일 전송하기 (0) | 2023.07.05 |
JSP, Oracle을 사용한 자료실형 게시판(MVC 패턴) (0) | 2023.06.29 |
서블릿(Servlet) (0) | 2023.06.26 |
파일 업로드 및 다운로드 (0) | 2023.06.23 |
댓글