2015년 2월 21일 토요일

[SocketIO] BestHTTP vs UniWeb

개요
UniWeb, BestHTTP를 SocketIO에 관련해 비교해 보고자 한다.
구입을 고려하는 개발자분들에게 참고가 되었으면 한다
Socket.IO에 관해서는 1.0과 0.9에 있어서 프로트콜상의 차이가 존재한다.
개인적으로 구현자체에 있어서는 BestHTTP쪽에 손을 들어주고 싶다는 생각이 들지만,
만약 서버쪽의 구현이 Socket.IO 0.9버전이며 이를 대체할 수 없다면 UniWeb이나 다른 구현체를 고려해야 하지 않나 하는 생각이 든다.

· BestHTTP UniWeb
가격
Price
55 USD (Pro) / 35 USD (Basic) 45 USD
Socket IO Transport Handshake with Long Polling & Websocket
(서버의 수평확장시 서버에서 Sticky Session이 지원되어야함)
Websocket
SocketIO Version (1.0을 기점으로 앱에 영향이 있다) ^1.0 ~0.9
기본 운용 방식
Basic Operations
From code From Code + Requires Component Attachment
문서화정도
Documentize
Document exists Sample only
Heartbeat Impl O X (+bugs)
Handshake parameters customization ready X (개발자에게 클레임 완료) O (from options)
Async Operation Event-handled Based Coroutine Based
SocketIO Binary Support O X

2015년 2월 20일 금요일

[nginx] nginx load balancer proxing

개요
nodejs는 싱글스레드 기반에서 운용되기 때문에, 멀티코어 환경에서 구동시 CPU를 100% 활용하지 않는다.

따라서, 1개 서버에서 코어수 만큼의 nodejs server를 기동한 환경을 전제로 했을때 nginx측에서 해당 서버의 각 nodejs server instance에 대해 load balancer proxy를 실시해주면 최대한의 자원을 이용할 수 있다.

참고자료
http://sailsjs.org/#!/documentation/concepts/Deployment/Scaling.html
# needs sticky session.

http://nginx.org/en/docs/http/load_balancing.html
#basics
#session presistance (sticky session)

수순

nodejs instance는 forever를 이용해 제각각 구동한다.
로컬의 파일시스템에서의 임시 파일들이 존재하므로, 소스들은 1개 instance단위로 분리해 배치토록 하자.

1. init script for forever nodejs app

우선 forever를 통한 sails.js app의 구동


forever start /<앱1의 경로>/app.js --port 5001 --prod
forever start /<앱2의 경로>/app.js --port 5002 --prod
forever start /<앱3의 경로>/app.js --port 5003 --prod
...

상기와 같은 코드를 /etc/init.d/{service_name}에 init script로서 배치한다.

#!/bin/bash
# chkconfig: 345 20 80
# description: portfolio sailsjs daemon
# processname: portfolio
### 상기 3줄을 통해 chkconfig에서 설정 가능케한다.
# ===============================
# init script
#
# @author nanhaprak
# @link http://nodeqa.com/nodejs_ref/25 # 참고한 설정
# ===============================
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin #forever나 node의 path문제 해결. 일반적인 path설정
APPHOME=/home/portfolio/ #예를들면 이번엔 이렇다.
case "$1" in
  start)
    forever start ${APPHOME}/app1/app.js --port 5001
    forever start ${APPHOME}/app2/app.js --port 5002
    ;;
  stop)
    forever stop ${APPHOME}/app1/app.js --port 5001
    forever stop ${APPHOME}/app2/app.js --port 5002
    ;;
  restart)
    forever restart ${APPHOME}/app1/app.js --port 5001
    forever restart ${APPHOME}/app2/app.js --port 5002
    ;;
  list)
    forever list
    ;;
  *)
    echo ###partial###C6F705E4-0637-40D0-B8A9-DF8D14EF4030quot;Usage: $0 {start|stop|list}"
    exit 1
esac

exit 0

2. nginx websocket load balancer configuration

1에서 nodejs자체의 구동을 완료했으므로, 이어서 nginx의 로컬서버내 로드밸런서 프록시를 작성토록 하자.

현재 작성중인 앱의 경우, socket.io, transport with websocket을 사용하고 있으므로, 프록시가 websocket에 대응될 필요가 있다.

우선 load balancer

/etc/nginx/nginx.conf

일부분만. 아래 upstream이 include되도록 하고, upstream목록은 따로 빼도록 하자.

...
http {
...
    include       /etc/nginx/upstreams;

이하, upstream 목록

/etc/nginx/upstreams

upstream sails_upstreams {
ip_hash; # sticky session support. (중요)
server 127.0.0.1:5001;
server 127.0.0.1:5002;
}

이하, virtual host 설정

/etc/nginx/conf.d/{appname}.conf

server {

    listen 80; # http인경우. https라면 443이 될것이다.
    server_name 호스트명;
    location / {

        # websocket proxy configuration
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
        proxy_http_version 1.1;

        # general proxy configuration
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Queue-Start "t=${msec}000";

        # pass
        proxy_pass http://sails_upstreams;
    }
}

[redis] install redis on aws ec2 instance

개요
aws에서 각 ec2에 직접 redis를 설치하려 한 경우, 직접 빌드를 하거나 다른 yum repository에서 모듈을 끌어올 필요가 있다.

elasticache는 비용이 들어가기 때문에 포트폴리오에서는 패스... 실제 운용에서는 상관없을법한 내용일듯 하다.

수순

cd /usr/local/src
wget http://download.redis.io/releases/redis-2.8.19.tar.gz
tar xvfz redis-2.8.19.tar.gz 
cd redis-2.8.19
./configure
make -j4 && make install -j4
cd utils/
./install_server.sh 

설치는 상기로 종료.

redis-cli

상기 cli tool을 이용해 진입후 테스트

127.0.0.1:6379> ping
PONG
127.0.0.1:6379> exit

2015년 2월 11일 수요일

[UniRx] Reactive Extension 4 : Array -> Observable

개요
배열을 Observable로 변환하는 방법

수순
IObservable<int> ob = new int[]{1,2,3,4}.ToObservable();

[UniRx] Reactive Extension 3 : DistinctUntilChanged, Throttle

개요

게임의 옵션메뉴에 있어서, 설정한 값들을 바로 저장, 적용하며 과도한 저장 작업이 일어나지 않도록, 메모리상의 데이터만 변경을 실시간으로 실시하며, 일정시간 변경이 없을때에 한정하여 저장작업을 실시하는 로직을 UniRx를 이용하여 표현 해 보고자 한다.

소스
   Observable  
                .EveryUpdate ()  
                .Select (_ => _hashConfigs (optionControl))  
                .DistinctUntilChanged ()  
                .Do(_=> {  
                     // update volume data on distinct.   
                     this._updateVolume(optionControl);  
                })  
                .Throttle (new TimeSpan(0,0,1))  
                .Do (_ => {  
                     // throttled value will be stored in 1 sec  
                     this._storeData(optionControl);  
                })  

updateVolume() 에서는, 내부적으로 구현되어있는 AudioManager에 대하여 볼륨을 적용하는 로직이 포함되어 있다.

optionControl은 ViewModel이다.

_hashConfigs는 ViewModel이 가지는 당시 시점에서의 데이터값의 hash값이다.
예를들어 MasterVolume,BGMVoulme,SEVolume이란 세가지 property가 있다고 가정한다면 이하와 같이 구현된다.

 int _hashConfigs(OptionControlViewModel optionControl) {  
           return      optionControl.MainVolume.GetHashCode () ^  
                     optionControl.SEVolume.GetHashCode () ^  
                     optionControl.BGMVolume.GetHashCode ()     ;  
      }  

상기 로직이 의미하는 바는

EveryUpdate() : 매 프레임마다 체크
.Select(_=> _hashConfigs(optionControl)) : 해싱값을 검사
.DistinctUntilChanged() : 변동이 있는 경우만을 추려서
.Do : 실시
.Throttle() : 일정 기간(1초) 동안 입력이 있었던 자료중의 마지막 하나.
.Do : 실시.

위와 같은 느낌으로 설명될 수 있다.

첫번째 Do의 경우, Distinct를 통해 중복값 만을 추려낸, 연속 입력에 개의치 않는 값이 주어지므로, storage에 기록하는 내용을 제외한 실질적인 볼륨 적용의 로직이 적용되는 것이 적절하다.

Throttle에서 연속입력이 추려지고 나면, 마지막의 한번에 대해서만 storage에 저장하는 로직을 적용한다면 무의미하게 많은 저장을 실시해 높은 비용을 지불하지 않고도 적절한 타이밍에 저장을 할 수 있다.

2015년 2월 6일 금요일

[uFrame 1.5] Customize Loading Scene

개요
현 시점에서 Loading Scene에 관한 문서화가 이루어져 있지 않은 관계로 사용방법을 정리하고자 한다.

참고
uFrame에 기본적으로 첨부되어있는 Loading scene을 참고

기본적으로 LevelLoaderView가 uFrameComplete에 구현되어 포함되어 있다.
OnGUI기반으로 작성된 UnityGUILevelLoaderView는 이 LevelLoaderView를 inherit하고있으며, 동일한 구조로 작성하면 된다.

Progress는 각 SceneManager의 *Scene.cs의 Load에서 progress에 대해 작업중인 메세지와 progress factor를 0.0-1.0 사이의 값으로 feedback해주면 된다.

수순

1. NGUI

이번에는 NGUI를 활용하여 Loading Scene을 구성해보고자 한다.

NGUI 3.x의 버전(이번에는 3.7.7)에서 하나의 UIRoot이하 UIWidget은, 각각 하나의 Layer를 가질 필요가 있다. Loading Scene은 로딩이 완료될때까지는 해당 Object가 파기되지 않으므로, 두개의 UIRoot가 공존하게 된다. 따라서 Loading용 UI는 Layer를 다르게 설정해 기존 UI와 구분되게할 필요가 있다.

이를 수행하지 않은 경우, 기존에 UIRoot에 대해 Anchor설정을 실시한 Object들은 제각각 알맞은 위치를 참조하지 않는 버그가 존재한다.

http://www.tasharen.com/forum/index.php?topic=11126.0

따라서, Loading용 UI레이어는 LoadingUI라던가 하는식으로 기존의 레이어와는 분리토록 하자.

그 이외에는 NGUI Slider나, 기타 NGUI용 위젯들을 이용하여 화면을 구성하고, 관련된 조작을 LevelLoaderView Inheritance에서 설정하면 되겠다.

2. LoadingSceneObject : MonoBehaviour

Loading scene용 Object는 DontDestroyOnLoad옵션이 걸려 이전 화면과 다음 화면의 transition이 종료될 때 까지화면에 존재하다, 특정 타이밍에 모두 파기되게 된다.

이를 위해서 Loading용 Scene의 GameObject에는 파기를 위한 마커와 같은것이 존재하는데, uFrame에서는 LoadingSceneObject : MonoBehaviour 와 같은 형태로 준비되어있다.
기본적으로 샘플로 제공된 Loading Scene의 LoadingSceneView라는 GameObject와 같은 구성에서 그 하위에 오브젝트들을 구성하면 LoadingSceneObject가 이미 첨부되어있기에 상관없으나, 그 밖에 화면을 구성하고자 한다면 LoadingSceneObject를 각 GameObject에 붙여두도록 하자. 이를 실시하지 않은 경우 Loading이 끝나더라도 오브젝트가 파기되지 않는다.

3. LevelLoaderView inheritances

Loading Scene의 LoadingSceneView(GameObject)에 LevelLoaderView의 inheritnace를 붙여줄 필요가 있다. UnityGUILevelLoaderView.cs를 참고하면 알겠지만, 아래에 간단하게 NGUI를 활용한 경우의 구성을 첨부한다.

 using UnityEngine;  
 public class CustomLevelLoaderView : LevelLoaderView  
 {  
      public UISlider m_slider;  
      public UILabel m_label;  
      public void Update() {  

           if(this.m_slider != null)
           {
                this.m_slider.value = Model.Progress;  
           }
           
           if (m_label != null) 
           {  
                this.m_label.text = Model.Status;  
           }  

      }  
 }  

4. Progress Feedback

예를들어 ItemScene.cs라는 SceneManager가 있다고 가정해보자, 그렇다면 해당 SceneManager의 경로는

{UnityProject}/Assets/{uFrameProject}/SceneManagers/ItemScene.cs

와 같은 경로가 될것이고, 정상적으로 작성된 SceneManager라면 아래와 같은 맴버 함수가 있을거라 생각된다.

public override System.Collections.IEnumerator Load(UpdateProgressDelegate progress)

여기서 progress에 대해 피드백을 실시하면 앞서 작성한 loading scene에서 LoadingSceneViewModel에서 보고된 데이터값을 subscribe하여 화면에 출력해주게 된다.

무의미하지만 적당하게 coroutine으로 작성된 예시를 보이자면

for (int i=0i<5i++) {
   progress("dummy "+i,i/5f);
   yield return new UnityEngine.WaitForSeconds(0.25f);        
}


와 같이 사용해볼 수 있겠다.