Where The Streets Have No Name

android + node.js에서 websocket을 이용한 파일전송 본문

Developement/Mobile

android + node.js에서 websocket을 이용한 파일전송

highheat 2012. 8. 10. 10:09

안드로이드에서 최신 websocket 스펙을 지원하는 라이브러리를 선정하는데 시간이 좀 걸렸습니다.

여러가지 테스트중  http://autobahn.ws/android 에서 구한걸로 테스트했습니다.

여러 websocket 라이브러리 비교는 http://en.wikipedia.org/wiki/Comparison_of_WebSocket_implementations 여기

를 참조하세요.

node.js에서 사용된 websocket 모듈은 http://einaros.github.com/ws/ 에서 구한걸로 테스트했습니다.

 

node.js소스

var WebSocketServer = require('ws').Server
  , express = require('express')
  , fs = require('fs')
  , http = require('http')
  , util = require('util')
  , path = require('path')
  , app = express()
  , events = require('events')
  , ansi = require('ansi')
  , cursor = ansi(process.stdout);

function BandwidthSampler(ws, interval) {
  interval = interval || 2000;
  var previousByteCount = 0;
  var self = this;
  var intervalId = setInterval(function() {
    var byteCount = ws.bytesReceived;
    var bytesPerSec = (byteCount - previousByteCount) / (interval / 1000);
    previousByteCount = byteCount;
    self.emit('sample', bytesPerSec);
  }, interval);
  ws.on('close', function() {
    clearInterval(intervalId);
  });
}
util.inherits(BandwidthSampler, events.EventEmitter);

function makePathForFile(filePath, prefix, cb) {
  if (typeof cb !== 'function') throw new Error('callback is required');
  filePath = path.dirname(path.normalize(filePath)).replace(/^(\/|\\)+/, '');
  var pieces = filePath.split(/(\\|\/)/);
  var incrementalPath = prefix;
  function step(error) {
    if (error) return cb(error);
    if (pieces.length == 0) return cb(null, incrementalPath);
    incrementalPath += '/' + pieces.shift();
    fs.exists(incrementalPath, function(exists) {
      if (!exists) fs.mkdir(incrementalPath, step);
      else process.nextTick(step);
    });
  }
  step();
}

cursor.eraseData(2).goto(1, 1);
app.use(express.static(__dirname + '/public'));

var clientId = 0;
var wss = new WebSocketServer({host:'192.168.0.59', port: 8081});
wss.on('connection', function(ws) {
  var thisId = ++clientId;
  cursor.goto(1, 4 + thisId).eraseLine();
  console.log('Client #%d connected', thisId);

  var sampler = new BandwidthSampler(ws);
  sampler.on('sample', function(bps) {
    cursor.goto(1, 4 + thisId).eraseLine();
    //console.log('WebSocket #%d incoming bandwidth: %d MB/s', thisId, Math.round(bps / (1024*1024)));
  });

  var filesReceived = 0;
  var currentFile = null;
  ws.on('message', function(data, flags) {
    
    if (!flags.binary) {    
      try{              
        currentFile = JSON.parse(data);
        console.log(currentFile);
      }catch(e){
        console.log(e);
      }      
      // note: a real-world app would want to sanity check the data
    }
    else {
      if (currentFile == null) return;
      makePathForFile(currentFile.path, __dirname + '/uploaded', function(error, path) {
        if (error) {
          console.log(error);
          ws.send(JSON.stringify({event: 'error', path: currentFile.path, message: error.message}));
          return;
        }
        fs.writeFile(path + '/' + currentFile.name, data, function(error) {
          ++filesReceived;
          console.log('received %d bytes long file, %s', data.length, currentFile.path);
          ws.send(JSON.stringify({event: 'complete', path: currentFile.path}));
          currentFile = null;
        });
      });
    }
  });

  ws.on('close', function() {
    cursor.goto(1, 4 + thisId).eraseLine();
    console.log('Client #%d disconnected. %d files received.', thisId, filesReceived);
  });

  ws.on('error', function(e) {
    cursor.goto(1, 4 + thisId).eraseLine();
    console.log('Client #%d error: %s', thisId, e.message);
  });
});

fs.mkdir(__dirname + '/uploaded', function(error) {
  // ignore errors, most likely means directory exists
  console.log('Uploaded files will be saved to %s/uploaded.', __dirname);
  console.log('Remember to wipe this directory if you upload lots and lots.');
  app.listen(8080);
  console.log('Listening on http://localhost:8080');
});

안드로이드 소스

package com.example.websocket2;

import java.io.File;
import java.io.IOException;
import java.net.URISyntaxException;
import java.util.HashMap;
import java.util.Map;

import net.neocorea.android.util.ExceptionUtil;
import net.neocorea.android.util.FileUtil;

import org.codehaus.jackson.map.ObjectMapper;

import android.app.Activity;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.util.Log;
import android.view.Menu;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import de.tavendo.autobahn.WebSocketConnection;
import de.tavendo.autobahn.WebSocketException;
import de.tavendo.autobahn.WebSocketHandler;
import de.tavendo.autobahn.WebSocketMessage;
import de.tavendo.autobahn.WebSocketOptions;

public class MainActivity extends Activity {

	public static final String TAG = "WebSocket2";
	private final CustomConnection mConnection = new CustomConnection();	
	public static final String wsuri = "ws://192.168.0.59:8081";
	private static final int FILE_SELECT_CODE = 0;
	private static String FILE_SELECT_NAME = "";

	@Override
	public void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);

		Button btnSelect = (Button) findViewById(R.id.btnSelect);
		btnSelect.setOnClickListener(new OnClickListener() {
			@Override
			public void onClick(View v) {
				Intent intent = new Intent();
				intent.addCategory(Intent.CATEGORY_OPENABLE);
				intent.setType("*/*");
				intent.setAction(Intent.ACTION_GET_CONTENT);
				startActivityForResult(Intent.createChooser(intent, "파일 선택"), FILE_SELECT_CODE);
			}
		});

		Button btnSend = (Button) findViewById(R.id.btnSend);
		btnSend.setOnClickListener(new OnClickListener() {
			@Override
			public void onClick(View v) {
				// 전송될 파일크기제한 설정
				if ("".equals(FILE_SELECT_NAME)) {
					Log.d(TAG, "선택된 파일이 없습니다.");
					return;
				}
				
				if (mConnection.isConnected()){
					mConnection.fileSend(FILE_SELECT_NAME);
				}else{
					try {						
						mConnection.connect(wsuri, new CustomHandler(mConnection, FILE_SELECT_NAME));
					} catch (Exception e) {
						ExceptionUtil.WriteStack(TAG, e);
					}
				}				
			}
		});		
	}

	@Override
	protected void onActivityResult(int requestCode, int resultCode, Intent data) {
		switch (requestCode) {
		case FILE_SELECT_CODE:
			if (resultCode == RESULT_OK) {
				Uri uri = data.getData();
				Log.d(TAG, "File Uri: " + uri.toString());				
				try {
					FILE_SELECT_NAME = FileUtil.getPath(this, uri);					
					Log.d(TAG, "File Path: " + FILE_SELECT_NAME);
				} catch (URISyntaxException e) {
					ExceptionUtil.WriteStack(TAG, e);
				}
			}
			break;
		}
		super.onActivityResult(requestCode, resultCode, data);
	}

}
package com.example.websocket2;

import java.io.File;
import java.util.HashMap;
import java.util.Map;

import net.neocorea.android.util.ExceptionUtil;
import net.neocorea.android.util.FileUtil;

import org.codehaus.jackson.map.ObjectMapper;

import android.util.Log;
import de.tavendo.autobahn.WebSocketConnection;
import de.tavendo.autobahn.WebSocketHandler;
import de.tavendo.autobahn.WebSocketOptions;

public class CustomHandler extends WebSocketHandler {

	public static final String TAG = MainActivity.TAG;
	private CustomConnection conn;
	private String filePath;

	public CustomHandler(CustomConnection conn, String filePath) {
		super();
		this.conn = conn;
		this.filePath = filePath;
	}

	@Override
	public void onClose(int code, String reason) {
		Log.d(TAG, "Status: Disconnected");
	}

	@Override
	public void onOpen() {
		Log.d(TAG, "Status: Connected");
		conn.fileSend(filePath);
	}

	@Override
	public void onTextMessage(String payload) {
		Log.d(TAG, "Got echo: " + payload);
	}

}
package com.example.websocket2;

import java.io.File;
import java.util.HashMap;
import java.util.Map;

import net.neocorea.android.util.ExceptionUtil;
import net.neocorea.android.util.FileUtil;

import org.codehaus.jackson.map.ObjectMapper;

import de.tavendo.autobahn.WebSocketConnection;
import de.tavendo.autobahn.WebSocketOptions;

public class CustomConnection extends WebSocketConnection {
	
	public static final String TAG = MainActivity.TAG;
	
	public void fileSend(String filePath){
		try {
			byte[] fileData = FileUtil.getBytesFromFile(new File(filePath));			
			mOptions.setMaxMessagePayloadSize(fileData.length);
						
			Map dummyData1 = new HashMap();
			dummyData1.put("name", (new File(filePath)).getName());
			dummyData1.put("path", "");
			ObjectMapper om = new ObjectMapper();
			
			this.sendTextMessage(om.writerWithDefaultPrettyPrinter().writeValueAsString(dummyData1));
			this.sendBinaryMessage(fileData);
		} catch (Exception e) {
			ExceptionUtil.WriteStack(TAG, e);
		}		
	}
}