wsClient.jsp -> 뷰, 자바스크립트를 이용해서 서버와 통신한다
자바스크립트에 있는 WebSocket 객체를 이용해서 서버에 json 문자열을 전달한다
<%@ page contentType="text/html; charset=utf-8" pageEncoding="utf-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<script type="text/javascript" src="../js/jquery-2.2.2.min.js"></script>
<script type="text/javascript" src="../js/draw.js"></script>
<title>Apache Tomcat WebSocket Examples: Chat</title>
<style type="text/css">
input#chat {
width: 410px
}
#console-container {
width: 400px;
float: left;
}
#console {
border: 1px solid #CCCCCC;
border-right-color: #999999;
border-bottom-color: #999999;
height: 177px;
overflow-y: scroll;
padding: 5px;
width: 100%;
}
#console p {
padding: 0;
margin: 0;
}
#guestList {
margin-right: 20px;
padding: 0px;
border: 1px solid #ccc;
width: 100px;
height: 210px;
float: left;
}
#guestList p {
margin: 0px;
padding: 0px;
}
canvas {
border: 1px solid #ccc;
}
</style>
<script type="application/javascript">
var Chat = {};
Chat.socket = null;
// connect() 함수 정의
Chat.connect = (function(host) {
// 서버에 접속시도
// 브라우져마다 WebSocket이 지원안 될수 있기 때문에 조건문으로 판단한다
if ('WebSocket' in window) {// window 객체에 WebSocket 속성이 있다면 if문 실행
Chat.socket = new WebSocket(host);// WebSocket은 브라우져가 제공하는 시스템 객체
} else if ('MozWebSocket' in window) {
// 아직 WebSocket이 표준이 안되서 브라우져마다 socket 객체를 만드는 것이 다르다
Chat.socket = new MozWebSocket(host);
} else {
Console.log('Error: WebSocket is not supported by this browser.');
return;
}
// 서버에 접속이 되면 호출되는 콜백함수
// socket에 정의 된 속성에 함수주소를 저장
Chat.socket.onopen = function () {
// Console은 개발자가 만든 객체
Console.log('Info: WebSocket connection opened.');
// 채팅입력창에 메시지를 입력하기 위해 키를 누르면 호출되는 콜백함수
$("#chat").on("keydown",function(event) {
// 엔터키가 눌린 경우, 서버로 메시지를 전송함
if (event.keyCode == 13) {
Chat.sendMessage();
}
});
/* document.getElementById('chat').onkeydown = function(event) {
// 엔터키가 눌린 경우, 서버로 메시지를 전송함
if (event.keyCode == 13) {
Chat.sendMessage();
}
}; */
};
// 연결이 끊어진 경우에 호출되는 콜백함수
Chat.socket.onclose = function () {
// 채팅 입력창 이벤트를 제거함
$("#chat").on("keydown",null);
// document.getElementById('chat').onkeydown = null;
Console.log('Info: WebSocket closed.');
};
// 서버로부터 메시지를 받은 경우에 호출되는 콜백함수
Chat.socket.onmessage = function (message) {
// 수신된 메시지를 화면에 출력함
var obj = eval(message.data);
var ok = false;
// $("#guestList").empty();
for(var i = 0; i < obj.length; i++) {
ok = false;
if(obj[i].cmd == "start") {
$("#guestList").find("p").each(function(idx) {
if($(this).text() == obj[i].nickname) {
ok = true;
}
});
if(!ok) {
guestList.log("<input type=checkbox>"+obj[i].nickname);
}
} else if(obj[i].cmd == "msg") {
Console.log(obj[i].nickname + " : " + obj[i].msg);
} else if(obj[i].cmd == "end") {
Console.log(obj[i].nickname + " : " + obj[i].msg);
$("#guestList").find("p").each(function(idx) {
if($(this).text() == obj[i].nickname) {
$(this).remove();
}
});
} else if(obj[i].cmd == "canvas") {
if(obj[i].shape == "line")
draw.line(obj[i].endPosition, obj[i].startPosition);
else
draw.shape(obj[i].endPosition, obj[i].startPosition, obj[i].shape);
}
}
};
});
// connect() 함수 정의 끝
// 위에서 정의한 connect() 함수를 호출하여 접속을 시도함
Chat.initialize = function() {
if (window.location.protocol == 'http:') {
//Chat.connect('ws://' + window.location.host + '/websocket/chat');
// connect 함수에 파라미터를 전달해서 접속 시도, 콜백함수 등록
// ws : tcp/ip 기반 프로토콜
Chat.connect('ws://192.168.8.19:7777/JavaWEB/websocket/chat');
} else {
Chat.connect('wss://' + window.location.host + '/websocket/chat');
}
};
var draw = {};
draw.shape = (function(ep,sp,shape) {
ctx.beginPath();
if(shape == "square") {
ctx.fillRect(sp.x, sp.y, ep.x - sp.x, ep.y - sp.y);
} else if(shape == "circle") {
ctx.arc(sp.x, sp.y, ep.x - sp.x, Math.PI * 2, false);
}
ctx.fillStyle = colorCode;
ctx.fill();
ctx.closePath();
});
draw.line = (function(ep,sp) {
ctx.beginPath();
ctx.moveTo(sp.x, sp.y);
ctx.lineTo(ep.x, ep.y);
ctx.lineWidth = 10;
ctx.strokeStyle = colorCode;
ctx.stroke();
ctx.closePath();
});
// 서버로 메시지를 전송하고 입력창에서 메시지를 제거함
Chat.sendMessage = (function() {
var message = $("#chat").val();
var arr = [];
// var message = document.getElementById('chat').value;
if (message != '') {
var obj = {};
var jsonStr;
$("#guestList p").find("input[type=checkbox]").each(function(idx) {
if($(this).is(":checked")) {
console.log($(this).parent().text());
arr.push($(this).parent().text());
obj.cmd = "msg";
obj.msg = message;
obj.nickArr = arr;
jsonStr = JSON.stringify(obj);
}
});
Chat.socket.send(jsonStr);
$("#chat").val("");
// document.getElementById('chat').value = '';
}
});
Chat.sendCanvas = (function() {
var obj = {};
var jsonStr;
var arr = [];
$("#guestList p").find("input[type=checkbox]").each(function(idx) {
if($(this).is(":checked")) {
arr.push($(this).parent().text());
obj.cmd = "canvas";
if(command == "line") {
obj.endPosition = p2;
obj.startPosition = p1;
} else {
obj.endPosition = ep;
obj.startPosition = sp;
}
obj.shape = command;
obj.nickArr = arr;
jsonStr = JSON.stringify(obj);
}
});
Chat.socket.send(jsonStr);
});
var guestList = {};
// 접속한 사용자리스트를 추가하는 메소드
guestList.log = (function(id) {
var $listBox = $("#guestList");
$listBox.append("<p></p>");
$listBox.find("p:last").html(id);
$listBox.scrollTop($listBox.prop("scrollHeight"));
});
var Console = {}; // 화면에 메시지를 출력하기 위한 객체 생성, json Object
// log() 함수 정의
Console.log = (function(message) {
var $console = $("#console");
$console.append("<p/>");
$console.find("p:last").css("wordWrap","break-word");
$console.find("p:last").html(message);
/* var console = document.getElementById('console');
var p = document.createElement('p');
// wordWrap 문자열이 창 최대 오른쪽 벽에 닿으면 줄바꿈
p.style.wordWrap = 'break-word';
p.innerHTML = message;
console.appendChild(p); // 전달된 메시지를 하단에 추가함
// 추가된 메시지가 25개를 초과하면 가장 먼저 추가된 메시지를 한개 삭제함 */
while ($console.find("p").length > 25) {
$console.find("p:first").remove();
}
$console.scrollTop($console.prop("scrollHeight"));
/* while (console.childNodes.length > 25) {
console.removeChild(console.firstChild);
} */
// 스크롤을 최상단에 있도록 설정함
// console.scrollTop = console.scrollHeight;
});
// 위에 정의된 함수(접속시도)를 호출함
Chat.initialize();
// $("<p/>")// $("<p></p>") // jQuery 함수를 이용하여 메모리에 p 태그 DOM 객체를 생성한다
</script>
</head>
<body>
<div id="guestList">
<input type="checkbox" id="all"><span>전체</span>
</div>
<div>
<p style="margin: 0px;">
<input type="text" placeholder="type and press enter to chat" id="chat" />
</p>
<div id="console-container">
<div id="console"></div>
</div>
<div style="clear: both;"></div>
</div>
<canvas id="chatCanvas" width="800" height="600"></canvas>
<button type="button" id="line" onclick="selector('line');">선</button>
<button type="button" id="square" onclick="selector('square');">사각형</button>
<button type="button" id="circle" onclick="selector('circle');">원</button>
<input type="color" id="select-color">
</body>
</html>
WebSocketServer.java -> 서버, Session을 통해서 사용자로부터 데이터를 전달 받는다.
package jun.mvc.websocket;
@ServerEndpoint(value = "/websocket/chat") // 클라이언트가 접속할 때 사용될 URI
public class WebSocketServer {
private static JSONArray jsonArr = new JSONArray();
private static Map<String, Session> sessionMap = new HashMap<>();
private static final String GUEST_PREFIX = "Guest";
// AtomicInteger 클래스는 getAndIncrement()를 호출할 때마다 카운터를 1씩 증가하는 기능을 가지고 있다
private static final AtomicInteger connectionIds = new AtomicInteger(0);
// CopyOnWriteArraySet 을 사용하면 컬렉션에 저장된 객체를 좀더 간편하게 추출할 수 있다
// 예를 들어, toArray()메소드를 통해 쉽게 Object[] 형의 데이터를 추출할 수 있다.
private static final Set<WebSocketServer> connections = new CopyOnWriteArraySet<WebSocketServer>();
private final String nickname;
// 클라이언트가 새로 접속할 때마다 한개의 Session 객체가 생성된다.
// Session 객체를 컬렉션에 보관하여 두고 해당 클라이언트에게 데이터를 전송할 때마다 사용한다
private Session session;
private JSONParser jsonParser = new JSONParser();
public WebSocketServer() { // 클라이언트가 새로 접속할 때마다 이 클래스의 인스턴스가 새로 생성됨
// 클라이언트가 접속할 때마다 서버측에서는 Thread 가 새로 생성되는 것을 확인할 수 있다
String threadName = "Thread-Name:" + Thread.currentThread().getName();
// getAndIncrement()은 카운트를 1 증가하고 증가된 숫자를 리턴한다
nickname = GUEST_PREFIX + connectionIds.getAndIncrement();
System.out.println(threadName + ", " + nickname);
}
// 메소드명은 개발자가 지정할 수 있다.
// 애노테이션에 따라 구분 된다.
@SuppressWarnings("unchecked")
@OnOpen
public void start(Session session) {// 통신은 스트림으로 한다, 즉 세션에서 스트림을 얻을 수 있다.
System.out.println("클라이언트 접속됨 " + session);
// Session:접속자마다 한개의 세션이 생성되어 데이터 통신수단으로 사용됨, 즉 접속자 마다 구분됨
// 한개의 브라우저에서 여러개의 탭을 사용해서 접속하면 Session은 서로 다르지만 HttpSession 은 동일함
this.session = session;
connections.add(this);// 사용자 세션이 저장된 객체를 connections Set객체에 저장
sessionMap.put(nickname, session);// 사용자 nickname을 키값으로 하고 session을 value로 하는 맵객체에 추가
JSONObject jsonObj = new JSONObject();
jsonObj.put("cmd", "start");// 클라이언트쪽에서 해당하는 작업을 하기 위해 명령어를 String으로 준다
jsonObj.put("nickname", nickname);
jsonObj.put("msg", "has joined");
jsonArr.add(jsonObj);
String jsonStr = jsonArr.toJSONString();
this.sendGuestList(jsonStr);
}
@SuppressWarnings("unchecked")
@OnClose
public void end() {
connections.remove(this);// 사용자가 접속을 중단하면 set에서 해당 사용자 객체를 삭제
sessionMap.remove(nickname);
JSONObject jsonObj = new JSONObject();
jsonObj.put("cmd", "end");
jsonObj.put("nickname", nickname);
jsonObj.put("msg", "has disconnected.");
JSONArray endArr = new JSONArray();
endArr.add(jsonObj);
try {
for(int i = 0; i < jsonArr.size(); i++) {
JSONObject obj = (JSONObject) jsonParser.parse(jsonArr.get(i).toString());
if(obj.get("nickname").equals(nickname)) {
jsonArr.remove(i);
}
}
} catch (Exception e) {
e.printStackTrace();
}
String jsonStr = endArr.toJSONString();
this.sendGuestList(jsonStr);
}
// 현재 세션과 연결된 클라이언트로부터 메시지가 도착할 때마다 새로운 쓰레드가 실행되어 incoming()을 호출함
@OnMessage
public void incoming(String json) {
String threadName = "Thread-Name:" + Thread.currentThread().getName();
System.out.println(threadName + ", " + nickname);
if (json == null || json.trim().equals(""))
return;
JSONObject jsonObj;
JSONArray arr;
try {
jsonObj = (JSONObject) jsonParser.parse(json);// 클라이언트쪽에서 문자열로 넘어온 json오브젝트를 jsonObject로 만들어준다
System.out.println(jsonObj);
arr = (JSONArray) jsonObj.get("nickArr");
this.selectNickToMsg(arr, jsonObj);
} catch (ParseException e) {
e.printStackTrace();
}
}
@SuppressWarnings("unchecked")
private void selectNickToMsg(JSONArray arr, JSONObject jsonObj) {
JSONArray jsonArr = new JSONArray();
jsonObj.put("nickname", nickname);
jsonArr.add(jsonObj);
try {
for (int i = 0; i < arr.size(); i++) {
String sNick = (String) arr.get(i);
if(!sNick.equals(nickname))
sessionMap.get(sNick).getBasicRemote().sendText(jsonArr.toJSONString());
}
session.getBasicRemote().sendText(jsonArr.toJSONString());
} catch (IOException e) {
e.printStackTrace();
}
}
@OnError
public void onError(Throwable t) throws Throwable {
System.err.println("Chat Error: " + t.toString());
}
// 현재 세션으로부터 도착한 메시지를 모든 접속자에게 전송한다
private void sendGuestList(String jsonStr) {
Iterator<WebSocketServer> ss = connections.iterator();
for (int i = 0; i < connections.size(); i++) {
WebSocketServer client = ss.next();
try {
synchronized (client) {// 여러 사용자의 쓰레드에서 이 메소드에 접근하기 때문에 rock을 건다
// 서버에 접속 중인 모든 이용자에게 메지지를 전송한다
// getBasicRemote()로 사용자의 스트림을 얻는다
client.session.getBasicRemote().sendText(jsonStr);
}
} catch (IllegalStateException ise) {
// 특정 클라이언트에게 현재 메시지 보내기 작업 중인 경우에 동시에 쓰기작업을 요청하면 오류 발생함
if (ise.getMessage().indexOf("[TEXT_FULL_WRITING]") != -1) {
new Thread() {
@Override
public void run() {
// 같은 에러가 발생하면 반복문을 통해서 다시 메세지를 전달하게 한다.
while (true) {
try {
client.session.getBasicRemote().sendText(jsonStr);
break;
} catch (IllegalStateException _ise) {
try {
Thread.sleep(100); // 메시지 보내기 작업을 마치도록
// 기다려준다
} catch (InterruptedException e) {
}
} catch (IOException ioe) {
ioe.printStackTrace();
}
}
}
}.start();
}
} catch (Exception e) {
// 메시지 전송 중에 오류가 발생(클라이언트 퇴장을 의미함)하면 해당 클라이언트를 서버에서 제거한다
System.err.println("Chat Error: Failed to send message to client:" + e);
connections.remove(client);
try {
// 접속 종료
client.session.close();
} catch (IOException e1) {
// Ignore
}
// 한 클라이언트의 퇴장을 모든 이용자에게 알린다
String message = String.format("* %s %s", client.nickname, "has been disconnected.");
this.sendGuestList(message);
}
}
}
}
'JAVASCRIPT > Basic' 카테고리의 다른 글
Json Array 사용 예제 (0) | 2016.03.21 |
---|