Websocket를 통한 Chat

2015. 7. 8. 14:05HTML5

Client 소스

<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
<style type="text/css">
input#chat {
 width: 410px
}

#console-container {
 width: 400px;
}

#console {
 border: 1px solid #CCCCCC;
 border-right-color: #999999;
 border-bottom-color: #999999;
 height: 170px;
 overflow-y: scroll;
 padding: 5px;
 width: 100%;
}

#console p {
 padding: 0;
 margin: 0;
}
</style>
</head>
<body>
<div>
 <p><input type="text" placeholder="type and press enter to chat" id="chat" /></p>
 <div id="console-container">
  <div id="console"></div>
 </div>
</div>

<script type="text/javascript">

var Chat = {};

Chat.socket = null;

Chat.connect = (function(host) {
 
 if( "WebSocket" in window) {
  Chat.socket = new WebSocket(host);
 } else if( "MozWebSocket" in window ){
  Chat.socket = new MozWebSocket(host);
 } else {
  Console.log("Error : 지원하지 않는 브라우저 입니다.");
  return;
 }
 
 Chat.socket.onopen = function() {
  Console.log("정보 : WebSocket 오픈!");
  document.getElementById("chat").onkeydown = function(event) {
   if( event.keyCode == 13 ) {
    Chat.sendMessage();
   }
  };
 };
 
 Chat.socket.onclose = function() {
  document.getElementById("chat").onkeydown = null;
  Console.log("정보 : WebSocket 오픈 종료!");
 };
 
 Chat.socket.onmessage = function(message) {
  console.log(message);
  Console.log(message.data);
 };
 
});

Chat.initialize = function() {
 if( window.location.protocol == "http:") {
  Chat.connect("ws://" + window.location.host + "/websocket/chat");
 } else {
  Chat.connect("wss://" + window.location.host + "/websocket/chat");
 }
};

Chat.sendMessage = (function() {
 var message = document.getElementById("chat").value;
 if( message != "") {
  Chat.socket.send(message);
        document.getElementById("chat").value = '';
 }
});

var Console = {};

Console.log = (function(message) {
 var console = document.getElementById("console");
 var p = document.createElement("P");
 p.style.wordWrap = "break-word";
 p.innerHTML = message;
 
 console.appendChild(p);
 
 while (console.childNodes.length > 25) {
        console.removeChild(console.firstChild);
    }

    console.scrollTop = console.scrollHeight;
});

Chat.initialize();
</script>
</body>
</html>

 

Server Source

package com.test.websocket.service;

import java.io.IOException;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.atomic.AtomicInteger;

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;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

@ServerEndpoint(value = "/websocket/chat")
// 클라이언트가 접속할 때 사용될 URI
public class ChatService {

 private static final Logger logger = LoggerFactory.getLogger(ChatService.class);

 private static final String GUEST_PREFIX = "Guest";

 private static final AtomicInteger connectionIds = new AtomicInteger(0);

 private static final Map<String, Session> sessionMap = new HashMap<String, Session>();

 private final String nickname;

 // 클라이언트가 새로 접속할 때마다 한개의 Session 객체가 생성된다.
 // Session 객체를 컬렉션에 보관하여 두고 해당 클라이언트에게 데이터를 전송할 때마다 사용한다
 private Session session;

 public ChatService() {

  // 클라이언트가 접속할 때마다 서버측에서는 Thread 가 새로 생성되는 것을 확인할 수 있다
  String threadName = "Thread-Name:" + Thread.currentThread().getName();

  // getAndIncrement()은 카운트를 1 증가하고 증가되기 전의 숫자를 리턴한다
  nickname = GUEST_PREFIX + connectionIds.getAndIncrement();
  logger.info("생성자:" + threadName + ", " + nickname);
 }

 @OnOpen
 public void start(Session session) {
  logger.info("클라이언트 접속 : " + session);
  this.session = session;
  sessionMap.put(nickname, session);

  String message = String.format("* %s %s", nickname, " 접속");
  broadcast(message);
 }

 @OnClose
 public void close() {
  sessionMap.remove(nickname);
  String message = String.format("* %s %s", nickname, " 종료");
  broadcast(message);
 }

 @OnMessage
 public void message(String message) {
  String threadName = "Thread-name : " + Thread.currentThread().getName();
  logger.info("메시지 도착 : " + threadName + ", " + nickname);
  
  logger.info("메시지  : " + message );
  if (null == message && "".equals(message))
   return;

  String filteredMessage = String.format("%s: %s", nickname, message);
  // Guest0의 메시지는 특정 클라이언트(Guest2)에게만 전달하는 경우
  //  if (this.nickname.equals("Guest0")) {
  //   sendToOne(filteredMessage, sessionMap.get("Guest2"));
  //  } else // 현재 접속된 모든 클라이언트에게 메시지를 전달하는 경우
  //  {
  //   broadcast(filteredMessage);
  //  }
  broadcast(filteredMessage);
 }

 @OnError
 public void onError(Throwable t) throws Throwable {
  System.err.println("오류/세션제거(" + nickname + "):Chat Error: " + t.toString());
  sessionMap.remove(this.nickname);
 }

 private void broadcast(String msg) {
  
  Set<String> keys = sessionMap.keySet();
  Iterator<String> it = keys.iterator();

  while (it.hasNext()) {
   String key = it.next();
   Session s = sessionMap.get(key);

   try {
    
    s.getBasicRemote().sendText(msg);
    
   } catch (IOException e) {

    sessionMap.remove(key);

    try {
     s.close();
    } catch (IOException e1) {
     e1.printStackTrace();
    }

    String message = String.format("* %s %s", key, "has been disconnected.");
    broadcast(message);
   }

  }

 }

 private void sendToOne(String msg, Session ses) {

  try {
   ses.getBasicRemote().sendText(msg);
  } catch (IOException e) {
   e.printStackTrace();
  }
 }

}