본문 바로가기

WebSocket/File Transfer

WebSocket File Transfer example

웹소켓을 이용하여 사이트에 접속한 특정 이용자에게 파일을 전송하는 예


loginForm.jsp

<%@ page contentType="text/html; charset=utf-8" pageEncoding="utf-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>로그인</title>
</head>
<body>
<p>
<form action="loginProc.jsp" method="post">
I D <input type="text" name="id" value="user01">
PWD <input type="password" name="pwd" value="1111">
<button type="submit">로그인</button>
</form>
</body>
</html>


loginProc.jsp

<%@page import="java.util.*"%>
<%@ page contentType="text/html; charset=utf-8" pageEncoding="utf-8"%>
<%
	request.setCharacterEncoding("utf-8");
	String id = request.getParameter("id");
	String pwd = request.getParameter("pwd");
	if(id!=null && !id.equals("")) {
		session.setAttribute("id", id);
	}
%>
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title> 로그인 결과 </title>
</head>
<body>
<p>
<a href="streamClient.jsp">채팅 페이지로 이동</a>
</body>
</html>


streamClient.jsp

<%@page import="java.util.List"%>
<%@ page contentType="text/html; charset=utf-8" pageEncoding="utf-8"%>
<%
	Object objList = application.getAttribute("usrList");
	List<String> usrList = null;
	if(objList!=null) usrList = (List<String>) objList;
%>
<!DOCTYPE html>
<html>
    <head>
        <title>Apache Tomcat WebSocket Examples: binary stream</title>
        <style type="text/css">
        	body { text-align: center;}
        	div { display:inline-block; }
            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;
            }
            canvas { border:1px solid black;}
        </style>
<script type="text/javascript" src="jquery-2.1.4.min.js"></script>
        <script type="application/javascript">
        	var clientId = '<%=(String) session.getAttribute("id")%>';
            var Chat = {};
     		var downloadFileName = '';
     		
            Chat.socket = null;
     
            // connect() 함수 정의
            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: WebSocket is not supported by this browser.');
                    return;
                }
                 
                 
                 // 서버에 접속이 되면 호출되는 콜백함수
                Chat.socket.onopen = function () {
                    Console.log('Info: WebSocket connection opened.\n'+Chat.socket.extensions);
                    // 채팅입력창에 메시지를 입력하기 위해 키를 누르면 호출되는 콜백함수
                    document.getElementById('chat').onkeydown = function(event) {
                        // 엔터키가 눌린 경우, 서버로 메시지를 전송함
                        if (event.keyCode == 13) {
                            Chat.sendMessage();
                        }
                    };
                };
                 
                // 연결이 끊어진 경우에 호출되는 콜백함수
                Chat.socket.onclose = function () {
                    // 채팅 입력창 이벤트를 제거함
                    document.getElementById('chat').onkeydown = null;
                    Console.log('Info: WebSocket closed.');
                };
                 
                // 서버로부터 메시지를 받은 경우에 호출되는 콜백함수
                Chat.socket.onmessage = function (evt) {
 
                    console.log('수신 message.data:'+evt.data);
                    if(evt.data instanceof Blob){
                        console.log('수신 데이터 타입:Blob');
                        saveData(evt.data, downloadFileName);
                        downloadFileName = '';
                    }else if(evt.data instanceof ArrayBuffer){
                        console.log('수신 데이터 타입:ArrayBuffer');
                        saveData2(evt.data, downloadFileName);
                        downloadFileName = '';
                    }else {// 파일이 아닌 텍스트인 경우
                    	var jsonObj = eval('('+evt.data+')');
                    	if('text' in jsonObj) { 		//채팅 메시지인 경우
                    		Console.log(jsonObj.text);
                    	}else if('points' in jsonObj) {	// 캔바스 그리기 좌표인 경우
                    		alert('캔바스 그리기 좌표');
                    	}else if('fname' in jsonObj) {	// 파일이름이 수신된 경우
                    		downloadFileName = jsonObj.fname;
                    	}else if('usrList' in jsonObj) {// 접속자 리스트가 수신된 경우
                    		$('#receiver').empty();
                    		var len = jsonObj.usrList.length;
                    		for(var i=0;i<len;i++){
                    			if(i==0) {
                    				$('#receiver').append($('<option selected>').text(jsonObj.usrList[i]));
                    			}else {
                    				$('#receiver').append($('<option>').text(jsonObj.usrList[i]));
                    			}
                    		}
                    	}
                        return;
                    }
                };
            });
            // connect() 함수 정의 끝
             
            // 위에서 정의한 connect() 함수를 호출하여 접속을 시도함
            Chat.initialize = function() {
                if (window.location.protocol == 'http:') {
                    //Chat.connect('ws://' + window.location.host + '/websocket/chat');
                    Chat.connect('ws://192.168.8.32:8888/MyWeb/websocket/stream');
                } else {
                    Chat.connect('wss://' + window.location.host + '/websocket/echoStreamAnnotation');
                }
            };
     
            // 서버로 메시지를 전송하고 입력창에서 메시지를 제거함
            Chat.sendMessage = (function() {
 
                var message = document.getElementById('chat').value;
                if (message != '') {
                	var receiver = $('select[name=receiver]').val();
                	var msg = {sender:clientId, receiver:receiver};
                	msg.text = message;
                    Chat.socket.send(JSON.stringify(msg));
                    document.getElementById('chat').value = '';
                }
            });
             
            // 서버로 바이너리 데이터를 전송
            Chat.sendBinary = (function() {
            	// Blob, ArrayBuffer 둘 중 한가지 방법으로 전송
                sendFileBlob(); // 
                //sendFileArrayBuffer();
            });
 
            var Console = {}; // 화면에 메시지를 출력하기 위한 객체 생성
     
            // log() 함수 정의
            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); // 전달된 메시지를 하단에 추가함
                // 추가된 메시지가 25개를 초과하면 가장 먼저 추가된 메시지를 한개 삭제함
                while (console.childNodes.length > 25) {
                    console.removeChild(console.firstChild);
                }
                // 스크롤을 최상단에 있도록 설정함
                console.scrollTop = console.scrollHeight;
            });
     
            // 위에 정의된 함수(접속시도)를 호출함
            Chat.initialize();
             
            function sendBinary() {
                Chat.sendBinary();
            }
             
            // Blob 를 파일에 저장
            function saveData(blob, fileName) {
                var a = document.createElement("a");
                document.body.appendChild(a);
                a.style = "display: none";
 
                url = window.URL.createObjectURL(blob);
                a.href = url;
                a.download = fileName;
                a.click();
                window.URL.revokeObjectURL(url);
            };
             
            // ArrayBuffer 를 파일에 저장
            function saveData2(arrayBuffer, fileName) {
                var a = document.createElement("a");
                document.body.appendChild(a);
                a.style = "display: none";
                var parts = [];
                parts.push(arrayBuffer);
                url = window.URL.createObjectURL(new Blob(parts));
                a.href = url;
                a.download = fileName;
                a.click();
                window.URL.revokeObjectURL(url);
            };
             
           //canvas의 이미지 데이터를 서버로 전송하는 예
           function sendImgArrayBuffer(){
                // Sending canvas ImageData as ArrayBuffer
                var img = canvas_context.getImageData(0, 0, 400, 320);
                var binary = new Uint8Array(img.data.length);
                for (var i = 0; i < img.data.length; i++) {
                  binary[i] = img.data[i];
                }
                Chat.socket.send(binary.buffer);
            };
             
            //파일을 Blob를 서버로 전송함
            function sendFileBlob() {
                // Sending file as Blob
                var file = document.querySelector('input[type="file"]').files[0];
                var receiver = $('#receiver').val();
                var msg = {sender:clientId, receiver:receiver};
                msg.fname = file.name;
                //파일 데이터에 앞서 송,수신자, 파일명을 텍스트로 전송한다
                Chat.socket.send(JSON.stringify(msg));
                //파일 데이터를 전송한다
                Chat.socket.send(file);
            }
             
            //파일을 ArrayBuffer를 서버로 전송함
            function sendFileArrayBuffer() {
                var file = document.querySelector('input[type="file"]').files[0];
                var fileReader = new FileReader();
                fileReader.onload = function() {
                    arrayBuffer = this.result;
                    Chat.socket.send(arrayBuffer);
                };
                fileReader.readAsArrayBuffer(file);
            }
// 파일박스에서 선택된 파일의 이름을 구하는 방법
var filename = '';
 function onChange(files){
     filename = files[0].name;
     alert('선택변경:'+files[0].name);
 }
        </script>
    </head>
    <body ><p>
    대화상대 선택<select id="receiver" name="receiver">
	<% 
		if(usrList!=null) {
			for(int i=0;i<usrList.size();i++) { %>
				<option><%=usrList.get(i)%></option>
	<% 		} 
		}
	%>
	</select><br>
    <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><br>
    <input type="file" onchange="onChange(this.files);">
    <input type="button" value="바이너리 데이터 전송" onclick="sendBinary();">
    <button type="button" id='btnClear' style="vertical-align: top;">캔바스 지우기</button>
    	<br>
<canvas width="600" height="480"></canvas>

    </body>
</html>


ServletAwareConfig.java

package org.kdea.web.socket;

import javax.servlet.ServletContext;
import javax.servlet.http.HttpSession;
import javax.websocket.HandshakeResponse;
import javax.websocket.server.HandshakeRequest;
import javax.websocket.server.ServerEndpointConfig;
      
public class ServletAwareConfig extends ServerEndpointConfig.Configurator {
 
    @Override
    public void modifyHandshake(ServerEndpointConfig config, HandshakeRequest request, HandshakeResponse response) {
         
        HttpSession session = (HttpSession) request.getHttpSession();
        //ServletContext ctx = session.getServletContext();
 
        config.getUserProperties().put(HttpSession.class.getName(), session);
        //config.getUserProperties().put(ServletContext.class.getName(), ctx);
    }
}



StreamAnnotation.java

package org.kdea.web.socket;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.Writer;
  
 
 
import java.util.*;

import javax.servlet.http.HttpSession;
import javax.websocket.EndpointConfig;
import javax.websocket.OnClose;
import javax.websocket.OnMessage;
import javax.websocket.OnOpen;
import javax.websocket.Session;
import javax.websocket.server.ServerEndpoint;

import org.json.simple.JSONArray;
import org.json.simple.JSONObject;
import org.json.simple.parser.JSONParser;
import org.json.simple.parser.ParseException;
 
@ServerEndpoint(value="/websocket/stream", configurator=ServletAwareConfig.class)
public class StreamAnnotation {
  
    Writer writer;
    OutputStream stream;

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

    private HttpSession httpSession;
     
    @OnOpen
    public void start(Session session, EndpointConfig config) {
        System.out.println("클라이언트 접속됨 wsSession: "+session);
        System.out.println("웹소켓 서버측 config :"+config);
        //Session:접속자마다 한개의 세션이 생성되어 데이터 통신수단으로 사용됨
        //한개의 브라우저에서 여러개의 탭을 사용해서 접속하면 Session은 서로 다르지만 HttpSession 은 동일함

        this.httpSession = (HttpSession) config.getUserProperties().get(HttpSession.class.getName());
        
        String userId = (String)httpSession.getAttribute("id");
        System.out.println("접속한 클라이언트 ID:"+userId);
        
        sessionMap.put(userId, session);
        Object objList = httpSession.getServletContext().getAttribute("usrList");
		if(objList==null) {
			List<String> usrList = new ArrayList<>();
			httpSession.getServletContext().setAttribute("usrList", usrList);
			objList = usrList;
		}
		List<String> usrList = (List<String>) objList;
		usrList.add(userId);
		//새로 접속한 이용자 리스트를 모든 이용자에게 전송한다
		broadcast(userId, usrList);
        System.out.println("웹소켓 서버측 세션확인 httpSession :"+httpSession);
    }
     
    StringBuffer sb = new StringBuffer();
    String sender, receiver, fname;
    @OnMessage
    public void textMessage(Session session, String msg, boolean last)
            throws IOException {
        sb.append(msg);
        if (last) {
        	JSONParser jsonParser = new JSONParser();
        	try {
				JSONObject jsonObj = (JSONObject)jsonParser.parse(sb.toString());
				receiver = (String)jsonObj.get("receiver");
				sender = (String)jsonObj.get("sender");
				fname = (String)jsonObj.get("fname");
		        if (writer == null) {
		        	writer = sessionMap.get(receiver).getBasicRemote().getSendWriter();
		        }
				System.out.println("수신자:"+receiver);
			} catch (ParseException e) {
				e.printStackTrace();
			}
        	System.out.println("완성된 텍스트:"+sb);
        	writer.write(sb.toString());
            writer.flush();
            writer.close();
            writer = null;
            sb.delete(0, sb.length());
        }
    }
  
    ByteArrayOutputStream bout = new ByteArrayOutputStream();
    @OnMessage
    public void binaryMessage(byte[] msg, Session session, boolean last)
            throws IOException {
        //System.out.println("클라이언트-->서버 바이너리 데이터 도착");
        bout.write(msg);
        if (last) {
            if (stream == null) {
            	stream = sessionMap.get(receiver).getBasicRemote().getSendStream();
            }
            stream.write(bout.toByteArray());
            stream.flush();
            stream.close();
            bout.reset();
            stream = null;
            System.out.println("서버-->클라이언트 바이너리 전송완료");
            fname = null;
            sender = null;
            receiver = null;
        }
    }
    
    private void broadcast(String sender, List<String> usrList) {
    	Set<String> set = sessionMap.keySet();
    	Iterator<String> it = set.iterator();
    	while(it.hasNext()){
    		String usrId = it.next();
    		if(usrId.equals(sender)) continue;
    		
    		JSONObject jsonObj = new JSONObject();
    		JSONArray jsonArr = new JSONArray();
    		jsonArr.addAll(0, usrList);
    		jsonObj.put("usrList", jsonArr);
    		try {
				Writer writer = sessionMap.get(usrId).getBasicRemote().getSendWriter();
				writer.write(jsonObj.toJSONString());
				writer.flush();
				writer.close();
			} catch (IOException e) {
				e.printStackTrace();
			}
    	}
    }
    
    @OnClose
    public void close() {
    	String usrId = (String)httpSession.getAttribute("id");
    	sessionMap.remove(usrId);
    	
    	Object objList = httpSession.getServletContext().getAttribute("usrList");
    	List<String> usrList = (List<String>) objList;
    	usrList.remove(usrId);
    }
  
}