소켓은 네트워크에서 동작하는 프로그램의 종착점이다.
IP주소와 포트 번호로 이루어져 있고, 서버와 클라이언트가 양방향 통신을 할 수 있게 해주는 장치이다.
양방향으로 통신하려면 서로를 알아야 하므로 클라이언트와 서버 둘 다 소켓을 생성 하여 연결한다.
일반적으로 클라이언트의 요청을 받으면 응답 후 바로 연결을 종료하는 비연결 동기 소켓 방식을 사용.
하지만 웹소켓은 응답 후에도 연결을 유지하는 연결지향 방식이다.
웹소켓 서버 구현 애너테이션은 다음과 같다.
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
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>
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>
웹소켓 서버에 연결될 때, 연결이 종료될 때, 에러가 발생했을 때, 서버로부터 메세지를 받았을 때 각 이벤트별 리스너가 감지하여 이 메서드들을 호출해 준다.