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 failedn”,
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 %dn”,
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: }