자바 커뮤니티 공동 3 세미나

자바 개발자를 위한 ‘共感(공감)을 찾아서 세번째

 

지난 2 세미나에 이어서 OKJSP, JBoss User Group, KSUG 공동 주최하고,

한국어도비시스템즈가 후원하는 자바 개발자를 위한개발 공감’ 세번째 세미나가

진행됩니다. 최근 IT 업계를 선도적으로 이끌어 가고 있는 모바일과 시장에서 차별화된

전략을 갖추실 있는 유용한 기술과 정보들을 자리에서 들으실 있는 기회입니다.

특별히, T스토어 주최 안드로이드 앱 공모전에서 금상을 수상한 블투맞고’ 개발자이신

김재철님을 초대하여 개발자 여러분과 함께할 수 있는 공감 세션을 마련했습니다.

 


행사 안내



 


AGENDA




특별 경품


5분께 화제의 서적 '안드로이드 프로그래밍 정복(김상형 저)'을 드립니다.



등록 : http://adoberia.co.kr/iwt/blog/blog.php?tn=flex&id=540



스타플에서 대학교 동아리 활동을 추첨을 통해 MT 지원품을 드립니다.

이벤트 참여방법은 간단합니다. 스타플에서 동아리 추가하고 맴버 초대하고 함께 활동하면 됩니다.

물론 활동이 높을 수록 당첨확률은 높겠죠?

당첨되면 1등부터 3등까지 푸짐한 MT 지원품을 드립니다. ^^

이벤트 참여 : http://starpl.com/main/event/view/62


스타플에서 KBS에서 8월 30일에 첫 방송한 성균관 스캔들 관련 이벤트를 진행중입니다.

박유천, 송중기, 유아인, 박민영 4인방중 최고의 꽃미남을 투표하고 응원글을 적으시면 성균관 유생들의 나날 도서와 콘피아 바로보기 정액권을 드립니다.

많은 참여 부탁드려요.

이벤트 페이지 : http://starpl.com/main/event/view/61

스타플에서 시크릿의 Madonna 곡이 들어가 있는 싸인 CD를 받아볼 수 있는 이벤트가 준비되어 있습니다. 그외에도 150곡 다운로드가 가능한 음악이용권을 받을 수 있고요.

기간은 9월 2일부터 9월 16일까지 입니다.

간단한 시크릿 응원 메시지로 상품 받아가세요. ^^

 

이벤트 참여 : http://starpl.com/main/event/view/63


제가 천문에 관련된 프로그래밍을 조금 할줄 알다보니 태양의 위치 및 일출/일몰에 대한 문의가 자주 들어옵니다. 개인적인 소스는 있지만 높은 정확도를 위한 것이라 일반인들이 이해하기에는 수준이 높습니다.

출처 : http://very-bored.com/index.php?option=com_content&task=view&id=135&Itemid=29



대부분 문의유형을 살펴보면 태양전지판을 효율적으로 사용하기 위해 태양의 방향에 따라 입사각이 90도가 이뤄지도록 구동하는 장치를 개발을 해야한다던가 수족관의 전등을 일출/일몰 시간에 맞게 자동으로 On/Off시켜줘야 한다는 뭐 아주 간단하면서 정확도가 그리 중요하지 않은 수준의 구현을 요구하는 것들이 대부분 입니다.

일전에 관련되어 문의를 받았지만 요즘에는 개인적으로 너무 바쁘고 여유가 없어서 설명을 드리고 싶지만 드릴 수 있는 시간조차 없었습니다. 몇주가 답변을 못드리다가 잠깐 짬을 내어 답변을 드렸지요.

웹상에서 찾아보면 일몰/일출 시간 소스는 얼마든지 있습니다. 그 방법도 엄청 많이 나와있지요. 단지 어찌 찾아야하는 것인지 있어도 어찌 활용해야하는지 모르시는 분들이 대부분이라 생각합니다. 그래서 여기에 이 부분에 대해 설명한 사이트들을 소개하고 마지막으로 웹에 공개된 C++소스에 주석처리한 것을 공유하겠습니다.

태양위치 계산은 정확도에 따라서 계산방법은 천차만별입니다. 그러므로 완벽한 정답은 없습니다. 상황에 맞게 쓰시면 됩니다.
 
아래내용은 태양의 일출/일몰 계산 방법을 간략하게 소개하고 있습니다.
http://www.stargazing.net/kepler/sunrise.html
 
PHP 언어로 만들어진 일출/일몰 소스입니다.
http://www.phpschool.com/gnuboard4/bbs/board.php?bo_table=tipntech&wr_id=39302&sca=%C7%D4%BC%F6&page=29
 
C또는 C++로 만들어진 일출/일몰 소스입니다.
http://www.sci.fi/~benefon/stuff.html
아래 붙여놓은 소스는 여기에서 구현한 C++소스를 주석처리 해놓은 것 입니다. 참고하시고 main()함수부터 천천히 보세요. 이것으로 근사적 일출/일몰 시간을 계산할 수 있습니다.

구현하신다음 맞는지 확인하기 위해 아래 링크를 참고하세요.
http://www2009.kasi.re.kr/knowledge/solun_riset.aspx

// C++ program calculating the sunrise and sunset for
// the current date and a fixed location(latitude,longitude)
// Jarmo Lammi 1999 - 2000
// Last update January 6th, 2000

#include <iostream.h>
#include <math.h>
#include <time.h>

extern double pi;
double tpi = 2 * pi; 
double degs = 180.0/pi;
double rads = pi/180.0;

double L,g,daylen;
double SunDia = 0.53;     // Sunradius degrees(지구에서 본 태양의 반지름각. 단위:도)

double AirRefr = 34.0/60.0; // athmospheric refraction degrees (지구대기 굴절각)//

//   Get the days to J2000
//   h is UT in decimal hours
//   FNday only works between 1901 to 2099 - see Meeus chapter 7
// 이 함수는 J2000 1월 1일 12시 DT를 기준으로 날수(Day Number)를 구합니다. (Meeus의 책 챕터 7을 참고하세요)
// J2000에 대해서는 제 블로그 http://blog.jidolstar.com/487 를 참고하세요.
// 입력값은 년,월,일,시간(실수값)입니다. 
double FNday (int y, int m, int d, float h) {
int luku = - 7 * (y + (m + 9)/12)/4 + 275*m/9 + d;
// type casting necessary on PC DOS and TClite to avoid overflow
luku+= (long int)y*367;
return (double)luku - 730531.5 + h/24.0;
};

//   the function below returns an angle in the range
//   0 to 2*pi
// 
// 들어온 값을 항상 0~2pi 값안으로 normalize시킵니다. 
double FNrange (double x) {
    double b = x / tpi;
    double a = tpi * (b - (long)(b));
    if (a < 0) a = tpi + a;
    return a;
};

// Calculating the hourangle
// 시간각을 계산합니다. 
double f0(double lat, double declin) {
double fo,dfo;
// Correction: different sign at S HS
dfo = rads*(0.5*SunDia + AirRefr); if (lat < 0.0) dfo = -dfo;
fo = tan(declin + dfo) * tan(lat*rads);
if (fo>0.99999) fo=1.0; // to avoid overflow //
fo = asin(fo) + pi/2.0;
return fo;
};

// Calculating the hourangle for twilight times
// 박명시간에 대한 시간각을 계산합니다. 
double f1(double lat, double declin) {
double fi,df1;
// Correction: different sign at S HS
df1 = rads * 6.0; if (lat < 0.0) df1 = -df1;
fi = tan(declin + df1) * tan(lat*rads);
if (fi>0.99999) fi=1.0; // to avoid overflow //
fi = asin(fi) + pi/2.0;
return fi;
};


//   Find the ecliptic longitude of the Sun
//  태양의 황경을 구합니다.
double FNsun (double d) {

//   mean longitude of the Sun (태양의 평균 황경)

L = FNrange(280.461 * rads + .9856474 * rads * d);

//   mean anomaly of the Sun (태양의 평균근점이각)

g = FNrange(357.528 * rads + .9856003 * rads * d);

//   Ecliptic longitude of the Sun (태양의 황경계산)

return FNrange(L + 1.915 * rads * sin(g) + .02 * rads * sin(2 * g));
};

// Display decimal hours in hours and minutes
// 이것은 실수(Real Number)로 표현된 시간을 시:분으로 표기 하기 위한 겁니다. 
void showhrmn(double dhr) {
int hr,mn;
hr=(int) dhr;
mn = (dhr - (double) hr)*60;
if (hr < 10) cout << '0';
cout << hr << ':';
if (mn < 10) cout << '0';
cout << mn;
};


// 메인함수입니다. 
int main(void){
double y,m,day,h,latit,longit;

time_t sekunnit;
struct tm *p;

//  get the date and time from the user
// read system date and extract the year
// 사용자의 시스템에서부터 날짜와 시간을 얻습니다.
/** First get time **/
time(&sekunnit);

/** Next get localtime **/

 p=localtime(&sekunnit);

 y = p->tm_year; //년 
 // this is Y2K compliant method
 y+= 1900;
 m = p->tm_mon + 1; //월 

 day = p->tm_mday; //일

 h = 12; //낮 12시를 기준으로 계산합니다.

//관측장소의 경도(단위 degree),위도(단위 degree),타임존(단위시간) 값을 입력받습니다.
double tzone=2.0;
cout << "Input latitude, longitude and timezone and month\n";
cin >> latit;
cin >> longit;
cin >> tzone;

// testing
// m=6; day=10;

double d = FNday(y, m, day, h);

//   Use FNsun to find the ecliptic longitude of the
//   Sun
// 태양의 황경 
double lambda = FNsun(d);

//   Obliquity of the ecliptic (황도기울기 계산)

double obliq = 23.439 * rads - .0000004 * rads * d;

//   Find the RA and DEC of the Sun (태양의 적경,적위 계산)

double alpha = atan2(cos(obliq) * sin(lambda), cos(lambda)); //태양의 적경
double delta = asin(sin(obliq) * sin(lambda)); //태양의 적위 

// Find the Equation of Time
// in minutes
// Correction suggested by David Smith
// 균시차 계산 
double LL = L - alpha;
if (L < pi) LL += tpi;
double equation = 1440.0 * (1.0 - LL / tpi);
double ha = f0(latit,delta);
double hb = f1(latit,delta);
double twx = hb - ha;	// length of twilight in radians (라디안 단위 박명길이)
twx = 12.0*twx/pi;		// length of twilight in hours (시간단위 박명길이)
cout << "ha=" << ha << "  hb=" << hb << endl; 
// Conversion of angle to hours and minutes (하루길이를 시간단위로 계산) //
daylen = degs*ha/7.5;
     if (daylen<0.0001) {daylen = 0.0;}
// arctic winter     //

double riset = 12.0 - 12.0 * ha/pi + tzone - longit/15.0 + equation/60.0; //뜨는시간
double settm = 12.0 + 12.0 * ha/pi + tzone - longit/15.0 + equation/60.0; //지는시간 
double noont = riset + 12.0 * ha/pi; //정오 시간 

//정오시에 태양의 고도(maximum altitude)
double altmax = 90.0 + delta * degs - latit; 
// Correction for S HS suggested by David Smith
// to express altitude as degrees from the N horizon
if (latit < delta * degs) altmax = 180.0 - altmax;

double twam = riset - twx;	// morning twilight begin 시민박명(아침)
double twpm = settm + twx;	// evening twilight end 시민박명(저녁)

if (riset > 24.0) riset-= 24.0;
if (settm > 24.0) settm-= 24.0;

cout << "\n Sunrise and set\n";
cout << "===============\n";

cout.setf(ios::fixed);
cout.precision(0);
cout << "  year  : " << y << '\n'; //년
cout << "  month : " << m << '\n'; //월
cout << "  day   : " << day << "\n\n"; //일
cout << "Days until Y2K :  " << d << '\n'; //J2000을 기준으로 센 날수 
cout.precision(2);
cout << "Latitude :  " << latit << ", longitude:  " << longit << '\n'; //관측자의 경도와 위도 출력 
cout << "Timezone :  " << tzone << "\n\n"; //타임존 출력 (한국은 UT+9h)
cout << "Declination   : " << delta * degs << '\n'; //태양의 적위
cout << "Daylength     : "; showhrmn(daylen); cout << " hours \n"; //하루길이 

//시민박명(아침) 
cout << "Civil twilight: ";
showhrmn(twam); cout << '\n';
//일출시간 
cout << "Sunrise       : "; //
showhrmn(riset); cout << '\n';

 //정오때 태양 고도(Amendment by D. Smith)
cout << "Sun altitude at noontime "; 
showhrmn(noont); cout << " = " << altmax << " degrees" 
    << (latit>=0.0 ? " S" : " N") << endl;

//일몰시간 
cout << "Sunset        : ";
showhrmn(settm); cout << '\n';
//시민박명(저녁)
cout << "Civil twilight: ";
showhrmn(twpm); cout << '\n';

return 0;
}

어떤가요? 조금만 공을 들이면 완벽히 이해는 못해도 구현은 얼마든지 가능합니다.

글쓴이 : 지돌스타 (http://blog.jidolstar.com/705)


스타플에서 이벤트를 진행중입니다.
술자리에 있었던 다양한 에피소드나 사진을 글로 담으면 이벤트 참여 완료합니다.
기간은 8.23~9.6(2주간)이며 9.10에 당첨자 발표합니다.
재미있는 에피소드 많이 공유해주세요. ^^

술자리 이벤트 페이지 : http://starpl.com/main/event/view/60

그외에 다양한 이벤트가 진행중입니다. http://starpl.com/main/event/ing/list


글쓴이 : 지돌스타(http://blog.jidolstar.com/754)

스타플(http://starpl.com)은 ‘별나라’에서 얘기 나누는 공간이다. ‘별자리 기반 사회관계망 서비스’(SNS)쯤 되겠다. 기본 기능은 여느 SNS 범주를 크게 벗어나지 않는다. 내 터전이 있고, 다른 사람들과 연결돼 정보나 관심사를 나누는 곳이다.

허나 ‘별자리’란 매혹적인 요소만큼은 다른 데선 찾아보기 어렵다. 일단 스타플에 발을 들여놓으면 나만의 ‘별’을 받게 된다. 상상으로 만들어낸 별이 아니라, 실제 우주에 존재하는 별이다. 이용자는 이 별을 터전 삼아 내 관심사를 담아두고, 다른 사람들과 관심사를 공유한다. 관심사가 같은 별끼리 자동으로 연결되고, 이런 네트워크가 커지며 거대한 관심사 기반 별지도가 만들어진다.

 

윤경석 위콘커뮤니케이션즈 대표가 스타플 같은 서비스를 만들고 싶다고 생각한 건 5년 전부터다. SNS란 용어도 제대로 없던 시절이었지만 나름 번뜩이는 영감이 있었단다. 감성을 자극하고 대중에게 친근하게 다가갈 수 있는 소통 서비스가 없을까. 윤 대표는 ‘별’에 ‘꽂혔다’. 별이란 게 뭔가. 만국 공통 감성 아이콘 아닌가. 그 별을 이용자에게 나눠줘 실제 삶의 요소와 사이버 공간의 특성을 잘 버무리면 대중적이면서도 매혹적인 서비스가 나오지 않을까.

현실은 녹록치 않았다.


“미국 해군관측소가 공개한 별자리 데이터베이스를 가져다 썼어요. 1940년대부터 지금까지 10억개에 이르는 밤하늘 별을 실제 관측한 자료들을 좌표화한 DB인데요. 실제 별자리 좌표를 서비스에 매칭하는 작업이 만만치 않았어요. 플래시 기반으로 별자리 지도를 만들었는데, 생각만큼 구동 속도가 안 나와 골머리도 적잖이 앓았죠. 2007년부터 본격 준비에 들어가 2008년말 공개 시범서비스를 내놓았는데요. 그 사이에만 서비스를 네 번은 갈아엎었을 정도에요, 하하.”

서비스를 연 뒤에도 사이에도 시행착오를 적잖이 겪었다. “처음 서비스를 접한 사람들의 반응은 한마디로 ‘어렵다’는 것이었어요.별지도와 타임라인, 관심사와 알림 기능이 모두 들어 있었거든요. 우리 입장에선 각기 다른 네 가지 서비스를 하나로 합치는 것도 무척 어려운 작업이었는데, 이용자 입장에서도 복잡하고 어렵게 받아들인 셈이죠.”

얽힌 매듭을 하나하나 풀기로 했다. 다시 밑그림 작업에 들어가, 8월초 군살을 뺀 ‘스타플2.0′을 새롭게 선보였다. 개편된 스타플은 ‘관심사’를 전면에 내세우고 다른 서비스들을 분리했다. ‘관심사 기반 소통’이란 큰 줄기 아래 다른 서비스들을 가지 치듯 엮어내기로 한 것이다. 느린 서비스 구동 속도도 개선했다. 서비스 컨셉트를 간명하게 전달하고 친숙한 이용자 화면(UI)을 도입해 ‘별 커뮤니티’를 확보하는 데 우선 집중하겠다는 전략인 셈이다.

 

 

대중에게 다가서려는 마케팅 활동도 본격화할 생각이다. 얼마 전에는 KBS인터넷과 손잡고 이용자들이 ‘드라마 별스킨’이나 인기 드라마 관심사를 공유할 수 있도록 했다. 태국관광청이나 코로나 등과도 공동 마케팅을 진행하고 있다.


“9월 안에 스마트폰용 애플리케이션도 선보일 생각입니다. 기존 스타플 주요 서비스별로 각각 애플리케이션을 내놓을 거에요. 관심사란 게 사람들마다 다양하잖아요. 음악이나 영화, 드라마부터 운세나 궁합처럼 다양한 외부 콘텐츠 제휴로 서비스를 채워나가려 해요. 올해 말까진 서비스 속도도 더욱 높이고, 자체 개발중인 소셜 게임도 연동할 생각입니다. 타임라인 기능을 되살리면 외부 SNS 연동 기능도 덧붙이고, 별지도를 보여주는 학습용 애플리케이션도 준비하고 있어요.”

윤경석 대표는 ‘별’이 지닌 대중적 매혹성을 굳게 믿는다. 처음부터 서비스 바탕에 별자리를 배열한 것도 같은 이유에서다. 전세계에서 통할 것이란 확신이 있었기 때문이다.


“별이란 게 만국 공통 관심사잖아요. 나이나 성별, 지역과 민족에 관계없이 동일하게 다가서는 아이템이죠. 서양만 해도 점성술이나 별자리에 대한 친근감이 문화적 코드로 잘 형성돼 있고요. 우주란 공간적 개념이 SNS와 유기적으로 엮이고 신선한 아이디어가 얹히면, 세계에서 통할 수 있는 경쟁력 있는 서비스가 될 수 있다고 믿어요.”




출처 : http://www.bloter.net/archives/37358

 


스타플-KBS인터넷, 전략적 제휴 체결
다양한 콘텐츠 서비스 제공하기로…전략적 제휴 이벤트도 진행


관심사를 중심으로 실시간 커뮤니케이션이 가능한 신개념 SNS 서비스업체 스타플이 KBS인터넷과 양사 마케팅 지원을 위한 전략적 제휴를 체결했다고 18일 밝혔다.


위콘커뮤니케이션즈의 스타플은 '별’이라는 친근한 소재를 개인공간으로 나눠주므로 젊은 층의 많은 공감을 얻고 있는 웹 서비스이다. 스타플은 지난 9일 리뉴얼을 통해 개인 기록 중심의 기존 서비스에서 '관심사’ 중심의 커뮤니케이션 서비스로 업그레이드되어 나와 같은 관심사를 가진 사람과 쉽게 만나 실시간 소통이 가능하게 됐다.
 

이번 KBS인터넷과의 전략적 제휴를 통해 스타플은 드라마 별스킨 등 디지털 콘텐츠를 제공할 수 있게 됐으며 스타플 내 인기드라마나 예능프로그램 관심사를 통해 정보와 시청의견을 나누는 등 해당 프로그램에 대한 사용자 간 실시간 소통이 가능하게 됐다.


스타플 관계자는 “다년간 우수한 콘텐츠를 제공해온 KBS인터넷과 스타플의 전략적 제휴를 통해 다양한 콘텐츠 서비스가 제공될 예정”이라고 밝혔다.


이와 함께 스타플과 KBS인터넷은 전략적 제휴를 통한 이벤트를 23일까지 스타플에서 진행하고 있다. KBS 드라마 '제빵왕 김탁구’와 '구미호 여우누이뎐’을 테마로 제빵왕 김탁구처럼 나는 무슨왕인지 적는 재치왕 선발대회와 구미호와 같이 나에게 가장 무서운 것을 적는 이벤트가 진행된다.


이벤트 참여자 전원에게는 스타플 별스킨을 제공하며 추첨을 통해 KBS 프로그램 VOD를 다시 볼 수 있는 콘피아 1개월 이용권을 제공한다.




스타플 : http://starpl.com


좀 지난 기사긴 합니다. ^^
일단 KBS관련 이벤트는 종료되었습니다.

앞으로도 지속적인 이벤트를 할 것입니다.


스타플, 서비스 차별화하여 리뉴얼 오픈

스스로 진화하는 ‘실시간 커뮤니티’ 서비스 제공, 서비스적인 차별화 강화


신개념 SNS 서비스업체 스타플(www.starpl.com)이 8월 9일 리뉴얼된 모습을 새롭게 선보였다.


스타플은 밤하늘에 나의 별을 통해 다른 별의 이용자(사용자)와 ‘관심사’을 중심으로커뮤니케이션의 장을 만들어주는 신개념 SNS서비스다. 스타플은 단편적인 소통이 아닌 3차원 이미지를 바탕으로 새로운 인맥 형성 및 정보 교류의 통로로 활용할 수 있어 오픈 이후 7개월 만에 10만 명을 돌파할 정도로 20대를 중심으로 폭발적인인기를 누리고 있다.


↑↑ ▲ 스타플을 최근 리뉴얼한 위콘커뮤니케이션즈 윤경석 대표 ⓒ2010 더리더/최자웅 ⓒ 더리더


이번 리뉴얼을 통해서는 개인 기록 중심의 기존 서비스에서 스스로 진화하는‘실시간 커뮤니티’ 서비스를제공하며 서비스적인 차별화를 강화했다.


기존의 SNS는 최근 급성장하고 있는 모바일 환경의 영향으로 새로운 이슈에 대한 관심이 실시간으로 이루어 지긴 하지만 모든 정보가 혼재하기 때문에 원하는 관심사를 쉽게 접하기 어렵고, 그 안에서의 커뮤니케이션도 쉽지 않다.


그와는 달리 스타플의 ‘관심사’ 실시간 커뮤니티는 자신의 ‘관심사’에 대해 쉽고 빠르게 정보를 교류하게 만들며, 시시각각변화하는 관심사에 따라 또 다른 커뮤니티의 생성을 유도하여 스스로 진화하게 된다.


스타플을 서비스하는 위콘커뮤니케이션즈 윤경석 대표는 "최근 스마트폰의 확산으로인해 정보의 확산 속도는 가히 상상을 초월하고 있다" 며 "스타플이 제시하는커뮤니케이션의 방법론은 점점 가속화되고 있는 모바일 환경에 딱 맞아떨어져 사용자에게 새로운 경험을 제공할 것이다"고 서비스에 자신감을 드러냈다.


이런 차별화된 커뮤니티로 리뉴얼된 스타플에서 다양한 오픈 이벤트가 진행 중이다.


오픈일부터 20일 동안 회원 가입을 한 이용자를 대상으로 매일 1명씩 사파 영어학습기 매직스터디를, 회원 가입 이벤트를 커뮤니티에 퍼가기만 해도 매일 1명씩 사파 마시멜로 MP3 플레이어를 제공한다.



↑↑ ▲ 위콘커뮤니케이션즈 스타플 리뉴얼관련 이벤트 ⓒ2010 더리더/최자웅 ⓒ 더리더


또, 여름휴가철을 맞아 여행 사진이나 에피소드를 여름휴가 관심사에 올리면 추첨을 통해 음악이용권과 MP3플레이어 등의 다양한 상품을 제공한다


이 외에 현재 인기몰이 중인 KBS 드라마 ‘제빵왕 김탁구’와 ‘구미호 여우누이뎐’ 을 테마로한 재미있는 이벤트도 진행 중에 있다. 제빵왕 김탁구처럼 나는 무슨왕 인지 적는 재치왕 선발대회와 구미호와 같이 나에게 가장 무서운 것을 적는 이벤트가 진행된다. 이벤트 참여자 전원에게는 스타플 별스킨을 제공하며, 추첨을 통해 KBS 다시 보기보기도 볼 수 있는 콘피아 1개월 이용권을 제공한다.


이벤트에 대한 자세한 내용은 http://starpl.com/main/event/ing/list 을 참고하면 된다.


최자웅 기자 pshbear@empal.com


드디어 스타플이 새롭게 단장해 오픈했습니다. 이번 오픈의 키워드는 "관심사"입니다. 관심을 가진 사람들끼리 함께 우주라는 배경아래 소통하는 모습을 담고자 심혈을 기울여 만들었습니다.

이 글은 개편한 스타플을 간단하게 소개합니다.

스타플 : http://starpl.com

1. 스타플 메인화면
좀 더 감성적이고 실제별을 받는다는 느낌을 주기로 했습니다. 이야기가 시작되는 곳, 바로 스타플입니다.



2. 스타플 회원가입
스타플은 간단한 성향테스트 및 친구초대, 별지도를 통한 별받기등을 통해 가입을 할 수 있습니다. 아래는 성향테스트를 통해 별을 받았을때 화면입니다.



별지도에 제 별이 보이네요. 안드로메다 자리에 위치했습니다. 구역은 A27-N40이네요.

스타플 회원이 아니신 분은 저의 초대링크를 타고 오세요. ^^
http://starpl.com/jidolstar/invitation/VBLFMVY8


3. 나의 별 - 소통의 장소
스타플에서 관심을 가진 사람들끼리 한데 묶여 이야기를 할 수 있습니다. 아래 화면은 나의 첫화면입니다. 짧은글(한줄) 또는 긴글을 통해 자신의 관심사를 남겨보세요. 어느덧 같은 관심사를 가진 사람들과 친해질 수 있을거예요.

지돌스타의 별 : http://starpl.com/jidolstar


앞서 설명했듯이 이번 개편의 가장 큰 이슈는 관심사입니다. 아래 화면은 나의 관심사를 보여줍니다.


화면 좌측에 뱃지가 있습니다. 뱃지는 나의 활동상황에 따라 주어집니다. 가장 크고 상단좌측에 있는 관심사 마스터는 해당 관심사에서 활동포인트가 가장 많은 사람에게 주어집니다.


저는 드럼에 관심이 많습니다. 관심사마다 포인트가 많은 사람은 마스터가 됩니다. 마스터는 해당 관심사 페이지에서 가장 상단에 표시되며 아래처럼 이미지(드럼이미지) 및 메시지를 편집할 수 있는 관한을 가져요. 하지만 포인트가 적어 다른 사람에게도 추월당할 수도 있으니 열심히 활동해야겠죠?


4. 업그레이드 된 별지도
기존 별꾸미기, 타임라인등이 서비스 개편하면서 없어졌지만 스타플은 여전히 별이 가장 큰 매개체입니다. 별이 없으면 스타플이라고 할 수 없지요? 별지도는 앞으로 스타플의 특징을 더욱 살려주고 사용자들에게 흥미로운 경험을 전달할 강력한 도구가 될 것입니다.



5. 별지도를 통한 별받기 또는 이사가기
이번 스타플의 새로운 기능으로 좋아하는 사람 옆 별로 이사가거나 별받기를 할 수 있습니다. 제 아내가 바로 제 별 옆에 이사왔습니다. ^^


6. 나의 별 바탕화면
부가적인 기능으로 밤하늘 상에 내별의 위치를 바탕화면 이미지로 다운로드 받을 수 있는 기능을 추가했습니다. 자신의 바탕화면에 맞는 사이즈를 골라보세요.

브랜드샵 가기 : http://starpl.com/wecon/shop



6. 다양한 이벤트!
오픈하면서 다양한 이벤트를 준비했습니다. 가입하거나 친구를 초대하거나 약간의 활동만으로도 멋진 이벤트 상품을 받을 수 있는 기회입니다. ^^

이벤트에 참여하시려면 자기의 별에서 초미의 관심사 부분을 찾아 가시거나 아래 링크를 따라 가시면 됩니다.

이벤트 페이지 : http://starpl.com/main/event/ing/list


이벤트 중 하나입니다. ^^
이벤트 자세히보기

7. 스타플에 대해 더 자세히 알아보기
스타플에 대한 간단한 영상 및 만화가 준비되어 있습니다.
1. 티져영상보기 : http://shared.starpl.com/html/renewal/menu1.html
2. 스타플의 재발견 : http://shared.starpl.com/html/renewal/menu2.html
3. 새로워진 스타플 : http://shared.starpl.com/html/renewal/menu3.html
4. 스타플 즐기기! : http://shared.starpl.com/html/renewal/menu4.html


이건 뭘까요? ^^


글쓴이 : 지돌스타(http://blog.jidolstar.com)


Java Spring Framework를 활용하는데 발생한 문제이다.  

@Controller 어노테이션을 가지고 한글과 영어가 섞인 문자열을 반환하기 위해 @ResponseBody 어노테이션을 이용해 다음과 같이 코딩했다. 

 

@Controller
@RequestMapping("/foo")
public class Foo {
    @RequestMapping(value="/foobar", method=RequestMethod.GET)
    @ResponseBody
    public string getFoobar() {
      // Do something that returns some UTF-8 stuff (probably some XML).
    }
}

 

그리고 문자 인코딩 필터 설정을 아래와 같이 했다. 

 

<filter>
        <filter-name>CharacterEncodingFilter</filter-name>
        <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
        <init-param>
            <param-name>encoding</param-name>
            <param-value>UTF-8</param-value>
        </init-param>
        <init-param>
            <param-name>forceEncoding</param-name>
            <param-value>true</param-value>
        </init-param>
</filter>

<filter-mapping>
        <filter-name>CharacterEncodingFilter</filter-name>
        <url-pattern>/*</url-pattern>
</filter-mapping>

 

 

하지만 위 설정은 Request에 대해서만 CharacterEncodingFilter가 먹힌다고 한다. 그래서 Response에 대해서도 UTF-8이 될 수 있도록 설정할 필요가 있다.

 

<bean  class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter" >
   <property name="messageConverters">
      <list>
         <bean class = "org.springframework.http.converter.StringHttpMessageConverter">
            <property name = "supportedMediaTypes">
               <list>
                  <value>text/plain;charset=UTF-8</value>
               </list>
            </property>
         </bean>
      </list>
   </property>
</bean>

 

위 설정은 @Controller에서 @ResponseBody로 지정시 반환값에 한글이 있으면 깨지는 현상을 방지하기 위해 사용한다.

 

명확히 이해하고 사용하는 건지 모르겠지만 분명 저렇게 하면 한글이 안깨지고 잘 반환한다.

 

참고글 

1. https://issues.springsource.org/browse/SPR-6443

2. http://static.springsource.org/spring/docs/3.0.x/javadoc-api/org/springframework/web/filter/CharacterEncodingFilter.html

3. http://dev.firnow.com/course/3_program/java/javajs/20100719/455191.html

 

글쓴이 : 지돌스타(http://blog.jidolstar.com/700)  

8월 9일 스타플이 새롭게 오픈합니다.

공통된 관심사를 가진 사람들을 쉽게 만나고 즐겁게 이야기 하는 세상, 스타플이 새로운 커뮤니케이션 장을 열어드립니다.


 















8월, 스타플의 새로운 커뮤니케이션 세상이 열립니다.
공통된 관심사를 가진 사람들을 쉽게 만나고 즐겁게 이야기하는 세상,
스타플이 새로운 커뮤니케이션의 장을 열어드립니다.

원본 영상 : http://starpl.com/html/simple_video.html
스타플 : http://starpl.com

저녁에 블로그 방문수를 보니 3000명이 넘는 사람이 들어왔다. 이거 무슨 희귀한 일인가 했다.

로그를 보니 전부 네이버 메인에서 온거다. 



알고 보니 네이버 오픈캐스트 추천에 내 캐스트가 있더라. 



ㅎㅎ 재미있네. ^^

지돌스타 Adobe RIA 오픈캐스트 놀러가기 


아래 언급한 내용이 정확한 건지는 나도 확신이 들지 않는 부분도 있다. 동적,정적 언어에 대한 교양을 쌓아본적도 없고 단순히 블로그와 여러 자료를 읽어보고 내린 나의 결론일 뿐이다. 이 생각을 이해하려면 먼저 ActionScript 3.0에 대한 언어적 구조를 이해할 필요가 있으므로 아래글을 꼭 읽어보길 바라며 잘못된 내용이 있으면 댓글로 언제든지 지적해주었으면 한다.


동적언어와 정적언어의 차이점은 실행시점에 메모리 관리와 관계있다. 정적 언어에서는 실행시 메모리상에 정의된 클래스,함수등을 수정될 수 없지만 동적언어는 언제든지 바꿀 수 있다.  

ActionScript 3.0은 JavaScript처럼 prototype 체인을 기반으로 하는 동적언어이다. 하지만 개발자가 느끼기에 정적언어처럼 사용을 강제하는 구조로 만들어져 있다. 즉, 겉으로 보기에는 정적언어인 Java나 C++처럼 Class, 메소드 기반의 개발을 유도한다. 

동적언어는 정적언어에 비해 속도,메모리 관점에서는 불리한 면이 많으나 유연성이 좋기 때문에 그에 따른 장단이 있다. 

ActionScript 3.0은 동적언어인데 왜 굳이 정적언어 흉내를 내려고 한 것일까? 여러가지 이유가 있겠지만 언어의 패러다임을 맞춰 Java, C++개발자들과 같이 객체지향개발에 익숙한 사람들도 쉽게 접근하기 위함이 있겠고 또한 속도개선을 위한 메커니즘을 만들어내기 위한 것이 아닌가 생각된다. 

Class기반의 객체지향프로그래밍은 Java,C++개발자라면 익숙하기 때문에 접근하기 좋다. ActionScript 2.0때 부터 Class나 extends, private, public 과 같은 키워드를 도입해서 객체지향적 프로그래밍을 강제했다. 내부적으로는 prototype 체인구조로 되어 있지만 문법적으로 이들 키워드를 이용해 개발하면 컴파일시 자동으로 prototype 구조를 변형되는 것으로 판단한다. 하지만 이런식의 단순 변화는 개발의 접근성이 좋아진 반면 속도적인 이득은 없다. 

ActionScript 2.0까지 AVM1(ActionScript Virtual Machine 1) 기반하에 동작할 때는 함수 및 속성에 접근하기 위해 prototype 체인을 검색하면서 실행했기 때문에 속도적으로 너무 느리다. 하지만 ActionScript 3.0부터는 새로운 AVM2 기반에서 동작하게 되어 느린 prototype 체인 검색을 많은 부분 개선하게 되었다. 즉, prototype대신 traits라는 일종의 캐쉬기능이 도입되었다. 이 기능의 도입으로 속도와 메모리가 상당부분 개선이 되었다. 

AVM2에서 A클래스의 객체구조(출처:어도비)




분명 AVM2에서 traits 도입은 ActionScript 3.0의 매력도를 굉장히 증가를 시키게 되었다.  이렇게 하여 접근 방법이 빨라지고 클래스에 정의된 메소드들의 this는 항상 클래스 객체를 가리킬 수 있게 된것이다. 이런 개념때문에 우리는 sprite1.addEventListener(myEvent, display1.hnFunc)와 같이 아무 문제없이 이벤트 핸들링을 할 수 있게 된것이다. display1.hnFunc 안에서 this를 사용하면 이것은 sprite1이 아닌 display1을 가리키게 된다. hnFunc는 display1 객체를 정의한 클래스 내에 정의된 메소드이기 때문이다. 만약 traits 개념이 없다면 ActionScript는 여전히 prototype 기반이기 때문에 의도하는 this의 행방이 애매모호해져서 ActionScript 2.0때 처럼 delegation(대리자) 개념을 이용해야하는 불편함을 가지게 된다. 

메소드 클로저는 Class내의 메소드 일때만 유효하다. 만약 다음과 같은 경우 this는 의도하는 this가 아닌 경우가 생긴다. 예제로 아래 ActionScript 3.0코드를 보자.

class MyClass {
	function MyClass() {
	}
	public  function method():void {
		trace(this);  //[object MyClass]
		hnRun(this);  //[object global]:[object MyClass]
		function hnRun($object:*):void {
			trace(this +":"+$object); 
		}		
	}
}

위에서 언급한 대로 위 클래스의 method()는 클래스의 메소드이므로 메소드 클로저 개념이 도입되어 method() 내에서 this는 그 클래스의 객체 자신을 가리킨다. 만약 prototype구조로만 정의된다면 당연히 this는 global을 가리켜야 한다. 이러한 이유로 ActionScript 3.0이 Java나 C++처럼 this의 의미가 같아지게 되어 헷갈리지 않고 사용할 수 있게 된다. 

ActionScript 3.0은 분명 동적언어이므로 method()내에 hnRun()과 같은 형태로 메소드 안에 또 함수를 정의할 수 있다. 이 함수는 내부함수라 지칭된다. 이 경우에도 this는 MyClass를 가리켰으면 좋겠지만 실제로 출력결과를 보면 global이 나온다. 즉, 내부함수는 prototype 체인기반의 함수 특성을 여전히 가지고 있다는 것을 의미한다. 

ActionScript 3.0는 이런 내부함수를 다룰 수 있는 여지는 남겨두었지만 잘 알지 못하고 사용하면 오히려 역효과를 줄 수 있기 때문에 신경을 써야한다. 왜냐하면 this는 앞선 이유 때문이고, 메모리 부분의 경우 내부함수는 그것을 감싼 메소드가 호출될때마다 새롭게 생성되기 때문에 참조를 누적해서 사용하면 메모리누수를 일으킬 수 있다.(실제로 method()를 두번이상 호출해보고 hnRun의 메모리값을 디버깅을 통해 보면 번지수가 다르다는 것을 볼 수 있다.) 이러한 특징과 문제가 되는 사항을 잘 알면 오히려 실보다 득이 많다. 왜냐하면 내부함수를 사용하면 전체코드의 가독성을 향상시켜주는 좋은 도구이기 때문이다. 

위 ActionScript 3.0 코드를 아래처럼 조금 변경해보자. 

class MyClass {
	function MyClass() {
	}
	public  function method():void {
		trace(this);  //[object MyClass]
		hnRun(this);  //[object global]:[object MyClass]
		var a:Object;
		a = {method1:hnRun};
		a.method1(this);  //[object Object]:[object MyClass]		
		function hnRun($object:*):void {
			trace(this +":"+$object); 
		}		
	}
}

a.method1(this)를 실행하면 이번에는 this가 아닌 Object가 된다. 즉 a를 가리킨다. Object내에 정의된 속성, 메소드는 traits 캐쉬에 등록되고 그 안에서 사용된 this는 traits를 가리키도록 강제되기 때문이다. 순수 prototype 기반이라면 당연히 global이 나와야 한다. 

this에 대해서 헷갈릴 수 있는 부분 한가지는 a.method( function():void{trace(this);}); 처럼 사용할 때이다. 이때 this는 a객체에 정의된 method()에서 실행되므로 this는 a가 된다. 

이처럼 동적언어인 ActionScript 3.0에서 this에 대해서 잘 이해하고 써야한다. 안그러면 내부함수를 사용하게 되는우 원하는 클래스의 인스턴스를 참조하는게 아닌 엉뚱한 global을 참고할 수 있게 되고 이런건 컴파일 타임에 에러를 발생하지 않고 왜 그런지 이유 조차 모를 수밖에 없기 때문이다.

이런 개념을 이해하고 개발하는 것은 생산성을 극대화 시켜준다는데는 경험상 확인되었다.





지난행사

행사추천
"Flex로 구현하는 엔터프라이즈 RIA 맥잡기"는 ACC(Adobe Community Champion) 엔터프라이즈팀에서 자발적으로 하는 행사입니다. 엔터프라이즈 기반 RIA 솔루션을 구축하는 기술에 대해서 실제 현업에서 종사하시는 분들의 기술들을 습득할 수 있는 아주 좋은 기회라고 생각합니다. 참가비용 5000원은 행사비용이 아닌 불우이웃을 돕기위한 위한 모금이며 최소한의 참가의지를 알기 위한 정도로 이해해주시면 되겠습니다. 단돈 5000원에 이 정도 급의 좋은 내용을 들을 수 있다는 것은 행운이라고 생각합니다. 관심있으신 분은 주저말고 참가해보세요. 




오랜만에 오픈캐스트를 올렸습니다. 즉흥적으로 만든거지만 유용하리라고 생각합니다.

좋은 정보가 되길 바랍니다.

http://opencast.naver.com/FL188/18

글쓴이 : 지돌스타(http://blog.jidolstar.com/694)

Adobe 본사 대회의실에서 Powerfl 2차모임을 가졌습니다. 첫번째 킥오프모임보다 다소 적은 인원이 참석했으나 분위기는 그때보다 더 좋았습니다.

1. Powerfl 구조 소개 - 맹기완(히카), 자료는 이후 공개 
2. 통합로더 소개 - 원종운(야매코더)
   - http://scripter.egloos.com/2500161
3. 외부라이브러리(pv3d, jiglibflash)를 이용한 개발  - 지용호(지돌스타)
   -  http://blog.jidolstar.com/691

4. 자기소개

위 4가지를 진행하면서 가장 좋았던 시간은 바로 4. 자기소개였다는... 전에 모임은 너무 많이 오셔서 이름과 얼굴이 매치가 되지 않았는데 소규모로 모이니 이제가 매치되었습니다. 또한 현업에서 어려움과 기술적 노하우를 함께 공유하는 시간을 자기소개 시간에 가지니깐 분위기가 업되더군요. 단순히 발표자가 발표하고 끝나버리는 기존의 모임과는 사뭇달라 좋았습니다.

파워플은 BSD 라이센스를 기반으로 SVN으로 Flex, ActionScript 3.0 라이브러리 및 애플리케이션을 공유하고 발전시키는 환경을 제공합니다. 파워플의 전체적인 구조를 기완님께서 소개해주셨고 기술적인 검증도 모두 마친 상태라고 합니다. 파워플은 약 15개의 프로젝트로 구성되며 한개한개 여러분의 참여를 필요로 하고 있습니다. 앞으로 지속적인 관심 부탁하고요. 

원종운님이 개인적으로 만들고 사용하고 계신 통합로더를 소개하는 자리가 있었습니다. 거창한 것은 아니였습니다만 그것을 통해서 다양한 이야기가 오고가고 또한 로더를 더 발전시킬 수 있는 아이디어를 공유할 수 있었습니다. 파워플에 올라오는 라이브러리나 애플리케이션들은 처음부터 거창할 수 없습니다. 문제가 있더라도 그 문제 있는 자체부터 시작해 함께 공유하며 완성되어 가는 겁니다. 사실 제 블로그도 그렇게 시작한겁니다. 

마지막으로 제가 얼마전에 소개한 축구게임(http://blog.jidolstar.com/691)을 어떤 기술을 이용해 만들었는지 설명하는 자리를 마련했습니다. 현업에서 있지만 실제로 공개라이브러리를 활용하지 못하고 있다는 것에 문제가 있다고 봅니다. 가령 3D나 물리엔진을 만든다고 생각해보세요. 답이 안나옵니다. 그리고 이런 것들을 몰라서 못하시는 분들도 계십니다. 파워플이 추구하는 바는 파워플을 통해 다양한 유용한 라이브러리들이 공개가 되면서 정말 좋은 애플리케이션들이 만들어지고 공개되어 지는 것입니다. 그래서 한국 Flash 업계에 새바람이 일어났으면 합니다.

파워플 모임은 최소한 1개월에 한번씩 진행할 것입니다. 주중에 잡아야 하는 점은 양해 부탁합니다. 

또한 Adobe 회의실을 빌려주시고 작은 선물도 챙겨주신 어도비 이수하 과장님께 감사의 말씀을 드립니다.
발표중이신 맹기완(히카)님 

상단 좌측부터 구준호 님, 오권우(소주) 님, 전은정(바나나킥) 님, 김주호 님, 박준우(June) 님, 윤도선(드럼캡) 님, 전재열 님, 김어진이(니케) 님(뒷모습 ^^;) 

우측부터 정진우(꼬깨비) 님, 임대찬(싸**임) 님, 박동준님  

좌측부터 정진우(꼬깨비)님, 이상훈 님, 사광호(쿠로) 님

발표하신 원종운(야매코더) 님

모임 끝나고 간단하게 음료수 한잔.... ^^;; 
정말 좋은 이야기 많이 했습니다.
이날.. 정진우님이 한턱 쏘셨다는.... 감사합니다. ^^

파워플 카페 : http://cafe.naver.com/powerfl
파워플 블로그 : http://blog.powerfl.com/
파워플 공식페이지 : http://powerfl.com (예정) 

글쓴이 : 지돌스타(http://blog.jidolstar.com/693
이번에 남아공 월드컵이 있어 스타플(http://starpl.com) 에서 관련 이벤트를 하고 있습니다.


여기서는 개발자분이라면 다들 궁금해하시는 기술적(?) 설명을 드릴까 합니다.

축구게임은 Flash로 만들었습니다. Flash로 게임제작은 처음해보는 것이라 생각보다 여러 어려움이 있었습니다.
  • 제작툴 : Flash Builder 4.0 Eclipse plug-in 버전
  • 언어 : ActionScript 3.0 (Flex는 사용안함)
  • 동작환경 : Flash Player 10 이상
  • 사용한 공개라이브러리 : Papervision3d(3d엔진), jiglibflash(3D 물리엔진), TweenMax(트윈엔진)
  • 제작기간 : 10일
  • 키워드 : 축구게임엔진, 물리엔진, 3D, 게임상태변화, 사운드, 랭킹시스템, MVC

축구라는 소재를 어떻게 풀어가야할까 고민을 많이 했습니다. 그러다가 3D와 물리엔진을 도입할 필요를 느꼈고 간단하게 Away3d+jiglibflash 또는 Papervision3d+jiglibflash 로 테스트를 해봤습니다. 관련 내용은 이미 http://blog.jidolstar.com/689 에 공개했었고요.

하지만 게임이 만만치 않은게... 저 정도로 끝나지 않는다는 겁니다. 자원관리, 속도향상, 게임상태변화, 사운드등 여러가지 다양한 문제가 있더군요. 하나하나 해결해 가면서 완성해 나갔습니다. 가장 어려웠던 부분은 사운드였습니다. 디자인 부분이야 우리 회사에 막강한 캐릭터 디자이너가 있어서 문제가 없었는데... 사운드는 그런 사람이 전혀 없다보니 자원을 찾고 편집하는데 시간을 너무 소비했죠. 물론 무료를 찾느라고 시간을 다 보낸겁니다. 역시 개발자가 디자인, 음원편집을 할 수 없듯이 그에 적절한 사람을 알아두는 것도 좋을 것 같습니다.

전체적으로는 정말 간단한 게임입니다. 너무 단순해서 10일이나 걸려서 만든게 시간이 아깝다는 생각이 드네요. 다음에 만들면 더 속도를 낼 수 있겠죠? ^^

아래는 스크린 샷입니다. 구경하시죠.




하는 방법은 매우 단순합니다. 공을 찰 지점을 마우스로 선택하고 마우스 Down으로 힘조절을 한뒤 마우스 Up하면 공이 차지는 방식입니다. 공 차는 지점을 정확히 잡고 힘조절을 잘할 필요가 있습니다.

환경적 요소로 골대, 골키퍼, 수비수, 바람등이 있습니다. 레벨이 올라갈때마다 수비수가 늘어나며, 바람도 더 세집니다. 1골당 점수도 높아지죠. 골위치도 랜덤하게 되므로 높은 점수 얻는건 쉽지 않을겁니다. 스타플(http://starpl.com/)에 연계되어 최고점수를 기록하면 자신의 별에 기록되는 기능도 포함합니다.

이 게임은 2D 처럼 보이지만 그건 배경만 그렇고 실제 움직임은 전부 3D입니다. 골대도 안보이지만 3D 골대가 배치 되어 있죠. ^^ 만약 Papervision3D와 jiglibflash와 같은 공개라이브러리를 사용하지 않았다면 10일이라는 짧은 시간안에 이런 게임 만드는 것은 거의 불가능입니다. 나름 고급기술 사용했다는... ㅋ

하지만 Flash에서 3D 엔진과 물리엔진은 속도에 지극히 악영향을 줍니다. 그래서 적절한 합의점을 찾아가야 합니다. 완성본에는 공의 그림자도 모두 표현되어 있었지만 저급 PC에서 FPS가 나오지 않아서 제외시킨게 그 예이죠. 아쉽다는.... 그리고 Papervision3D 보다 Away3dlite를 이용했으면 더 좋았을 뻔 했습니다. 아무래도 Flash Player 10에서 3D API를 제공하기 때문에 이를 이용해서 만들어진 Away3dlite가 퍼포먼스 측면에서는 더 이득일 겁니다. 하지만 표현력에 있어서는 Papervision3D가 더 우세라는...

3D 물리엔진으로 jiglibflash를 사용했습니다. wow엔진이라는 것이 있는데... 이는 망한듯 합니다. 더 이상 업데이트도 안되고요. 그나마 jiglibflash가 튜토리얼도 있고 어느정도 예시도 있어서 사용했죠. jiglibflash는 papervision3d, away3d, away3dlite, sandy3d등 다양한 3D 엔진에 대응하여 물리엔진을 도입할 수 있도록 반쪽짜리 추상층을 제공합니다. 아무튼 이것을 이용하면 별 무리없이 물리엔진과 같은 효과를 만날 수 있죠. 하지만 FPS에 따라서 다르게 결과가 나오는 부분이 있어서 그다지 실용적이지는 못합니다. 말이 물리엔진이지... 그냥 눈속임이라는... 어쨌든 그나마 있어서 유용하게 썼습니다.

이 게임은 아쉽게도 1회성 이벤트 용입니다.

축구게임 이벤트 : http://starpl.com/#/main/event/big/ing/view/48

이벤트용이므로 게임을 즐기시려면 스타플(http://starpl.com)에 가입하셔야 합니다. ^^

글쓴이 : 지돌스타(http://blog.jidolstar.com/691)
직장을 옮기면서 Flash에 관심을 가진지도 3년이 넘었다. 당시만해도 PHP정도만 경험이 있었고 수년동안 웹과는 단절된 상태였기 때문에 Ajax로 만들어진 구글맵을 보고 "이런걸 만들 수 있구나..."라는 말을 중얼거리며 꽤 놀랐던 적이 있었다. Web 2.0, RIA라는 단어가 당시 트렌드였고 이 모든 것이 나에게는 매우 신선했고 가능성을 느끼게 하는데 충분했다. 

하지만 3년전 RIA 대표주자인 Flex 2를 처음 접했을때 국내 관련서적은 거의 없었다. Flash IDE를 기반으로 하는 책은 수도 없이 많았지만 개발요구사항을 만족시키기에는 매우 역부족이였고 Flex도 옥상훈님이 쓴 서적 딱 한권 뿐이였다. 사실 모두 기초서적들이라 초반학습에는 도움되었지만 실무에 적용하기에는 너무 부족했다. 또한 기술적으로 도움을 받을 수 있는 인맥도 전혀 없었다.

결국 내가 선택한 방법은 이 블로그이다. 말이 되던 안되던 Flex 하나를 중구장창 파고 들면서 얻은 귀중한 지식을 블로그에 하나씩 써내려갔다. 초반에는 livedocs가 어디 있는지도 몰랐고 구글 검색도 처음 해봤던 시절이라 너무 힘들었다. 블로그의 내용은 관련 카페에 소개하면서 계속 점접을 만들어갔다. 이러한 노력은 몇개월이 지나지 않아 바로 사람들에게 좋은 반응으로 이어졌고 지식공유가 일어났다. 또한 이쯤에 flexdocs.kr 사이트도 운영하게 되었다. 그때 생각하면 너무 어렵게 공부했던 기억뿐이다.

3년이 지난 지금... 사정은 너무도 많이 달라졌다. 3년전 한창 붐을 일으키던 RIA라는 단어는 이미 일상이 되버렸고 모바일이 이슈가 되면서 관련 서적도 쏟아지기 시작했다. Flash Platform에 관련된 서적도 엄청 많이 쏟아졌고 오프라인 서점만 찾아가면 듣도보지도 못한 관련서적을 꽤 많이 발견할 수 있었다.

아무튼 이젠 Flash 공부하는데 천국이 된 것임에 틀림없다.

나는 최근에 국내에 출판된 좋은 서적 4권을 소개하고자 한다.


okgosu의 액션스크립트 정석



okgosu의 액션스크립트 정석은 Flex 서적 2편으로 유명하신 옥상훈님의 책이다. 이 책은 ActionScript 3.0의 전반적인 내용을 다루고 있다. 기초문법부터 시작해 개발환경 및 디스플에이 객체를 다루는 방법을 꽤 상세하게 다루고 있다. 또한 Flash Player 10에 추가된 3D API의 기초를 다루고 있기 때문에 그동안 3D에 대해서 잘 몰랐던 사람이 공부하기에 딱 좋은 서적인 것 같다. 책의 내용이 방대하고 수많은 예시가 있어 ActionScript 3.0을 처음하는 사람에게 꽤 도움이 될 것이다.


플래시 ON 디바이스



이철규님이 번역하신 플래시 ON 디바이스는 Flash Platform을 이용해 모바일 애플리케이션을 개발하고자 하는 사람들을 위한 책이다. 사실 모바일 분야는 Flash Lite에 한정되어 있어서 필자처럼 Flex/ActionScript 3.0 기반으로만 했던 사람들에게는 넘어가기 힘든 장벽이였다. 하지만 이책은 Flash 모바일의 역사를 자세하게 소개하고 꽤 고급주제 심도있게 다루고 있어서 Flash를 이용한 모바일 개발에 관심을 가지는 사람에게 추천해줄 수 있는 책이다.  


비주얼 플렉스 UX 디자인

열이아빠님으로 활동중인 이준하님이 번역하신 비주얼 플렉스 UX 디자인은 Flex 3의 시각 컴포넌트를 사용하면서 생길 수 있는 다양한 어려움을 극복하는데 큰 도움을 준다. 레이아웃, 효과, 스타일, 스킨등을 전반적으로 다루면서 좀더 심도있게 컴포넌트를 적용할 수 있는 방법을 잘 설명해주고 있다. Flex를 이용해 풍부한 UX를 사용자들에게 경험시키고자 할때 이책은 큰 도움이 될 것이다.


ActionScript 3.0 Animation


윤도선님이 번역하신 시작하세요! 액션스크립트 3.0 애니메이션은 내가 베타 리더로도 참여했던 책이다. Flash IDE 타임라인을 이용해 애니메이션을 적용하던 수준을 넘어서 코드 기반으로 수학,물리를 이용한 다양한 에니메이션을 주는 기법에 대해서 아주 쉽게 설명하고 있다. 삼각함수, 속도, 가속도, 마찰, 바운드, 모션공식 일반적으로 수학/물리에 어려움을 느끼는 개발자들에게 꽤 유용하다. 또한 3D API를 이용하는 예도 담겨 있어 더욱 다양한 경험을 준다.


정리하며
최근에 발간된 총 4권의 책을 소개했다. 앞서 설명했듯이 3년전에 이런 고귀한 책들이 있었다면 아마도 지금까지 공부했던 시간의 절반이상은 절약할 수 있었을 것이다. 아무튼 좋은 책은 돈에 구애받지 말고 구입해서 자신의 것으로 만들어 가려는 노력이 빠른 배움의 지름길이다.

글쓴이 : 지돌스타(http://blog.jidolstar.com/690)

한창 축구게임을 만들고 있다. 이에 관련되어 테스트 해본 코드를 공유해본다.

3D를 표현하기 위해 Papervision3d와 물리엔진을 표현하기 위해 jiglibflash를 이용했다. 공을 클릭하면 임의의 힘이 주어지고 클릭한 부분을 참조하여 방향이 결정된다. 키보드 A,S,D,W로 카메라 조정이 된다.

원더플(wonderfl.net)에 소스를 올려두었으니 부디 많은 Fork가 이뤄져서 더 좋은 모습으로 탈바꿈 되는 모습을 보고 싶다. ^^

http://wonderfl.net/c/j8ly

package {
	import flash.display.Bitmap;
	import flash.display.BitmapData;
	import flash.display.BlendMode;
	import flash.display.Graphics;
	import flash.display.Sprite;
	import flash.display.StageAlign;
	import flash.display.StageQuality;
	import flash.display.StageScaleMode;
	import flash.events.Event;
	import flash.events.KeyboardEvent;
	import flash.events.MouseEvent;
	import flash.filters.BlurFilter;
	import flash.geom.Vector3D;
	import flash.system.IME;
	import flash.system.IMEConversionMode;
	import flash.ui.Keyboard;
	import flash.ui.Mouse;
	import flash.ui.MouseCursor;
	import flash.utils.getTimer;
	
	import jiglib.cof.JConfig;
	import jiglib.geometry.JBox;
	import jiglib.geometry.JCapsule;
	import jiglib.geometry.JPlane;
	import jiglib.geometry.JSphere;
	import jiglib.geometry.JTerrain;
	import jiglib.math.JMatrix3D;
	import jiglib.physics.RigidBody;
	import jiglib.plugin.papervision3d.Papervision3DPhysics;
	import jiglib.plugin.papervision3d.Pv3dMesh;
	import jiglib.plugin.papervision3d.pv3dTerrain;
	
	import net.hires.debug.Stats;
	
	import org.papervision3d.Papervision3D;
	import org.papervision3d.core.utils.InteractiveSceneManager;
	import org.papervision3d.core.utils.Mouse3D;
	import org.papervision3d.events.FileLoadEvent;
	import org.papervision3d.events.InteractiveScene3DEvent;
	import org.papervision3d.lights.PointLight3D;
	import org.papervision3d.materials.BitmapMaterial;
	import org.papervision3d.materials.ColorMaterial;
	import org.papervision3d.materials.MovieAssetMaterial;
	import org.papervision3d.materials.MovieMaterial;
	import org.papervision3d.materials.WireframeMaterial;
	import org.papervision3d.materials.shadematerials.FlatShadeMaterial;
	import org.papervision3d.materials.shaders.FlatShader;
	import org.papervision3d.materials.shaders.GouraudShader;
	import org.papervision3d.materials.shaders.LightShader;
	import org.papervision3d.materials.shaders.PhongShader;
	import org.papervision3d.materials.shaders.ShadedMaterial;
	import org.papervision3d.materials.special.FogMaterial;
	import org.papervision3d.materials.utils.MaterialsList;
	import org.papervision3d.objects.DisplayObject3D;
	import org.papervision3d.objects.parsers.Collada;
	import org.papervision3d.objects.parsers.DAE;
	import org.papervision3d.objects.primitives.Cube;
	import org.papervision3d.objects.primitives.Cylinder;
	import org.papervision3d.objects.primitives.Plane;
	import org.papervision3d.view.BasicView;
	import org.papervision3d.view.layer.ViewportLayer;
	import org.papervision3d.view.layer.util.ViewportLayerSortMode;
	
	[SWF(width="465", height="465", backgroundColor="#000000", frameRate="24")]
	/**
	 * Papervision3D + jiglibflash 을 이용한 축구게임 프로토 타입 
	 * 간단하게 만들어 봤습니다. Fork를 통해 향상되고 멋진 기능을 가진 게임이 탄생하기 바랍니다. 
	 * 1. click ball
	 * 2. press key w,a,s,d
	 * 
	 * author : Yongho Ji, jidolstar@gmail.com
	 */ 
	public class SoccerGamePrototype extends BasicView {
		private var _physics:Papervision3DPhysics;
		private var _light:PointLight3D;
		private var _vpBallLayer:ViewportLayer;
		
		private var _ground:Plane;
		private var _ball:RigidBody;
		private var _ballRadius:Number = 30;
		
		private var _isShooting:Boolean = false;
		private var _isIniting:Boolean = false;
		private var _firstTime:Number;
		private var _ballX:Number;
		private var _windForce:Vector3D;
				
		private var _keyForward:Boolean=false;
		private var _keyBackward:Boolean=false;
		private var _keyLeft:Boolean=false;
		private var _keyRight:Boolean=false;
		
		/**
		 * 생성자 
		 */ 
		public function SoccerGamePrototype() {
			super(465, 465, true, true, "Target");
			configStage();
			configScene();
			initPhysics();
			createBall();
			createGround();
			createGoalNet();
			createGoalPost();
			createBox();
			startRendering();
			addEventHandler();
			addChild(new Stats);
		}
		
		/**
		 * 스테이지 환경 설정 
		 */ 
		private function configStage():void {
			stage.scaleMode=StageScaleMode.NO_SCALE;
			stage.align=StageAlign.TOP_LEFT;
			stage.quality=StageQuality.BEST;
			stage.frameRate = 24;
			stage.showDefaultContextMenu = false;
		}
		
		/**
		 * 이벤트 핸들러 함수 등록 
		 */ 
		private function addEventHandler():void {
			stage.addEventListener(KeyboardEvent.KEY_DOWN, keyDownHandler);
			stage.addEventListener(KeyboardEvent.KEY_UP, keyUpHandler);
		}
		
		/**
		 * 3D 환경 기본 설정 
		 */ 
		private function configScene():void {
			viewport.containerSprite.sortMode=ViewportLayerSortMode.INDEX_SORT;
			viewport.containerSprite.addLayer(_vpBallLayer=new ViewportLayer(viewport, null));
			_vpBallLayer.sortMode=ViewportLayerSortMode.Z_SORT;	

			camera.x=-500;
			camera.y=200;
			camera.z=0;
			
			_light=new PointLight3D(true, false);
			_light.x=-400;
			_light.y=300;
			_light.z=-100;
		}
		
		/**
		 * 물리엔진 기본 설정 
		 */ 
		private function initPhysics():void {
			_physics=new Papervision3DPhysics(scene, 10);
		}
		
		/**
		 * 바닥 생성  
		 */ 
		private function createGround():void {
			_ground = new Plane( new WireframeMaterial(0xff0000), 1000, 1000, 10, 10 );
			scene.addChild(_ground);
			var jGround:JPlane = new JPlane(new Pv3dMesh(_ground));
			jGround.setOrientation(JMatrix3D.getRotationMatrixAxis(90));
			_physics.addBody(jGround);
			jGround.y = 0;
			jGround.friction = 10;
		}
		
		/**
		 * 그물망 생성
		 */ 
		private function createGoalNet():void {
			var i:Number, dx:Number = 15, dy:Number = 15, dd:Number = 15;
			var w:Number = 600, h:Number = 300, d:Number = 100;
			var movie : Sprite = new Sprite();
			var g:Graphics = movie.graphics;
			var jGoalNet:RigidBody;
			var material:MovieMaterial,materials:MaterialsList;
			
			//Back
			g.lineStyle(3,0xaaaaaa,2);
			for( i = 0; i <= w; i+=dx ) {
				g.moveTo(i,0);
				g.lineTo(i,h);
			}
			for( i = 0; i <= h; i+=dy ) {
				g.moveTo(0,i);
				g.lineTo(w,i);
			}
			material = new MovieMaterial(movie,true,false,false,null);
			material.oneSide = false;
			materials=new MaterialsList();
			materials.addMaterial(material, "back");
			jGoalNet = _physics.createCube( materials, w, 100, h, 5, 1, 5, 0, 0 );
			jGoalNet.movable = false;
			jGoalNet.rotationY = 90;
			jGoalNet.material.restitution = 0.01;
			jGoalNet.moveTo(new Vector3D(600+50,150,0,0));
			
			//Top
			movie = new Sprite;
			g = movie.graphics;
			g.lineStyle(3,0xaaaaaa,2);
			for( i = 0; i <= w; i+=dx ) {
				g.moveTo(i,0);
				g.lineTo(i,d);
			}
			for( i = 0; i <= d; i+=dd ) {
				g.moveTo(0,i);
				g.lineTo(w,i);
			}			
			jGoalNet = _physics.createCube( materials, w, 1, d, 3, 1, 1, 0, 0 );
			jGoalNet.movable = false;
			jGoalNet.rotationY = 90;
			jGoalNet.rotationZ = 90;
			jGoalNet.material.restitution = 0.01;
			jGoalNet.moveTo(new Vector3D(550,300,0,0));
			
			//Left
			movie = new Sprite;
			g = movie.graphics;
			g.lineStyle(3,0xaaaaaa,2);
			for( i = 0; i <= d; i+=dd ) {
				g.moveTo(i,0);
				g.lineTo(i,h);
			}
			for( i = 0; i <= h; i+=dy ) {
				g.moveTo(0,i);
				g.lineTo(d,i);
			}			
			jGoalNet = _physics.createCube( materials, d, 1, h, 1, 1, 1, 0, 0 );
			jGoalNet.movable = false;
			jGoalNet.material.restitution = 0.01;
			jGoalNet.rotationY = -180;
			jGoalNet.moveTo(new Vector3D(550,h/2,-w/2,0));
			
			//Right
			jGoalNet = _physics.createCube( materials, d, 1, h, 1, 1, 1, 0, 0 );
			jGoalNet.movable = false;
			jGoalNet.material.restitution = 0.01;
			jGoalNet.moveTo(new Vector3D(550,h/2,w/2,0));
			
		}
		
		/**
		 * 볼 포스트 생성 - Cylinder 3개로 생성 
		 */ 
		private function createGoalPost():void {
			function createCapsule($radius:Number,$height:Number):JCapsule {
				var material:FlatShadeMaterial = new FlatShadeMaterial(_light, 0xFFFFFF, 0x555555, 5);
				var cylinder:Cylinder = new Cylinder(material,$radius,$height,15,1,-1,true,false);
				var jCapsule:JCapsule = new JCapsule(new Pv3dMesh(cylinder),$radius,$height);
				jCapsule.movable = false;
				jCapsule.material.restitution = 0.9;
				scene.addChild(cylinder);
				_physics.addBody( jCapsule );
				return jCapsule;
			}	
			
			var jCapsule:JCapsule;
			jCapsule = createCapsule(10,300);
			jCapsule.moveTo(new Vector3D(500,150,-300,0));
			jCapsule = createCapsule(10,300);
			jCapsule.moveTo(new Vector3D(500,150,300,0));
			jCapsule = createCapsule(10,610);
			jCapsule.rotationX = 90;
			jCapsule.moveTo(new Vector3D(500,300-5,0,0));
		}
		
		/**
		 * 4개의 Box들 생성 
		 */ 
		private function createBox():void {
			var materials:MaterialsList=new MaterialsList();
			materials.addMaterial(new FlatShadeMaterial(_light, 0x00FF00, 0x555555, 5), "all");
			var box:RigidBody;
			for (var i:int=1; i < 5; i++) {
				box=_physics.createCube(materials, 100, 100, 100, 3, 3, 3);
				box.mass=1;
				box.x = 300;
				box.y=300 * i;
				_vpBallLayer.addDisplayObject3D(_physics.getMesh(box));
			}
		}
		
		/**
		 * 축구공 생성 
		 */ 
		private function createBall():void {
			var material:FlatShadeMaterial = new FlatShadeMaterial(_light, 0xFFFFFF, 0x555555, 105);
			_ball=_physics.createSphere(material, _ballRadius, 10, 8);
			material.interactive = true;
			_vpBallLayer.addDisplayObject3D(_physics.getMesh(_ball));
			_ball.x=-300;
			_ball.y=300;
			_ball.mass=1;
			_ball.friction=10;
			
			var mesh:DisplayObject3D = _physics.getMesh(_ball);
			mesh.addEventListener(InteractiveScene3DEvent.OBJECT_PRESS, press);
			
			//공을 누른다.
			function press($e:InteractiveScene3DEvent=null):void {
				mesh.removeEventListener(InteractiveScene3DEvent.OBJECT_PRESS, press );
				mesh.addEventListener(InteractiveScene3DEvent.OBJECT_RELEASE, release );
			}
			//공을 찬다!
			function release($e:InteractiveScene3DEvent=null):void {
				mesh.addEventListener(InteractiveScene3DEvent.OBJECT_PRESS, press );
				mesh.removeEventListener(InteractiveScene3DEvent.OBJECT_RELEASE, release );				
				shootBall();
			}			
		}
		
		/**
		 * 축구공을 찬다.
		 */ 
		private function shootBall():void {
			_isShooting = true;
			_ballX = _ball.x;
			_firstTime = getTimer();
			
			var mesh:DisplayObject3D = Pv3dMesh(_ball.skin).mesh;
			mesh.calculateScreenCoords(camera); //공의 스크린 좌표 계산. 3D->2D
			var kickPower:Number = Math.random()*500+200;
			var kickPosZTheta:Number = Math.tan( (stage.mouseX-mesh.screen.x-viewport.width/2)/_ballRadius )
			var kickPosYTheta:Number = Math.tan( (stage.mouseY-mesh.screen.y-viewport.height/2)/_ballRadius );
			var kickPowerX:Number = kickPower * Math.cos(kickPosYTheta)*Math.cos(kickPosZTheta);
			var kickPowerY:Number = kickPower * Math.sin(kickPosYTheta);
			var kickPowerZ:Number = kickPower * Math.cos(kickPosYTheta)*Math.sin(kickPosZTheta);
			_ball.addWorldForce(new Vector3D(kickPowerX,kickPowerY,kickPowerZ),_ball.currentState.position);
			_ball.addBodyTorque(new Vector3D(0, 0, 0));
			
			_windForce = new Vector3D( 0, 0, Math.random()*10-5);
		}
				
		/**
		 * 3D 렌더링, 물리엔진 가동, 키보드에 따른 카메라 이동, 축구공 액션 
		 */ 
		protected override function onRenderTick(event:Event=null):void {
			_physics.step(); 
			if( _isShooting ) {
				_ball.addWorldForce(_windForce,_ball.currentState.position);
				if( _ball.x > 500 || _ball.x < _ballX || getTimer() - _firstTime > 2000 ) {
					_isShooting = false;
					_isIniting = true;
					_firstTime = getTimer();
				}
				_ballX = _ball.x;
			}
			if( _isIniting ) {
				if( getTimer() - _firstTime > 2000 ) {
					_isIniting = false;
					_ball.x=-300;
					_ball.y=400;
					_ball.z = 0;
					_ball.setVelocity( new Vector3D(0,0,0) );
				}
			}
			moveCamera();
			super.onRenderTick(event);
		}
		
		/**
		 * 카메라 이동 
		 */ 	
		private function moveCamera():void {
			var dist:Number = 30;
			if (_keyForward) {
				camera.moveForward(dist);
			}
			if (_keyBackward) {
				camera.moveBackward(dist);
			}
			if (_keyLeft) {
				camera.moveLeft(dist);
			}
			if (_keyRight) {
				camera.moveRight(dist);
			}
		}
		
		/**
		 * 키보드 Down 핸들러 
		 */ 
		private function keyDownHandler(e:KeyboardEvent):void {
			if (e.keyCode == 229) {
				IME.conversionMode=IMEConversionMode.ALPHANUMERIC_HALF;
			}
			switch (e.keyCode) {
				case 87:
					_keyForward=true;
					break;
				case 83:
					_keyBackward=true;
					break;
				case 65:
					_keyLeft=true;
					break;
				case 68:
					_keyRight=true;
					break;
				
			}
		}
	
		/**
		 * 키보드 Up 핸들러 
		 */ 
		private function keyUpHandler(e:KeyboardEvent):void {
			switch (e.keyCode) {
				case 87:
					_keyForward=false;
					break;
				case 83:
					_keyBackward=false;
					break;
				case 65:
					_keyLeft=false;
					break;
				case 68:
					_keyRight=false;
					break;
			}
		}

	}
}




글쓴이 : 지돌스타(http://blog.jidolstar.com/689)
이 글은 JPetStore라는 간단한 쇼핑몰 웹애플리케이션을 Eclipse에서 테스트하기 위한 방법을 소개한다. JPetStore는 원래  MS에서 소개된 샘플이나 나중에 java진영에 컨버팅되면서 더 유명해진듯 하다. 그러므로 JPetStore는 java만을 위한 것이 아님을 알고 접근하는 것이 좋겠다.

Spring Framework에 번들로 제공하는 JPetStore샘플은 Spring을 학습하는데 많이 유용하다. Spring 서적을 통해 학습한 내용을 실습한다는 차원에서 접근하면 좋겠다. Spring framework 2.5iBatis로 구성된 예제를 참고했다.

Eclipse 갈릴레오(JEE), JDK 1.6.0.18, Tomcat 6.0 , MySQL 5.0, Window 7 환경에서 테스트했음을 밝힌다. 즉, 이들 환경을 구축하는 방법은 모두 생략한다.


1. Spring 2.5 버전을 다운로드
현재 Spring 3.0 버전이 배포되고 있지만 JPetStore의 경우 2.5버전에만 있다. 다운로드 받을때는 2.5 최상위 버전을 다운로드 받되 뒤에 dependencies가 붙은 압축파일을 받도록 하자. 이 압축파일은 spring 2.5에 의존하는 lib나 예제들이 수록되어 있다. JPetStore 샘플도 여기에 있다.



다운로드 받으러 가기 : http://www.springsource.com/download/community

위 주소로 접속하면 첫화면에서 개인정보를 넣으라고 할것이다. 그냥 무시하고 download page 링크를 찾아 클릭하길 바란다. 다운로드를 완료하면 아무데나 압축을 풀면 되겠다.


2. 배포파일(war) 만들기
다운로드를 받아 samples/jpetstore 로 들어가면 아래와 같은 폴더 및 파일을 볼 수 있다.

원래는 ANT를 설치한 상태에서 ANT 스크립트를 담은 build.xml 만을 이용해 내부에 있는 명령인 "clean", "build", "warfile" 명령들을 이용해 최종배포파일인 war파일을 만들 수 있다. 하지만 그렇게 하면 많은 부분 불편하므로 spring 배포자가 이와 더불어 warfile.bat와 build.bat를 추가해서 ANT 환경을 구축해야하는 번거로움을 없애줬다. warfile.bat만 더블클릭으로 실행하면 자동으로 war를 만들어준다.

warfile.bat 구조은 다음과 같다.
build.bat warfile

이것은 위 build.bat를 실행하고 "warfile" ANT 스크립트 명령을 실행하는 것을 의미한다.

build.bat 내용은 다음과 같다.
"%JAVA_HOME%/bin/java" -cp ../../lib/ant/ant.jar;../../lib/ant/ant-launcher.jar;"%JAVA_HOME%/lib/tools.jar" org.apache.tools.ant.Main %1

build.bat는 JDK 설치경로에 ANT관련 jar를 복사해서 ANT를 실행할 수 있도록 해주는 것이 주 목표인것이다. 마지막에 org.apache.tools.ant.Main %1은 1개의 parameter를 받아 실행하겠다는 의미이다. 결국 %1이 warfile.bat의 "warfile" 문자열로 대체되어 build.xml의 warfile명령을 실행하도록 하는 것을 의미한다.

이제 warfile.bat만 두번 클릭해서 실행하면 된다. 혹시 제대로 실행이 되지 않을지도 모른다. 이것은 JDK 경로 설정이 잘못되었기 때문인데 build.bat에 자신의 JDK 경로를 아래와 같이 추가해보고 warfile.bat를 재실행해보길 바란다.

set JAVA_HOME=C:\Program Files\Java\jdk1.6.0_18
"%JAVA_HOME%/bin/java" -cp ../../lib/ant/ant.jar;../../lib/ant/ant-launcher.jar;"%JAVA_HOME%/lib/tools.jar" org.apache.tools.ant.Main %

이제 %JAVA_HOME% 변수를 제대로 설정했기 때문에 이제 war파일을 제대로 만들 수 있게 된다.

만약 모두 지우고 war를 새로 만들고 싶다면 warfile.bat에서 warfile을 all로 수정하자.
build.bat all

이 모든과정을 이해하기 위해 build.xml에 있는 ANT 스크립트를 한번 훑어보는 것을 추천한다.

모든 과정을 마쳤다면 다음과 같이 .classes, dist 폴더가 추가되었을 것이다.


dist 폴더로 들어가면 jpetstore.war가 있다. 또한 war/WEB-INF/lib가 생성되어 그안에 필요한 jar가 복사된 것도 확인하자.


3. Eclipse에서 프로젝트 만들기

2가지 방법중 하나만 사용하면 되겠다. 먼저 Eclipse를 실행하기 바란다.


3.1. Web Project를 직접 만들기
File > New > New Project로 들어가 아래처럼 Dynamic Web Project를 선택한뒤 Next 버튼을 누른다.



Project name으로 jpetstore를 입력하고 Target runtime은 Tomcat 6.0을 선택하면 되겠다. Finish 버튼을 누른다.

2번 항목에서 samples/jpetstore 폴더로 가서 src와 war 내에 있는 파일을 각각 복사해 Eclipse내에 생성된 src와 WebContent 폴더에 파일을 복사한다.



3.2 war파일을 이용해서 프로젝트 만들기

이것은 3.1 방법외에 또 다른 방법이다.

2번 항목에서 samples/jpetstore/dist 폴더가 생성되었고 그 안에 jpetstore.war 를 생성했었다. 이것을 이용해 프로젝트를 생성한다. 이클립스에서 File > Import 를 들어가 아래처럼 WAR file을 선택한다.


아래 그림처럼 WAR file 경로(samples/jpetstore/dist)를 찾아 jpetstore.war를 선택하고 web project 이름과 target runtime을 설정한다. Finish 버튼을 누른다.


이렇게 프로젝트를 생성하면 src에는 아무것도 없다. 이 상태에서 실행해도 되지만 실제로 우리는 이클립스라는 개발툴을 활용하고 있으므로 java소스도 가져와야겠다. 그러므로 samples/jpetstore/src 에 있는 내용을 프로젝트의 src에 복사하도록 하자. Project Explorer에는 다음처럼 보여야 하겠다.



4. 실행해보기
3번 항목으로부터 만들어진 프로젝트는 이제 테스트해볼 수 있는 준비가 마련되었다.

이클립스에서 Window > Show View > Other 를 선택하면 아래와 같은 창이 나오고 거기서 Server를 선택한다.


이클립스 View 창에 아래처럼 Servers가 추가되어 있는 것을 확인할 수 있을 것이다. 테스트할 서버를 추가하자. 먼저 빈공간에 오른쪽 버튼을 눌러 New>Server를 선택한다.

아래와 같은 창이 나오면 Tomcat v6.0 Server를 선택한다.(필자는 Tomcat 6.0이 이미 설치되어 있으므로...) Next를 누른다.


다음과 같은 창이 나오면 우리가 만든 jpetstore 프로젝트가 왼쪽에 나오는데 이것을 선택한뒤 Add> 버튼을 눌러 Configured 부분에 추가한다. 이로써 우리 프로젝트는 Tomcat 서버위에서 하나의 웹애플리케이션으로 동작할 수 있게 된다. Finish 버튼을 누른다.


만들어진 서버에 jpetstore가 올라가 있음을 아래처럼 Servers View에서 확인할 수 있다. 이제 아래 그림처럼 실행버튼을 눌러 서버를 실행한다. 단, Tomcat 서버가 이미 실행되고 있으면 중지시킨뒤 해야하겠다.


참고로 필자는 서버 설정시 Ports를 80으로 지정한뒤 실행했다.



http://localhost/jpetstore 를 웹브라우저 주소창에 넣으면 아래와 같은 화면을 볼 수 있겠다. 만약 Ports가 8080이면 http://localhost:8080/jpetstore로 접속하자. 내용을 보면 이 jpetstore 애플리케이션은 Spring과 iBatis로 만들어졌음을 보여주고 있다. Enter the Store 버튼을 누르면 실제 Store로 입장할 수 있게 된다.


5. 데이타베이스(MySQL)과 연동
아직 설정이 완료된 것은 아니다. 데이타 베이스 설정이 남아 있기 때문이다. JPetStore의 이곳저곳 들어가보면 MySQL 접속문제로 에러가 발생하는 것을 알 수 있을 것이다.

Spring Framework는 데이터베이스와의 연동을 위한 ORM(Object-Relation Mapping)을 지원하여 iBatis, Hibernate, JPA와 같은 영속성 프레임워크와 연동을 지원한다. 본 JPetStore는 그중에 iBatis를 이용하게 된다. 근간으로는 모두 JDBC와 같은 추상층을 사용하기 때문에 DB종류에 상관없이 간단한 프로퍼티 설정만으로 DB접속이 가능하며 JPetStore 예제는 hsqldb, mysql, oracle, postgres로 테스트 할 수 있도록 하고 있다.

필자는 많은 DB중에 MySQL 환경이 이미 구축되어 있으므로 이것을 사용하도록 하겠다. 다른 DB도 거의 비슷하므로 따로 설명은 필요 없겠다.


5.1 JDBC MySQL 드라이버 설치

MySQL 데이타베이스에 접근하기 위해 JDBC 드라이버를 다운로드 받아 프로젝트내에 포함시켜야 한다. 먼저 아래 링크에서 다운로드 받자.

http://dev.mysql.com/downloads/connector/j/

압축을 푼 뒤, mysql_connector_java 로 시작하는 jar 파일이 존재할 것이다. 이것은 프로젝트 내에 WebContent/WEB-INF/lib에 복사한다. 여기서 lib는 JPetStore를 동작시키기 위한 프레임워크 및 라이브러리 jar가 포함되어 있다. Spring, iBatis도 있는 것을 확인하자.



5.2 MySQL 계정정보 설정
프로젝트내 WebContent/WEB-INF/jdbc.properties 는 JDBC 접속정보를 설정할 수 있도록 되어 있다.

jdbc.driverClassName=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/jpetstore
jdbc.username=jidolstar
jdbc.password=mypassword

위와 같은 설정은 5.1 항목에서 추가한 mysql driver에서 제공하는 MySQL 드라이버 클래스 사용한다는 것을 지정하고 있다. 또한 url, username, password를 설정하고 있다. 3306는 데이타 베이스 접속하기 위한 기본 포트번호 이며 그 뒤에 붙은 jpetstore는 접속할 DB 명이다. 그러므로 이들 정보는 필요에 따라서 수정해서 사용하면 되겠다.

5.3 MySQL DB를 만들고 테이블 생성
다운로드 받은 Spring Framework 폴더에 samples/jpetstore/db에 들어가면 총 4가지 자동 테이블 생성 코드가 있다. 여기서 mysql 폴더에 sql문이 존재한다.


먼저 MySQL에 "jpetstore" 이름을 가진 DB를 먼저 만든다.  jpetstore-mysql-schema.sql 을 열어보면 use jpetstore 부분이 맨 처음에 있다. 만약 DB 이름이 다른 이름을 가진다면 jpetstore 대신 다른 이름을 넣으면 되겠다. 그런 다음 위 두개의 sql 파일을 이용해 콘솔창에서 다음과 같이 해당 DB에 테이블과 데이터를 넣을 수 있다.
> mysql -uroot -p < jpetstore-mysql-schema.sql
Enter password: ***********
> mysql -uroot -p < jpetstore-mysql-dataload.sql
Enter password: ***********

물론 MySQL을 위한 클라이언트 애플리케이션(SQLGate나 SQLyog등)을 사용해도 되겠다.

이제 JPetStore는 모든 페이지에서 제대로 동작하게 될 것이다. 이를 이용해 Spring과 iBatis에 대해 공부하면 되겠다.

참고글

글쓴이 : 지돌스타(http://blog.jidolstar.com/688)

AOP(Aspect Oriented Programming, 관점 지향 프로그래밍)는 OOP(Object Oriented Programming)의 한계를 극복하는데 매우 좋은 개념이다.

[출처]Toby's Epril : http://toby.epril.com


위 그림은 AOP 예를 간단히 보여주고 있다. 계좌이체, 입출금, 이자계산을 담당하는 Class는 핵심모듈로 구분된다. 보안, 로깅, 트렌젝션과 같은 것은 핵심을 보조하는 횡단모듈로 구분된다. OOP만으로 하다보면 핵심안에 횡단이 섞이는 현상이 발생하기 쉽다. 가령, 로깅기능이 계좌,입출금,이자계산에 모두 섞여서 나중에 유지보수 및 확장성이 매우 힘들게 된다. 그 예로, 핵심기능에 문제가 발생했을때 관리자에게 문자로 메시지를 보내는 기능도 포함한다고 하자. 그럼 핵심기능에 문자라는 횡단기능을 추가해서 더 복잡해지는 상황이 발생한다. AOP는 이러한 문제를 깔끔하게 해결해준다. 그러니깐 핵심모듈 Class내에 횡단모듈 Class가 섞이지 않도록 하는 것이 AOP의 핵심이다라고 할 수 있다. 일종의 Class 단일책임의 원칙을 실현할 수 있다.

Java 진영에서 엔터프라이즈급 웹애플리케이션을 제작하면서 AOP 개념을 사용하지 않고서는 확장/유지보수이 매우 어려울지 모른다. Spring framework는 AOP를 지원함으로서 보다 좋은 확장성과 유지보수의 유용성을 보장한다. 또한 다른 클래스에 대한 의존성 없는 클래스 제작이 좋아져 보다 완벽한 단위테스트가 가능해지는 장점도 있다.

.net 계열에서는 Spring.NET에서 AOP를 지원해준다.

하지만 AOP가 장점만 있지는 않을 것이다. 필연적으로 Proxy개념을 도입해 기존 클래스를 감싸서 다른 클래스와의 의존성을 더해주는 형태이므로 속도적인 측면에서 이득은 별로 없다.
AOP는 컴파일, 클래스 로딩, 런타임 3가지 형태로 적용(이 과정을 Weaving이라 함)하는 방법이 있다. 자바 Spring AOP는 Proxy개념을 도입하기 때문에 런타임시 기존 클래스를 감싸서 다른 클래스와의 의존성을 더해주는 형태이므로 속도적인 측면에서는 이득이 별로 없을 수 있지만 장점이 더 많기 때문에 잘 사용된다.

AOP에 대한 개념을 알기 위해 다음 글들이 매우 좋은 것 같다. 참고 바란다.


Java 진영에서 Spring Framework로 AOP에 관심을 가지면 다음 링크 정도는 알고 있어야 하지 않을까 생각한다.


다음글은 Spring 을 통한 AOP를 개발하기 위한 참고 글들이다. 좋은 참고가 될 것이다.


간단한 테스트를 하려면 Eclipse에 AJDT, SpringIDE를 설치하고 아래 소스로 감각을 익힐 수 있지 않을까 생각한다. 소스는 원래 http://whiteship.me/1173 의 소스를 약간 변경한것이다. Spring Framework 3.0을 다운받아 사용했다.


Eclipse에서 Java Project를 만들고 그 프로젝트에 Import 시켜서 테스트 하면 되겠다. 단, SpringAOPAspectJ는 반드시 AJDT 플러그인이 설치되어 있어야 컴파일/실행이 가능하겠다.

AOP는 웹애플리케이션에서는 큰 이득이 있겠으나 일반 애플리케이션에서는 속도 및 용량 측면에서 이득보다 실이 더 많을 수 있을지 모르겠다는 판단이 든다. 더 공부해 봐야겠다.

글쓴이 : 지돌스타(http://blog.jidolstar.com/687)


flexdocs.kr은 2007년 이래로 지금까지 벌써 4년째 운영되고 있습니다. 조광제님 및 많은 분들의 도움으로 지금까지 명맥을 이어가고 있습니다. 비록 Flex 2.0 문서이지만 아마도 한글문서가 절실하셨던 분들은 이 사이트가 상당히 유용했으리라 생각합니다.

서비스는 현재 무료호스팅을 받아 운영되기 때문에 별도의 자본이 필요하지 않으나 도메인은 항상 비용이 들어갑니다. 지금까지는 호스팅, 도메인 모두 제 자비를 들여서 운영해 왔습니다. 이런 중에 몇일전 아래와 같은 메일을 받았습니다.

사실 도메인 하나만 볼때 비싼 것은 아니지만 제가 관리하고 있는 도메인만 해도 수십개 됩니다. 이런 도메인들을 전부 제 개인비용으로 부담하는 것은 문제가 있다고 봅니다. 게다가... 전 가정이 있어서~ ^^;

그래서 제안하는 바, 오늘 부로 flexdocs.kr에 들어가는 모든 비용은 기부 형태로 운영되었으면 합니다. 다음 조건에서 제안하는 바입니다.

1. 기부액은 자유롭게 합니다. (하지만 너무 많이 기부하면 부담됩니다. ^^)
2. 모든 기부자는 flexdocs.kr에 날짜, 이름, 액수를 공개합니다.(필요하다면 가칭을 사용해드립니다.)
3. 기부금 사용한 내역은 flexdocs.kr에 투명하게 공개합니다.
4. 사이트는 개인적 이득 및 상업적으로 운영되지 않습니다.
5. 사이트 운영 및 사이트 발전을 위한 활동외에 다른 목적으로 기부금을 사용하지 않습니다.
6. 운영은 지돌스타(http://blog.jidolstar.com)가 맡으나 다른 분에게 양도 될 수 있습니다.
7. 만약 피치 못한 사정으로 사이트를 닫는 경우, 기부금이 남는다면 여타 다른 좋은 활동 단체에 기부금이 전달될 수 있습니다. 이때 제 블로그를 통해 공지합니다.

기부는 다음 계좌로 하시면 됩니다.

신한은행 753-02-426937 지용호

입금자는 본인으로 하시고 입금하신후 확인을 위해 아래 번호로 문자를 주시거나 메일을 보내주세요.
010-2018-영삼이일
jidolstar@gmail.com


flexdocs.kr가 정상적으로 운영될 수 있도록 노력하겠습니다. 감사합니다.

지돌스타(http://blog.jidolstar.com)




파워플 프로젝트 공식 카페를 오픈했습니다.

http://cafe.naver.com/powerfl

프로젝트 진행에 적절한 커뮤니케이션의 필요로 오픈하게 되었습니다.

현재 http://powerfl.net, http://powerfl.com, http://powerfl.co.kr 로도 접속이 가능합니다. 이 도메인은 공식페이 주소이지만 아직 없는 관계로 카페로 포워딩 처리 했습니다.

가입은 자유이며 일정수준의 활동률을 보이면 자동으로 등업처리 됩니다. 단, 참여멤버, 개발멤버는 실제 프로젝트에 참여하는 사람에 한해 운영자가 지정하며, 솔루션 제공자에게는 감사멤버로 지정할 계획입니다.


Mac에서도 Flash Player가 H.264 비디오 디코딩 하드웨어 가속을 지원한다. 공개된 Flash Player는 코드네임이 "Gala"로 명칭되며 현재 Flash Player 다운로드 페이지에서 받을 수 있도록 되어 있다. 정식버전은 아니다.

현재 공개된 Flash Player에서는 Mac OS X 10.6.3 이상의 OS에 NVIDA GeForce 9400M, GeForce 320M, GeForce GT 330M등의 GPU 환경을 지원한다. 

Mac에서 H.264 비디오 코덱은 이미 Flash Player에서 지원하고 있었으나 하드웨어 가속은 아니였다. 그렇게 됨에 따라 많은 CPU 자원을 소비할 수 밖에 없었다. 이미 Flash Player 10.1 베타가 출시되면서 윈도우 PC나 x86기반 넷북에 설치된 Flash Player을 H.264 하드웨어 가속을 지원했다. 이제 "Gala"로 Mac 사용자도 Flash Player에서 H.264 비디오 디코딩시 하드웨어 가속을 된 것이다. 

하드웨어 가속을 지원한다는 것은 CPU를 통해 비디오를 디코딩하지 않고 GPU로 하기 때문에 훨씬 더 쾌적한 H.264 비디오를 감상할 수 있다는 것을 의미한다. 

이는 Open Screen Project의 일환으로 가능한 것이라 판단한다. GPU 가속을 하기 위해 그래픽 카드 제작회사인 Nvida와 Adobe가 이 프로젝트를 통해 Flash Player에 대한 하드웨어 가속을 지원받을 수 있게 된 것이다.
이는 Open Screen Project와는 전혀 무관한 일이였군요. 지금껏 Apple이 자신의 OS API를 열어주지 않아서였습니다. 결국 Adobe는 완전 피해자가 된 셈이네요. 역시 Apple이 플랫폼을 개방안한것을 Adobe에 덤탱이 씌운거라는...

앞으로 Open Screen Project를 통해 더욱 다양한 기기에서 이런 협력이 많이 일어날 것으로 보이며 Flash Player는 더욱 Cross Device 세계로 나갈 것으로 기대한다.

Flash Player "Gala" Preview Release : http://labs.adobe.com/technologies/flashplayer10/gala/
Adobe Labs - Flash Player 10.1 : http://labs.adobe.com/technologies/flashplayer10/

 
글쓴이 : 지돌스타(http://blog.jidolstar.com/683


Adobe 한국지사 대회의실에서 파워플 킥오프 모임을 무사히 마쳤습니다. 30명정도의 분들이 모여서 함께 정보를 공유하고 의견을 조율해보는 귀한 시간이였습니다.

앞으로 Powerfl은 이번 모임을 시작으로 1개월에 한번씩 컨퍼런스를 지속적으로 개최할 방침을 세우고 있습니다. Powerfl 개발과 더불어 다양한 루트로 파트너쉽을 맺어 실제 오픈시에는 괜찮은 Flex/ActionScript/AIR 솔루션을 공유하고 Flash를 개발 플랫폼으로 바라보는 시각을 가질 수 있는 환경을 만들고 더불어 한국의 IT업계에 큰 힘을 실어줄 수 있는 모델이 되길 바랍니다.

다시 한번 관심을 가져주신 분들께 감사드립니다.

또한 회의실을 빌려주시고 행사를 원할하게 할 수 있도록 도와주신 Adobe 이수하 과장님과 이 모임을 위해 지원해주신 기묘의 조규형 대표님께 감사의 말씀을 드립니다.


공지사항
1. 킥오프때 찍은 사진 및 동영상이 있다면 메일(jidolstar@gmail.com)으로 부탁드립니다.
2. 작성한 설문은 정리해서 되도록 효율적인 인력배치를 위한 귀중한 자료로 사용할 것입니다.
3. 참여신청은 했으나 오시지 못하신 분들은 따로 메일 드리겠습니다.

모임사진


PPT



글쓴이 : 지돌스타(http://blog.jidolstar.com)
C로 이미지,구조체등 바이너리 데이터를 MySQL에 담는 방법을 알아보자.  또한 C로 저장한 바이너리 데이터를 Flash에서 엑세스하는 법도 다루겠다.

이 글을 이해하려면 기본적으로 C와 ActionScript 3.0, Flex 4에 대해 알아야 한다. 그리고 예제에서 테스트한 C코드는 전부 이클립스+CDT+MinGW 환경에서 작업한 것들이다. 이런 환경을 구축해 본적이 없다면 다음 글을 참고한다.

  • Eclipse Galileo에서 C,C++ 개발환경 구축하기 - CDT, MinGW 
  • Eclipse, MinGW, CDT 환경에서 C/C++과 MySQL 연동



  • 1. Binary 타입 데이터를 DB에 저장하는 것에 대해
    바이너리(Binary) 타입의 데이터를 DB에 저장한다는 것은 가령, 이미지나 사운드와 같은 데이터를 DB에 저장한다는 의미로 해석하면 편하다.

    Binary파일을 DB에 넣을때 어떤 형태로 넣을 수 있을까? 

    한가지 예는 익히 잘 알고 있는 Base64로 치환해서 text형태로 넣는 방식을 생각해볼 수 있다. Base64는 ASCII코드로 화면에 출력할 수 있는 64개 문자(ABCD...89+/e등)을 이용해 Binary 데이터를 Text로 표현해준다. 하지만 이 방식의 단점은 데이타를 64진수로 표현한 문자열이기 때문에 용량이 필연적으로 커지고 또 encode/decode를 해야하는 부담이 존재한다. base 64에 대해서 알고 싶다면 다음 링크를 보자.

    Tistory에서 블로그 데이터 백업파일은 XML이다. 이 XML에 이미지와 같은 바이너리 파일은 담을 수 없으므로 Base64로 변형해 담게된다. 단지 대체하는 문자의 순서가 표준 방식인 ABCD...89+/e 순이 아니라 Vezh...식인 점이 다를 뿐이다. 참고로 아래 링크에 우야꼬님이 Flash로 컨버팅 해놓은 것이 있다.


    티스토리에서 백업기능을 왜 XML로 했을까? XML은 화면에 출력해볼 수 있는 언어이다. 화면에 출력할 수 없는 Binary보다는 훨씬 사용하기 쉽고 공유하기 쉽다.

    Base64는 아스키 시대의 유물이다. 요즘에는 pc에서 모바일까지 UTF8을 지원해주므로 이제 Base64에 의존하지 않고 Base256과 같은 것을 만들 수 있다. 이는 꽤 유용하다. 아래 hika님의 블로그를 보면 ActionScript 3.0으로 이와 관련된 글이 있다. 참고 바란다.


    하지만 Binary를 text로 decode/encode하는 과정이 반복되는 환경에 Base64와 같은 것을 적용하면 오히려 부담이 된다. 그러므로 필요하다면 Binary는 직접 Binary 자체를 저장하는 것이 좋겠다. 

    많은 DB에 BLOB(Binary Large OBject) 타입을 지원한다. 이 타입을 이용하면 DB에 어떤 형태의 Binary 데이타든지 넣을 수 있게 된다. 이 타입은 Text와 거의 동일하며 Binary 데이터를 넣을 수 있는 것만 다르다.

    아래는 인터넷에서 찾은 BLOB에 대해 설명이다.
    컴퓨터에서, BLOB[블랍]은 대체로 이미지나 사운드 파일과 같은 하나의 커다란 파일을 말하며, 그 크기 때문에 특별한 방법으로 다루어져야한다. 에릭 레이몬드에 따르면, BLOB에 관한 주요 아이디어는 DBMS와 같은 파일 처리기가 BLOB을 어떻게 처리할 것인가를 해결하기 위해 파일을 이해할 방법이 없다는 것이다. 다른 자료들에서는, 이 용어가 커다란 데이터 객체를 지칭하기 위해, 그리고 이러한 것들을 다룰 때 존재하는 문제점들을 암시하기 위해 만들어졌다고 강조한다. BLOB을 다루는 애플리케이션은 영화나 TV 프로그램 등과 같은 대형 멀티미디어 객체들의 데이터베이스 저장이다. BLOB은 이미지, 비디오, 사운드 등과 같은 멀티미디어 객체들을 저장하기 위해 주로 사용된다. 그러나 모든 DBMS가 BLOB을 지원하는 것은 아니다.

    컴퓨터 그래픽에서, blob(소문자로 쓴 BLOB)은 "재미있는 모양 형태"를 갖는 시각적 객체로서, 유연하고, 애니메이션을 따른다.

    일반적으로, blob은 무정형이며, 정의를 내리기 힘든 객체이다. UFO 연구에서는 물론, 천문학에도 blob이라는 것이 있다. "The Blob"이라는 제목의 과학 공상영화도 있었다.


    2. 이미지를 DB에 저장하기
    사실 Binary타입의 일종인 이미지(jpg,png등)를 DB에 저장하는 예제는 정말 많다(php에서 만큼은...). 그래서 굳이 여기에 예제를 적을 필요가 있겠냐만은.. 요즘 C를 이용해 다양한 시도를 하는 만큼, 이 부분도 적어본다.

    필자는 앞서 Eclipse기반에서 C/C++ 개발을 위한 환경만들기에 관련된 글을 적었다.

    아래에 소개할 예제들은 모두 위 글에서 언급한 환경에서 작성되었음을 밝힌다.

    간단히 이미지를 저장할 수 있는 테이블을 만들어보자. DB는 MySQL을 사용한다.
    mysql> describe images;
    +-------+------------+------+-----+---------+-------+
    | Field | Type       | Null | Key | Default | Extra |
    +-------+------------+------+-----+---------+-------+
    | id    | int(11)    | NO   | PRI |         |       |
    | data  | mediumblob | YES  |     | NULL    |       |
    +-------+------------+------+-----+---------+-------+
    2 rows in set (0.00 sec)
    


    위와 같은 구조의 테이블을 만들것이다. 다음 Create문으로 위 테이블을 생성한다.

    CREATE TABLE `images` (
      `id` int(11) NOT NULL auto_increment,
      `data` mediumblob,
      PRIMARY KEY  (`id`)
    ) ENGINE=MyISAM DEFAULT CHARSET=utf8

    테이블을 만들때 data field가 mediumblob라는 것에 주목하자. 참고로 MySQL에서 BLOB타입은 Tiny BLOB(255, 2^8-1 bytes), BLOB(65,535=2^16-1 bytes), Medium BLOB(16777215=2^16-1 bytes), Long BLOB(4294967295=2^32-1 bytes)등을 지원한다.

    다음은 임의 크기의 이미지를 불러와 제작한 테이블에 저장하는 C코드이다. (공개된 PHP코드는 많은데 이외로 C코드는 많이 없다.)
    #define SOCKET int
    
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <mysql/mysql.h>
    
    #define DB_HOST "localhost or ip or domain"
    #define DB_USER "DB 접속 ID"
    #define DB_PASS "DB 접속 password"
    #define DB_NAME "데이타베이스 이름"
    
    int main(void) {
    	MYSQL *connection, conn;
    	FILE *fp;
    	unsigned long len, file_size;
    	int query_stat;
    	char *buf , *buf_to, *query, *stat = "INSERT INTO images(data) VALUES('%s')";
    
    	//DB 초기화 및 접속
    	mysql_init(&conn);
    	connection = mysql_real_connect(&conn, DB_HOST, DB_USER, DB_PASS, DB_NAME, 3306, (char*)NULL, 0);
    	if(connection==NULL) {
    		fprintf(stderr, "Mysql connection error : %s", mysql_error(&conn));
    		return EXIT_FAILURE;
    	} else {
    		printf("Mysql connected\n");
    	}
    
    	//이미지를 불러오기
    	fp = fopen("d:/image.png", "rb");
    	if(fp==NULL) {
    		fprintf(stderr, "image open error");
    		mysql_close(connection);
    		return EXIT_FAILURE;
    	}
    	rewind(fp);
    	fseek(fp,0,SEEK_END);
    	file_size = ftell(fp); //파일의 사이즈를 찾음
    	fseek(fp,0,SEEK_SET);
    	buf = (char*)malloc(file_size);
    	fread(buf, sizeof(char), file_size, fp); //파일을 읽어와 buf에 참고
    	fclose(fp);
    
    	//이미지내 escape string 적용
    	buf_to = (char*)malloc(file_size*2+1);
    	mysql_real_escape_string(connection, buf_to, buf, file_size); 
    
    	//Query 문 작성
    	query = (char*)malloc(strlen(stat)+file_size*2+1);
    	len = snprintf(query, strlen(stat)+file_size*2+1, stat, buf_to); 
    
    	//Query 문 실행
    	query_stat = mysql_real_query(connection, query, len); 
    	if(query_stat != 0) {
    		fprintf(stderr, "Mysql query error : %s\n", mysql_error(&conn));
    	}
    
    	//할당한 메모리 반환
    	free(buf);
    	free(buf_to);
    	free(query);
    
    	//DB 커넥션 닫기
    	mysql_close(connection);
    	return EXIT_SUCCESS;
    }
    

    위 코드는 d:/image.png 를 로드해 DB 테이블에 insert 하는 과정을 보여주고 있다. 실제로 테스트가 완료되었고 잘 동작한다.

    mysql_real_escape_string() 함수는 바이너리 데이터를 insert하기 위해 SQL insert 문을 작성하는데 있어서 '\0'과 같은 문자가 들어가는 것을 방지해준다. SQL insert문은 텍스트 데이터로 작성되어야 하므로 '\0'과 같은 문자가 중간에 삽입되어 있으면 그것이 문자열의 종료를 의미하기 때문에 이 함수의 호출은 필연적이다.

    mysql_real_escape_string() 함수의 2번째 인자는 to, 3번째 인자는 from이다. 즉 from에서 '\0'과 같은 문자를 피하도록(escape) "\\0"등으로 치환해줘 그것이 to에서 참조되도록 하는 것이다. 이때 to의 사이즈는 from의 2배+1 이어야 한다.  buf_to = (char*)malloc(file_size*2+1); 문에서도 볼 수 있듯이 buf는 file_size의 크기를 가지는데 buf_to는 그의 2배+1로 메모리를 할당했다. 그 이유는 최악의 상황에서 escape처리할 문자가 from데이터의 2배가 될 수 있기 때문이다. 그리고 마지막 +1은 문자열의 마지막은 항상 '\0' 처리가 되어야 하므로 그 공간을 만들어주는 것을 의미한다.

    mysql_real_escape_string()에 대해서는 다음 링크를 참고한다.


    Query문을 작성하는데 있어서 snprintf() 함수를 이용했다. 이 함수는 printf나 sprintf와 용법이 비슷하게 문자열 치환 용도로 사용하는데 이 함수는 특별히 buffer 길이에 맞게 null문자까지 포함해 안전하게 복사해주는 기능을 가진다. 다음글을 참고하길 바란다.


    mysql_real_query() 함수는 바이너리 데이터를 포함한 Query를 수행하기 위해 사용한다. mysql_real_query에 대해서는 다음 링크를 참고한다.


    이제 저장한 이미지를 불려들어 saved-image.png로 파일에 저장해보자.


    #define SOCKET int
    
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <mysql/mysql.h>
    
    #define DB_HOST "localhost or ip or domain"
    #define DB_USER "DB 접속 ID"
    #define DB_PASS "DB 접속 password"
    #define DB_NAME "데이타베이스 이름"
    
    int main(void) {
    	MYSQL *connection, conn;
    	MYSQL_RES *result;
    	MYSQL_ROW row;
    	FILE *fp;
    	int query_stat;
    	unsigned long *lengths;
    
    	//DB 초기화 및 접속
    	mysql_init(&conn);
    	connection = mysql_real_connect(&conn, DB_HOST, DB_USER, DB_PASS, DB_NAME, 3306, (char*)NULL, 0);
    	if(connection==NULL) {
    		fprintf(stderr, "Mysql connection error : %s", mysql_error(&conn));
    		return EXIT_FAILURE;
    	} else {
    		printf("Mysql connected\n");
    	}
    
    	//Query문 수행
    	query_stat = mysql_query(connection,"SELECT data FROM images WHERE id=1");
    	if(query_stat != 0) {
    		fprintf(stderr, "Mysql query error : %s\n", mysql_error(&conn));
    		return EXIT_FAILURE;
    	}
    
    	//Query문 수행한 결과를 통해 데이터 참조
    	result = mysql_store_result(connection);
    	if(result == NULL) {
    		fprintf(stderr, "result is null\n");
    		return EXIT_FAILURE;
    	}
    	row = mysql_fetch_row(result);
    	lengths = mysql_fetch_lengths(result);
    	mysql_free_result(result);
    	printf("file lengths : %ld\n", lengths[0]);
    
    	//이미지 저장
    	fp = fopen("d:/savedImage.png", "wb");
    	if(fp==NULL) {
    		fprintf(stderr, "image save error");
    		mysql_close(connection);
    		return EXIT_FAILURE;
    	}
    	fwrite(row[0], lengths[0], 1, fp);
    	fclose(fp);
    
    	//DB 커넥션 닫기
    	mysql_close(connection);
    	return EXIT_SUCCESS;
    }
    
    코드를 보면 이미지를 insert하는 것보다 select하는게 오히려 쉽다. 실행후 마지막에 d:/savedImage.png가 있다면 성공한 것이다.


    3. 구조체를 DB에 저장하기
    대학시절 C언어의 구조체를 파일로 저장해본 경험이 있는가? 또 저장한 구조체를 다시 읽어 화면에 출력해본 적이 있는가? 이런 짓을 해봤다면 당신은 이미 DB에 구조체도 넣을 수 있다. 이 구조체도 일반 이미지와 같은 Binary파일이기 때문이다.

    이 방법은 일반 이미지를 DB에 넣어본 경험이 있는 분이라면 쉽게 이해할 수 있다.


    3.1 테스트 구조체 만들기

    저장할 구조체를 만들어보자. 개인정보를 담는 아주 간단한 구조체인 Personal을 만든다.

    #include <stdio.h>
    #include <stdlib.h>
    
    //#pragma pack(1) //struct member alignment를 1로 설정, 기본 설정이 8이라면 이것으로 20->19로 줄인다.
    typedef struct st_person {
    	unsigned __int16 user_id; //id, 2bytes 정수
    	char user_name[15]; //이름, 15bytes 캐릭터형
    	unsigned __int16 age; //나이, 2bytes 정수형
    
    }PERSON;
    //#pragma pack() //struct member alignment 원상복구
    
    int main(void) {
    	printf("struct PERSON size : %d\n",sizeof(PERSON));
    	return EXIT_SUCCESS;
    }
    


    분석할 것도 없다. 총 19bytes의 데이터를 담는 구조체를 만들었고 main함수에서 이 구조체의 크기를 출력한다.

    하지만 이 프로그램을 실행하면 실제 구조체 크기인 19bytes가 아닌 20bytes로 출력된다. 왜 그럴까 찾아보다가 발견한 것이 struct member alignment라는 용어였고 이것을 설정하는 코드중에 #pragma pack()이 있다는 것이다.

    위 코드에서 #pragma pack() 2부분 주석처리를 해제하고 다시 실행하면 19byte로 나온다. 19가 20로 된다는 것은 운영체제 별, 기본설정별로 전부 다르기 때문에 자세한 설명은 관련 내용을 찾아보길 바란다.

    필자는 DB에 저장시에 하나의 Field에 여러명의 Personal 정보를 넣는것을 목적으로 하기 때문에 1~2bytes 차이가 저장되는 사람의 숫자만큼 증가되는 문제가 생길 수 있다. 이런 경우에 #pragma pack()을 적절히 사용하면 남는 공간을 줄일 수 있다.

    #pragma pack는 온라인 게임이나 네트워크 프로그램에서 이기종간 통신 간 패킷단위를 struct로 정의해 통신하는 경우에 유용하다. 그리고 사용시 주의 사항도 있으므로 이에 대해서는 더욱 학습이 필요하다.


    3.2 구조체를 담을 DB 테이블 제작

    위 구조체를 담을 DB 테이블은 다음 쿼리로 만들면 되겠다.


    CREATE TABLE `persons` (
      `AREA` int(11) NOT NULL auto_increment,
      `COUNT` smallint(5) unsigned default NULL,
      `DATA` blob NOT NULL,
      PRIMARY KEY  (`AREA`)
    ) ENGINE=MyISAM DEFAULT CHARSET=utf8
    


    area가 key값이고 area별로 사람들의 데이터가 data에 담긴다. 이 data는 여러명의 데이터가 담겨질 것이다. 아마도 이게 유용한거냐?라는 질문을 한다면 이것은 한가지 예일 뿐이고 필자는 실제로 유용한 방법이였다. ^^


    3.3 구조체 포인터 배열을 만들어 DB에 저장하고 읽어오기
    구조체를 DB 테이블에 저장하는 것은 이미지를 저장할때와 별반 다른 것이 없다. 위에서 제시한 구조체로 이루어진 데이터의 경우가 특별할 것 같지만 실제로는 똑같다. 구조체를 만들때 user_id, user_name, age 순으로 만들었기 때문에 각각 2+15+2 = 19바이트가 1개의 개인정보 바이너리 데이터가 된다. 만약 2명이면 19x2=38 바이트의 바이너리 데이터가 되는 것이다. 이 모든 것을 수행하는 간단한 코드는 다음과 같다.


    #define SOCKET int
    
    #include <stdio.h>
    #include <stdlib.h>
    #include <memory.h>
    #include <string.h>
    #include <mysql/mysql.h>
    
    #define DB_HOST "localhost or ip or domain"
    #define DB_USER "DB 접속 ID"
    #define DB_PASS "DB 접속 password"
    #define DB_NAME "데이타베이스 이름"
    
    #define TABLE_NAME "persons"
    #define SQL_DROP_TABLE "DROP TABLE IF EXISTS %s"
    #define SQL_CREATE_TABLE "CREATE TABLE `%s` (\
    `AREA` int NOT NULL AUTO_INCREMENT,\
    `COUNT` smallint unsigned,\
    `DATA` BLOB NOT NULL,\
    PRIMARY KEY(`AREA`)\
    ) ENGINE=MyISAM DEFAULT CHARSET=utf8"
    #define SQL_INSERT_DATA "INSERT INTO %s(DATA,COUNT) values('%s',%d)"
    #define SQL_SELECT_DATA "SELECT `DATA`, `COUNT` FROM %s WHERE `AREA`=%d"
    
    #pragma pack(1) //struct member alignment를 1로 설정, 기본 설정이 8이라면 이것으로 20->19로 줄인다.
    typedef struct st_person {
    	unsigned __int16 user_id; //id, 2bytes 정수
    	char user_name[15]; //이름, 15bytes 캐릭터형
    	unsigned __int16 age; //나이, 2bytes 정수형
    
    }PERSON;
    #pragma pack() //struct member alignment 원상복구
    
    int connect();
    int close();
    int create_table();
    int drop_table();
    PERSON* add_data(PERSON *person_list, int index, unsigned __int16 user_id, unsigned __int16 age, char* user_name);
    int remove_all_data(PERSON* person_list);
    int insert_data(PERSON* person_list, int count);
    int select_data(int area);
    
    MYSQL *connection, conn;
    
    int main(void) {
    	printf("PERSON struct size : %d\n",sizeof(PERSON));
    	connect();
    	drop_table();
    	create_table();
    
    	PERSON *person_list;
    	person_list = add_data(NULL,0,1,30,"jidolstar");
    	person_list = add_data(person_list,1,2,22,"joykim");
    	person_list = add_data(person_list,2,3,2,"tae hyun");
    	insert_data(person_list,3);
    	free(person_list);
    	select_data(1);
    
    	person_list = add_data(NULL,0,4,30,"addlayer");
    	person_list = add_data(person_list,1,5,22,"eye");
    	person_list = add_data(person_list,2,6,2,"unclejoe");
    	person_list = add_data(person_list,3,7,3,"miracle");
    	insert_data(person_list,4);
    	free(person_list);
    	select_data(2);
    
    
    	close();
    	return EXIT_SUCCESS;
    }
    
    /**
     * DB 접속
     */
    int connect() {
    	// mysql 초기화
    	mysql_init(&conn);
    
    	// DB 연결
    	connection = mysql_real_connect(&conn, DB_HOST, DB_USER, DB_PASS, DB_NAME, 3306, (char*)NULL, 0);
    	if(connection==NULL) {
    		fprintf(stderr, "Mysql connection error : %s", mysql_error(&conn));
    		exit(0);
    	} else {
    		printf("Mysql connected\n");
    	}
    	return 0;
    }
    /**
     * 데이타 베이스 닫음
     */
    int close() {
    	mysql_close(connection);
    	printf("mysql closed\n");
    	return 1;
    }
    
    /**
     * 테이블 생성
     */
    int create_table() {
    	int query_stat;
    	char buff[1024];
    
    	memset(buff,0x00,sizeof(buff));
    	sprintf( buff, SQL_CREATE_TABLE, TABLE_NAME );
    	query_stat = mysql_query(connection,buff);
    	if( query_stat != 0 ) {
    		fprintf(stderr, "Mysql query error : %s\n", mysql_error(&conn));
    		exit(0);
    	} else {
    		printf("created table\n");
    	}
    	return 0;
    }
    
    /**
     * 기존 테이블 삭제
     */
    int drop_table() {
    	int query_stat;
    	char buff[1024];
    
    	//기존 테이블 삭제
    	memset(buff,0x00,sizeof(buff));
    	sprintf( buff, SQL_DROP_TABLE, TABLE_NAME );
    	query_stat = mysql_query(connection,buff);
    	if( query_stat != 0 ) {
    		fprintf(stderr, "Mysql query error : %s\n", mysql_error(&conn));
    		exit(0);
    	} else {
    		printf("table dropped\n");
    	}
    	return 0;
    }
    
    /**
     * 데이터 추가
     */
    PERSON* add_data(PERSON *person_list, int index, unsigned __int16 user_id, unsigned __int16 age, char* user_name) {
    	PERSON *current;
    	if(person_list == NULL ) {
    		person_list = (PERSON*)malloc(sizeof(PERSON));
    		current = person_list;
    	} else {
    		person_list = (PERSON*)realloc(person_list, sizeof(PERSON)*(index+1));
    		current = person_list + index;
    	}
    	current->user_id = user_id;
    	current->age = age;
    	memset(current->user_name,0x00,sizeof(current->user_name));
    	strcpy(current->user_name,user_name);
    	//printf("added user_id:%d, age:%d, name:%s\n",current->user_id,current->age,current->user_name);
    	return person_list;
    }
    
    /**
     * 모든 데이터 삭제
     */
    int remove_all_data(PERSON* person_list) {
    	free(person_list);
    	return 0;
    }
    
    /**
     * 데이타를 디비 테이블에 삽입
     */
    int insert_data(PERSON* person_list, int count) {
    	int query_stat;
    	unsigned long len, size;
    	char *buf_to, *query;
    
    	size = sizeof(PERSON)*count*2+1;
    	buf_to = (char*)malloc(size);
    	mysql_real_escape_string(connection, buf_to, (char*)person_list, sizeof(PERSON)*count);
    
    	//Query 문 작성
    	query = (char*)malloc(sizeof(SQL_INSERT_DATA)+size);
    	len = snprintf(query, sizeof(SQL_INSERT_DATA)+size, SQL_INSERT_DATA, TABLE_NAME, (char*)buf_to, count );
    
    	//Query 문 실행
    	query_stat = mysql_real_query(connection, query, len);
    	if(query_stat != 0) {
    		fprintf(stderr, "Mysql query error : %s\n", mysql_error(&conn));
    	} else {
    		printf("insert complete\n");
    	}
    
    	//할당한 메모리 반환
    	free(buf_to);
    	free(query);
    	return 0;
    }
    
    /**
     * 데이타를 디비에서 읽어옴
     */
    int select_data(int area) {
    	char query[1024];
    	int query_stat;
    	int count=0;
    	int i;
    	PERSON *person_list, *person;
    	MYSQL_RES *result;
    	MYSQL_ROW row;
    
    	memset(query,0x00,sizeof(query));
    	sprintf(query, SQL_SELECT_DATA, TABLE_NAME, area);
    	query_stat = mysql_query(connection,query);
    	if(query_stat != 0) {
    		fprintf(stderr, "Mysql query error : %s\n", mysql_error(&conn));
    		exit(0);
    	}
    
    	//갯수만큼 화면에 데이터 출력
    	result=mysql_store_result(connection);
    	row = mysql_fetch_row(result);
    	mysql_free_result(result);
    	count = atoi(row[1]); //갯수
    	person_list = (PERSON*)row[0]; //한개의  데이터
    	for( i=0; i<count; i++ ) {
    		person = person_list + i;
    		printf("user_id:%d, age:%d, name:%s\n",person->user_id,person->age,person->user_name);
    	}
    	free(person_list);
    	return 0;
    }
    

    위 코드를 천천히 분석하면 어떻게 구조체 정보를 데이타베이스에 저장하고 읽어올 수 있는지 어렵지 않게 분석할 수 있을 것이다. 코드 자체는 연습용으로 급조된 거라서 중간중간 엉성한 부분은 그냥 넘어가자 ^^


    4. ActionScript 3.0으로 바이너리 데이터 읽어오기 

    필자가 C를 지금까지 파고 든 가장 큰 목적은 Flash에서 C로 저장된 구조체 데이터를 읽어오는 것에 있다. ActionScript 3.0 API에는 바이너리 데이터를 다룰 수 있는 강력한 클래스가 존재한다. 그것은 ByteArray이다. 이 ByteArray를 이용하면 구조체 바이너리 데이터를 아주 쉽게 사용할 수 있게 된다.

    DB에 저장된 바이너리 데이터를 Java로 로드해 AMF로 flash로 전송해주고 Flash에서는 이 데이터를 해석해 사용한다.

    자바에서 해당 데이터를 로드할 때 변환되는 Value Object의 형태는 다음과 같을 것이다.
    package com.jidolstar.person.domain;
    
    public class PersonArea {
    	private int area;
    	private int count;
    	private byte[] data;
    	
    	public int getArea() {
    		return area;
    	}
    	public void setArea(int area) {
    		this.area = area;
    	}
    	public int getCount() {
    		return count;
    	}
    	public void setCount(int count) {
    		this.count = count;
    	}
    	public byte[] getData() {
    		return data;
    	}
    	public void setData(byte[] data) {
    		this.data = data;
    	}
    }
    


    위 자바객체를 BlazeDS나 LCDS를 이용해 AMF(ActionScript Message Format)으로 전송하면 Flex나 ActionScript 3.0에서 직렬화되는 객체는 다음과 같을 것이다.
    package {
    	import flash.utils.ByteArray;
    
    	[RemoteClass(alias="com.jidolstar.person.domain.PersonArea")]
    	public class PersonArea	{
    		public var area:int;
    		public var count:int;
    		public var data:ByteArray;
    	}
    }
    


    위 클래스에서 자바에서 AMF로 넘겨주는 PersonArea를 직렬화 처리하기 위해 [RemoteClass(alias="com.jidolstar.person.domain.PersonArea")] 를 사용했다는 것에 주목하자. 또한 자바의 byte[] 포멧은 ActionScript 3.0의 ByteArray로 직렬화 처리된다.

    우리는 최종적으로 다음 ActionScript 3.0 클래스로 변환되기를 희망한다.

    package {
    	public class AreaDetail {
    		public var area:int;
    		public var id:int;
    		public var name:String;
    		public var age:int;
    	}
    }
    


    다음 코드는 이를 진행해 줄 Flex 4 코드이다.
    <?xml version="1.0" encoding="utf-8"?>
    <s:Application xmlns:fx="http://ns.adobe.com/mxml/2009" 
    			   xmlns:s="library://ns.adobe.com/flex/spark" 
    			   xmlns:mx="library://ns.adobe.com/flex/mx" minWidth="955" minHeight="600" 
    			   xmlns:halo="library://ns.adobe.com/flex/halo">
    	<fx:Declarations>
    		<s:RemoteObject id="ro" destination="personRO">
    			<s:method name="getPersonArea" result="result_handler(event)" fault="mx.controls.Alert.show(event.fault.faultString)"/> 
    		</s:RemoteObject>
    		<s:ArrayCollection id="ac"/>
    	</fx:Declarations>
    	<fx:Script>
    		<![CDATA[
    			import mx.collections.ArrayCollection;
    			import mx.controls.Alert;
    			import mx.rpc.events.ResultEvent;
    			
    			private function result_handler($event:ResultEvent):void {
    				var personArea:PersonArea = $event.result as PersonArea;
    				var count:int = personArea.count;
    				var person:PersonDetail;
    				var data:ByteArray = personArea.data;
    				var area:int = personArea.area;
    				ac.removeAll();
    				data.endian = Endian.LITTLE_ENDIAN; //구조체로 저장된 데이터가 little endian인 경우 이 설정을 해야한다.//http://blog.naver.com/kimsumin75/20055881438
    				ac.disableAutoUpdate();
    				while( count-- ) {
    					person = new PersonDetail;
    					person.area = area;
    					person.id = data.readShort()
    					person.name = data.readUTFBytes(15,"utf-8");
    					person.age = data.readShort();
    					//trace(data.readByte()); //만약 c에서 #progma pack(1)을 사용하지 않았다면 19가 아닌 20바이트가 넘어올 것이므로 1바이트가 비게 된다. 이때는 이 부분의 주석이 풀어져야 제대로 읽어올 수 있다.
    					ac.addItem(person);
    				}
    				ac.enableAutoUpdate();
    				ac.refresh();
    			}
    
    			protected function button1_clickHandler(event:MouseEvent):void
    			{
    				ro.getPersonArea(parseInt(tiArea.text));
    			}
    
    		]]>
    	</fx:Script>
    	<halo:Form x="10" y="7" width="225" height="132">
    		<halo:FormItem label="area">
    			<s:TextInput id="tiArea" text="1"/>
    		</halo:FormItem>
    		<s:Button label="불러오기" click="button1_clickHandler(event)"/>
    	</halo:Form>
    	<halo:DataGrid x="243" y="10" height="360" width="385" dataProvider="{ac}">
    		<halo:columns>
    			<halo:DataGridColumn headerText="AREA" dataField="area"/>
    			<halo:DataGridColumn headerText="ID" dataField="id"/>
    			<halo:DataGridColumn headerText="NAME" dataField="name"/>
    			<halo:DataGridColumn headerText="AGE" dataField="age"/>
    		</halo:columns>
    	</halo:DataGrid>
    </s:Application>
    


    위 코드에서 중요한 부분은 result_handler() 메소드 부분이다. ByteArray로 직렬화되어 들어온 데이터를 어떻게 읽어들이고 사용하는지 보여주고 있다. 중요한 것은 구조체의 정의시 user_id, user_name, age순으로 했기 때문에 ByteArray로 읽을때도 그와 같은 순으로 읽어야 하며 각각 읽어들일때 Byte도 잘 따져야 한다. 각각 2, 15, 2 바이트씩 총 19바이트를 저장했기 때문에 ByteArray에서 2바이트 정수를 읽어주는 readShort()메소드를 사용했다. 또한 문자열을 읽기 위해 readUTFBytes()를 이용했다.

    //trace(data.readByte()); 으로 주석된 부분이 있다. 이 부분은 c코드에서 #progma pack(1)으로 처리하지 않았을때 구조체의 크기는 19바이트가 아닌 20바이트가 되므로 ByteArray로 읽을때 항상 1바이트가 남게된다. 그러므로 c코드에서 #progma pack(1)가 꼭 필요하다는 것을 여기서도 알 수 있다. 

    마지막으로 ByteArray를 읽기전에 data.endian = Endian.LITTLE_ENDIAN 처리를 했다. Endian의 종류는 두가지이다. Big Endian과 Little Endian이다. 이것은 데이타를 메모리에 쓰는 방식의 차이로 필자가 c코드를 돌려 구조체를 저장할때 방식이 Little Endian방식으로 저장했기 때문에 ByteArray로 넘어온 데이타도 그렇게 읽을 수 밖에 없게 된다. 이 방식의 차이와 장단점은 아래 링크를 통해 살펴보길 바란다.

    Big Endian과 Little Endian


    5. 정리하며

    바이너리 데이터를 데이타 베이스에 저장하고 최종적으로 Flash기반에서 로드하는 것까지 다뤄봤다. 꽤 많은 내용을 다룬 것 같지만 실상은 매우 단순하다. 바이트 단위로 데이타를 이해한다면 그리 어렵지 않은 내용들이며 결국 이런 처리를 익숙하게 할 수 있겠는가가 중요한 것 같다.

    필자는 ActionScript나 Java등에 익숙하다 보니 이들 언어에서 메모리 관리등을 모두 다 해주니깐 게을러지는 것 같다. 이들 언어를 하더라도 c언어는 꾸준히 해주어야하고 종종 실무에도 적용해줘야겠다.

    글쓴이 : 지돌스타(http://blog.jidolstar.com/681)
    2010.04.27 10:00 AM 현재 27분이 참석인원으로 등록되었습니다.
    맹기완, 김어진, 지용호, 김주호, 안세원, 박준우, 이성우, 한재화, 김범람, 문길섭, 정태현, 정진우, 사광호,  임대찬, 구준호, 이슬기, 이상훈, 윤도선, 김동진, 이진건, 유지남, 김학영, 박동준, 김민규, 김현우, 오현식 (이상 26명, 무작위순)

    참석대상중에 개발자가 아니더라도 프로젝트에 관심이 있으신 회사대표나 이사급분들이 오셔도 좋을 것 같습니다. 아직 10분이 오실 수 있는 여유가 있으니 참석하실 분은 제게 문자(성함,회사,직급 정도)로 남겨주세요.



    파워플에 대한 소개가 1주전에 있은 후에 많은 분들의 관심과 성원이 있었습니다. 그에 힘입어 파워플(Powerfl) 킥오프 모임을 하고자 합니다.

    장소 : 한국 Adobe 대회의실. (서울 강남 교보타워 B동 16층 - 지도)
    일정 : 2010년 4월 27일(화요일) 저녁 7시~9시
    주최자 : 맹기완(Bsidesoft 대표), 지용호(위콘 커뮤니케이션즈 개발팀장, ACC)
    연락 : 지용호 (010 - 2018 - 0321, jidolstar@gmail.com)
    참여 대상
    • Flash,Flex,AIR 관련 업계 관계자 또는 개발자
    • 백엔드 서비스 관계자 또는 개발자
    • 각종 포탈, 에이전시, SI업계 관계자

    내용

    • 파워플에 대해
    • 파워플 아키텍쳐 
    • 파워플 서비스 전략 및 로드맵
    참석인원
    • 30명 내 (Adobe 대회의실 정원입니다.)
    • 정원이 제한되어 있는 관계로 참석을 희망하시는 분은 미리 아래 연락처로 연락주시길 바랍니다.
      지용호(010 - 2018 - 0321)
    • 기존 참가 신청자도 저한테 참석여부를 보내주시길 바랍니다.

    공식페이지 : powerfl.com, powerfl.net, powerfl.co.kr 중 예정에 있습니다.
    2차 모임 : 분위기 봐서 만들겠습니다. 그럼 회비가 필요하겠군요. ^^

    파워플에 대해서 잘 모르시겠다면 다음 링크를 보세요.



    파워플에 대한 이해를 돕기 위해 아래 이미지를 참고해주세요.




    파워플은 단순히 Wonderfl 사이트를 모방한 한국 사이트가 아닙니다. Wonderfl을 밴치마킹했지만 Flex, Flash를 알고리즘 및 코드 단위가 아닌 아키텍쳐 기반의 솔루션 단위까지 승격한 서비스를 제공합니다. 솔루션 단위로 Flex, Flash 애플리케이션 및 라이브러리를 공유하게 되면 기업차원의 마케팅 및 홍보를 도모할 수 있게 되고 솔루션 프로젝트 단위로의 확장 및 재생산을 할 수 있게 됩니다. 그 결과 어도비 기술의 활용도를 최대한 끌어올림으로써 한국 IT분야의 새로운 혁신을 꽤할 수 있게 된다고 생각합니다.

    애초에 Wonderfl는 flash이기 때문에 가능했던 겁니다. 반대로 Flash가 아니라면 이러한 시도는 할 수 없었습니다. 그만큼 Flash의 활용도는 뛰어납니다. Powerfl도 Flash이기 때문에 이러한 서비스가 가능해지는 겁니다. Flash의 다양한 활용성과 범용성에도 불구하고 한국 IT에서의 인식은 차갑기만 합니다. 파워플은 Flash 기술의 장점을 최대한 살려 사업적인 영역까지 재확대 시키고 사용자의 인식을 바로잡는데 크게 일조할 것이라 생각합니다.

    여러분들이 그 주인공이 되었으면 합니다.

    글쓴이 : 지돌스타(http://blog.jidolstar.com/680)

    Eclipse에서 CDT 플러그인과 MinGW에서 지원하는 C/C++ 컴파일러를 통해 개발하는 방법에 대해서 소개했었다.
    Eclipse Galileo에서 C,C++ 개발환경 구축하기 - CDT, MinGW

    참고로 오래전에 MS Visual Studio 환경에서 MySQL과 연동하는 방법과 ActionScript 3.0 으로 만들어진 MySQL Driver에 대해 소개한 적이 있다.
    MS Visual C++ 6.0 환경에서 MySQL 연동하는 방법
    Actionscript 3 Mysql Driver

    이 글은 Eclipse + MinGW 환경에서 C/C++로 MySQL과 연동하는 방법을 소개한다. MySQL 연동을 위해서 Eclipse환경을 선택한 이유는 기존 프로젝트들이 Java, Flash 로 제작되어서 하나의 툴에서 개발하고 싶은 욕구가 있기 때문이다. 하나의 Eclipse로 Java, PHP, C/C++, Flash 개발을 다할 수 있기 때문에 지원하는 각종 플러그인을 함께 공유할 수 있고 툴을 이리저리 이동할 필요도 없어진다.

    Eclipse와 MinGW으로 개발환경을 구축한 만큼 그에 맞게 개발방식을 알아야 할 것이다.


    1. MinGW Utils 설치

    MinGW에서 지원하는 Utils을 다운로드 받아서 설치해야 한다. 이것이 필요한 이유는 reimp.exe 가 필요하기 때문이다. 자세한 내용은 다음에 설명한다.

    다음 링크로 들어가서 아래 리스트에 MinGW Utilities > mingw-utils > mingw-utils-0.3 로 가면 mingw-utils-0.3.tar.gz 파일이 있다. 이것을 다운로드 받는다.

    http://sourceforge.net/projects/mingw/files/

    압축을 풀면 bin, doc 폴더가 있는데 이 두개 폴더를 복사해서 MinGW 설치폴더에 덮어씌우면 된다. 그것으로 설치 끝이다.


    2. MySQL 라이브러리 설치
    MySQL 서버가 이미 설치가 되어 있는 경우라고 가정한다. 일단 다음 링크로 가서 MySQL MSI 설치자를 다운로드 받는다.

    http://dev.mysql.com/downloads/mysql

    다운로드 받은수 설치시에 아래와 같은 Setup Type을 묻는 내용이 나오면 custom을 선택한다.

    MySQL이 이미 설치되어 있거나 어떠한 경로라도 접속할 수 있는 환경이 이미 조성되어 있는 경우에는 Server를 굳이 설치할 필요가 없으므로 아래처럼 C개발시 필요한 파일만 설치가 되도록 설정한다.

    아마도 C:/Program Files/MySQL/MySQL Server 5.1/ 내에 설치될 것이다. 그 안에는 include와 lib만 존재할 것이다. include는 c언어의 header 파일들이 존재할 것이다. 그리고 lib에는 MySQL에 접속하기 위한 각종 Lib파일들이 존재한다.


    3. MySQL 연동을 위한 MinGW 컴파일러 라이브러리로 만들기
    MySQL설치 폴더에 lib/opt/libmysql.lib 가 있다. 이것은 MySQL을 접속하기 위한 구현체 정의가 담겨있다. C,C++로 개발할 때는 이것을 참조해서 개발하고 컴파일해야 할것이다.

    하지만 MinGW에서 제공하는 컴파일러는 .lib를 직접 사용할 수 없다. 그래서 대신 확장자가 .a인 MinGW 전용의 라이브러리로 변경해야하는데 앞서 소개한 MinGW Utils에 reimp.exe와 dlltool.exe가 그것을 가능하게 해준다.

    먼저 CMD 창에 들어가서 다음과 같이 입력해본다.

    C:\>cd C:/Program Files/MySQL/MySQL Server 5.1/lib/opt
    C:\>reimp -d libmysql.lib
    C:\>dlltool -d LIBMYSQL.def -D libmysql.dll -k -l libmysql.a

    위처럼 명령후 opt 폴더안에 libmysql.a가 있는지 확인한다. 만약 없다면 opt폴더를 c:\ 하위로 옮겨보고 그 안에서 위 명령을 다시 해보길 바란다.

    reimp -d libmysql.lib 는 LIBMYSQL.def를 만들어내는데 이것을 열어보면 라이브러리에 정의된 함수가 쭉 나열되어 있음을 확인할 수 있다. dlltool 은 이것을 이용해 libmysql.a를 뽑아낸다.

    libmysql.a는 복사해서 MinGW의 lib폴더에 복사한다.

    그리고 MySQL 설치폴더에 include은 mysql로 수정하고 MinGW의 include 내에 복사하면 아래처럼 사용할 수 있게 된다.

    #include <mysql/mysql.h>


    5. Eclipse에서 C프로젝트를 생성해 MySQL 접속해보기
    이제 C로 MySQL에 접속하기 위한 준비는 완료했다. 이제 C프로젝트를 만들고 MySQL 접속하기 위한 간단한 예시를 만들어보자. File>New>C Project를 아래처럼 생성한다.



    Project Explorer에 MySQLTest 프로젝트가 만들어졌을 것이다. src 폴더를 열어 MySQLTest.c를 다음 코드로 수정하자.


    #define SOCKET int
    
    #include <stdio.h>
    #include <mysql/mysql.h>
    
    #define DB_HOST "localhost 또는 아이피, 도메인"
    #define DB_USER "DB 사용자 ID"
    #define DB_PASS "DB 사용자 Password"
    #define DB_NAME "DB 이름"
    
    int main() {
    	MYSQL *conn_ptr;
    	conn_ptr = mysql_init(NULL);
    	if(!conn_ptr) {
    		printf("mysql_init failed!!\n");
    		return 0;
    	}
    
    	conn_ptr = mysql_real_connect(conn_ptr, DB_HOST, DB_USER, DB_PASS, DB_NAME, 3306,(char *)NULL, 0);
    
    	if(conn_ptr) {
    		printf("연결이 성공 되었습니다.\n");
    	} else {
    		printf("연결에 실패했습니다.\n");
    	}
    	mysql_close(conn_ptr);
    	return 1;
    }
    
    

    코드를 만들었으니 이제 MySQL 라이브러리를 사용한다는 컴파일 옵션을 주어야 한다. 방법은 다음과 같이 하면 되겠다.

    이클립스 메뉴에서 Project > Properties로 들어간다. C/C++Build > Setting으로 들어가 Tool Setting 탭으로 누른다. 다음으로 MinGW C Linker > Libraries를 선택한다. 아래처럼 mysql을 등록한다. 이렇게 하면 컴파일시 -lmysql로 옵션을 주는 것과 같아진다.


    Ctrl+B나 메뉴>Project>Build All을 선택해서 컴파일 해보자. 다음과 같처럼 컴파일이 정상적으로 나왔다면 성공한 것이다. 
     

    **** Build of configuration Debug for project MySQLTest ****

    **** Internal Builder is used for build               ****
    gcc -O0 -g3 -Wall -c -fmessage-length=0 -osrc\MySQLTest.o ..\src\MySQLTest.c
    gcc -oMySQLTest.exe src\MySQLTest.o -lmysql
    Build complete for project MySQLTest
    Time consumed: 578  ms. 

    Ctrl+F11이나 Run > Run 을 해보자. 콘솔창에 아래처럼 나오면 성공한 것이다.


    만약 위처럼 아무 메시지 없이 실행되지 않는 것처럼 보인다면 C:/Windows/System32 내에 libmysql.dll이 없는 것을 의심해본다.

    실제로 CMD창을 열고 프로젝트의 Debug안에 있는 MySQLTest.exe를 CMD 창에 드래그해 붙여넣은 다음 Enter를 눌러 실행해보자. 아래처럼 "연결이 성공 되었습니다"라고 보이지 않고 그 아래처럼 libmysql.dll이 없다고 나오면 아래서 소개하는 두가지 방법으로 해결할 수 있다.



    일단 libmysql.dll 이 있다고 가정한다면 아래 둘중에 하나를 선택해서 하면 된다.

    1. libmysql.dll을 C:/windows/System32에 복사한다.
    2. 프로젝트의 Debug폴더나 Release폴더(즉, exe 실행파일과 같은 경로)에 libmysql.dll을 복사한다.


    dll과 함께 컴파일 처리하면 좋겠는데 아직 방법은 모르겠다.

    libmysql.dll을 얻는 방법은 http://www.dll-files.com/ 에 접속해 libmysql.dll을 찾는다. libmysql.dll을 다운로드 받을 수 있는 페이지가 나오면 다른거 누르지 말고 아래 그림과 같은 Free download의 Download 버튼을 눌러 다운로드 받는다.



    6. 테이블 생성, 선택, 삽입, 편집, 삭제 해보기
    접속까지 했으니 응용하는 것은 누워서 떡먹기다. Create table, Select, Insert, Update, Delete 예제는 다음과 같다.

    #define SOCKET int
    
    #include <stdio.h>
    #include <mysql/mysql.h>
    
    #define DB_HOST "localhost 또는 아이피, 도메인"
    #define DB_USER "DB 사용자 ID"
    #define DB_PASS "DB 사용자 Password"
    #define DB_NAME "DB 이름"
    
    #define SQL_CREATE_TABLE "CREATE TABLE `mysql_api_test` (\
        `id` INT NOT NULL AUTO_INCREMENT PRIMARY KEY ,\
        `num` INT NULL ,\
        `string` VARCHAR( 20 ) NULL \
        ) TYPE = MYISAM ;"
    #define SQL_INSERT_RECORD "INSERT INTO `mysql_api_test` ( `id` , `num` , `string` ) \
        VALUES (\
        NULL , '%d', '%s'\
        );"
    #define SQL_SELECT_RECORD "SELECT * FROM `mysql_api_test`"
    #define SQL_DROP_TABLE "DROP TABLE `mysql_api_test`"
    
    int main() {
    	MYSQL *connection=NULL, conn;
    	MYSQL_RES *sql_result;
    	MYSQL_ROW sql_row;
    	int query_stat;
    	int i;
    
    	char query[255];
    
    	mysql_init(&conn);
    
    	// DB 연결
    	connection = mysql_real_connect(&conn, DB_HOST, DB_USER, DB_PASS,DB_NAME, 3306,(char *)NULL, 0);
    	if(connection==NULL) {
    		fprintf(stderr, "Mysql connection error : %s", mysql_error(&conn));
    		return 1;
    	}
    
    	// 테이블 생성
    	query_stat=mysql_query(connection,SQL_CREATE_TABLE);
    	if (query_stat != 0) {
    		fprintf(stderr, "Mysql query error : %s", mysql_error(&conn));
    		return 1;
    	}
    
    	// 레코드 삽입
    	for(i=0;i<5;i++) {
    		sprintf(query,SQL_INSERT_RECORD,100+i,"안녕하세요 지돌스타예요~");
    		query_stat = mysql_query(connection, query);
    		if (query_stat != 0) {
    			fprintf(stderr, "Mysql query error : %s", mysql_error(&conn));
    			return 1;
    		}
    	}
    
    	// 셀렉트
    	query_stat=mysql_query(connection,SQL_SELECT_RECORD);
    	if (query_stat != 0) {
    		fprintf(stderr, "Mysql query error : %s", mysql_error(&conn));
    		return 1;
    	}
    
    	// 결과 출력
    	sql_result=mysql_store_result(connection);
    	while((sql_row=mysql_fetch_row(sql_result))!=NULL) {
    		printf("%2s %2s %s\n",sql_row[0],sql_row[1],sql_row[2]);
    	}
    	mysql_free_result(sql_result);
    
    	// 테이블 삭제
    	query_stat=mysql_query(connection,SQL_DROP_TABLE);
    	if (query_stat != 0) {
    		fprintf(stderr, "Mysql query error : %s", mysql_error(&conn));
    		return 1;
    	}
    
    	// DB 연결 닫기
    	mysql_close(connection);
    	return 0;
    }
    
    

    7. 정리하며
    이미 많은 분들이 삽질(?)을 많이 해주셔서 비교적 쉽게 MySQL 개발 환경을 조성할 수 있었다. 뭔가 이런 환경에서 개발한다면 크로스 플랫폼 애플리케이션을 만드는데 도움이 될 것 같다. 관련된 정보를 찾아봐서 개발방법에 대해서 좀더 숙지해야할 필요가 있을 것 같다.


    8. 참고글
    Eclipse Galileo에서 C,C++ 개발환경 구축하기 - CDT, MinGW
    MS Visual C++ 6.0 환경에서 MySQL 연동하는 방법
    VC용 lib화일을 mingw용 라이브러리로 변환하기
    MinGW로 Mysql plugin driver compile하는 방법
    DLL 다운받기


    글쓴이 : 지돌스타(http://blog.jidolstar.com/679)

    + Recent posts