자바에서 제공하는 웹 소켓을 사용해 채팅 서버를 구현하고자 한다.
웹 소켓은 Endpoint 클래스를 상속하고 @ServerEndpoint 애너테이션을 사용해 설정할 수 있다.
Endpoint 클래스에서는 클라이언트로부터 요청이 들어왔을 때 상황에 따라 처리할 수 있는 메서드들을 정의하고 있으며, @ServerEndpoint는 서버에 연결할 URL을 지정할 수 있다.
Endpoint에서 제공하는 메서드는 onOpen(), onMessage(), onClose(), onError() 등이 있다.
@ServerEndpoint("/chat")
public class ChatServer extends Endpoint {
private static final Set<Session> sessions = Collections.synchronizedSet(new HashSet<>());
@OnOpen
public void onOpen(Session session, EndpointConfig config) {
session.addMessageHandler(new MessageHandler.Whole<String>() {
@OnMessage
public void onMessage(String message) {
if(message.startsWith("username")) {
handleNewJoin(session, message);
} else {
handleChatMessage(session, message);
}
}
});
}
@OnClose
public void onClose(Session session, CloseReason closeReason) {
sessions.remove(session);
String username = (String) session.getUserProperties().get("username");
if(username == null) return;
sendAnnounce(username, "퇴장");
}
/*
@Override
public void onError(Session session, Throwable thr) {
super.onError(session, thr);
}
*/
private void handleChatMessage(Session session, String message) {
String username = (String) session.getUserProperties().get("username");
String img1 = (String) session.getUserProperties().get("profileImg1");
String img2 = (String) session.getUserProperties().get("profileImg2");
String data = "";
if(img1 == null) {
data = "username=" + username + "&message=" + message;
} else {
data = "username=" + username + "&message=" + message + "&profileImg=" + img1 + img2;
}
broadcast(data);
}
private void handleNewJoin(Session session, String message) {
String[] joinData = message.split("&");
String username = joinData[0].split("=")[1];
session.getUserProperties().put("username", username);
if(joinData.length > 1) {
String profileImg = joinData[1].split("=")[1];
int half = profileImg.length() / 2;
String img1 = profileImg.substring(0, half);
String img2 = profileImg.substring(half);
session.getUserProperties().put("profileImg1", img1);
session.getUserProperties().put("profileImg2", img2);
}
sessions.add(session);
sendAnnounce(username, "입장");
}
private void sendAnnounce(String username, String info) {
String data = "announce=님이 " + info + "하셨습니다.&username=" + username;
broadcast(data);
}
private void broadcast(String message) {
for(Session session : sessions) {
session.getAsyncRemote().sendText(message);
}
}
}
onOpen()은 클라이언트가 처음 요청을 보내어 최초로 연결되었을 때 수행되는 메서드이다.
위 코드의 경우 클라이언트가 처음 접속했을 때 addMessageHandler()를 통해 해당 세션에 대해 onMessage() 메서드로 메시지에 대한 작업을 처리할 수 있도록 한다.
onMessage()는 클라이언트로부터 메시지가 왔을 때 수행되는 메서드이다.
위 코드의 경우 사용자가 채팅에 처음 참여했을 때와 채팅 메시지를 보낼 때로 나누어진다.
0. 새로운 사용자 입장
사용자가 채팅에 처음 참여했을 경우, 프로필 이미지로 사용할 사진과 닉네임을 입력해 쿼리스트링 형식(key1=value1&key2=value2)으로 서버에 전송한다. 이 때 이미지는 Base64 형식으로 전송이 이루어진다.
이 쿼리스트링을 '&'와 '='를 기준으로 쪼개어(split) 닉네임과 이미지 데이터를 세션에 저장한다.
단, 사용자가 이미지를 업로드하지 않을 수 있는데 이 경우에는 이미지 데이터를 세션에 저장하지 않고 서버에 저장된 기본 이미지를 사용하게 된다.
이미지 데이터는 Base64이다보니 조금 크다고 느껴져 데이터를 쪼개어 세션에 분할 저장했다.
데이터를 모두 저장한 후에는 서버에 저장되어있는 세션 목록(Set)의 세션을 모두 불러와 입장 메시지를 전송한다.
1. 채팅 메시지
사용자가 채팅 메시지를 전송한 경우, 세션에 저장되어 있던 닉네임과 이미지 데이터를 꺼내어 이를 다시 쿼리스트링 형식으로 만들어 클라이언트 전체에 전송한다.
onClose()는 클라이언트와 서버의 연결이 끊어졌을 때 수행되는 메서드이다.
위 코드에서는 사용자가 퇴장했을 경우 세션 목록에서 해당하는 세션을 제거한 후, 세션에서 닉네임을 가져와 사용자들에게 퇴장 메시지를 전송한다.
'웹 개발 > 웹 개발' 카테고리의 다른 글
필터를 사용한 로그 파일 생성 (0) | 2023.01.17 |
---|---|
HttpServletRequest getServletPath() 와 getPathInfo() 의 차이 (0) | 2023.01.17 |
부트스트랩 span 태그 안의 글자 가운데 정렬하기(수평, 수직) (1) | 2023.01.10 |
commons-fileupload 라이브러리 ServletFileUplolad 클래스를 사용한 파일 업로드 (0) | 2023.01.10 |
전방탐색 정규식 (0) | 2023.01.09 |