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)

    2009년 한국천문연구원 아마추어 천문학 발전을 위한 위탁과제로 만든 대한민국 인터넷 천문대(http://realsky.org) 입니다.

    이 사업은 언제 어디서나 누구나 인터넷을 통해 쉽게 밤하늘을 관측하고 정보를 얻을 수 있는 것을 목표로 최초 제작되었습니다. 2009년 6월 부터 12월까지 프로젝트가 진행되었으며 이를 위해 미국 우주망원경연구소의 디지털하늘탐사(Digitized Sky Survey, DSS) 데이터를 이용해 전천의 밤하늘 이미지를 가공했고 이것을 보여주기 위해 Adobe Flash 기술을 이용했습니다.

    이미지의 편집 및 이용권한은 천문노트(http://astronote.org) 에게만 있습니다.

    클라이언트 엔진은 ActionScript 3.0이며 일부 Flex 4를 이용해 개발했습니다. 서버측 통신은 ZendAMF를 이용했습니다. 최초 개발한 만큼 아직 부족하나 앞으로 유용하고 다양한 형태로 개발할 계획입니다.

    http://realsky.org

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

    LCDS, BlazeDS, ZendAMF등을 이용해 AMF(ActionScript Message Format)으로 데이터를 주고받는 형태는 이미 많은 예제들과 문서들이 있다. AMF는 데이터 통신을 위한 일종의 약속된 규약이고 이 데이터를 주고 받고 하는 과정에서 서로의 언어에 맞게 직렬화(Serialization)하는 것은 각 언어에서 지원해주는 라이브러리를 사용하면 된다. ActionScript 3.0은 기본 API에서 지원해준다. 자바의 경우는 BlazeDS나 LCDS를 사용하면 된다. PHP의 경우에는 ZendAMF를 사용하면 된다. 이들을 이용하면 가령, 자바와 java.lang.Interger는 ActionScript의 int나 uint로... java.lang.Double은 ActionScript의 Number형과 직렬화된다. 이는 일종의 기본적으로 지원되는 직렬화이다.

    다음은 BlazeDS에서 기본적으로 지원되는 직렬화이다.
    Serializing between ActionScript and Java

    하지만 이런 기본 직렬화과정을 사용하지 않고 직렬화 자체를 커스터마이징(customizing)할 수 있다. 가령 클라이언트(예,Flash 애플리케이션)에서는 아이디, 이름, 속성, 가격의 정보가 중요하지만 서버(예,자바)에서는 재고(inventory)가 중요한 경우가 있다. 이런 경우에는 Flex와 Java쪽의 직렬화하는 클래스를 다음과 같이 디자인 할 수 있겠다.

    // Product.as
    package samples.externalizable {
    
    import flash.utils.IExternalizable;
    import flash.utils.IDataInput;
    import flash.utils.IDataOutput;
    
    [RemoteClass(alias="samples.externalizable.Product")]
    public class Product implements IExternalizable {
        public function Product(name:String=null) {
            this.name = name;
        }
    
        public var id:int;
        public var name:String;
        public var properties:Object;
        public var price:Number;
    
        public function readExternal(input:IDataInput):void {
            name = input.readObject() as String;
            properties = input.readObject();
            price = input.readFloat();
        }
    
        public function writeExternal(output:IDataOutput):void {
            output.writeObject(name);
            output.writeObject(properties);
            output.writeFloat(price);
        }
    }
    }
    


    // Product.java
    package samples.externalizable;
    
    import java.io.Externalizable;
    import java.io.IOException;
    import java.io.ObjectInput;
    import java.io.ObjectOutput;
    import java.util.Map;
    
    /**
    * This Externalizable class requires that clients sending and 
    * receiving instances of this type adhere to the data format
    * required for serialization.
    */
    public class Product implements Externalizable {
        private String inventoryId;
        public String name;
        public Map properties;
        public float price;
    
        public Product()
        {
        }
    
            /**
            * Local identity used to track third party inventory. This property is
            * not sent to the client because it is server-specific.
            * The identity must start with an 'X'.
            */
            public String getInventoryId() {
                return inventoryId;
            }
    
            public void setInventoryId(String inventoryId) {
                if (inventoryId != null && inventoryId.startsWith("X"))
                {
                    this.inventoryId = inventoryId;
                }
                else
                {
                    throw new IllegalArgumentException("3rd party product
                    inventory identities must start with 'X'");
                }
            }
    
            /**
             * Deserializes the client state of an instance of ThirdPartyProxy
             * by reading in String for the name, a Map of properties
             * for the description, and 
             * a floating point integer (single precision) for the price. 
             */
            public void readExternal(ObjectInput in) throws IOException,
                ClassNotFoundException {
                // Read in the server properties from the client representation.
                name = (String)in.readObject();
                properties = (Map)in.readObject();
                price = in.readFloat();
                setInventoryId(lookupInventoryId(name, price));
            }
            /**
             * Serializes the server state of an instance of ThirdPartyProxy
             * by sending a String for the name, a Map of properties
             * String for the description, and a floating point
             * integer (single precision) for the price. Notice that the inventory 
             * identifier is not sent to external clients.
             */
            public void writeExternal(ObjectOutput out) throws IOException {
                // Write out the client properties from the server representation
                out.writeObject(name);
                out.writeObject(properties);
                out.writeFloat(price);
            }
            
            private static String lookupInventoryId(String name, float price) {
                String inventoryId = "X" + name + Math.rint(price);
                return inventoryId;
            }
    }
    

    위 코드는 ActionScript의 flash.utils.IExternalizable 인터페이스와 Java의 java.io.Externalizable 인터페이스를 이용해 기본직렬화를 무시하고 이들 인터페이스에 정의된 readExternal와 writeExternal 메소드를 호출하여 직렬화 자체를 커스터마이징 할 수 있다는 것을 의미한다. Externalizable 인터페이스를 구현한 클래스에 정의된 메소드가 기본 직렬화보다 우선순위가 높다는 것을 기억하면 되겠다.

    Flex의 ArrayCollection을 다시 한번 보기 바란다. 이 클래스는 flash.utils.IExternalizable를 구현했다.
    mx.collections.ArrayCollection

    꼭 이런 경우만은 아니다. 쓸데없는 데이터의 크기를 줄이기 위한 방법도 해당한다. 예를들어 ActionScript 코드를 보면 다음과 같다.


    class Example implements IExternalizable {
      
          public var one:Boolean;
          public var two:Boolean;
          public var three:Boolean;
          public var four:Boolean;
          public var five:Boolean;
          public var six:Boolean;
          public var seven:Boolean;
          public var eight:Boolean;
           public function writeExternal(output:IDataOutput) {
               var flag:int = 0;
               if (one) flag |= 1;
              if (two) flag |= 2;
              if (three) flag |= 4;
              if (four) flag |= 8;
              if (five) flag |= 16;
              if (six) flag |= 32;
              if (seven) flag |= 64;
              if (eight) flag |= 128;
               output.writeByte(flag);
          }
           public function readExternal(input:IDataInput) {
               var flag:int = input.readByte();
               one = (flag & 1) != 0;
              two = (flag & 2) != 0;
              three = (flag & 4) != 0;
              four = (flag & 8) != 0;
              five = (flag & 16) != 0;
              six = (flag & 32) != 0;
              seven = (flag & 64) != 0;
              eight = (flag & 128) != 0;
          }
     }
    

    데이터 통신을 위해 쓸데없이 Boolean객체를 주고 받을 필요없다. 위 코드처럼 직렬화를 커스터마이징한다면 송수신 데이터 자체의 크기도 줄일 수 있다. 이는 매우 유용하다.


    참고글
    flash.utils.IExternalizable
    커스텀 직렬화의 사용
    ActionScript 3.0 데이터 유형 및 직렬화(serialization)
    Serializing between ActionScript and Java
    AMF 3 스팩
    AS3 BitmapData AMF solution using IExternalizable
    Flex and PHP: remoting with Zend AMF

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

    이 글은 ActionScript 3.0 프로젝트에서 BlazeDS와 통신하는 환경을 배경으로 한다.  Flex MXML를 배제하고 단지 Flex에 제공되는 RPC, 리모팅 관련 클래스를 사용하고자 한다. Flex MXML를 배제하는 이유는 다음과 같다.

     

    Flex SDK의 Visual 컴포넌트들은 일반 솔루션 제품을 개발하는데 매우 유용하지만 어떤 경우에는 프로그램의 용량과 퍼포먼스 향상에는 도움이 안될 수 있다. 가령, Flex SDK에서 제공하는 Button과 List만 사용한다고 가정하자. 이 컴포넌트만 사용하는데도 불구하고 Flex 컴파일러는 그와 관련된 클래스까지 함께 컴파일해서 프로그램 용량을 크게 한다. 또한 Visual 컴포넌트들은 커다란 프레임워크로 만들어져 있기 때문에 Sprite만을 이용해서 만든 프로그램보다 퍼포먼스가 떨어진다.

     

    물론 Flex에서 제공하는 컴포넌트를 적극적으로 다양하게 사용할 필요가 있다면 개발 효율성 및 유지보수 측면에서 Flex 프로젝트로 개발해야한다. 하지만 겨우 컴포넌트 몇개만 가져다가 사용하는 경우에는 Flex로 개발하는 것이 오히려 프로그램의 질을 떨어뜨릴 수 있는 결과를 초래할 수 있게 된다.(Flex가 나쁘다는 것이 아니라 필요에 맞게 사용해야한다는 것을 언급하는 것임을 강조한다.)  

     

    Flex 환경에서 AMF3 통신

     

    다음 예를 보자.

     

    아래처럼 Flex 프로젝트로 만들어 Release 버전으로 컴파일 하게 되면 262kb가 나온다. 매우 단순한 프로그램인데도 불구하고 용량이 꽤 크다. 이유는 앞서 설명했다.

     

    <?xml version="1.0" encoding="utf-8"?>
    <mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute">
    	<mx:RemoteObject id="ro" destination="my-dest" showBusyCursor="true">
    		<mx:method
    			name="getData"
    			result="returnHandler(event)"
    			fault="mx.controls.Alert.show(event.fault.faultString)"/>
    	</mx:RemoteObject>
    	<mx:Button name="load" click="load()"/>
    	 <mx:Script>
    	<![CDATA[
    		import mx.rpc.events.ResultEvent;
    
    		private function returnHandler(e:ResultEvent):void
    		{
    			trace( e.result );
    		}
    
    		private function load():void
    		{
    			ro.getData( 1, 2 );
    		}
    	]]>
    	</mx:Script>
    </mx:Application>

     

     

    AMF3 직렬화 처리가 안된 ActionScript 3.0 프로젝트 개발

     

    AMF3 통신만 원한다면 앞서 설명한 이유 때문에 굳이 Flex로 개발할 필요가 없다. ActionScript 3.0 프로젝트 환경에서도 충분히 AMF 통신이 가능하기 때문이다. 아래는 OpenAMF나 PHPAMF와 같은 서버측 프로그램을 이용할 때 ActionScript 3.0 코드이다.

     

    package {
    	import flash.display.Sprite;
    	import flash.net.NetConnection;
    	import flash.net.ObjectEncoding;
    	import flash.net.Responder;
    	import flash.net.registerClassAlias;
    
    	public class AS3NetConnectionTest extends Sprite
    	{
    		private var nc:NetConnection;
    
    		public function AS3NetConnectionTest()
    		{
    			//create a netconnection
    			nc = new NetConnection();
    			nc.objectEncoding = ObjectEncoding.AMF3;
    			nc.connect("http://192.168.*.*/_FLEX/amf");
    
    			//create a responder
    			var responder:Responder = new Responder(result, fault);
    
    			//make the call
    			nc.call("com.jidolstar.service.MyClass.getData", responder, 2);
    		}
    
    		private function result( data:Object ):void
    		{
    			trace("RPC Ok");
    		}
    
    		private function fault( event:Object ):void {
    			 trace("RPC Fail");
    			 trace(ObjectUtil.toString(event););
    		}
    	}
    }

     

    위 코드는 Flex에 관련된 것이 없고 단순히 flash.* 패키지 기반의 순수 ActionScript 3.0 환경에서 개발한 것이다. 용량도 Release로 컴파일하면 1kb도 안된다. Flex로 개발할때와 거의 250배 이상 용량차이를 보인다.

     

     

    AMF3 직렬화 처리가 된 ActionScript 3.0 프로젝트 개발

     

    하지만 BlazeDS, LCDS 서버의 리모트 객체와 클라이언트 객체간에 직렬화(serialization)가 필요한 경우 위 ActionScript 코드만으로는 해결할 수 없다. 이 문제는 본인의 경우 기존 프로젝트에서 BlazeDS로 만들어진 서버측의 코드를 변경하지 않고 Flex대신 ActionScript 3.0로 개발할때 문제가 발생했다. Flex Builder에서 ActionScript 3.0 프로젝트로 하면 playerglobal.swc, flex.swc, utilites.swc만 라이브러리로 등록되어 있기 때문에 BlazeDS에서 AMF3를 통해 넘겨주는 message관련 객체나 ArrayCollection 객체등을 직렬화 처리를 할 수 없게 된다. 왜냐하면 이들 3개 swc에는 이와 관련된 클래스가 정의되어 있지 않기 때문이다.

     

    BlazeDS의 message관련 객체에 대한 직렬화를 위해서는 Flex SDK에 있는 rpc.swc가 필요하다. 또 ArrayCollection 직렬화를 위해서는 framework.swc 가 필요하다. 그럼 Flex를 사용하는 것과 무엇이 다르냐라고 반문할 수 있다.  그러나 ActionScript 3.0 기반이기 때문에 framework.swc에 정의된 비주얼 컴포넌트를 전부 사용하지 않고 단지 ArrayCollection과 그와 관련된 클래스만 사용하기 때문에 이것만으로도 프로그램의 용량을 획기적으로 줄이게 된다.

     

    아래에서 설명하는 대로 프로젝트 환경을 구성해보자.

     

     

    1. Flex Builder 3 Professional에서 ActionScript 3.0 프로젝트를 만든다.

     

    2. 프로젝트에 Flex SDK에 있는 rpc.swc, rpc_rb.swc, framework.swc, framework_rb.swc를 라이브러리로 추가한다.

     

    참고로, SWC는 윈도우의 경우 C:/Program Files/Adobe/Flex Builder 3/sdks/SDK버전/frameworks/에 libs 폴더와 locale 폴더에 있다. SWC에서 _rb라고 붙은 것은 resource bundle의 약어이다. Flex는 리소스 번들을 지원해주는데 우리의 목적과는 상관없는 것이지만 컴파일을 위해서는 추가해주어야 에러가 없이 컴파일이 된다.

     

     

    3. 아래처럼 컴파일러 옵션에 -locale=en_US -include-resource-bundles=collections,rpc,messaging 를 추가한다.

     

     

    4. 아래 코드처럼 코딩한다. (서버측 BlazeDS 설정은 여기서 설명을 생략한다.)

     

    package {
    	import com.jidolstar.vo.Class1;
    	import com.jidolstar.vo.Class2;
    
    	import flash.display.Sprite;
    	import flash.net.registerClassAlias;
    
    	import mx.collections.ArrayCollection;
    	import mx.collections.ArrayList;
    	import mx.core.mx_internal;
    	import mx.logging.targets.TraceTarget;
    	import mx.messaging.ChannelSet;
    	import mx.messaging.channels.AMFChannel;
    	import mx.messaging.config.ConfigMap;
    	import mx.messaging.config.LoaderConfig;
    	import mx.messaging.events.*;
    	import mx.messaging.messages.*;
    	import mx.rpc.events.FaultEvent;
    	import mx.rpc.events.ResultEvent;
    	import mx.rpc.remoting.RemoteObject;
    	import mx.utils.ObjectProxy;
    
    	use namespace mx_internal;
    
    	/**
    	 * RemoteObject 이용
    	 */
    	public class AS3RemoteObjectTest extends Sprite
    	{
    		public function AS3RemoteObjectTest()
    		{
    
    			registerClassAlias("flex.messaging.messages.CommandMessage",CommandMessage);
    			registerClassAlias("flex.messaging.messages.RemotingMessage",RemotingMessage);
    			registerClassAlias("flex.messaging.messages.AcknowledgeMessage", AcknowledgeMessage);
    			registerClassAlias("flex.messaging.messages.ErrorMessage",ErrorMessage);
    			registerClassAlias("flex.messaging.io.ArrayList", ArrayList);
    			registerClassAlias("flex.messaging.config.ConfigMap",ConfigMap);
    			registerClassAlias("flex.messaging.io.ArrayCollection",ArrayCollection);
    			registerClassAlias("flex.messaging.io.ObjectProxy",ObjectProxy);
    			registerClassAlias("flex.messaging.messages.HTTPMessage",HTTPRequestMessage);
    			registerClassAlias("flex.messaging.messages.SOAPMessage",SOAPMessage);
    			registerClassAlias("flex.messaging.messages.AsyncMessage",AsyncMessage);
    			registerClassAlias("flex.messaging.messages.MessagePerformanceInfo", MessagePerformanceInfo);
    			registerClassAlias("DSA", AsyncMessageExt);
    			registerClassAlias("DSC", CommandMessageExt);
    			registerClassAlias("DSK", AcknowledgeMessageExt);
    
    			registerClassAlias("com.jidolstar.service.domain.Class1", Class1 );
    			registerClassAlias("com.jidolstar.service.domain.Class2", Class2);
    
    			//Sets up some more descriptive tracing on the client
    			var target:TraceTarget = new TraceTarget();
    			target.level = 0;
    
    			//This is needed so the Flex libraries have access to the root objects properties
    			var swfURL:String = this.loaderInfo.url;
    			LoaderConfig.mx_internal::_url = this.loaderInfo.url;
    			LoaderConfig.mx_internal::_parameters = this.loaderInfo.parameters;
    
    			//This is the channel definition
    			//This tells RemoteObject where to go
    			var amfChannel:AMFChannel = new AMFChannel( "my-amf", "http://192.168.0.17/amf" );
    			amfChannel.requestTimeout = 3;
    			amfChannel.connectTimeout = 3;
    			amfChannel.addEventListener(ChannelFaultEvent.FAULT, handleChannelFault);
    			amfChannel.addEventListener(ChannelEvent.CONNECT, handleChannelConnect);
    			amfChannel.addEventListener(ChannelEvent.DISCONNECT, handleChannelDisconnect);
    
    			var channelSet:ChannelSet = new ChannelSet();
    			channelSet.addChannel( amfChannel );
    
    			//Make sure you include the right RemoteObject at
    			//import mx.rpc.remoting.RemoteObject;
    			//not
    			//import  mx.rpc.remoting.mxml.RemoteObject;
    			var ro:RemoteObject = new RemoteObject;
    			ro.destination = "mydest";
    			ro.channelSet = channelSet;
    			ro.addEventListener(ResultEvent.RESULT, onResult );
    			ro.addEventListener(FaultEvent.FAULT, onFault );
    			ro.getData( 1, "jidolstar" );
    		}
    
    		public function onResult(event:ResultEvent):void
    		{
    			trace("RPC Ok");
    			trace(event.result);
    
    		}
    
    		public function onFault(event:FaultEvent):void
    		{
    			trace("RPC Fail");
            	trace(event.message);
    		}
    
    		public function handleChannelFault(e:ChannelFaultEvent):void {
    		    trace("Channel Fault");
    			trace(e);
    		}		
    
    		public function handleChannelConnect(e:ChannelEvent):void {
    		    trace("Channel Connect");
    			trace(e);
    		}
    
    		public function handleChannelDisconnect(e:ChannelEvent):void {
    		    trace("Channel Disconnect");
    			trace(e);
    		}
    
    	}
    }

     

    위 코드는 BlazeDS나 LCDS 환경에서 RPC기반인 RemoteObject로 AMF3 통신처리를 하는 ActionScript 3.0 프로젝트 예제이다.

     

    registerClassAlias()는 BlazeDS에서 오는 AMF3 메시지 객체를 ActionScript 3.0으로 직렬화처리를 위한 것이다. 이 함수에 message관련 클래스와 ArrayCollection이 등록되어 있는 것을 확인하기 바란다. 이것으로서 ActionScript 3.0 프로젝트를 통해서도 충분히 ArrayCollection을 쓸 수 있게 된다.

     

    registerClassAlias("com.jidolstar.service.domain.Class1", Class1 )는 서버측에 정의된 com.jidolstar.service.domain.Class1 객체를 ActionScript 3.0에 정의된 com.jidolstar.vo.Class1으로 직렬화해준다. Flex에서는 com.jidolstar.vo.Class1이 만들어진 Class1.as파일의 Class명 위에 [RemoteClass(alias="com.jidolstar.service.domain.Class1")] 를 정의함으로서 registerClassAlias()에서 한 것과 동일하게 처리할 수 있지만 ActionScript 3.0환경에서 개발하므로 [RemoteClass]는 사용하기에 적합하지 못하다.

    이 프로그램을 Release모드로 컴파일하면 89kb였다. Flex로 개발시에 262kb이였던 것에 비해서 분명 크게 줄었다. 이는 Flex 비주얼 컴포넌트에 관련된 것이 컴파일에서 제외되었기 때문이다. 하지만 순수하게 NetConnection으로만 만들었던 것이 1kb였다는 것을 감안한다면 여전히 크기가 크다. 위 코드에서는 RemoteObject를 이용했는데 NetConnection을 이용해보면 어떨까? 다음 코드를 보자.

     

    package {
    	import com.jidolstar.vo.Class1;
    	import com.jidolstar.vo.Class2;
    
    	import flash.display.Sprite;
    	import flash.net.NetConnection;
    	import flash.net.ObjectEncoding;
    	import flash.net.Responder;
    	import flash.net.registerClassAlias;
    
    	import mx.collections.ArrayCollection;
    	import mx.messaging.messages.*;
    
    	/**
    	 * NetConnection이용
    	 */
    	public class AS3NetConnectionTest extends Sprite
    	{
    		public function AS3NetConnectionTest()
    		{
    			registerClassAlias("flex.messaging.messages.RemotingMessage",RemotingMessage);
    			registerClassAlias("flex.messaging.messages.AcknowledgeMessage", AcknowledgeMessage);
    			registerClassAlias("flex.messaging.messages.ErrorMessage",ErrorMessage);
    			registerClassAlias("flex.messaging.io.ArrayCollection",ArrayCollection);
    
    			registerClassAlias("com.jidolstar.service.domain.Class1", Class1  );
    			registerClassAlias("com.jidolstar.service.domain.Class2", Class2  );
    
    			//create a netconnection
    			var nc:NetConnection = new NetConnection();
    			nc.objectEncoding = ObjectEncoding.AMF3;
    			nc.connect("http://192.168.0.17/amf");
    
    			//create the arguments that will be sent to the method
    			var methodArguments:Array = [ 1, "jidolstar" ];
    
    			//create a remoting message
    			var remotingMessage:RemotingMessage = new RemotingMessage();
    			remotingMessage.operation = "getData";
    			remotingMessage.body = methodArguments;
    
    			//The values of these 2 values come from the services-config.xml which is located in the ColfFusion server
    			remotingMessage.destination = "mydest";
    			remotingMessage.headers = {DSEndpoint: "my-amf"};
    
    			//create a responder
    			var responder:Responder = new Responder(result, fault);
    
    			//make the call
    			nc.call(null, responder, remotingMessage);
    		}
    
    		private function result(e:AcknowledgeMessage):void
    		{
    			trace("RPC Ok");
    			trace(e.body);
    		}
    
    		private function fault(e:ErrorMessage):void {
    			 trace("RPC Fail");
    			 trace(e.faultString);
    		}
    	}
    }

     

    위 프로그램은 RemoteObject를 사용할 때와 동일하게 동작한다. 하지만 더 저레벨로 만들었기 때문에 프로그램 용량은 44kb로 RemoteObject를 사용할때보다 1/2로 줄었다. Flex로 개발할때와 비교할때 거의 6배 차이가 난다. 그러나 여전히 순수하게 NetConnection만으로 만들었을 때와 용량차이가 나는데 그것은 메시지 관련 클래스와 ArrayCollection을 컴파일할때 그와 연관된 클래스들이 포함되어 컴파일 되기 때문이다. 그 중에서는 우리의 목적인 AMF3 직렬화와는 상관없는 resource bundle에 관련된 것도 포함되어 있다. 필요하다면 resource bundle부분을 제외한 라이브러리를 만들어 내는 것도 하나의 방법이 될 수 있겠다. 일단 6배 이상 프로그램 용량을 줄일 수 있다는 것에 만족하려고 한다. 만약 이 부분에 대해서 더욱 가볍게 만드신 분이 있다면 함께 공유했으면 한다.(제가 그것까지 하기에는 시간이 ^^;;)

     

    위 프로그램은 단순한 예제이므로 필요할 때 클래스화 시켜서 만드는 것이 좋을 것이라 생각한다.

     

     

    관련글

     

     

    + Recent posts