본문

OMNeT++ Signals #1. Signal/Listener

OMNeT++ 4.1버전부터 등장한 simulation signals, 즉, signal이라는 개념은 모델의 통계 속성을 외부에 노출시키고, 시뮬레이션 동작시에 모델의 변화를 즉각적으로 인지하고 처리할 수 있게 해준다. 특히 publish-subscribe 형태의 통신을 사용함으로서, 서로간에 긴밀한 연결관계를 필요로하지 않으며 또한 1-대-다 혹은 다-대-다 형태의 관계를 수립하게 해준다. 


* Signal & Listener

시그널은 컴포넌트(모듈, 채널)으로부터 발생되고, 모델의 최상위 계층까지 전파된다. 어느 수준에서든, 이를 수용할 리스너를 등록할 수 있으며(callback object) 이러한 리스너는 시그널 값이 발생될 때 이를 인지할 수 있다. 시스템 모듈상에 등록된 리스너는 시뮬레이션 전반으로부터 발생하는 모든 시그널을 수신할 수 있다. 참고로, 채널의 상위계층은 연결을 담당하는 (compound) 모듈이지, 이와 연결된 게이트나 채널을 가진 주체가 아니라는것을 알아야 한다.


시그널은 자신의 시그널 이름으로 구분된다. 하지만, 효율성을 고려하여, OMNeT++에서는 simsignal_t로 typedef된 수량적 구분자(numeric identifier)를 사용한다. 시그널 ID에대한 시그널 이름의 매핑은 전역적이어서, 특정 시그널 이름을 호출하는 모든 모듈이나 채널들은 동일한 시그널 ID를 반환받게 된다.


리스너는 시그널의 출처에 관계없이 시그널 이름 혹은 시그널 ID으로 시그널을 구분한다. 예를 들어, 만약 Queue, Buffer라는 두개의 서로 관계없는 모듈이 존재하고 이들이 'length'라는 시그널을 발생시킬때, 'length'라는 시그널을 처리하는 리스너는 이 둘으로부터 시그널을 받게 된다. 원한다면, 리스너는 callback 함수에서 인자를 사용하여 시그널의 출처를 알아낼 수 있겠지만 시그널 프레임워크상에서는 이런 기능은 기본적으로 탑재되어 있지는 않는다. 시그널의 호출과 수신은, 컴포넌트 상에서 오버로드된 emit()함수를 통해 시그널을 호출하고, 리스너 내에서 오버로드된 recieiveSignal()함수를 사용하여 이를 받을 수 있다.


* Signal

시그널에 관련한 메소드는 cComponent에 정의되어 있으며, 이는 cModule과 cChannel상에서 사용할 수 있다. registerSignal()메소드에서 시그널을 등록할 수 있으며, 이에 대한 반환값은 simsignal_t 자료형을 가진다(위에서 언급했듯, 비록 시그널은 문자열로 지정되지만 내부 효율을 위해 simsignal_t 자료형으로 저장되는 것이다) 이에 대한 상대적인 메소드로 getSignalName()메소드가 있어서, simsignal_t 값을 받으면 이에 대한 문자열을 const char *으로 반환한다.


simsignal_t lengthSignalId = registerSignal("length");

const char *signalName = getSignalName(lengthSignalId); // --> "length"


emit()함수에서는 모듈이나 채널로부터 시그널을 발생시킬 수 있다. emit()은 시그널 ID(simsignal_t)와, 전달할 값, 총 두개의 인자를 사용하는데, 전달 할 값으로는 long, double, simtime_t, const char * ,또는 cObject * .가 지원이 된다. 다른 자료형을 전달하고 싶다면 위의 자료형으로 캐스팅 하거나, cObject에 서브클래스된 객체로 wrapping시키는 방법이 있다.


mayHaveListeners() 또는 hasListeners()메소드를 사용하여 해당 시그널에 대한 리스너가 존재하는지 판별할 수 있다. mayHaveListeners() 메소드는 캐시된 정보상에서 이를 판별하기 때문에 매우 효율적이다. 반면 hasListeners()는 캐시된 정보 뿐 아니라 모듈 계층을 전반적으로 검색할 수 있기 때문에 비교적 느리다 할 수 있다. 

if (mayHaveListeners(distanceToTargetSignal)){

     double d = sqrt((x-targetX)*(x-targetX) + (y-targetY)*(y-targetY));emit(distanceToTargetSignal, d);

}


* Listener

subscribe() 메소드는 리스너를 등록하는 데에 사용된다. 리스너는 cIListener 클래스를 상속하는 객체들이다. 하나의 리스너 객체는 다중의 시그널을 수신할 수 있다. subscribe()는 시그널(ID 또는 문자열)과 리스너 객체, 두개의 인자를 가진다. 물론 지역적(local)이지 않은 다른 모듈에서도 시스템 모듈 수준에서 리스너를 등록할 수 있다. unsubscribe()도 마찬가지이다. 단, 하나의 리스너에 동일한 시그널을 두번 이상 수신하도록(subscribe) 하는것은 불가능하다. 또한 리스너가 삭제되기(deleted) 전에 subscribe하는 모든 컴포넌트에서 unsubscribe가 이루어 져야 한다. 리스너가 시그널을 위해 등록되었는가(subscribed)를 판별하는 것은 isSubscribed()를 통해 가능하다.


cIListener* listener = ...;

simsignal_t lengthSignalId = registerSignal("length");

subscribe(lengthSignalId, listener);  //subscribe("length", listener);

simulation.getSystemModule()->subscribe("length", listener);

unsubscribe(lengthSignalId, listener);   //unsubscribe("length", listener);

if (isSubscribed(lengthSignalId, listener)) ...


추가적으로, 컴포넌트에 등록된 시그널의 리스트를 받아오는 메소드(getLocalListenedSignals())를 사용할 수 있으며, 해당하는 시그널에 대한 리스너의 리스트를 받아오는 메소드(getLocalSignalListeners()) 또한 존재한다. 전자는 std::vector<simsignal_t>; 를 반환하고, 후자는 시그널 ID (simsignal_t)를 인자로 받아 std::vector<cIListener*>;을 반환한다.


EV << "Signal listeners:\n";

std::vector<simsignal_t> signals = getLocalListenedSignals();

for (unsigned int i = 0; i < signals.size(); i++) {

     simsignal_t signalID = signals[i];

     std::vector<cIListener*> listeners = getLocalSignalListeners(signalID);

     EV << getSignalName(signalID) << ": " << listeners.size() << " signals\n";

}


리스너는 cIListener 클래스에서 서브클래스된 객체이며, 이의 메소드는 아래와 같이 선언되어있다.


class cIListener {

     public:

          virtual ~cIListener() {}

          virtual void receiveSignal(cComponent*src, simsignal_t id, long l) = 0;

          virtual void receiveSignal(cComponent*src, simsignal_t id, double d) = 0;

          virtual void receiveSignal(cComponent*src, simsignal_t id, simtime_t t) = 0;

          virtual void receiveSignal(cComponent*src, simsignal_t id, const char*s) = 0;

          virtual void receiveSignal(cComponent*src, simsignal_t id, cObject*obj) = 0;

          virtual void finish(cComponent*component, simsignal_t id) {}

          virtual void subscribedTo(cComponent*component, simsignal_t id) {}

          virtual void unsubscribedFrom(cComponent*component, simsignal_t id) {}

};


receiveSignal() 메소드에서는 (emit()을 통해)시그널이 발생될 때 마다, 해당하는 리스너에서의 receiveSignal()메소드가 호출된다. 현재 컴포넌트의 finish() 메소드가 호출될 경우 그 컴포넌트에 포함된 리스너들에서도 마찬가지로 finish()메소드를 호출한다. 만약 리스너가 다양한 신호 또는 다양한 컴포넌트와 연관되어 있다면, 이 메소드는 여러번 호출 될 것이다. 중요한것은, 만약 시뮬레이션이 오류로 인해 중단될 경우에는 finish()가 호출되지 않는다는 것이며, 따라서 자원정리를 하는데 사용되어서는 안된다는 것이다. subscribedTo(), unsubscribedFrom()메소드들은 각각 현재 리스너 객체가 신호가 subscribe/unsubscribe될때 호출되어, 리스너의 등록정보에 대해 추적할 수 있다. 물론 unsubscribedFrom() 메소드를 사용하여 자기 자신을 삭제하는 방법을 사용할 수 있겠지만, 기억할 것은 다른 시그널에도 현재 리스너가 등록되어 있으면 안된다는 것이다(그렇지 않으면 emit(), unsubscribedFrom()등이 호출될 때에 오류가 발생한다)


시뮬레이션 모델에 있어서, 다른 모듈이나 채널, 객체 또는 모델 토폴로지상의 캐시정보에 대한 참조(reference)를 가지고 있는 것이 이 도움이 될 때가 있다.  하지만, 이러한 포인터 혹은 데이터는, 런타임시 모델이 변경되는 경우에 있어 무용지물(invalid)이 될 수 있다. 때문에 이는 모델 변경시마다 갱신되거나 다시 계산 될 필요가 있으며, 이를 위해 PRE_MODEL_CHANCE와 POST_MODEL_CHANGE OMNeT++내장 시그널(이들은 simsignal_t 매크로이다)을 사용할 수 있다. 이 signal이 발생될때 cModelChangeNotification 클래스의 서브클래스인 객체들이 함께 넘어가게 된다. 리스너 상에서 dynamic_cast<>를 통해 어떠한 notification 클래스가 전달되었는지 확인할 수 있다.


class MyListener : public cListener{

     ...

};

void MyListener::receiveSignal(cComponent *src, simsignal_t id, cObject *obj){

     if (dynamic_cast<cPreModuleDeleteNotification *>(obj)){

          cPreModuleDeleteNotification *data = (cPreModuleDeleteNotification *)obj;

          EV << "Module " << data->module->getFullPath() << " is about to be deleted\n"

     }

}


또한 아무 모듈에서나 발생하는 notification을 처리하고자 한다면, 아래와 같이 시스템 모듈상에 리스너를 등록할 수도 있다.

simulation.getSystemModule()->subscribe(PRE_MODEL_CHANGE, listener);



원문출처 : OMNeT++ User Manual Version 4.2.2 : 4.14 Signals

댓글

Holic Spirit :: Tistory Edition

design by tokiidesu. powerd by kakao.