Implementing a New Manet Unicast Routing Protocol in NS2 Version 0.2
| Network/Ns2_Lecture 2009. 1. 18. 03:47Implementing a New Manet Unicast Routing Protocol in NS2 번역입니다.
수정할 내용이 있으면 바로 알려주세요.
NS2에서 새로운 Manet 유니캐스트 라우팅 프로토콜을 구현하기
1 소개
작년 동안에, 우리는 ns-유저들 메일링 리스트에서 같은 질문을 요구하는 많은 사람들을 목격해왔다. 내가 NS2에 적합한 내 소유의 프로토콜을 어떻게 개발하는가? 이 문서를 작성함으로써 우리는 NS-2.27에 적합한 manet 라우팅 프로토콜 더할 필요가 있는 그런 연구자들을 돕기를 희망한다. 우리는 우리의 문서를 유니캐스트 프로토콜에 초점을 맞춘다. 그 문서는 ns-2에서 시뮬레이션들을 수행하는 것에 그럭저럭 잘 아는 그런 사람들을 목표로 하고, 그들은 그들 소유의 프로토콜들을 구현하기 위해서 한 단계 앞으로 가기를 원한다. 이 문장에서 우리가 기술한 모든 것은 NS2의 2.27 버전과 관련이 있지만, 그것은 다른 버전들에서도 마찬가지로 유용할 것이다.
우리는 독자가 NS2 기본들을 잘 안다고 가정한다. 그것은 “Marc Greis’ Tutorial” [1]을 읽었고 적어도 대부분 이해했다는 것을 의미한다. 만약 네가 “The ns Manual” [2], 특히 3-5, 10-12, 15-16, 23, 25 그리고 29장을 또한 훑어본다면 매우 유용할 것이다. 우리는 이런 문장 처음부터 끝까지 여러 번 그것들을 언급할 것이고 네가 그것들을 읽도록 격려할 것이다. 너의 소유의 라우팅 프로토콜을 코딩하기 전에, 너는 다른 구현된 프로토콜들로 시뮬레이션들을 어떻게 수행하는 지 알아야 하고 너는 시뮬레이터를 잘 알고 편안하게 느낄 것으로 예상된다. 이것은 이 문서를 읽는 동안에 많은 오해들과 의심들을 피할 것이다.
게다가 이 tutorial은 프로그래밍에 관한 것이다. 너는 C++과 (조금) Tcl 프로그래밍에 대한 지식이 필요하다. 만약 네가 이런 프로그래밍 언어들을 충분히 경험되지 않았다면, 너는 인터넷에서 자유로이 이용 가능한 그것들에 관한 임의의 훌륭한 자원들을 첫째로 읽어야 한다.
2 시작하면서
우리는 protoname으로 불리는 새로운 manet 라우팅 프로토콜을 단계별로 구현할 예정이다. 이 프로토콜은 유용하지 않지만, 다른 라우팅 프로토콜들과 함께 몇 개의 공통점들을 가지기엔 충분히 일반적이다. 너도 알 것이기 때문에 (만약 아니라면, 우리가 말했던 것을 첫째로 읽어라!) 우리는 C++를 사용해서 라우팅 프로토콜을 구현할 예정이고 그리고 나서 우리는 Tcl 스트립트들로 시나리오들을 기술하는 시뮬레이션들을 할 것이다.
우리의 코드를 할당하기 위해서 우리는 너의 NS2 기반 디렉토리 안쪽에 protoname으로 불리는 새로운 디렉토리를 첫째로 생성할 것이다. 우리는 거기에 5 개의 파일들을 생성할 것이다:
protoname.h 이것은 (만약 있다면) 모든 필요한 타이머들이 정의될 것인 헤더 파일이고 프로토콜의 기능성을 수행하는 라우팅 에이전트이다.
protoname.cc 이 파일 안에는 모든 타이머들, 라우팅 에이전트 그리고 Tcl 후크들이 실제로 구현된다.
protoname_pkt.h 여기에는 MANET에서 노드들 사이에 교환할 필요가 있는 모든 패킷들 protoname 프로토콜이 선언된다.
protoname_rtable.h 우리 소유의 라우팅 표가 선언되는 헤더 파일
protoname_rtable.cc 라우팅 표 구현
너는 원하는 대로 너의 코드를 조직화할 수 있다. 즉, 너는 그런 이름들 또는 다른 것들과 함께 파일들을 더 또는 덜 생성할 수 있다; 그것은 단지 힌트이다. 우리의 조언은 그런 파일들을 적어도 사용하는 것이고 그것들이 필요한 만큼 더 생성하는 것이다.
이제 우리는 우리의 “물리적인” 구조 (파일들)를 가지고 있기 때문에, “논리적인” 것 (클래스들)을 계속하자. NS2에서 라우팅 프로토콜을 구현하기 위해서 너는 에이전트 클래스로부터 물려받음으로써 에이전트를 생성해야 한다. 10장 [2]의 맨 처음에서 우리는 “에이전트들은 네트워크-레이어 패킷들이 구성되거나 소비되는 엔드포인트들을 의미하고, 다양한 레이어들에서 프로토콜들의 구현 안에 사용된다”를 읽을 수 있다. 네가 이해할 수 있는 대로, 이것은 우리가 우리의 라우팅 프로토콜을 구현하기 위해서 코드를 해야할 것인 주된 클래스이다. 게다가, 이 클래스는 Tcl 인터페이스와 연계를 제공하고, 그래서 우리는 Tcl 안에 쓰여진 시뮬레이션 스크립트들을 통해서 우리의 라우팅 프로토콜을 제어할 수 있을 것이다.
우리의 라우팅 에이전트는 내부의 상태와 (항상 필요하지는 않는) 라우팅 표를 유지할 것이다. 내부의 상태는 새로운 클래스로써 또는 라우팅 에이전트 안쪽에 속성들의 집합으로써 나타낼 수 있다. 우리는 새로운 클래스, protoname_rtable로써 라우팅 표를 다룰 수 있을 것이다.
또한 우리의 새로운 프로토콜은 자신의 제어 패킷들의 형식을 의미할 것인 적어도 하나의 새로운 패킷 유형을 정의해야 한다. 우리가 말했던 대로 이런 패킷 유형들은 protoname/protoname_pkt.h에 정의된다. 그 프로토콜이 주기적으로 또는 이벤트의 발생으로부터 약간의 시간 후에 패킷들을 보낼 필요가 있을 때, 타이머 클래스에 의지하는 것은 매우 유용하다. 우리는 규칙적인 간격들에서 이런 패킷들을 보내기 위해서 우리 소유의 타이머들을 코드하는 예를 보여준다. 타이머들은 많은 다른 경우들에서도 또한 유용하다. 정해진 시간에서 지워져야 하는 내부 정보의 몇 가지 종류들을 저장할 필요가 있는 protoname을 상상해라. 가장 좋은 해결책은 그런 일을 할 수 있는 맞춤의 타이머를 생성하는 것이다. 타이머는 라우팅 표 안에서 입력의 시간수명을 명시하기 위해서 또한 사용되어야 한다. 일반적으로, 우리가 주어진 시간에서 업무를 스케쥴해야할 때는 언제나 타이머를 사용할 것이다.
세부적인 것들로 가기전에 우리가 알아야 하는 또 다른 중요한 클래스가 있다. 트레이스 클래스는 시뮬레이션 동안에 무엇이 일어 났는지에 대한 정보를 가진 로그 파일들을 쓰기 위한 기반이다.
그리고 마지막 힌트: 네가 너의 코드 안에 디버그 메시지를 프린트 하기를 원할 때, 25장 [2]에서 제안되는 것처럼 디버그() 함수를 사용하는 것은 도움이 된다. 이것은 네가 너의 시뮬레이션 스크립트들로부터 디버깅을 켜거나 끄도록 허락하고 다른 프로그래머들이 읽는 것이 쉽다.
3 패킷 유형들
이제 네가 기초들을 벌써 알기 때문에, 새로운 파일을 생성하고 그것을 protoname/protoname_pkt.h 이라고 부르자. 여기에서 우리는 우리의 새로운 패킷(들) 유형과 관련된 모든 데이터 구조들, 상수들, 매크로들을 넣을 예정이다. 다음 예제를 보자.
protoname/protoname_pkt.h
1: #ifndef __protoname_pkt_h__
2: #define __protoname_pkt_h__
3:
4: #include <packet.h>
5:
6: #define HDR_PROTONAME_PKT(p) hdr_protoname_pkt::access(p)
7:
8: struct hdr_protoname_pkt {
9:
10: nsaddr_t pkt_src_; // Node which originated this packet
11: u_int16_t pkt_len_; // Packet length (in bytes)
12: u_int8_t pkt_seq_num_; // Packet sequence number
13:
14: inline nsaddr_t& pkt_src() { return pkt_src_; }
15: inline u_int16_t& pkt_len() { return pkt_len_; }
16: inline u_int8_t& pkt_seq_num() { return pkt_seq_num_; }
17:
18: static int offset_;
19: inline static int& offset() { return offset_; }
20: inline static hdr_protoname_pkt* access(const Packet* p) {
21: return (hdr_protoname_pkt*)p->access(offset_);
22: }
23:
24: };
25:
26: #endif
줄들 8-24는 우리가 정의하는 중인 새로운 패킷 유형을 의미하는 hdr_protoname_pkt를 선언한다. 줄들 10-12에서 우리는 우리의 패킷이 가지는 3가지 처리되지 않은 속성들을 볼 수 있다. 그것들은 다음의 유형들이다:
nsaddr_t 네가 NS2에서 네트워크 주소를 선언하기를 원할 때마다 매번 너는 이 유형을 사용해야 한다.
u_int16_t 16 비트들 부호 없는 정수
u_int8_t 8 비트들 부호 없는 정수
이런 모든 유형들과 더 많은 것들은 헤더 파일 config.h 안에 정의된다. 우리는 독자가 이런 파일을 훑어보고 거기에 정의된 유형들을 사용하는 것을 격려한다. 다른 변수들과 그것들을 구별하기 위한 밑줄과 함께 끝내기로 예상되는 처리 되지 않은 속성들의 이름들은 또한 언급할 만한 가치가 있다. (25장 [2]를 보라)
줄들 14-16은 정의된 속성들을 위한 멤버 함수들이다. 이것은 의무적이지는 않지만 12장 [2]에서 제안되는 “좋은 습관”이다 (그리고 우리는 그것을 실제로 지원한다!)
줄 4는 패킷 클래스를 정의하는 파일 common/packet.h를 포함한다. (12장 [2]를 보라) 패킷들은 시뮬레이션 안에서 오브젝트들 사이에 정보를 교환하기 위해서 사용되고, 우리의 목표는 우리의 새로운 struct hdr_protoname_pkt를 그것들에 더하는 것이다. 우리의 제어 패킷들을 그렇게 하는 것은 시뮬레이션 안에서 노드들에 의해서 보내지고 받아들여 질 수 있을 것이다. 우리가 그것을 어떻게 할 수 있는가? 이런 질문에 답하기 위해서 우리는 모든 패킷 헤더들이 패킷 클래스에 의해서 어떻게 저장되는 지를 알아야 하고, 그 답은 패킷들의 필드들이 보관되는 부호 없는 케릭터들의 배열 (비트들의 가방)을 사용하는 것이다. 구체적인 패킷 헤더에 접속하기 위해서 그것이 위치되는 오프셋을 제공할 필요가 있다. 그리고 그것은 정확하게 우리가 줄들 18-22를 통해서 하는 것이다. 우리는 고정된 (모든 hdr_protoname_pkt 구조들에 공통적인) 오프셋, 그것에 접속하기 위한 하나의 멤버 함수 그리고 하나의 패킷이 주어진 hdr_protoname_pkt을 되돌려보내는 함수를 정의한다. 게다가, 줄 6에서 우리는 이런 마지막 함수를 사용하기 위한 매크로를 생성한다.
하나의 업무가 남아있다: 우리의 패킷 헤더를 Tcl 인터페이스에 묶는 것. 우리는 다음의 코드로 protoname/protoname.cc 안에 그렇게 할 것이다. 우리가 볼 수 있듯이 우리는 우리의 패킷 헤더의 오프셋을 Tcl을 통해서 접근하기 쉽게 하는 중이다.
protoname/protoname.cc
1: int protoname_pkt::offset_;
2: static class ProtonameHeaderClass : public PacketHeaderClass {
3: public:
4: ProtonameHeaderClass() : PacketHeaderClass("PacketHeader/Protoname",
5: sizeof(hdr_protoname_pkt)) {
6: bind_offset(&hdr_protoname_pkt::offset_);
7: }
8: } class_rtProtoProtoname_hdr;
4 라우팅 에이전트
이제 우리는 에이전트 자체의 프로그래밍을 시작한다. protoname/protoname.h 안쪽에 우리는 그것의 작업을 하는데 있어서 프로토콜을 돕도록 요구되는 속성들과 함수들을 포함하는 Protoname이라 불리는 새로운 클래스를 정의한다. 타이머들의 사용을 묘사하기 위해서 우리는 protoname이 몇 개의 제어 패킷들을 주기적으로 발산할 필요가 있는 예방의 라우팅 프로토콜임을 가정한다. 다음 코드는 그런 예제를 보여준다.
protoname/protoname.h
1: #ifndef __protoname_h__
2: #define __protoname_h__
3:
4: #include "protoname_pkt.h"
5: #include <agent.h>
6: #include <packet.h>
7: #include <trace.h>
8: #include <timer-handler.h>
9: #include <random.h>
10: #include <classifier-port.h>
11:
12: #define CURRENT_TIME Scheduler::instance().clock()
13: #define JITTER (Random::uniform()*0.5)
14:
15: class Protoname; // forward declaration
16:
17: /* Timers */
18:
19: class Protoname_PktTimer : public TimerHandler {
20: public:
21: Protoname_PktTimer(Protoname* agent) : TimerHandler() {
22: agent_ = agent;
23: }
24: protected:
25: Protoname* agent_;
7
26: virtual void expire(Event* e);
27: };
28:
29: /* Agent */
30:
31: class Protoname : public Agent {
32:
33: /* Friends */
34: friend class Protoname_PktTimer;
35:
36: /* Private members */
37: nsaddr_t ra_addr_;
38: protoname_state state_;
39: protoname_rtable rtable_;
40: int accesible_var_;
41: u_int8_t seq_num_;
42:
43: protected:
44:
45: PortClassifier* dmux_; // For passing packets up to agents.
46: Trace* logtarget_; // For logging.
47: Protoname_PktTimer pkt_timer_; // Timer for sending packets.
48:
49: inline nsaddr_t& ra_addr() { return ra_addr_; }
50: inline protoname_state& state() { return state_; }
51: inline int& accessible_var() { return accessible_var_; }
52:
53: void forward_data(Packet*);
54: void recv_protoname_pkt(Packet*);
55: void send_protoname_pkt();
56:
57: void reset_protoname_pkt_timer();
58:
59: public:
60:
61: Protoname(nsaddr_t);
62: int command(int, const char*const*);
63: void recv(Packet*, Handler*);
64:
65: };
66:
67: #endif
줄들 4-10은 우리의 에이전트에 의해서 요구되는 헤더 파일들을 포함하기 위해서 사용된다. 아래에 우리는 그것들이 무엇을 위해 유용한지를 설명한다.
protoname/protoname_pkt.h 우리의 패킷 헤더를 정의한다.
common/agent.h 에이전트 기반 클래스를 정의한다.
common/packet.h 패킷 클래스를 정의한다.
common/timer-handler.h TimerHandler 기반 클래스를 정의한다. 우리는 우리의 맞춤 타이머들을 생성하기 위해서 그것을 사용할 것이다.
trace/trace.h 시뮬레이션 결과를 트레이스 파일로 쓰기 위해 사용되는, 트레이스 클래스를 정의한다.
tools/random.h 가짜의-랜덤 숫자들을 발생시키는 데 유용한, 랜덤 클래스를 정의한다. 우리는 그것을 곧 사용할 것이다.
classifier/classifier-port.h 패킷들을 상위 레이어들에게 올려 보내기 위해서 사용되는 포트분류자 클래스를 정의한다.
줄 12는 시뮬레이터 시계 안에 현재 시간을 얻기 위한 유용한 매크로를 정의한다. 그것은 스케쥴러 클래스의 단일의 인스턴스에 접속함으로써 실행된다. 이 오브젝트는 시뮬레이션 동안에 생산된 모든 이벤트들과 시뮬레이터의 내부 시계를 관리한다. (4장 [2]을 보라)
또 다른 매크로는 줄 13에 있다. 그것은 [0-0.5] 간격 안쪽에 랜덤 숫자를 획득하기 위한 단지 쉬운 방법이다. 이것은 결국에 충돌들을 생산하고 그래서 이런 패킷들을 송신하는 시간에서 딜레이되는, 자신의 이웃들과 함께 노드의 동기화를 피하기 위해 제어 패킷들의 송신을 랜덤화하기 위해서 보통 사용된다.[1]
줄들 19-27은 주기적인 제어 패킷들을 송신하기 위한 우리의 맞춤 타이머를 선언한다. 우리의 Protoname_PktTimer 클래스는 TimerHandler로부터 물려받고 그것을 생성하는 라우팅 에이전트에 관계가 있다. 이것은 라우팅 에이전트에게 새로운 제어 패킷을 전송하고 다음 것을 스케쥴하라고 말하기 위한 콜백으로써 사용된다. 우리는 expire() 방법에 어떻게 과부하가 걸리는 지를 기술할 때, 뒤에 이것을 더 봐야 한다. 이런 콜백들을 실행하기 위해서 라우팅 에이전트는 동료 클래스로써 Protoname_PktTimer를 다룰 필요가 있다. (줄 34)
Protoname 클래스는 줄들 31-65 내에 정의된다. 그것은 자신 소유의 주소, 내부의 상태, 라우팅 표, Tcl에서 접근하기 쉬운 변수 그리고 시퀀스 넘버들을 출력 패킷들에 할당하기 위한 카운터 (줄들 37-41)를 캡슐화한다. protoname_state_는 클래스 자체 또는 그것의 작업을 하기 위해서 Protoname 클래스에 의해 요구되는 속성들의 집합일 수 있다. accessible_var_는 Tcl 스크립트들 또는 쉘 명령어들로부터 읽혀지고 쓰여지는 것으로 생각된다. 이것은 많은 상황들에서 유용한데 왜냐하면 그것은 유저들이 시뮬레이터의 재-컴파일없이 자신들의 스크립트들을 통해서 시뮬레이션 행동을 바꾸는 것을 허락하기 때문이다.
포트분류자 오브젝트는 줄 45에서 선언된다. 너는 노드의 구조를 이해하기 위해서 5장 [2]를 읽어야 한다. 거기서 노드가 주소 분류자와 포트 분류자로 어떻게 구성되는 지 너는 볼 수 있을 것이다. 첫 번째는 적절한 링크로 들어오는 패킷들을 안내하거나 그것들을 상위 레이어 에이전트에게 충당하기 위해서 그것들을 운반할 것인, 포트 분류자에게로 그것들을 통과하기 위해서 사용된다. 그것이 라우팅 에이전트가 포트 분류자를 필요로 하는 이유이다. 그것이 자신으로 예정된 데이터 패킷들을 받을 때 그것은 일치하는 에이전트에게 그것들을 주기 위해서 dmux_를 사용할 것이다.[2] 모바일 노드의 세부적인 아키텍처는 [2]의 16장에서 설명된다.
또 다른 중요한 속성은 트레이스 오브젝트이다. (줄 46을 보라) 그것은 트레이스 파일 안에 저장되기 위한 로그들을 생산하기 위해서 사용된다. 우리의 예제에서 유저가 Tcl 인터페이스에서 그것을 요청할 때마다 라우팅 표의 내용들을 쓰기 위해서 우리는 그것을 사용한다. 만약 네가 패킷들에 관해서 트레이싱 정보를 쓰는 데만 단지 관심이 있다면 이것이 필수는 아니다. 그 경우에서, 그런 로깅 함수들은 다른 위치에서 구현된다. (우리가 6절에서 봐야 할 것처럼)
줄 47은 우리의 맞춤 타이머를 선언한다. 그리고 줄들 49-51은 몇 개의 내부 속성들에 접속하기 위한 멤버 함수들이다.
줄 53에서 함수는 데이터 패킷들을 그것들의 올바른 목적지로 전송하기 위해서 사용될 것이다. 줄 54에서 함수는 제어 패킷이 받아들여질 때마다 호출될 것이고, 줄 55에서 그것은 제어 패킷을 전송하기 위해서 선포된다. 줄 57은 우리의 맞춤 타이머 기한 만료를 스케쥴하기 위해서 사용되는 함수를 선언한다.
줄들 61-63은 클래스 Protoname의 공개 함수들을 포함한다. 건설자는 라우팅 에이전트의 주소로써 사용되는 식별자를 인수로써 받는다. Protoname은 구현될 필요가 있는 두 개의 메인 함수들을 에이전트 기반 클래스로부터 물려받는다: recv()와 command(). recv()는 에이전트가 패킷을 받을 때마다 불려진다. 노드 자체 (실제로는 UDP 또는 TCP와 같은 상위 레이어)가 패킷을 발생하는 중이거나 다른 노드로부터 패킷을 받는 중일 때 이것은 일어날 것이다. command() 함수는 3장 [2]에서 기술된 것처럼 Tcl로부터 선포된다. 그것은 우리의 Tcl 코드로부터 몇 개의 업무를 하도록 C++ 오브젝트에게 요청하기 위한 방식이다. 한번 우리가 절 4.3을 경험하면 너는 이것을 더 이해할 것이다.
이제 너는 Protoname의 인터페이스가 어떻게 있는 지 알기 때문에, 그것의 구현을 계속할 시간이다. 다음 하위 절들은 protoname/protoname.cc 파일과 관련된다.
4.1 Tcl hooks
우리는 우리 소유의 패킷을 Tcl에 묶는 법을 3절에서 보았다. 이제 우리는 우리의 에이전트 클래스를 위해 같은 것을 할 것이다. 그 목적은 Protoname에게 Tcl로부터 예시되도록 하는 것이다. 그렇게 하기 위해서 우리는 다음의 코드에서 묘사된 것처럼 클래스 TclClass로부터 물려받아야 한다.
protoname/protoname.cc
1: static class ProtonameClass : public TclClass {
2: public:
3: ProtonameClass() : TclClass("Agent/Protoname") {}
4: TclObject* create(int argc, const char*const* argv) {
5: assert(argc == 5);
6: return (new Protoname((nsaddr_t)Address::instance().str2addr(argv[4])));
7: }
8: } class_rtProtoProtoname;
클래스 건설자는 줄 3에 있고 그것은 인수로써 스트링 “Agent/Protoname”을 가진 기반 클래스를 호출한다. 이것은 본문의 방법에서 이 에이전트를 위한 클래스 계층을 의미한다.
줄들 4-7에서 우리는 TclObject로써 새로운 Protoname 인스턴스를 되돌려주는 create()로 불리는 함수를 구현한다. argv는 “<오브젝트의 이름> <$self> <$클래스> <$proc> <유저 인수>” 형태이다. (더 많은 정보를 위해서 [2]의 3장을 보라) 이런 특별한 경우에서 그것은 “<오브젝트의 이름> <$self> Agent/Protoname create-shadow <id>”이다. 이것 때문에, 줄 6에서 우리는 argv[4]에서 시작되는 식별자를 가진 새로운 Protoname 오브젝트를 되돌려준다. 우리는 스트링에서 nsaddr_t 유형을 얻기 위해 주소 클래스를 사용한다.
4.2 타이머들
우리가 타이머들에 대해 protoname/protoname.cc 안에서 코드해야 하는 모두는 expire() 방법이다. 타이머들은 11장 [2]에 세부적으로 되어있다. 이것을 구현하는 것은 매우 쉬운데 왜냐하면 우리는 단지 새로운 제어 패킷을 보내고 타이머 자체를 다시 스케쥴하기를 단지 원하기 때문이다. 우리의 설계 결정들에 따라서 두 개의 업무들은 라우팅 에이전트에 의해서 실행되어야 하고, 그래서 우리는 다음의 예제에서처럼 이런 콜백들을 선포한다.
protoname/protoname.cc
1: void
2: Protoname_PktTimer::expire(Event* e) {
3: agent_->send_protoname_pkt();
4: agent_->reset_protoname_pkt_timer();
5: }
4.3 에이전트
4.3.1 건설자
건설자 구현과 함께 시작해보자. 아래 줄 1에서 볼 수 있는 것처럼, 우리는 PT_PROTONAME을 통과하는 기반 클래스를 위한 건설자를 인수로써 호출함으로써 시작한다. 이런 상수는 뒤에 정의될 것이고 그것은 이런 라우팅 에이전트에 의해서 보내지고 받아들여지는 제어 패킷들을 식별하기 위해서 사용된다. 같은 줄에서 우리는 우리의 Protoname_PktTimer 오브젝트를 생성한다.
하자마자 우리는 이제 Tcl로부터 읽혀지고 쓰여질 것인 논리 속성으로써 accessible_var_을 묶는다. 정수로써 이 변수를 묶기 위해서, 우리는 bind_bool() 대신에 bind() 함수를 사용해야 한다.
줄 3은 주어진 식별자를 라우팅 에이전트의 주소로 저장한다.
protoname/protoname.cc
1: Protoname::Protoname(nsaddr_t id) : Agent(PT_PROTONAME), pkt_timer_(this) {
2: bind_bool("accessible_var_", &accessible_var_);
3: ra_addr_ = id;
4: }
Tcl 스크립트들로부터 접근하는 것은 꽤 단순하다. 다음 예제는 accessible_var_의 값을 true로 설정한다.
simulation.tcl
1: Agent/Protoname set accesible_var_ true
4.3.2 command()
코드의 다음 조각은 조금 더 복잡하다. 그것은 우리의 에이전트가 에이전트 클래스로부터 물려받는 command() 방법의 구현으로 구성된다.
protoname/protoname.cc
1: int
2: Protoname::command(int argc, const char*const* argv) {
3: if (argc == 2) {
4: if (strcasecmp(argv[1], "start") == 0) {
5: pkt_timer_.resched(0.0);
6: return TCL_OK;
7: }
8: else if (strcasecmp(argv[1], "print_rtable") == 0) {
9: if (logtarget_ != 0) {
10: sprintf(logtarget_->pt_->buffer(), "P %f _%d_ Routing Table",
11: CURRENT_TIME,
12: ra_addr());
13: logtarget_->pt_->dump();
14: rtable_.print(logtarget_);
15: }
16: else {
17: fprintf(stdout, "%f _%d_ If you want to print this routing table "
18: "you must create a trace file in your tcl script",
19: CURRENT_TIME,
20: ra_addr());
21: }
22: return TCL_OK;
23: }
24: }
25: else if (argc == 3) {
26: // Obtains corresponding dmux to carry packets to upper layers
27: if (strcmp(argv[1], "port-dmux") == 0) {
28: dmux_ = (PortClassifier*)TclObject::lookup(argv[2]);
29: if (dmux_ == 0) {
30: fprintf(stderr, "%s: %s lookup of %s failed\n",
31: __FILE__,
32: argv[1],
33: argv[2]);
34: return TCL_ERROR;
35: }
36: return TCL_OK;
37: }
38: // Obtains corresponding tracer
39: else if (strcmp(argv[1], "log-target") == 0 ||
40: strcmp(argv[1], "tracetarget") == 0) {
41: logtarget_ = (Trace*)TclObject::lookup(argv[2]);
42: if (logtarget_ == 0)
43: return TCL_ERROR;
44: return TCL_OK;
45: }
46: }
47: // Pass the command to the base class
48: return Agent::command(argc, argv);
49: }
argv[0]은 선포되는 방법 (항상 “cmd", 3장 [2]를 보라)의 이름을 포함하고, argv[1]은 요청되는 동작이며, argv[2..argc-1]은 통과되었던 인수들의 나머지이다. 이런 함수 내에서 우리가 Tcl로부터 접근하기 쉽게 만들기를 원하는 임의의 다른 동작뿐만 아니라 몇 가지 의무적인 동작들을 코드해야 한다. 한 예로써 우리는 라우팅 표의 내용들을 트레이스 파일로 덤프하는 print_rtable로 불리는 동작을 코드할 것이다.
우리는 네가 그것들을 어떻게 처리하는 지 볼 수 있도록, 2개 또는 3개의 인수들을 가진 경우들에서만 우리의 코드의 초점을 맞춘다. 각각의 경우는 TCL_OK (만약 모든 것이 괜찮으면) 또는 TCL_ERROR (만약 임의의 에러가 발생되었다면) 둘 중의 하나를 되돌려주면서 그것의 실행을 끝내야 한다.
줄들 4-7은 우리가 항상 구현해야 하는 의무적인 명령어를 기술한다: start. 이런 명령어의 예상되는 행동은 그것의 실행을 시작하기 위한 에이전트를 구성하는 것이다. 우리의 경우에서 그것은 자신의 패킷 전송 타이머를 시작한다. 우리는 라우팅 에이전트가 자신의 동작을 시작하기 위해서 수행해야 하는 모든 요구되는 행동들을 여기에서 구현해야 한다.
줄들 8-23은 우리의 print_rtable 명령어를 구현한다. 우리는 logtarget_이 초기화되는 지를 첫 번째로 체크한다. (줄 9) 그리고 나서 우리는 줄들 10-13에 보여지는 것처럼 표를 트레이스 파일 안으로 덤프한다. 코드의 이런 조각을 이해하기 위해서 네가 trace/trace.h 헤더 파일을 조사하는 것은 유용할 것이다. 트레이스 클래스가 정의되는 곳이 있다. 그것은 BaseTrace 클래스의 pt_와 관계가 있다. 이런 마지막 클래스는 출력이 버퍼되는 변수를 얻기 위해 사용되는 buffer()와 그 버퍼에서 출력 파일로 쏟아져 나오기 위해 사용되는 dump() 함수들 각각 구현한다. 마지막으로, 줄 14는 자신 소유의 내용을 트레이스 파일 안에 쓰기 위한 우리의 라우팅 표의 print() 함수를 호출한다. 아래의 TCL 코드는 시뮬레이션 스크립트로부터 정해진 시간에 print_rtable 동작을 실행하는 법을 보여준다. ns_는 시뮬레이터의 인스턴스를 포함하고 node_는 ns_에 의해서 생성된 노드임을 가정한다. 우리는 인수로써 255를 통과시키는 중인데 왜냐하면 이것은 라우팅 에이전트가 부착되는 포트의 숫자이기 때문이다.
simulation.tcl
1: $ns_ at 15.0 "[$node_ agent 255] print_rtable"
구현하기 위한 또 다른 의무적인 명령어는 port-dmux이다. 그것의 구현은 줄들 27-37에서 제공된다. [2]의 3장에서 설명되는 것처럼, NS는 그것의 이름이 주어지는 그것들 각각에 빠른 접속을 제공하기 위해서 해시 표 안에 모든 콤파일된 오브젝트 (C++ 오브젝트)에 대한 참조를 저장한다. 우리는 그것의 이름이 주어지는 포트분류자 오브젝트를 획득하기 위해서 줄 28에서 그 편의를 이용한다.
유사하게, 그것의 이름이 주어지는 트레이스 오브젝트를 단순히 획득하는 tracetarget (우리는 게다가 그것이 log-target으로 불려지도록 허락한다는 것을 주목해라)이라 불리는 또 다른 의무적인 동작이 있다.
만약 우리가 요청되는 명령어를 처리하는 법을 알지 못한다면, 우리가 줄 48에서 실행한 것처럼, 이런 책임을 기반 클래스에 위임한다.
4.3.3 recv()
다음 함수는 recv()이고 우리가 아는 것처럼 라우팅 에이전트가 패킷을 받을 때마다 언제나 선포된다. 모든 패킷은 common/packet.h에서 정의된 hdr_cmn이라 불리는 공통의 헤더를 가진다. 이런 헤더에 접속하기 위해서 우리 소유의 패킷 유형을 위해서 우리가 전에 정의했던 것과 같은 매크로가 있고, 우리는 그것을 줄 3에서 사용한다. 줄 4는 같은 것을 실행하지만 ip.h 안에 기술된, hdr_ip, IP 헤더를 얻기 위한 것이다.
1: void
2: Protoname::recv(Packet* p, Handler* h) {
3: struct hdr_cmn* ch = HDR_CMN(p);
4: struct hdr_ip* ih = HDR_IP(p);
5:
6: if (ih->saddr() == ra_addr()) {
7: // If there exists a loop, must drop the packet
8: if (ch->num_forwards() > 0) {
9: drop(p, DROP_RTR_ROUTE_LOOP);
10: return;
11: }
12: // else if this is a packet I am originating, must add IP header
13: else if (ch->num_forwards() == 0)
14: ch->size() += IP_HDR_LEN;
15: }
16:
17: // If it is a protoname packet, must process it
18: if (ch->ptype() == PT_PROTONAME)
19: recv_protoname_pkt(p);
20: // Otherwise, must forward the packet (unless TTL has reached zero)
21: else {
22: ih->ttl_--;
23: if (ih->ttl_ == 0) {
24: drop(p, DROP_RTR_TTL);
25: return;
26: }
27: forward_data(p);
28: }
29: }
우리가 해야 하는 첫 번째 것은 우리가 우리 자신에게 전송했던 패킷을 받는 중이 아님을 체크하는 것이다. 만약 그것이 그 경우라면, 우리가 줄들 8-11에서 실행한 것처럼, 패킷을 버려야 하고 되돌려 보내야 한다. 게다가, 만약 그 패킷이 (노드의 상위 레이어들에 의해서) 노드 내에서 발생되었다면 우리는 라우팅 프로토콜이 (바이트 단위로) 더하는 중인 그 오버헤드를 패킷의 길이에 더해야 한다. 줄들 13-14에서 보여지는 것처럼, 우리는 protoname이 IP 위에서 동작한다고 가정한다.
받아들여진 패킷이 유형 PT_PROTONAME일 때 그때 우리는 그것을 처리하기 위해서 recv_protoname_pkt()를 호출할 것이다. (줄들 18-19) 만약 TTL[3]이 0에 도달하지 않았다면, 만약 그것이 데이터 패킷이라면 그때 우리는 그것을 (만약 그것이 다른 노드로 예정된 것이라면) 전송해야 하거나 (만약 그것이 브로드캐스트 패킷이었거나 우리 자신에게로 예정되었던 것이라면) 상위 레이어들에게 그것을 전달하기 위해서 전송해야 한다. 줄들 21-28은 forward_data() 함수의 사용하는 것을 단지 기술했던 것을 실행한다.
너는 drop() 함수가 패킷을 버리기 위해서 사용된다는 것을 깨달았을 것이다. 그것의 인수들은 패킷 자체로 향하는 포인터이고 그것을 버리기 위한 이유를 주는 상수이다. 이런 상수들이 여러 개가 존재한다. 너는 파일 trace/cmu-trace.h에서 그것들을 볼 수 있다.
4.3.4 recv_protoname_pkt()
라우팅 에이전트가 protoname 패킷을 받았고, 선포되기 위한 recv_protoname_pkt()를 만들었다고 가정하자. 이런 함수의 구현은 구체적인 프로토콜에 따라서 많이 변할 것이지만, 우리는 다음 예제에서 일반적인 설계를 볼 수 있다.
줄들 3-4는 평소대로 IP 헤더와 protoname 패킷 헤더를 얻는다. 그 후에 우리는 줄들 8-9에서 소스와 목적지 포트들이 RT_PORT임을 확인한다. 이 상수는 common/packet.h안에 정의되고 그것은 255와 같다. 이 포트는 라우팅 에이전트를 붙이기 위해서 예약된다.
그 후에, protoname 패킷은 우리의 라우팅 프로토콜의 규격에 따라서 처리되어야 한다.
마지막으로 우리는 줄 14에서 우리가 실행한 것처럼 자원들을 해제해야 한다.
1: void
2: Protoname::recv_protoname_pkt(Packet* p) {
3: struct hdr_ip* ih = HDR_IP(p);
4: struct hdr_protoname_pkt* ph = HDR_PROTONAME_PKT(p);
5:
6: // All routing messages are sent from and to port RT_PORT,
7: // so we check it.
8: assert(ih->sport() == RT_PORT);
9: assert(ih->dport() == RT_PORT);
10:
11: /* ... processing of protoname packet ... */
12:
13: // Release resources
14: Packet::free(p);
15: }
4.3.5 send_protoname_pkt()
우리는 4.2절에서 우리의 맞춤 타이머가 그것이 만료가 될 때마다 send_protoname_pkt() 함수를 어떻게 호출하는 지를 보았다. 우리는 아래에 이런 함수의 견본 구현을 보여준다. 물론 각각의 프로토콜은 무언가를 다르게 요구하고 이것은 단지 예제일 뿐이다.
protoname/protoname.cc
1: void
2: Protoname::send_protoname_pkt() {
3: Packet* p = allocpkt();
4: struct hdr_cmn* ch = HDR_CMN(p);
5: struct hdr_ip* ih = HDR_IP(p);
6: struct hdr_protoname_pkt* ph = HDR_PROTONAME_PKT(p);
7:
8: ph->pkt_src() = ra_addr();
9: ph->pkt_len() = 7;
10: ph->pkt_seq_num() = seq_num_++;
11:
12: ch->ptype() = PT_PROTONAME;
15
13: ch->direction() = hdr_cmn::DOWN;
14: ch->size() = IP_HDR_LEN + ph->pkt_len();
15: ch->error() = 0;
16: ch->next_hop() = IP_BROADCAST;
17: ch->addr_type() = NS_AF_INET;
18:
19: ih->saddr() = ra_addr();
20: ih->daddr() = IP_BROADCAST;
21: ih->sport() = RT_PORT;
22: ih->dport() = RT_PORT;
23: ih->ttl() = IP_DEF_TTL;
24:
25: Scheduler::instance().schedule(target_, p, JITTER);
26: }
패킷을 보내기 위해서 우리는 첫 번째 그것을 할당할 필요가 있다. 우리는 그것을 위해서 allocpkt() 함수를 사용한다. 이 함수는 모든 에이전트들을 위해서 정의된다. 그리고 나서 우리는 평소대로 공통의, IP와 protoname 패킷 헤더들을 얻는다. (줄들 3-6) 우리의 목표는 우리가 원하는 값들로 모든 이런 헤더들을 채우는 것이다.
Protoname 패킷 헤더는 줄들 8-10에서 채워진다. 우리의 단순한 예제에서 우리는 단지 에이전트의 소스 주소, 메시지의 (바이트 단위로) 길이 그리고 시퀀스 넘버를 필요로 한다. 이런 필드들은 protoname의 패킷 규격에 완전히 의존적이다.
NS에서 공통의 헤더는 여러 개의 필드들을 가진다. 우리는 우리가 흥미가 있는 그런 것들에만 초점을 맞춘다. (줄들 12-17) 우리는 패킷 유형을 protoname 패킷으로 설정할 필요가 있다. (줄 12) 우리는 줄 13에서 패킷 방향을 또한 할당한다. 우리가 패킷을 보내는 중이기 때문에, 그것은 hdr_cmn::DOWN 상수에 의해서 의미되는, 아래로 가는 중이다. 패킷의 크기는 줄 14에서 주어진다. 그것은 바이트 단위이고 이것은 NS2 계산들을 위해 사용되는 값이다. 우리가 의도하는 것은 너의 hdr_protoname_pkt struct의 실제 크기는 문제가 되지 않는다는 것이다. 전달 딜레이와 같은 것들을 계산하기 위해서 NS2는 네가 여기 안에 넣는 값을 사용할 것이다. 공통의 헤더와 함께 계속해서, 줄 15에서 우리는 전달에서 어떤 에러를 갖지 않는다고 결정한다. 줄 16은 패킷이 보내져야 하는 쪽으로 다음 홉을 할당한다. 이것은 매우 중요한 필드이고, 우리의 프로토콜에서 그것은 IP_BROADCAST로써 설립되는데 왜냐하면 우리는 그 이웃하는 노드들 모두가 이런 제어 패킷을 받기를 원하기 때문이다. 그 상수는 common/ip.h에서 정의되고 너는 다른 매크로들을 위해서 거기서 체크할 수 있다. 우리가 채우는 그 마지막 필드는 주소 유형이다. 그것은 NS_AF_NONE, NS_AF_ILINK 또는 NS_AF_INET이 될 수 있다. (common/packet.h를 보라) 우리는 NS_AF_INET을 선택하는데 왜냐하면 우리는 인터넷 프로토콜을 구현하는 중이기 때문이다.
이제 우리는 IP 헤더의 구성을 계속한다. 그것은 줄들 19-23에서 볼 수 있는 것처럼 매우 단순하다. common/ip.h에서 정의되고 IP 패킷들을 위한 초기 TTL 값을 의미하는 IP_DEF_TTL로 불리는 새로운 상수가 있다. 그 IP 헤더는 IPv6 시뮬레이션들을 위해 사용되는 다른 필드들을 가지지만, 우리는 우리의 예제를 위해서 그것들이 필요하지 않다.
이제 우리는 패킷을 보내는 것을 단지 계속할 수 있다. 패킷들은 이벤트들이고 ([2]의 12장을 보라) 그래서 그것들은 스케쥴될 필요가 있다. 사실, 패킷을 보내는 것은 그것을 정해진 시간에서 스케쥴하는 것과 동등하다. 줄 25는 몇 개의 지터를 소개하는 패킷을 전송하는 법을 보여준다. 그 패킷 클래스는 커넥터 클래스로부터 물려받는데, 이것은 target_으로 불리는 TclObject와 관계가 있다. 이것은 그 이벤트를 처리할 것인 핸들러이고 인수로써 schedule() 함수에게로 통과된다.
4.3.6 reset_protoname_pkt_timer()
우리의 패킷 전송 타이머는 그것 자체를 다시 스케쥴하기 위한 다른 콜백 (4.2절)을 수행한다. 그것은 함수 reset_protoname_pkt_timer()에서 실행된다. 우리는 pkt_timer_가 5초 후에 기한 만료가 되도록 다시 스케쥴되는, 다음 예제에서 그것을 보여준다.
protoname/protoname.cc
1: void
2: Protoname::reset_protoname_pkt_timer() {
3: pkt_timer_.resched((double)5.0);
4: }
4.3.7 forward_date()
지금까지 우리는 protoname 패킷들에 주로 초점을 맞춰왔지만, 데이터 패킷들을 다룰 시간이다. forward_data() 함수는 패킷이 상위-레이어 에이전트들에게 배달되어야 하는지 또는 다른 노드에게 전송되어야 하는 지를 결정한다. 우리는 줄들 6-10에서 첫 번째 경우를 체크한다. 그것이 안으로 들어오는 패킷이고 목적지 주소가 노드 자체이거나 브로드캐스트라면, 그때 우리는 안으로 들어오는 패킷을 받아들이기 위해서 (만약 우리가 그것이 포트분류자 오브젝트임을 기억한다면) 노드의 dmux_를 사용한다.
만약 그렇지 않으면, 우리는 패킷을 전송해야 한다. 이것은 우리가 줄들 12-28에서 실행한 것처럼 공통의 헤더를 적당히 설정함으로써 달성된다. 만약 그 패킷이 브로드캐스트 패킷이라면, 그때 다음 홉은 적절히 채워질 것이다. 만약 아니라면, 우리는 다음 홉을 알아내기 위해서 우리의 라우팅 표를 사용한다. (줄 17) 우리의 구현은 목적지 주소로 향하는 루트가 없을 때 IP_BROADCAST를 되돌려준다. 그런 경우에서 우리는 디버그 메시지를 프린트하고 (줄들 19-22) 패킷을 버린다. (줄 23) 만약 모든 것이 괜찮다면 그때 우리는 줄 29에서 실행한 것처럼 패킷을 전송할 것이다.
protoname/protoname.cc
1: void
2: Protoname::forward_data(Packet* p) {
3: struct hdr_cmn* ch = HDR_CMN(p);
4: struct hdr_ip* ih = HDR_IP(p);
5:
6: if (ch->direction() == hdr_cmn::UP &&
7: ((u_int32_t)ih->daddr() == IP_BROADCAST || ih->daddr() == ra_addr())) {
8: dmux_->recv(p, 0.0);
9: return;
10: }
11: else {
12: ch->direction() = hdr_cmn::DOWN;
13: ch->addr_type() = NS_AF_INET;
14: if ((u_int32_t)ih->daddr() == IP_BROADCAST)
15: ch->next_hop() = IP_BROADCAST;
16: else {
17: nsaddr_t next_hop = rtable_.lookup(ih->daddr());
18: if (next_hop == IP_BROADCAST) {
19: debug("%f: Agent %d can not forward a packet destined to %d\n",
20: CURRENT_TIME,
21: ra_addr(),
22: ih->daddr());
23: drop(p, DROP_RTR_NO_ROUTE);
24: return;
25: }
26: else
27: ch->next_hop() = next_hop;
28: }
29: Scheduler::instance().schedule(target_, p, 0.0);
30: }
31: }
5 라우팅 표
너는 라우팅 표를 필요로 하지 않을지라도, 만약 너의 프로토콜이 그것을 사용한다면 그때 이 절을 읽어라. 우리는 다른 클래스로써 또는 임의의 다른 데이터 구조 (예를 들어, 해쉬 표)로써 라우팅 표를 구현할 수 있다. 우리는 라우팅 표가 가지기로 되어 있는 그 기능성을 캡슐화하는 클래스를 보일 예정이다. 내부의 정보는 프로토콜에서 프로토콜까지 많이 변할 것이다. 라우팅 표에서 각각의 입력을 위해서 목적지 주소들, 다음 홉 주소들, 루트들과 결합된 거리들 또는 비용, 시퀀스 넘버들, 수명들 기타 등등을 저장하기를 원할 것이다. 물론 우리의 예제는 매우 단순한 라우팅 표와 그것을 프린트하기 위한 방법을 묘사한다. 우리가 각각의 입력에 저장할 유일한 정보는 목적지와 다음 홉 주소들이다. 우리는 저장 구조로써 해시 표 (map)을 사용한다. 이런 경우는 너무 단순해서 새로운 클래스를 구현할 수 없지만, 우리는 예제로써 그것을 실행할 것이다. 코드의 다음 조각은 protoname/protoname_rtable.h에 일치한다.
protoname/protoname_rtable.h
1: #ifndef __protoname_rtable_h__
2: #define __protoname_rtable_h__
3:
4: #include <trace.h>
5: #include <map>
6:
7: typedef std::map<nsaddr_t, nsaddr_t> rtable_t;
8:
9: class protoname_rtable {
10:
11: rtable_t rt_;
12:
13: public:
14:
15: protoname_rtable();
16: void print(Trace*);
17: void clear();
18: void rm_entry(nsaddr_t);
19: void add_entry(nsaddr_t, nsaddr_t);
20: nsaddr_t lookup(nsaddr_t);
21: u_int32_t size();
22: };
23:
24: #endif
이런 함수들의 구현은 매우 쉽다. 사실 그 건설자는 매우 단순해서 그것 안에 실행할 것이 아무것도 없다.
protoname/protoname_rtable.cc
1: protoname_rtable::protoname_rtable() { }
그 print() 함수는 노드의 라우팅 표의 내용들을 트레이스 파일로 덤프할 것이다. 그것을 실행하기 위해서 우리는 절 4.3에서 언급했던 트레이스 클래스를 사용한다.
protoname/protoname_rtable.cc
1: void
2: protoname_rtable::print(Trace* out) {
3: sprintf(out->pt_->buffer(), "P\tdest\tnext");
4: out->pt_->dump();
5: for (rtable_t::iterator it = rt_.begin(); it != rt_.end(); it++) {
6: sprintf(out->pt_->buffer(), "P\t%d\t%d",
7: (*it).first,
8: (*it).second);
9: out->pt_->dump();
10: }
11: }
다음의 함수는 라우팅 표 안에 모든 입력들을 제거한다.
protoname/protoname_rtable.cc
1: void
2: protoname_rtable::clear() {
3: rt_.clear();
4: }
그것의 목적지 주소가 주어진 입력을 제거하기 위해서 우리는 rm_entry() 함수를 구현한다.
protoname/protoname_rtable.cc
1: void
2: protoname_rtable::rm_entry(nsaddr_t dest) {
3: rt_.erase(dest);
4: }
아래의 코드는 그것의 목적지와 다음 홉 주소들이 주어진 라우팅 표 안에 새로운 입력을 더하기 위해서 사용된다.
protoname/protoname_rtable.cc
1: void
2: protoname_rtable::add_entry(nsaddr_t dest, nsaddr_t next) {
3: rt_[dest] = next;
4: }
Lookup()은 그것의 목적지 주소가 주어진 입력의 다음 홉 주소를 되돌려준다. 만약 그런 입력이 존재하지 않는다면, (즉, 그 목적지를 위한 루트가 없다면) 그 함수는 IP_BROADCAST를 되돌려준다. 물론 이 상수를 사용하기 위해서 common/ip.h를 포함한다.
protoname/protoname_rtable.cc
1: nsaddr_t
2: protoname_rtable::lookup(nsaddr_t dest) {
3: rtable_t::iterator it = rt_.find(dest);
4: if (it == rt_.end())
5: return IP_BROADCAST;
6: else
7: return (*it).second;
8: }
마지막으로, size()는 라우팅 표 안에 입력들의 수를 되돌려준다.
protoname/protoname_rtable.cc
1: u_int32_t
2: protoname_rtable::size() {
3: return rt_.size();
4: }
6 요구되는 변화들
우리는 거의 끝냈다. 우리는 NS2 안쪽에 프로토콜 protoname을 위한 라우팅 에이전트를 구현해왔다. 그러나 우리의 코드를 시뮬레이터 안쪽에 통합하기 위해서 우리가 실행할 필요가 있는 몇 가지 변화들이 있다.
6.1 패킷 유형 선언
만약 우리가 기억한다면 우리는 우리의 새로운 패킷 유형, PT_PROTONAME을 가리키기 위한 상수를 사용해야 했었다. 우리는 파일 common/packet.h 안쪽에 그것을 정의할 것이다.
모든 패킷 유형들이 리스트된, packet_h 목록을 찾아보자. 우리는 코드의 다음 조각에서 보이는 것처럼 PT_PROTONAME을 이런 목록에 더할 것이다. (줄 6)
common/packet.h
1: enum packet_t {
2: PT_TCP,
3: PT_UDP,
4: PT_CBR,
5: /* ... much more packet types ... */
6: PT_PROTONAME,
7: PT_NTYPE // This MUST be the LAST one
8: };
바로 아래에 같은 파일에 p_info 클래스의 정의가 있다. 건설자 안쪽에 우리는 우리의 패킷 유형을 위한 본문의 이름을 제공할 것이다. (줄 6)
common/packet.h
1: p_info() {
2: name_[PT_TCP]= "tcp";
3: name_[PT_UDP]= "udp";
4: name_[PT_CBR]= "cbr";
5: /* ... much more names ... */
6: name_[PT_PROTONAME]= "protoname";
7: }
6.2 트레이싱 지원
우리가 알다시피 시뮬레이션의 목적은 실행 동안에 무엇이 일어났는지 기술하는 트레이스 파일을 얻는 것이다. 트레이스들에 친밀함을 느끼기 위해서 23장 [2]를 읽어봐라. 트레이스 오브젝트는 패킷이 받아들여지거나, 보내지거나, 또는 버려질 때마다 매번 패킷의 요구되는 정보를 쓰기 위해서 사용된다. 우리의 패킷 유형에 관한 정보를 기록하기 위해서 우리는 CMUTrace 클래스 안쪽에 format_protoname() 함수를 구현한다. 무선 시뮬레이션들을 위한 트레이스 지원은 CMUTrace 오브젝트들에 의해서 제공되고 그것은 16장 [2]에서 기술된다.
trace/cmu-trace.h 파일을 편집해보자. 우리는 다음의 예제의 줄 수 6 안에서처럼 우리의 새로운 함수를 더해야 한다.
trace/cmu-trace.h
1: class CMUTrace : public Trace {
2: /* ... definitions ... */
3: private:
4: /* ... */
5: void format_aodv(Packet *p, int offset);
6: void format_protoname(Packet *p, int offset);
7: };
(trace/cmu-trace.cc로부터 추출된) 코드의 다음 조각은 트레이스들의 다른 유형들을 보여준다.
trace/cmu-trace.cc
1: #include <protoname/protoname_pkt.h>
2:
3: /* ... */
4:
5: void
6: CMUTrace::format_protoname(Packet *p, int offset)
7: {
8: struct hdr_protoname_pkt* ph = HDR_PROTONAME_PKT(p);
9:
10: if (pt_->tagged()) {
11: sprintf(pt_->buffer() + offset,
12: "-protoname:o %d -protoname:s %d -protoname:l %d ",
13: ph->pkt_src(),
14: ph->pkt_seq_num(),
15: ph->pkt_len());
16: }
17: else if (newtrace_) {
18: sprintf(pt_->buffer() + offset,
19: "-P protoname -Po %d -Ps %d -Pl %d ",
20: ph->pkt_src(),
21: ph->pkt_seq_num(),
22: ph->pkt_len());
23: }
24: else {
25: sprintf(pt_->buffer() + offset,
26: "[protoname %d %d %d] ",
27: ph->pkt_src(),
28: ph->pkt_seq_num(),
29: ph->pkt_len());
30: }
31: }
우리는 위의 코드로부터 3가지 다른 트레이스 포맷들이 있다는 것을 추론할 수 있다: 태그가 붙은 트레이스들, 새로운 포맷 트레이스들 그리고 고전적인 트레이스들. 문법에 이어서 각각은, 비록 다르지만, 네가 말할 수 있는 것처럼 매우 쉽고 직관적이다. 태그가 붙은 것과 새로운 트레이스 포맷들 양쪽에는 프린트되는 중인 정보의 각각의 필드를 식별하기 위해서 사용되는 표지들이 존재한다. 우리는 소스 주소로써 “o” (기원), 시퀀스 넘버로써 “s”, 일치하는 패킷의 길이로써 “l”을 사용하기로 결정해왔다.
이것을 최근에 생성된 함수로 호출하기 위해서 우리는 trace/cmu-trace.cc 안에 format()을 바꿔야 한다.
trace/cmu-trace.cc
1: void
2: CMUTrace::format(Packet* p, const char *why)
3: {
4: /* ... */
5: case PT_PING:
6: break;
7:
8: case PT_PROTONAME:
9: format_protoname(p, offset);
10: break;
11:
12: default:
13: /* ... */
14: }
6.3 Tcl 라이브러리
이제 우리는 Tcl 파일들에서 몇 개의 변화들을 실행할 필요가 있다. 실제로 우리는 우리의 패킷 유형을 더하고, 묶여진 속성들을 위한 초기 값들을 주고 우리의 protoname 라우팅 프로토콜을 실행하는 무선 노드들을 생성하기 위한 요구되는 인프라스트럭처를 제공할 예정이다.
tcl/lib/ns-packet.tcl에서 우리는 다음의 코드의 위치를 정해야 하고 protoname을 목록에 더해야 한다. (우리가 줄 2에서 실행한 것처럼)
tcl/lib/ns-packet.tcl
1: foreach prot {
2: Protoname
3: AODV
4: ARP
5: # ...
6: NV
7: } {
8: add-packet-header $prot
9: }
묶여진 속성들을 위한 초기 값들은 tcl/lib/ns-packet.tcl 안쪽에 주어져야 한다. 우리는 파일의 끝으로 가야하고 다음의 코드와 같이 무언가를 써야 한다:
tcl/lib/ns-default.tcl
1: # ...
2: # Defaults defined for Protoname
3: Agent/Protoname set accessible_var_ true
마지막으로 우리는 tcl/lib/ns-lib.tcl을 변경해야 한다. 우리는 노드를 생성하기 위한 프로시저들을 더할 필요가 있다. 우리의 흥미는 라우팅 프로토콜로써 protoname을 가진 무선 노드를 생성하는 것을 중심으로 행해질 것이다.
프로시저 노드는 create-wireless-node 프로시저에게로 호출한다. 다른 업무들 중에서, 이 마지막 것은, 하나의 노드를 위한 그 라우팅 에이전트를 설정하려는 의도이다. 우리는 우리의 protoname 프로토콜의 인스턴스를 생성하기 위해서 이런 프로시저를 교묘히 바꿀 필요가 있다.
tcl/lib/ns-lib.tcl
1: Simulator instproc create-wireless-node args {
2: # ...
3: switch -exact $routingAgent_ {
4: Protoname {
5: set ragent [$self create-protoname-agent $node]
6: }
7: # ...
8: }
9: # ...
10: }
그리고 나서 create-protoname-agent는 다음의 예제에서 보여지는 것처럼 아래에 코드될 것이다.
tcl/lib/ns-lib.tcl
1: Simulator instproc create-protoname-agent { node } {
2: # Create Protoname routing agent
3: set ragent [new Agent/Protoname [$node node-addr]]
4: $self at 0.0 "$ragent start"
5: $node set ragent_ $ragent
6: return $ragent
7: }
줄 3은 노드의 주소를 가진 새로운 protoname 에이전트를 생성한다. 이런 에이전트는 시뮬레이션의 처음에 시작되도록 스케쥴되고, (줄 4) 줄 5에서 노드의 라우팅 에이전트로써 할당된다.
6.4 우선순위 큐
네가 너의 시뮬레이션들에서 우선순위 큐들을 사용할 것은 매우 적당하다. 이 큐 유형은 높은 우선순위 패킷들로써 라우팅 패킷들을 취급하고, 그것들을 큐의 처음에 삽입한다. 그러나 우리는 protoname 패킷들이 라우팅 패킷들이고 그래서 높은 우선순위로써 취급된다고 PriQueue 클래스에게 말할 필요가 있다.
우리는 queue/priqueue.cc 파일에 recv() 함수를 변경해야 한다. 코드의 다음 조각에서 줄 13은 우리가 실행할 필요가 있는 유일한 변경이다.
queue/priqueue.cc
1: void
2: PriQueue::recv(Packet *p, Handler *h)
3: {
4: struct hdr_cmn *ch = HDR_CMN(p);
5:
6: if (Prefer_Routing_Protocols) {
7:
8: switch(ch->ptype()) {
9: case PT_DSR:
10: case PT_MESSAGE:
11: case PT_TORA:
12: case PT_AODV:
13: case PT_PROTONAME:
14: recvHighPriority(p, h);
15: break;
16:
17: default:
18: Queue::recv(p, h);
19: }
20: }
21: else {
22: Queue::recv(p, h);
23: }
24: }
6.5 Makefile
이제 모든 것이 구현되고 우리는 단지 그것을 컴파일할 필요가 있다! 그렇게 하기 위해서 우리는 다음의 코드 (줄 4)에서처럼 OBJ_CC 변수 안쪽에 우리의 오브젝트 파일들을 더함으로써 Makefile 파일을 편집할 것이다.
Makefile
1: OBJ_CC = \
2: tools/random.o tools/rng.o tools/ranvar.o common/misc.o common/timer-handler.o \
3: # ...
4: protoname/protoname.o protoname/protoname_rtable.o \
5: # ...
6: $(OBJ_STL)
우리가 common/packet.h를 변경했지만 common/packet.cc는 변경하지 않았기 때문에 우리는 다시 컴파일되는 것을 위해서 이 마지막 파일을 “touch”해야 한다. 그런 후에 우리는 make를 실행할 수 있고 우리 소유의 라우팅 프로토콜을 즐길 수 있다. (또는 아마 모든 편집 문제들을 해결할 수 있다!)
[ns-2.27]$ touch common/packet.cc
[ns-2.27]$ make
7 레이어-2 프로토콜들로부터 정보를 받기
몇 개의 라우팅 프로토콜들은 패킷이 레이어-2로부터 보내질 수 없을 때 반응에 흥미가 있을 것이다. 이것은 우리가 아래에 설명한 것처럼, 우리의 라우팅 에이전트에 의해서 쉽게 달성될 수 있다.
그것은 어떻게 작동하는가? 패킷의 공통의 헤더는 그 패킷이 그 레이어-2 에이전트에 의해서 보내질 수 없다면 호출될 것인 함수를 네가 명시할 수 있는 필드를 가진다. 그 함수를 protoname_mac_failed_callback()이라 부르자. 우리는 그런 레이어-2 실패에 대해 반응을 담당하는 역할을 하는 라우팅 에이전트 내에 또 다른 것을 호출하는 이런 함수를 사용할 것이다. 우리는 이런 두 번째 함수를 mac_failed()로 부를 것이다. 그래서 우리는 protoname/protoname.h의 줄 9만 단지 변경해야 한다
protoname/protoname.h
1: class Protoname : public Agent {
2: /* ... */
3:
4: public:
5:
6: Protoname(nsaddr_t);
7: int command(int, const char*const*);
8: void recv(Packet*, Handler*);
9: void mac_failed(Packet*);
10: };
11: #endif
그 protoname/protoname.cc 파일은 더 많은 변화들을 요구한다. 무엇보다도 우리는 공통의 헤더 안쪽에 등록되는 그 함수를 구현해야 한다. 그 함수는 Protoname 클래스의 mac_failed() 함수로 단순히 호출할 것이다. 너는 아래에 구현을 볼 수 있다.
protoname/protoname.cc
1: static void
2: protoname_mac_failed_callback(Packet *p, void *arg) {
3: ((Protoname*)arg)->mac_failed(p);
4: }
mac_failed()에 의해서 구현된 기능성은 protoname 규격에 매우 의존한다. 예를 들어, 코드의 다음 조각은 디버그 메시지를 프린트하고 (줄 6-9) 패킷을 버린다. (줄 11)
protoname/protoname.cc
1: void
2: Protoname::mac_failed(Packet* p) {
3: struct hdr_ip* ih = HDR_IP(p);
4: struct hdr_cmn* ch = HDR_CMN(p);
5:
6: debug("%f: Node %d MAC layer cannot send a packet to node %d\n",
7: CURRENT_TIME,
8: ra_addr(),
9: ch->next_hop());
10:
11: drop(p, DROP_RTR_MAC_CALLBACK);
12:
13: /* ... do something ... */
14: }
만약 우리가 라우팅 패킷이 레이어-2 프로토콜들에 의해서 보내지지 않을 때를 알기 원한다면 우리는 send_protoname_pkt()를 변경할 필요가 있다. 유사하게 만약 우리가 데이터 패킷들에 주의하기를 원한다면 forward_data()는 가볍게 마찬가지로 변경되어야 한다. 양쪽의 경우들에서 우리는 그 패킷의 공통의 헤더를 업데이트할 때 다음의 두 개의 줄들을 단지 더해야 한다.
protoname/protoname.cc
1: ch->xmit_failure_ = protoname_mac_failed_callback;
2: ch->xmit_failure_data_ = (void*)this;
protoname_mac_failed_callback()은 무슨 경우들에서 호출될 것인가? NS-2.27에서 우리는 두 개의 다른 상황들을 설립할 수 있다.
mac/arp.cc 노드가 목적지 주소를 (ARP를 통해서) 결정하기를 원하지만 재시도들의 최대 숫자가 초과될 때
mac/mac-802_11.cc 두 개의 가능성들이 있다. 첫 번째 것은 RTS가 보내졌지만 대응하는 CTS가 받아들여지지 않았고 재시도의 최대 숫자가 초과될 때 일어날 수 있다. 두 번째 것은 데이터 패킷이 전달되었지만 절대 ACK를 받지 못했고 (받아들여진 ACK가 없다) 재시도들의 최대 숫자가 초과될 때 발생한다.
8 유선이-붙은-무선 환경들을 위한 지원
이제까지 우리는 평평한 manet들, 즉, 무선-유일한 시나리오들에 대해서만 관련이 있었다. 이번 절에서 우리는 하이브리드 manet들 (NS2 용어를 따르면, 유선이-붙은-무선 시나리오들)을 다루기 위한 기본적인 개념들을 소개할 것이다. 유선이-붙은-무선 스크립트들은 계층적인 주소를 사용할 필요가 있고, 그래서 너는 주소의 이런 유형에서 필요한 지식을 얻기 위해서 15장과 29장 [2]을 읽어야 한다.
최소의 변화들과 함께 우리는 유선이-붙은-무선 시뮬레이션들에서 우리의 프로토콜을 사용할 수 있다. 이런 것들에서는 고정된 노드들, 무선 노드들 그리고 베이스 스테이션들이 있다. 베이스 스테이션은 유선과 무선 도메인들 사이에 게이트웨이이고, 모든 무선 노드는 자신이 어느 베이스 스테이션에 결합되는지를 알 필요가 있다. 우리가 유선이-붙은-무선 지원을 제공하기 위해서 실행할 필요가 있는 모든 것은 각각의 노드를 위해 일치하는 베이스 스테이션을 설정하는 것이다.
유선이-붙은-무선 시나리오들을 기술하는 시뮬레이션 스크립트들은 각각의 모바일 노드에서 시간적으로 앞선 동작을 수행하는데, 즉, 모든 모바일 노드는 베이스 스테이션 (노드 API의 베이스-스테이션 함수)에 부착된다. 그러나 우리는 여러 개의 베이스 스테이션들이 사용되는 시나리오들에 관심이 있고, 우리는 모바일 노드들이 그들의 결합된 베이스 스테이션들을 동적으로 바꾸기를 원한다는 것을 상상해라. 우리가 다수의 베이스 스테이션들이 허락되는 하이브리드 ad hoc 네트워크들을 지원하는 라우팅 프로토콜을 코드하기를 원한다면 이것은 유용하다. 만약 이것이 너의 경우라면, 그 절을 계속해서 읽어라.
다음의 코드에서 보여지는 것처럼 protoname/protoname.h를 다시 편집하자. 줄들 1과 11은 더해지고, 반면에 나머지는 변하지 않는다.
protoname/protoname.h
1: #include <mobilenode.h>
2:
3: /* ... */
4:
5: class Protoname : public Agent {
6:
7: /* ... */
8:
9: protected:
10:
11: MobileNode* node_;
12:
13: /* ... */
14: };
우리는 라우팅 에이전트가 부착되는 노드를 의미하는 (common/mobilenode.h에서 정의된) 모바일노드 오브젝트에 대한 참조를 더했다. 이런 참조를 얻기 위해서 우리는 Protoname 건설자 안쪽에 다음의 줄 4를 더할 필요가 있다.
protoname/protoname.cc
1: Protoname::Protoname(nsaddr_t id) : Agent(PT_PROTONAME), pkt_timer_(this) {
2: bind_bool("accessible_var_", &accessible_var_);
3: ra_addr_ = id;
4: node_ = (MobileNode*)Node::get_node_by_address(id);
5: }
모바일노드 클래스는 우리가 관심이 있는 두 개의 함수들을 소유한다. 우선 첫째로 모바일 노드가 부착되는 베이스 스테이션의 식별자를 되돌려주는, base_stn()이다. 두 번째는 그 모바일 노드를 위한 적합한 베이스 스테이션을 설립할 수 있는 set_base_stn()이다. 그래서 우리는 유선이-붙은-무선 시뮬레이션들을 이런 두 개의 함수들을 사용함으로써 다룰 수 있다. 예를 들어, 다음의 코드는 그 모바일 노드 자체가 베이스 스테이션인지를 체크하고; 만약 아니라면 그때 그것은 베이스 스테이션을 할당 받는다.
protoname/protoname.cc
1: if (node_->base_stn() == ra_addr()) {
2: // I’m a base station
3: /* ... */
4: }
5: else {
6: // I’m not a base station
7: node_->set_base_stn(base_stn_addr);
8: }
앞의 예제는 결합된 베이스 스테이션을 동적으로 바꾸는 법을 보여준다. 이런 바꿈들을 수행하기 위해서 사용되는 접근들은 프로토콜 자체에 의존한다.
참조
[1] Marc Greis. Tutorial for the Network Simulator ”ns”.
http://www.isi.edu/nsnam/ns/tutorial/index.html.
[2] The VINT Project. The ns Manual, December 2003.
http://www.isi.edu/nsnam/ns/ns-documentation.html.
[1] 이것은 manet 라우팅 프로토콜들에서 일반적인 특징이지만, 그러나 실제 목적은 랜덤 숫자들을 얻는 예제를 제공하는 것이다.
[2] 실제 이것은 사실이 아니다. 사실 데이터 패킷들은 자신들의 일치하는 에이전트에게 직접 배달되고, 그래서 포트 분류자는 우리의 라우팅 에이전트에서 필요하지 않다. 그러나 우리는 이런 설명을 유지하는데 왜냐하면 NS-2.27은 라우팅 에이전트가 자신의 API의 부분으로써 port-dmux 동작 (4.3.2절을 보라)을 받아들이는 것을 요구하기 때문이다.
[3] IP 헤더에 따라서 패킷의 Time To Live
'Network > Ns2_Lecture' 카테고리의 다른 글
5.3 Node Configuration Interface (0) | 2009.01.18 |
---|---|
ns-2 Software and Simulation Results (0) | 2009.01.18 |
NS2 - 무선환경 시뮬레이션 파라메터 설명 (0) | 2009.01.18 |
network components, ns by example (0) | 2009.01.18 |
NS by Example - Network Components (0) | 2009.01.18 |