Archive for the ‘Stream’ Category.

Stream을 실전에 활용하기 1

지난 두 차례에 걸쳐 Stream을 공부해봤습니다. 하지만 실제 Stream을 공부한 목적은 이번 장에 있습니다. Flash10에 적용된 다양한 Stream관련 객체를 근본부터 이해하고 사용하기 위해서 여태 길게 설명했던 것이죠. 이제 하나하나 as3 class lib에 내장된 Stream객체를 살펴보겠습니다.

1. URLStream

일단 이름부터 Stream아니겠습니까? 당연히 Stream을 다루고 있습니다. 먼저 adobe문서에 기술된 본문을 살짝 참고해보죠.

URLStream 클래스는 URL 다운로드에 대한 낮은 레벨의 액세스를 제공합니다. 데이터는 다운로드되는 즉시 응용 프로그램 코드에서 사용할 수 있으며 URLLoader에서 처럼 전체 파일이 완전히 다운로드 될 때까지 기다릴 필요가 없습니다. URLStream 클래스를 사용하면 다운로드가 완료되기 전에 스트림을 닫을 수도 있습니다. 다운로드 된 파일의 내용은 원시 이진 데이터 형태로 사용할 수 있습니다.

뭔가 앞서 배웠던 Stream의 느낌이 짠하고 오시나요? 이제 좀 깊이 이해해봅시다.
URLStream은 load라는 method를 제공하는데 인자로 URLRequest를 받습니다. URLRequest는 HTTP규격의 request header를 문자열로 생성해주는 역활을 하는데, 이미 URLStream이 URLRequest로 load를 한다는 사실 자체가 HTTP를 기반으로 통신하고 있다는 뜻이 됩니다.

HTTP는 잘 알려진 특징을 갖고 있습니다. 가장 많이 들어보셨을 게 아마도 상태유지를 하지 않는다는 것이겠죠? 하지만 더 파고 들어보면 HTTP는 TCP기반으로 통신하고 text기반으로 protocol을 정의합니다. 이 기저의 두 가지로 말미암아 아래와 같은 특성이 자동으로 나옵니다.

1. 요청 및 응답이 TCP에 의해 순차적으로 수신, 송신된다(이것은 바로 Stream의 개념!)
2. 바이너리 데이터는 text로 encoding 된다(base64가 기본입니다)

따라서 URLStream은 URLRequest로 요청한 자원을 Stream으로 수신하는 클래스라고 생각할 수 있습니다.
(바로 위의 문장이 조금이라도 어렵다고 느끼시면 다시 복습 하시길 권장합니다 ^^)

그럼 사용할 때는 어떻게 사용할까요? Stream의 장점을 활용하려면 역시 로드와 동시에 활용해야 합니다. 만약 onComplete에 처리할 생각이었으면 처음부터 Stream계열의 객체를 사용할 이유가 없습니다.
URLStream의 경우엔 최초 로딩이 시작되는 순간을 알아차리기 위해 ProgressEvent를 활용하게 되어있습니다. 또한 전에 살짝 언급한대로 모든 Stream 계열은 소비자측이 더 개발하기가 복잡합니다. URLStream도 어지간히 무책임한 클래스입니다. adobe문서에는 다음과 같은 내용이 있습니다.

URLStream의 읽기 작업은 비차단 방식입니다. 따라서 데이터를 읽기 전에 bytesAvailable 속성을 사용하여 충분한 데이터가 있는지 확인해야 합니다. 충분한 데이터가 없는 경우 EOFError 예외가 발생합니다.

즉 클라이언트가 알아서 구현하시라는 뜻입니다. 구체적으로 해석하면 URLStream에게 ProgressEvent Listener를 걸어서 통보를 받은 직후부터 쭉 URLStream.bytesAvailable을 감시하며 원하는 만큼 꾸준히 데이터를 얻어가라 라는 지시입니다.

이제 일전에 만들었던 CstringStream을 개조하여 CURLStringStream으로 만들어봅시다.

package {

	import flash.events.*;
	import flash.net.*;

	public CURLStringStream extends EventDispatcher{

		static public const UPDATE:String='update';

		private var _stream:NetStream;
		private var _readByte:uint=0;
		private var _buffer:uint=5;

		public function CURLStringStream(){}

		public function load($url:String):void{
			//기존에 loading중인 Stream있다면 제거한다.
			if(_stream!==null){
				_stream.close();
			}
			//읽어들인 위치를 초기화한다.
			_readByte=0;
			_stream=new URLStream(new URLRequest($url));
			//Loading 상황을 처리할 내부 Listener설정
			_stream.addEventListener(ProgressEvent.PROGRESS,hnUpdate);
			_stream.load();
		}
 		//data를 출력한다.
		public function get data():String{
			//read위치를 buffer만큼 전진시키고
			_readByte+=_buffer;
			//buffer만큼 출력한다.
			return _stream.readMultiByte(_buffer);
		}
 		//buffer를 셋팅한다.
		public function set buffer($buffer:uint):void{
			_buffer=$buffer;
		}
		private function hnUpdate(e:Event):void{
			//Loading된 분량
			var loaded:uint=_stream.bytesAvailable;
			//수신 data가 read data보다 buffer이상 크다면 update!
			if(loaded

설명을 주석으로 자세하게 달았기 때문에 따로 언급할 부분은 별로 없습니다만 큰 흐름을 보면 인스턴스를 생성하고 buffer를 셋팅한 뒤 load를 하면 buffer만큼 읽어들일 때마다 데이터를 갱신 받는 스타일입니다. 일전에 짜둔 CtextSubscribe 에서 유일하게 수정해야 하는 부분은 _stream이 CstringStream에서 CURLStringStream으로 타입이 바뀐 것 뿐입니다. 하지만 URL에서 Loading이 순식간에 일어나기 때문에 매번 갱신하던걸 콜론으로 연결해가는 것으로 대체하겠습니다.아래와 같이 새로운 CURLtextSubscribe를 만들어보죠.

package {
	import flash.text.*;
	import flash.events.*;

	public class CURLtextSubscribe extends TextField{

		private var _stream:CURLStringStream;

		public CURLtextSubscribe(){
			text='';
		}

		//stream을 등록하고 listener를 설정한다.
		public set textStream($stream:CURLStringStream):void{
			_stream=$stream;
			_stream.addEventListener(CURLStringStream.UPDATE,hnUpdate);
		}

		//update 발생시마다 문자열추가
		private function hnUpdate($e:Event):void{
			appendText(':'+_stream.data);
		}
	}
}

Main에서는 약간의 변화가 있습니다. CURLStringStream이 셋팅하는 순서가 다르기 때문입니다.

package {

	import flash.display.*;
	import flash.utils.*;

	public class Main extends Sprite{

		private var _stream:CURLStringStream;

		public function Main(){
			//stream생성
			_stream=new CURLStringStream();
			//10개의 문자열이 수신될때마다 갱신
			_stream.buffer=10;
			//text수신자 생성
			var subscribe:CtextSubscribe=new CtextSubscribe();
			//수신자에게 stream 설정
			subscribe.textStream=_stream;
			//화면에 등록
			addChild(subscribe);
			//stream Loading시작
			_stream.load('http://test.test/test.txt');
		}
	}
}

실제 실행하면 URL의 txt가 워낙 빨리 로딩되기 때문에 순식간에 콜론으로 연결된 문자열을 보실 수 있을 겁니다.

이것 참 원래 계획은 이번 글에서 NetStream, Sound, Video 등을 다 다루려고 했습니다만, 완전히 무리군요. 다음 글로 넘기겠습니다.

Stream을 만들어보기

Stream을 이해하기에 이어서 이번엔 만들어보기를 해보겠습니다.
가장 간단한 text Stream부터 시작해볼까요.
일단 문자열을 담을 선형공간이 필요합니다. 이 공간은 Queue의 특성을 갖도록 만드는게 좋습니다. 즉 입력하면 계속 머리쪽에 들어가게 하는 것이죠. 다행히 Vector클래스나 Array클래스는 모두 unshift라는 메쏘드를 제공하는데 이는 자료의 젤 앞 쪽에 데이터를 삽입하게 해줍니다. Vector를 이용해서 데이터를 지정한다면 아래와 같이 되겠죠.

private var _stream:Vector.=new Vector.();

Stream에 데이터를 입력할 method와 Stream에서 데이터를 수신할 method가 필요합니다.

public function set data($val:String):void{
	_stream.unshift($val);
}
public function get data():String{
	return _stream[_location];
}

위에 보시면 제가 _location에 있는 데이터를 반환하게 했는데 _location을 지정한 적이 없으니 이를 위한 wrapping method가 필요합니다.

private var _location:uint=0;
public function set location($location:uint):void{
	_location=$location;
}

이로써 기본적인 모양은 갖춰졌습니다. 하지만 일단 어떤 변화가 Stream에 가해지면, Stream을 수신하고 있던 수신자들은 이를 알아차릴 수 있어야 합니다. as3는 이런 경우 EventDispatcher를 사용하길 권장합니다.

그럼 이를 적용하기 위해 EventDispatcher를 상속하고 이를 wrapping하는 부분을 set data에 반영해 봅시다.

static public const UPDATE:String='update';
public function set data($val:String):void{
	_stream.unshift($val);
	eventDispatch(new Event(UPDATE));
}

일단 여기까지 정리하여 하나의 클래스로 묶어봅시다. 클래스 이름은 CstringStream으로 짓고 필요한 클래스를 import 해보면 아래와 같이 정리됩니다.

package {

	import flash.events.*;

	public CstringStream extends EventDispatcher{

		static public const UPDATE:String='update';

		private var _stream:Vector.=new Vector.();
		private var _location:uint=0;

		public function CstringStream(){}

		public function set data($val:String):void{
			_stream.unshift($val);
			eventDispatch(new Event(UPDATE));
		}

		public function get data():String{
			return _stream[_location];
		}

		public function set location($location:uint):void{
			_location=$location;
		}
	}
}

소스에 대단할 게 없죠? 이제 이 Stream을 수신할 수신기를 만들어봅시다.

수신기는 여러 가지 방법으로 구현할 수 있겠습니다만, 눈에 보여야 잔잔한 감동이 오니 Textfield클래스를 이용해서 화면에 잘 보일 수 있게 만들어 보겠습니다. 수신기는 이벤트수신부분만 wrapping하는 수준이라 크게 어려운 점이 없기 때문에 곧장 전체 소스를 짜보겠습니다.

package {

	import flash.text.*;
	import flash.events.*;

	public class CtextSubscribe extends TextField{

		private var _stream:CstringStream;

		public CtextSubscribe(){}

		//stream을 등록하고 listener를 설정한다.
		public set textStream($stream:CstringStream):void{
			_stream=$stream;
			_stream.addEventListener(CstringStream.UPDATE,hnUpdate);
		}

		//update 발생시마다 갱신
		private function hnUpdate($e:Event):void{
			text=_stream.data;
		}
	}
}

수신측도 간단합니다 TextField를 상속받은 상태에서 CtextStream을 지정하면 Listener가 등록되며, Stream이 Update되었을 때, 그 내용을 갱신하도록 되어있습니다.

이제 화면에 표시해봅시다.

package {

	import flash.display.*;
	import flash.utils.*;

	public class Main extends Sprite{

		private var _stream:CstringStream;

		public function Main(){
			//stream생성
			_stream=new CstringStream();
			//text수신자 생성
			var subscribe:CtextSubscribe=new CtextSubscribe();
			//수신자에게 stream 설정
			subscribe.textStream=_stream;
			//화면에 등록
			addChild(subscribe);
			//마우스 클릭시마다 메세지를 바꾸기 위해 Listener등록
			stage.addEventListener(MouseEvent.Click,hnClick);
		}
		private function hnClick($e:MouseEvent):void{
			//현재 시간에 맞춰 데이터를 갱신
			_stream.data='데이터갱신'+getTimer().toString();
		}
	}
}

이제 화면을 클릭할 때마다 text가 갱신되는걸 볼 수 있습니다.

추가해서 두 가지 정도 숙제로 해보실 만한 게 있습니다.

1. location을 만들었는데 아직 사용을 안 해 봤네요? 사용하는 샘플을 만들어보세요.

2. 이대로는 클릭 시마다 Vector의 용량이 무제한 늘어납니다. 일정 수비범위까지만 커버하게 개선해보세요.


다음 번 Stream글에서는 실제 as3에서 Stream기반을 제공하고 있는 NetStream객체와 이를 수신하는 다양한 클래스들(Video등)을 살펴보겠습니다.

Stream을 이해하기

프로그래밍을 간단히 정의하면(개인적으로) 메모리에 명령과 데이터를 적재하고 실행하는 것이라 생각합니다.
명령부분에 특화된 기술이 알고리즘이라면 데이터에 관련된 기술은 자료구조라 하겠습니다.

결국 프로그래머라는 인종은 알고리즘의 근간이 되는 논리력을 갖추고 실제 구현의 기본이 되는 물리, 수학 등을 튼튼히 해야 함과 동시에 데이터를 저장하기 위한 자료구조에 대해 끊임없이 연구하고 공부해야 합니다.

알고리즘은 다른 글에서 언급하기로 하고 이번엔 자료구조에 눈을 돌려보죠.

자료구조는 데이터를 ‘어떻게?’ 라는 질문으로 시작합니다. 더 상세하게 생각해보면 ‘어떻게 삽입, 삭제, 수정을 할까?’로 시작해서 dump, backup, linking, sync 등의 주제로 넘어가게 됩니다. 이러한 목적에 부합하는 좋은 자료구조를 설계하고 활용하는 부분이 자료구조의 핵심입니다.

자료구조는 각 프로그램에서 요구하는 요구사항이 다양하기 때문에, 마찬가지로 여러 가지 구현방식이 있습니다. 모든 상황에 적합한 자료구조는 사실 상 제작이 불가능하기 때문에 요구사항을 분류하여 각 분류 별로 적합한 자료구조를 짜놓고 사용하는 게 일반적입니다.

이 중에 Stream이란 방식이 있습니다. 바로 오늘 설명할 주제입니다.

image영어사전을 찾아보시면, 아니 영어 처음 배울 때를 떠올려보시면 이 단어가 흐르는 개울 같은 걸 의미한다게 기억 나실 겁니다. Stream에서 가장 핵심적인 부분은 흐른다는 점으로, 한 지점을 보고 있으면 그 위치의 물이 언제나 새롭게 흘러오고 다시 흘러나간다는 것이죠.

즉 Stream이란 자료구조가 적합한 것은 바로 왼쪽 그림처럼 사용해야 할 부분은 빨간 점선 박스처럼 고정되어있는데, 거기에 데이터만 바뀌는 경우 입니다.

대표적으로 동영상이나 음악데이터가 있습니다. 이들은 언제나 똑같은 플레이어를 보여준 상태에서 그 안에 데이터만 계속 흐름에 맞춰 갱신하여 보여주거나 들려줍니다. 우리가 Stream이전에 Streaming이란 단어에 더욱 길들여져 있는 이유입니다. Streaming이란 Stream데이터를 구체적으로 이용하고 있는 상황을 나타냅니다.

이제 Stream의 기본 개념이 잡혔으니 자료구조로서의 Stream의 특징을 정리해보겠습니다.

1. FIFO
선입선출 데이터구조입니다. 위에 그림을 보시면 당연히 이해되시겠지만 먼저 넣은 데이터부터 나옵니다.
이러한 Stream의 특성은 Queue와 매우 잘 맞기 때문에 실제로 Stream을 Queue나 CircleQueue로 구현하는 경우가 많습니다.

2. Location
Stream에는 위치라는 개념이 있습니다. 위의 그림에선 바로 빨간 박스의 위치로 물줄기 위냐 아래냐에 따라서 같은 시점에도 만나는 물이 다릅니다. 이러한 위치라는 개념은 다시 세 가지로 나눠볼 수 있습니다.
- View Location : 데이터를 지켜보는 위치입니다(빨간 박스)
- Start Location : Stream이 시작된 시작점입니다.
- End Loaction : Stream의 끝을 나타내는 포인트입니다.

3. async(hronism)
이거 어려운 단어인데 ajax의 약자이기도 해서 제법 유명해졌죠? 비동기화 특성입니다. 어떤 것들 사이가 비동기냐 하면 데이터를 생성하는 측과 그 데이터를 이용하는 측 간에 동기화가 필요 없다는 점입니다.
계속해서 위의 예를 들자면, 개울은 저 멀리 산꼭대기에서 생성되었습니다. 하지만 빨간 박스에서 바라보고 있는 시점과 동기화 될 필요가 없습니다. 언제 이 물이 만들어져서 흘러왔는지는 모르겠지만 그저 지금 그 물을 떠 마시고 구경할 수 있습니다. 현실 세계에서 서버가 동영상을 여러분의 브라우저로 보내면 서버가 언제 보낸 건지는 사실 중요하지 않습니다. 여러분은 그걸 받아서 재생합니다. 서버가 빠르게 보낸다고 해도 여러분의 브라우저가 그것을 차곡차곡 쌓아서 일정한 속도로 재생하게 됩니다. 여기서 포인트는 쌓았다가 아니라 서버가 그걸 생성해서 보내는 것과 여러분이 그걸 플레이 하는 것과 비동기 라는 점입니다.

4. Publisher & Subscriber
이 단어 익숙하신 분들은 아마도 WAS개발자나 플래시 쪽이라면 FMS쪽 개발자 분들이 아닐까요?
이 개념은 유사한 다른 단어로 대체되는데, 가장 유명한 Client, Server를 비롯하여 생산자, 소비자모델 같은 제법 친숙한 개념입니다. 이들 사이에 미묘한 차이점이 존재하지만 공통점은 누군가는 만들고 누군가는 그걸 이용한다는 점이겠죠. Stream은 사실 만드는 측과 소비하는 측을 완전히 분리할 수 있다는 게 큰 장점입니다. 만드는 쪽에서는 소비하는 측의 상황을 전혀 고려하지 않아도 괜찮고, 소비하는 측 역시 만들어내는 쪽의 상황을 고려하지 않습니다. 단 공급해주는 측에서 데이터가 동시에 너무 많이 오거나 끊어지거나 하는 상황을 만나는데, 이 때의 처리를 소비자측에서 해야하기 때문에 소비측의 구현이 항상 더 복잡합니다.

이러한 4가지 대표적인 특징으로 도출되는 Stream의 큰 장점은 원격어플리케이션 모델에 매우 적합하다는 점입니다.

즉 데이터를 생산해내는 측이 네트웍 너머에 있어도 소비하는 측에 큰 무리없이 구현할 수 있는 근간이 됩니다. 따라서 현대 분산 프로그래밍 환경에서는 동영상이나 음악 분야 외에도 엄청나게 Stream형태의 데이터를 이용하고 있습니다. Stream을 Queue로 구현한 경우 신뢰성도 매우 뛰어나기 때문에(오류검출이 가능하므로) 사실상 인터넷세상인 현재 상황에서 여러분이 직면하는 어플리케이션은 전부 Stream을 사용하고 있다고 봐야겠죠.

Stream은 우리 컴퓨팅 기저부터 있습니다. TCP소켓이 어떻게 데이터를 처리하는가부터 시작에서 Window의 Message가 어떻게 OS에 전달되는가까지 일단 Stream이 전부 깔려 있다고 생각하시면 됩니다.

다음에는 Stream을 실제 구현해보고, Stream을 어디에 활용할 수 있는가에 대한 기초를 다뤄보겠습니다.