59 | | in: from_udp; // gate from the UDP layer |
60 | | out: to_udp; // gate to the UDP layer |
61 | | in: from_app; // gate from the application |
62 | | out: to_app; // gate to the application |
63 | | submodules: |
64 | | innerOverlay: MyOverlay; |
65 | | connections nocheck: // connect our gates with the inner module |
66 | | from_udp --> innerOveray.from_udp; |
67 | | to_udp <-- innerOverlay.to_udp; |
68 | | from_app --> innerOverlay.from_app; |
69 | | to_app <-- innerOverlay.to_app; |
70 | | endmodule |
71 | | }}} |
72 | | |
73 | | == 2.2 Setting parameters == |
74 | | |
75 | | The user can set custom values for module parameters in the file omnetpp.ini, or in default.ini for general default values, both located in the Simulation folder (see Section 4). Parameters are hierarchic, separated by dots for each layer. For example, setting a parameter for a specific overlay module in a node can be done as follows: |
76 | | {{{ |
77 | | SimpleOverlay.overlayTerminal[5].overlay.myProt.myParam1 = 1024 |
78 | | }}} |
79 | | |
80 | | In this example, we are working with the network SimpleOverlay. From there, we need node number 5, and then its overlay module. For that module, we'll set the parameter myParam1 to 1024. This case, however, is too specific. We may not always work with the SimpleOverlay network. Or we may need that parameter to be set for all nodes. For those cases the wildcards * and ** are of use. For example: |
81 | | |
82 | | {{{ |
83 | | *.overlayTerminal[5].overlay.myProt.myParam1 = 1024 |
84 | | **.overlay.myProt.myParam1 = 1024 |
85 | | }}} |
86 | | |
87 | | * replaces exactly one step of the hierarchy (or a part of a name), while ** replaces any number of steps. In the first case, * means that the parameter is set for any network (first step). In the second case, ** means the parameter is set for any network, and any node in it (first and second steps). Wildcards should be used sparingly, since it makes complicated for other users to calculate the scope, and may end up causing unexpected results (including rewriting other parameters). |
88 | | |
89 | | Should a module parameter not be set in either omnetpp.ini or default.ini, or match any wildcard, !OverSim will prompt the user to enter a value for each instance of the module. For simulations with a big amount of nodes, setting each parameter individually quickly becomes overwhelming. Therefore, it is recommended that every module parameter be assigned a default value in default.ini. |
90 | | |
91 | | == 3. Implementation using !BaseOverlay == |
92 | | |
93 | | Overlay modules are implemented in C++ and should derive from the class BaseOverlay, which contains the necessary interface to work with other !OverSim modules. |
94 | | |
95 | | == 3.1 Important attributes == |
96 | | |
97 | | {{{ |
98 | | #!cpp |
99 | | cModule *thisTerminal; |
100 | | }}} |
101 | | Pointer to the overlay module. |
102 | | |
103 | | {{{ |
104 | | #!cpp |
105 | | NodeHandle thisNode; |
106 | | }}} |
107 | | Information about the overlay node (IP address, port and overlay key).[[BR]] |
108 | | |
109 | | {{{ |
110 | | #!cpp |
111 | | BootstrapOracle* bootstrapOracle; |
112 | | }}} |
113 | | Pointer to a database of registered overlay nodes, to be used for bootstrapping.[[BR]] |
114 | | |
115 | | {{{ |
116 | | #!cpp |
117 | | NotificationBoard* notificationBoard; |
118 | | }}} |
119 | | Pointer to the notification board, which is used to generate node events.[[BR]] |
120 | | |
121 | | == 3.2 Initialization and finalization == |
122 | | |
123 | | When the module has been created, the first function to be called is initialize(). This starts the internals of the module, and in turn calls initializeOverlay() which initializes the overlay. These initialization functions should only be used to start internal variables, like timers and statistic vectors, as there are no guarantees that module creation has been finished, or that any other overlay node (if any) has been created already. For that reason, bootstrapping should be first attempted after joinOverlay() has been called. When the overlay module is about to be destroyed or the simulation finishes, the overlay can use finishOverlay() to finalize itself. |
124 | | |
125 | | {{{ |
126 | | #!cpp |
127 | | void initialize(int stage); |
128 | | }}} |
129 | | First function to be called, it initializes the bare bones of the overlay module. Fills in necessary parameters, initializes RPCs, sets watches and statistic vectors. When it's done it calls initializeOverlay(). |
130 | | If the joinOnApplicationRequest parameter is not set, it automatically calls join() with a random key. Else the application should manually call the join() function to start the joining process. |
131 | | |
132 | | {{{ |
133 | | #!cpp |
134 | | void initializeOverlay(int stage); |
135 | | }}} |
136 | | To be overriden by the overlay, this is the implementable overlay initialization function. |
137 | | |
138 | | {{{ |
139 | | #!cpp |
140 | | void join(OverlayKey nodeID); |
141 | | }}} |
142 | | Begins the bootstrapping. This function needs only to be manually called when joinOnApplicationRequest is true. When finished it calls joinOverlay(). |
143 | | |
144 | | {{{ |
145 | | #!cpp |
146 | | void joinOverlay(); |
147 | | }}} |
148 | | To be overriden by the overlay, to start bootstrapping. An overlay can obtain information about other nodes for bootstrapping through the bootstrapOracle functions getBootstrapNode() and getRandomNode(). When bootstrapping is finished the overlay should call setOverlayReady(true). |
149 | | |
150 | | {{{ |
151 | | #!cpp |
152 | | void setOverlayReady(bool ready); |
153 | | }}} |
154 | | The overlay should call this function when it has finished bootstrapping and is in a ready state (or inversely, when it leaves that state). |
155 | | |
156 | | {{{ |
157 | | #!cpp |
158 | | void finishOverlay(); |
159 | | }}} |
160 | | To be overriden by the overlay, this function is called when the module is about to be destroyed, in order for the overlay to finalize itself. |
161 | | |
162 | | == 3.3 Messages == |
163 | | |
164 | | The main way to communicate to other nodes will be through packets. To do that use the sendMessageToUDP, with the needed transport address (IP address plus port number) and message as parameters. To receive UDP messages the overlay needs to override handleUDPMessage. For communication with the application module, the functions sendMessageToApp and handleAppMessage can be used in a similar way. |
165 | | |
166 | | {{{ |
167 | | #!cpp |
168 | | void sendMessageToUDP(const TransportAddress& dest, cMessage* msg); |
169 | | }}} |
170 | | Sends the given message to address dest. |
171 | | |
172 | | {{{ |
173 | | #!cpp |
174 | | void handleUDPMessage(BaseOverlayMessage* msg); |
175 | | }}} |
176 | | Called when a non-RPC/non-BaseRouteMessage message from UDP arrives. May be overriden by the overlay. |
177 | | |
178 | | {{{ |
179 | | #!cpp |
180 | | void sendMessageToApp(cMessage *msg); |
181 | | }}} |
182 | | Sends the given message to the application module (TBI) |
183 | | |
184 | | {{{ |
185 | | #!cpp |
186 | | void handleAppMessage(cMessage* msg); |
187 | | }}} |
188 | | Called when a non-RPC/non-CommonAPI message from the application arrives. May be overriden by the overlay. |
189 | | |
190 | | |
191 | | == 3.4 Key Based Routing (KBR) == |
192 | | |
193 | | To send a key through the overlay the function sendToKey is called. It uses a generic routing algorithm, using the results from findNode, to search for the corresponding node. The function findNode, the center of the KBR system, must be implemented by the overlay, and returns a list of nodes close to the given overlay key. handleFailedNode is called whenever a node given by findNode could not be reached. |
194 | | |
195 | | {{{ |
196 | | #!cpp |
197 | | void sendToKey(const OverlayKey& key, BaseOverlayMessage* message, |
198 | | int numSiblings = 1, |
199 | | const std::vector<TransportAddress>& sourceRoute = TransportAddress::UNSPECIFIED_NODES, |
200 | | RoutingType routingType = DEFAULT_ROUTING); |
201 | | }}} |
202 | | Sends the given message to the overlay key. |
203 | | sourceRoute determines the route that the message will follow. If not specified, it sends the message using a generic routing algorithm using the node vector given by findNode. |
204 | | routingType specifies how the message will be routed. |
205 | | |
206 | | {{{ |
207 | | #!cpp |
208 | | NodeVector* findNode(const OverlayKey& key, int numRedundantNodes, int numSiblings, BaseOverlayMessage* msg = NULL); |
209 | | }}} |
210 | | Must be overriden by the overlay, it returns the numSiblings closest nodes known to key in the routing topology. |
211 | | |
212 | | {{{ |
213 | | #!cpp |
214 | | bool isSiblingFor(const NodeHandle& node, const OverlayKey& key, int numSiblings, bool* err); |
215 | | }}} |
216 | | |
217 | | Must be overriden by the overlay, it determines whether the node parameter is among the numSiblings closest nodes to key. If numSiblings equals 1, then it answers whether the node is the closest to key. Note that this function should be consistent with findNode: if isSiblingFor returns true, an equivalent call to findNode should return the node parameter as part of the vector. The err parameter returns whether an error happened. |
218 | | |
219 | | {{{ |
220 | | #!cpp |
221 | | bool handleFailedNode(const TransportAddress& failed); |
222 | | }}} |
223 | | Called whenever a node given by findNode was unreachable. May be overriden by the overlay. |
224 | | |
225 | | |
226 | | == 3.5 Remote Procedure Calls == |
227 | | |
228 | | RPCs are remote procedure calls which are used by nodes to ask for information to each other. Two calls can be used to initiate an RPC query: sendRouteRpcCall, which sends an RPC to the given key, sendUdpRpcCall, which sends it to the given transport address, and sendInternalRpcCall, which sends it to the same node but a different tier. A node receiving an RPC manages it through handleRpc, and responds to it using sendRpcResponse(). In turn, the starting overlay node can use handleRpcResponse to manage returning RPC responses. handleRpcTimeout is called whenever an RPC could not be delivered. See Common/BaseRpc.h for a detailed explanation of the parameters. |
229 | | |
230 | | == 3.5.1 Sending Remote Procedure Calls == |
231 | | |
232 | | {{{ |
233 | | #!cpp |
234 | | inline uint32_t sendUdpRpcCall(const TransportAddress& dest, |
235 | | BaseCallMessage* msg, |
236 | | cPolymorphic* context = NULL, |
237 | | simtime_t timeout = -1, |
238 | | int retries = 0, int rpcId = -1, |
239 | | RpcListener* rpcListener = NULL); |
240 | | }}} |
241 | | Sends the RPC message msg through UDP the address dest. |
242 | | Context is a pointer to an arbitrary object which can be used to store additional state information. |
243 | | Timeout is the time to wait until a call is declared as lost, retries is the amount of times to retry a lost call. |
244 | | !RpcId is an RPC identifier to differentiate between calls. |
245 | | !RpcListener is a listener object that will be notified for responses and timout events. |
246 | | |
247 | | {{{ |
248 | | #!cpp |
249 | | inline uint32_t sendRouteRpcCall(CompType destComp, |
250 | | const TransportAddress& dest, |
251 | | const OverlayKey& destKey, |
252 | | BaseCallMessage* msg, |
253 | | cPolymorphic* context = NULL, |
254 | | RoutingType routingType = DEFAULT_ROUTING, |
255 | | simtime_t timeout = -1, |
256 | | int retries = 0, |
257 | | int rpcId = -1, |
258 | | RpcListener* rpcListener = NULL); |
259 | | }}} |
260 | | |
261 | | Sends the RPC message through the overlay to the key destKey. |
262 | | !DestComp specifies the destination tier, and can be OVERLAY_COMP for the overlay, TIER1_COMP for the first application tier, TIER2_COMP and so on. The tier of the calling node can be obtained with getThisCompType(). |
263 | | Context is a pointer to an arbitrary object which can be used to store additional state information. |
264 | | !RoutingType determines the routing algorithm. |
265 | | Timeout is the time to wait until a call is declared as lost, retries is the amount of times to retry a lost call. |
266 | | !RpcId is an RPC identifier to differentiate between calls. |
267 | | !RpcListener is a listener object that will be notified for responses and timout events. |
268 | | |
269 | | {{{ |
270 | | #!cpp |
271 | | inline uint32_t sendInternalRpcCall(CompType destComp, |
272 | | BaseCallMessage* msg, |
273 | | cPolymorphic* context = NULL, |
274 | | simtime_t timeout = -1, |
275 | | int retries = 0, |
276 | | int rpcId = -1, |
277 | | RpcListener* rpcListener = NULL); |
278 | | }}} |
279 | | Sends the RPC message to the same node but the tier destComp. |
280 | | !DestComp specifies the destination tier, and can be OVERLAY_COMP for the overlay, TIER1_COMP for the first application tier, TIER2_COMP and so on. The tier of the calling node can be obtained with getThisCompType(). |
281 | | Timeout is the time to wait until a call is declared as lost, retries is the amount of times to retry a lost call. |
282 | | !RpcId is an RPC identifier to differentiate between calls. |
283 | | !RpcListener is a listener object that will be notified for responses and timout events. |
284 | | |
285 | | == 3.5.2 Receiving Remote Procedure Calls == |
286 | | |
287 | | {{{ |
288 | | #!cpp |
289 | | bool handleRpc(BaseCallMessage* msg); |
290 | | }}} |
291 | | To be overriden by the overlay, it is called whenever an RPC is received. |
292 | | An alternative to using a switch to manage incoming RPCs is given by the macros in Common/RpcMacros.h, and can be used the following way: |
293 | | |
294 | | {{{ |
295 | | #!cpp |
296 | | RPC_SWITCH_START( msg ) |
297 | | RPC_DELEGATE( Join, rpcJoin ); |
298 | | RPC_DELEGATE( Notify, rpcNotify ); |
299 | | RPC_SWITCH_END( ) |
300 | | }}} |
301 | | In this example, RPC_SWITCH_START inits the switch. RPC_DELEGATE casts the message to a structure with "Call" appended to the end of the first parameter (in these cases, !JoinCall and !NotifyCall) and sends it to the function in the second parameter (rpcJoin() and rpcNotifiy()). RPC_SWITCH_END ends the switch. RPC_HANDLED can be queried at any moment to see if the RPC has been already handled. |
302 | | |
303 | | {{{ |
304 | | #!cpp |
305 | | void handleRpcTimeout(BaseCallMessage* msg, const TransportAddress& dest, int rpcId, const OverlayKey& destKey); |
306 | | }}} |
307 | | To be overriden by the overlay, it is called when an RPC times out. |
308 | | |
309 | | |
310 | | |
311 | | == 3.5.3 Replying to Remote Procedure Calls == |
312 | | |
313 | | {{{ |
314 | | #!cpp |
315 | | void sendRpcResponse(BaseCallMessage* call, BaseResponseMessage* response); |
316 | | }}} |
317 | | Must be called by the overlay to respond to a given RPC. |
318 | | |
319 | | {{{ |
320 | | #!cpp |
321 | | void handleRpcResponse( BaseResponseMessage* msg, int rpcId, simtime_t rtt ); |
322 | | }}} |
323 | | To be overriden by the overlay, it is called whenever an RPC response is received. |
324 | | |
325 | | == 3.5.4 Ping RPC Calls == |
326 | | |
327 | | Ping RPC calls are convenience functions already implemented. |
328 | | |
329 | | {{{ |
330 | | #!cpp |
331 | | void pingNode(const TransportAddress& dest, |
332 | | simtime_t timeout = -1, |
333 | | int retries = 0, |
334 | | cPolymorphic* context = NULL, |
335 | | const char* caption = "PING", |
336 | | RpcListener* rpcListener = NULL, |
337 | | int rpcId = -1, |
338 | | TransportType transportType = INVALID_TRANSPORT, |
339 | | bool overrideCache = false); |
340 | | }}} |
341 | | Pings the node dest. When a node replies, the callback function pingResponse is invoked. |
342 | | Parameters timeout, retries, rpcListener and rpcId are the same as sendRouteRpcCall. |
343 | | !OverrideCache determines whether the RTT value of the ping call should be cached. |
344 | | |
345 | | {{{ |
346 | | #!cpp |
347 | | void pingResponse(PingResponse* response, cPolymorphic* context, int rpcId, simtime_t rtt); |
348 | | }}} |
349 | | To be overriden by the overlay, it is called when a ping response arrives. |
350 | | Response is the RPC reply message. Context, rpcIdare the same parameters as the calling pingNode. The rtt parameter returns the RTT value. |
351 | | |
352 | | {{{ |
353 | | #!cpp |
354 | | void pingTimeout(PingCall* call, const TransportAddress& dest, cPolymorphic* context, int rpcId); |
355 | | }}} |
356 | | To be overriden by the overlay, it is called after a ping RPC times out. |
357 | | Call is the RPC call message. Context, rpcIdare the same parameters as the calling pingNode. |
358 | | |
359 | | |
360 | | == 4. Setting up the network: the configuration file == |
361 | | |
362 | | Now that the overlay module is implemented, we still need to set up a network in order to run that overlay. To do that, we need to edit the omnetpp.ini file, or set up a custom configuration file. The configuration file should be located in the Simulation folder. |
363 | | |
364 | | The first line of the configuration file is the inclusion of the default values. That is done by adding the following line: |
| 128 | input udpIn; // gate from the UDP layer |
| 129 | output udpOut; // gate to the UDP layer |
| 130 | input from_lowerTier; // gate from the lower tier |
| 131 | input from_upperTier; // gate from the upper tier |
| 132 | output to_lowerTier; // gate to the lower tier |
| 133 | output to_upperTier; // gate to the upper tier |
| 134 | input trace_in; // gate for trace file commands |
| 135 | |
| 136 | submodules: |
| 137 | myApplication: MyApplication; |
| 138 | |
| 139 | connections allowunconnected: |
| 140 | from_lowerTier --> myApplication.from_lowerTier; |
| 141 | to_lowerTier <-- myApplication.to_lowerTier; |
| 142 | udpIn --> myApplication.udpIn; |
| 143 | udpOut <-- myApplication.udpOut; |
| 144 | trace_in --> myApplication.trace_in; |
| 145 | } |
| 146 | }}} |
| 147 | |
| 148 | Notice that we now use the ITier interface. Additionaly, the sections "gates" and "connections" look a bit different, but there is still nothing you need to change. |
| 149 | |
| 150 | == 3. Messages == |
| 151 | |
| 152 | For sending messages, we can use the basic cMessage class. However, we'll be needing a few custom fields for our application, so we'll use a customized message instead. Custom message declarations are like those of modules, but without sections: simply include the parameters directly. The framework will automatically create getter- and setter-functions for each of them. |
| 153 | |
| 154 | And like modules, messages should be declared in their own (.msg) files. |
| 155 | |
| 156 | In this case, let's see MyMessage.msg: |
| 157 | |
| 158 | {{{ |
| 159 | |
| 160 | // Import the C++ TransportAddress class |
| 161 | |
| 162 | cplusplus {{ |
| 163 | #include <TransportAddress.h> |
| 164 | }} |
| 165 | class noncobject TransportAddress; // noncobject means it's not a message class. |
| 166 | |
| 167 | // First, we declare an enum for the message type: |
| 168 | // Enumerations in messages MUST have a value. |
| 169 | |
| 170 | enum MessageType { |
| 171 | MYMSG_PING = 1; // outgoing message |
| 172 | MYMSG_PONG = 2; // returning message |
| 173 | } |
| 174 | |
| 175 | // now we declare the message proper |
| 176 | |
| 177 | packet MyMessage { |
| 178 | int type enum (MessageType); // message type |
| 179 | TransportAddress senderAddress; // address of the node that sent the message |
| 180 | } |
| 181 | }}} |
| 182 | |
| 183 | In order to route packets in a realistic way, the "length" parameter of the message should be set to an appropiate value. This is done manually by using the setBitLength(bits) or setByteLength(bytes) function when sending the message. Forgetting to set this value will default the length to a size of 0, potentially making measurements such as latencies less meaningful. |
| 184 | |
| 185 | All messages can encapsulate any other message (just avoid recursivity!) by using the encapsulate() function. To decapsulate the message, use decapsulate(). To peek the encapsulated message without decapsulating it, use getEncapsulatedMsg(). The length of the message is automatically changed accordingly. |
| 186 | |
| 187 | Additionaly, each message that is created must be deleted exactly once. Messages that aren't deleted will cause memory leaks, and those that are deleted twice can cause a segmentation fault. It is the responsability of the programmer to know when a message should be deleted. |
| 188 | |
| 189 | == 4. Implementing the overlay and application modules == |
| 190 | |
| 191 | In this section, we'll implement our overlay and application. But first, let's do a introduction about some of the important variable types used in OverSim. |
| 192 | |
| 193 | BaseOverlay: Template overlay module class. All overlay modules should derive from it.[[BR]] |
| 194 | BaseApp: Template application module class. All application modules should derive from it.[[BR]] |
| 195 | |
| 196 | OverlayKey: Class to describe overlay keys. Internally it stores the key as an bit array. [[BR]] |
| 197 | Its value can be set in different ways when it's being initialized:[[BR]] |
| 198 | {{{ |
| 199 | !OverlayKey(); // initializes the key as UNSPECIFIED. While in this value, most of the key operations are invalid! [[BR]] |
| 200 | !OverlayKey(5); // initializes the value as an 32-bit integer.[[BR]] |
| 201 | !OverlayKey(buffer, 32); // initializes the value from the first 32 bytes of buffer.[[BR]] |
| 202 | !OverlayKey().random(); // initializes the value as a random bit array.[[BR]] |
| 203 | }}} |
| 204 | OverlayKey operations include equality (==), order (<, >) and range (isBetween(key1, key2)).[[BR]] |
| 205 | |
| 206 | IPvXAddress: Generic IP address, can contain either an IPv4 address (IPAddress) or an IPv6 address (IPv6Address).[[BR]] |
| 207 | TransportAddress: A structure containing bot an IPvXAddress (field "ip"), and a port (field "port").[[BR]] |
| 208 | NodeHandle: A child class of TransportAddress, additionaly contains an OverlayKey (field "key"). In overlay modules, the NodeHandle value representing the node in which the module is can be retrieved with "thisNode". In application modules, thisNode also exists, but the "key" field may not be set![[BR]] |
| 209 | |
| 210 | Now, let's continue with the implementation. |
| 211 | |
| 212 | == 4.1 Implementing the Application == |
| 213 | |
| 214 | First, we'll start with MyApplication, since the application interface is easier to understand. Our application will implement a simple PING-type program, in which a packet is sent to another node, and that node sends the packet back. When we start, we'll wait <sendPeriod> seconds, then we'll route <numToSend> packets to random keys through the overlay. When a node receives a packet, it will send it back to the owner through UDP. The process is repeated until the simulation ends. |
| 215 | |
| 216 | This is the declaration of MyApplication from MyApplication.h: |
| 217 | |
| 218 | {{{ |
| 219 | class MyApplication : public BaseApp |
| 220 | { |
| 221 | // module parameters |
| 222 | simtime_t sendPeriod; // we'll store the "sendPeriod" parameter here |
| 223 | int numToSend; // we'll store the "numToSend" parameter here |
| 224 | int largestKey; // we'll store the "largestKey" parameter here |
| 225 | |
| 226 | // statistics |
| 227 | int numSent; //number of packets sent |
| 228 | int numReceived; //number of packets received |
| 229 | |
| 230 | // our timer |
| 231 | cMessage *timerMsg; |
| 232 | |
| 233 | // application routines |
| 234 | void initializeApp(int stage); // called when the module is being created |
| 235 | void finishApp(); // called when the module is about to be destroyed |
| 236 | void handleTimerEvent(cMessage* msg); // called when we received a timer message |
| 237 | void deliver(OverlayKey& key, cMessage* msg); // called when we receive a message from the overlay |
| 238 | void handleUDPMessage(cMessage* msg); // called when we receive a UDP message |
| 239 | }; |
| 240 | }}} |
| 241 | |
| 242 | Now comes the implementation of MyApplication from MyApplication.cc: |
| 243 | |
| 244 | {{{ |
| 245 | |
| 246 | // This line tells the simulator that MyApplication is going to be extended using C++ code. |
| 247 | // It *must* be present (only once) somewhere in your code, outside a function, for each module you'll be extending. |
| 248 | |
| 249 | Define_Module(MyApplication); |
| 250 | |
| 251 | // initializeApp is called when the module is being created. |
| 252 | // Use this function instead of the constructor for initializing variables. |
| 253 | void MyApplication::initializeApp(int stage) { |
| 254 | |
| 255 | // initializeApp will be called twice, each with a different stage. |
| 256 | // stage can be either MIN_STAGE_APP (this module is being created), or MAX_STAGE_APP (all modules were created). |
| 257 | // We only care about MIN_STAGE_APP here. |
| 258 | |
| 259 | if (stage != MIN_STAGE_APP) return; |
| 260 | |
| 261 | // copy the module parameter values to our own variables |
| 262 | |
| 263 | sendPeriod = par("sendPeriod"); |
| 264 | numToSend = par("numToSend"); |
| 265 | largestKey = par("largestKey"); |
| 266 | |
| 267 | // initialize our statistics variables |
| 268 | |
| 269 | numSent = 0; |
| 270 | numReceived = 0; |
| 271 | |
| 272 | // tell the GUI to display our variables |
| 273 | |
| 274 | WATCH(numSent); |
| 275 | WATCH(numReceived); |
| 276 | |
| 277 | // start our timer! |
| 278 | |
| 279 | timerMsg = new cMessage("MyApplication Timer"); |
| 280 | scheduleAt(simTime() + sendPeriod, timerMsg); |
| 281 | } |
| 282 | |
| 283 | |
| 284 | // finalize is called when the module is being destroyed |
| 285 | |
| 286 | void MyApplication::finishApp() { |
| 287 | |
| 288 | // first we'll delete our timer |
| 289 | |
| 290 | delete timerMsg; |
| 291 | |
| 292 | // finalize() is usually used to save the module's statistics. |
| 293 | // We'll use globalStatistics->addStdDev(), which will calculate min, max, mean and deviation values. |
| 294 | // The first parameter is a name for the value, you can use any name you like (use a name you can find quickly!). |
| 295 | // In the end, the simulator will mix together all values, from all nodes, with the same name. |
| 296 | |
| 297 | globalStatistics->addStdDev("MyApplication: Sent packets", numSent); |
| 298 | globalStatistics->addStdDev("MyApplication: Received packets", numReceived); |
| 299 | } |
| 300 | |
| 301 | |
| 302 | // handleTimerEvent is called when a timer event triggers |
| 303 | |
| 304 | void MyApplication::handleTimerEvent(cMessage* msg) { |
| 305 | |
| 306 | if (msg == timerMsg) { // is this our timer? |
| 307 | |
| 308 | scheduleAt(simTime() + sendPeriod, timerMsg); // reschedule our message |
| 309 | |
| 310 | // if the simulator is still busy creating the network, let's wait a bit longer |
| 311 | if (underlayConfigurator->isInInitPhase()) return; |
| 312 | |
| 313 | for (int i = 0; i < numToSend; i++) { |
| 314 | |
| 315 | OverlayKey randomKey(intuniform(1, largestKey)); // let's create a random key |
| 316 | |
| 317 | MyMessage *myMessage; // the message we'll send |
| 318 | myMessage = new MyMessage(); |
| 319 | myMessage->setType(MYMSG_PING); // set the message type to PING |
| 320 | myMessage->setSenderAddress(thisNode); // set the sender address to our own |
| 321 | myMessage->setByteLength(100); // set the message length to 100 bytes |
| 322 | |
| 323 | numSent++; // update statistics |
| 324 | |
| 325 | callRoute(randomKey, myMessage); // send it to the overlay |
| 326 | } |
| 327 | |
| 328 | } else { |
| 329 | |
| 330 | delete msg; // who knows what this packet is? |
| 331 | |
| 332 | } |
| 333 | } |
| 334 | |
| 335 | // deliver is called when we receive a message from the overlay. |
| 336 | // Unhandled or unknown packets can be safely deleted here. |
| 337 | |
| 338 | void MyApplication::deliver(OverlayKey& key, cMessage* msg) { |
| 339 | |
| 340 | // we are only expecting messages of type MyMessage, throw away any other |
| 341 | |
| 342 | MyMessage *myMsg = dynamic_cast<MyMessage*>(msg); |
| 343 | if (myMsg == 0) { |
| 344 | delete msg; // unknown! |
| 345 | return; |
| 346 | } |
| 347 | |
| 348 | // are we a PING? send a PONG! |
| 349 | |
| 350 | if (myMsg->getType() == MYMSG_PING) { |
| 351 | |
| 352 | myMsg->setType(MYMSG_PONG); // change type |
| 353 | sendMessageToUDP(myMsg->getSenderAddress(), myMsg); // send it back to its owner |
| 354 | |
| 355 | } else { |
| 356 | |
| 357 | delete msg; // unhandled! |
| 358 | |
| 359 | } |
| 360 | } |
| 361 | |
| 362 | // handleUDPMessage is called when we receive a message from UDP. |
| 363 | // Unhandled or unknown packets can be safely deleted here. |
| 364 | |
| 365 | void MyApplication::handleUDPMessage(cMessage* msg) { |
| 366 | |
| 367 | // we are only expecting messages of type MyMessage |
| 368 | |
| 369 | MyMessage *myMsg = dynamic_cast<MyMessage*>(msg); |
| 370 | |
| 371 | if (myMsg && myMsg->getType() == MYMSG_PONG) { |
| 372 | |
| 373 | numReceived++; |
| 374 | |
| 375 | } |
| 376 | |
| 377 | // Whatever msg was, we won't need it anymore. |
| 378 | |
| 379 | delete msg; |
| 380 | } |
| 381 | |
| 382 | }}} |
| 383 | |
| 384 | |
| 385 | == 4.2 Implementing the Overlay == |
| 386 | |
| 387 | Now, we'll implement our overlay module. Our overlay will be rather simple, and based on only two concepts. First, an overlay key maps directly to the IP address of its holder (for example, key 16 will be held by 1.0.0.16). And second, routing a packet from key A to key B will consist of sending that packet to all nodes with keys between A and B (for example, sending a node from key 4 to key 1 will create a route of 4 - 3 - 2 - 1). |
| 388 | |
| 389 | This is the declaration of MyOverlay in MyOverlay.h |
| 390 | |
| 391 | {{{ |
| 392 | class MyOverlay : public BaseOverlay { |
| 393 | public: |
| 394 | |
| 395 | // Routing parameters |
| 396 | int myKey; // our overlay key |
| 397 | NodeHandle prevNode; // next node in chain |
| 398 | NodeHandle nextNode; // previous node in chain |
| 399 | |
| 400 | //module parameters |
| 401 | double dropChance; // we'll store the "dropChance" parameter here |
| 402 | |
| 403 | // statistics |
| 404 | int numDropped; // how many packets have we dropped? |
| 405 | |
| 406 | // overlay routines |
| 407 | void initializeOverlay(int stage); // called when the overlay is being initialized |
| 408 | void setOwnNodeID(); // (optional) called to set the key of this node (random otherwise) |
| 409 | void joinOverlay(); // called when the node is ready to join the overlay |
| 410 | void finalizeOverlay(); // called when the module is about to be destroyed |
| 411 | |
| 412 | // obligatory: called when we need the next hop to route a packet to the given key |
| 413 | NodeVector* findNode(const OverlayKey& key, // key to route to |
| 414 | int numRedundantNodes, // how many candidates for next hop we want (see getMaxNumSiblings) |
| 415 | int numSiblings, // how many siblings we'll return (?) (see getMaxRedundantNodes) |
| 416 | BaseOverlayMessage* msg); // message being routed |
| 417 | |
| 418 | // obligatory: In general, called when we need to know whether node is amongst numSiblings closest nodes to key. |
| 419 | // But normally it is called with node set to this node, and asking whether this node is responsible for key. |
| 420 | bool isSiblingFor(const NodeHandle& node, // which node (usually thisNode) we're referring to |
| 421 | const OverlayKey& key, // key in question |
| 422 | int numSiblings, // how many siblings we're querying about |
| 423 | bool* err); // set to false when we couldn't determine the range |
| 424 | |
| 425 | // obligatory: Set the maximum number of siblings that can be queried about (usually 1) |
| 426 | int getMaxNumSiblings(); |
| 427 | |
| 428 | // obligatory: Set the maximum number of redundant nodes that can be queried about (usually 1) |
| 429 | int getMaxNumRedundantNodes(); |
| 430 | |
| 431 | }; |
| 432 | }}} |
| 433 | |
| 434 | Now comes the implementation of MyOverlay from MyOverlay.cc: |
| 435 | |
| 436 | {{{ |
| 437 | // Important! This line must be present for each module you extend (see MyApplication) |
| 438 | Define_Module(MyOverlay); |
| 439 | |
| 440 | // To convert between IP addresses (which have bit 24 active), and keys (which don't), we'll need to set or remove this bit. |
| 441 | #define BIGBIT (1 << 24) |
| 442 | |
| 443 | // Called when the module is being initialized |
| 444 | void MyOverlay::initializeOverlay(int stage) { |
| 445 | |
| 446 | if (stage != MIN_STAGE_OVERLAY) return; // see BaseApp.cc |
| 447 | |
| 448 | myKey = thisNode.ip.get4().getInt() & ~BIGBIT; // get our key from our IP address |
| 449 | |
| 450 | // initialize the rest of variables |
| 451 | numDropped = 0; |
| 452 | if (!(par("enableDrops"))) { |
| 453 | dropChance = 0; |
| 454 | } else { |
| 455 | dropChance = par("dropChance"); |
| 456 | } |
| 457 | } |
| 458 | |
| 459 | // Called to set our own overlay key (optional) |
| 460 | void MyOverlay::setOwnNodeID() { |
| 461 | thisNode.key = OverlayKey(myKey); // create the corresponding overlay key |
| 462 | } |
| 463 | |
| 464 | // Called when the module is ready to join the overlay |
| 465 | void MyOverlay::joinOverlay() { |
| 466 | |
| 467 | // Set the information of the previous step in the chain |
| 468 | prevNode.ip = IPAddress(BIGBIT | (myKey - 1)); |
| 469 | prevNode.port = thisNode.port; |
| 470 | prevNode.key = OverlayKey(myKey - 1); |
| 471 | |
| 472 | // Set the information of the next step in the chain |
| 473 | nextNode.ip = IPAddress(BIGBIT | (myKey + 1)); |
| 474 | nextNode.port = thisNode.port; |
| 475 | nextNode.key = OverlayKey(myKey + 1); |
| 476 | |
| 477 | // tell the simulator that we're ready |
| 478 | setOverlayReady(true); |
| 479 | } |
| 480 | |
| 481 | // Return whether we know if the given node is responsible for the key |
| 482 | bool MyOverlay::isSiblingFor(const NodeHandle& node, |
| 483 | const OverlayKey& key, |
| 484 | int numSiblings, |
| 485 | bool* err) |
| 486 | { |
| 487 | if (node == thisNode && key == thisNode.key) { // is it our node and our key? |
| 488 | return true; |
| 489 | } |
| 490 | return false; // we don't know otherwise |
| 491 | } |
| 492 | |
| 493 | // Return the next step for the routing of the given message |
| 494 | NodeVector *MyOverlay::findNode(const OverlayKey& key, |
| 495 | int numRedundantNodes, |
| 496 | int numSiblings, |
| 497 | BaseOverlayMessage* msg) |
| 498 | { |
| 499 | NodeVector* nextHops; |
| 500 | |
| 501 | if (uniform(0, 1) < dropChance) { // do we drop the packet? |
| 502 | nextHops = new NodeVector(0); // if yes, return an empty node vector |
| 503 | numDropped++; // (don't delete msg here! the overlay will delete it later) |
| 504 | return nextHops; |
| 505 | } |
| 506 | |
| 507 | nextHops = new NodeVector(1); // else, set the response vector with one node |
| 508 | |
| 509 | if (key == thisNode.key) { // are we responsible? next step is this node |
| 510 | nextHops->add(thisNode); |
| 511 | } else if (key < thisNode.key) { // is the key behind us? next step is the previous node |
| 512 | nextHops->add(prevNode); |
| 513 | } else { // otherwise, the next step is the next node |
| 514 | nextHops->add(nextNode); |
| 515 | } |
| 516 | return nextHops; |
| 517 | } |
| 518 | |
| 519 | // Called when the module is about to be destroyed |
| 520 | void MyOverlay::finalizeOverlay() { |
| 521 | // remove this node from the overlay |
| 522 | setOverlayReady(false); |
| 523 | |
| 524 | // save the statistics (see MyApplication) |
| 525 | globalStatistics->addStdDev("MyOverlay: Dropped packets", numDropped); |
| 526 | } |
| 527 | |
| 528 | // Return the max amount of siblings that can be queried about |
| 529 | int MyOverlay::getMaxNumSiblings() { |
| 530 | return 1; |
| 531 | } |
| 532 | |
| 533 | // Return the max amount of redundant that can be queried about |
| 534 | int MyOverlay::getMaxNumRedundantNodes() { |
| 535 | return 1; |
| 536 | } |
| 537 | }}} |
| 538 | |
| 539 | == 5. Configuring the Simulation == |
| 540 | |
| 541 | Now that the overlay module is implemented, we still need to set up a network in order to run that overlay. To do that, we need to edit the omnetpp.ini file, or set up a custom configuration file. The configuration file should be located in the <OverSim>/simulation folder. |
| 542 | |
| 543 | == 5.1 Setting Parameters == |
| 544 | |
| 545 | The user can set custom values for module parameters in the file omnetpp.ini (or whichever configuration file you use), or in default.ini for general default values, located in the <OverSim>/simulation folder. Parameters are hierarchic, separated by a dot for each layer. For example, setting a parameter for a specific overlay module in a node can be done as follows: |
| 546 | |
| 547 | {{{ |
| 548 | SimpleOverlay.overlayTerminal[5].overlay.myOverlay.enableDrops = true |
| 549 | }}} |
| 550 | |
| 551 | In this example, we are working with the network SimpleOverlay. From there, we need node number 5, and then its overlay module. For that module, we'll use the submodule myOverlay, and set the parameter enableDrops to true. |
| 552 | |
| 553 | This case, however, is too specific. We may not always work with the SimpleOverlay network. Or we may need that parameter to be set for all nodes. For those cases the wildcards * and ** are of use. For example: |
| 554 | |
| 555 | {{{ |
| 556 | *.overlayTerminal[5].overlay.myOverlay.enableDrops = true |
| 557 | **.overlay.myOverlay.enableDrops = true |
| 558 | }}} |
| 559 | |
| 560 | * replaces exactly one step of the hierarchy (or a part of a name), while ** replaces any number of steps. In the first case, * means that the parameter is set for any network (first step). In the second case, ** means the parameter is set for any network, and any node in it (first and second steps, and further). Wildcards should be used sparingly, since it makes complicated for other users to calculate the scope, and may end up causing unexpected results (including rewriting other parameters). |
| 561 | |
| 562 | Should a module parameter not be set in either configuration file, or match any wildcard, !OverSim will prompt the user to enter a value for each instance of the module. For simulations with a big amount of nodes, setting each parameter individually quickly becomes overwhelming. Therefore, it is recommended that every module parameter be assigned a default value in default.ini. |
| 563 | |
| 564 | == 5.2 Creating our Configuration File == |
| 565 | |
| 566 | Let's create a configuration file in <OverSim>/simulations called mysim.ini. |
| 567 | |
| 568 | The default.ini file contains the default values for many overlay parameters, and therefore it's highly useful to include it in our file. Since the simulator will use the first value it sees, the default value should go at the end. |
| 569 | |
| 570 | Therefore, the last line of omnetpp.ini should look like this: |
369 | | Each simulation environment class is defined as a run. A configuration file can contain different amounts of runs, each with a different number. We need to set up the network in that run. There are two base networks: SimpleUnderlay and Ipv4Underlay. !SimpleUnderlay is a simplified flat network where each node is assigned coordinates, packet latencies are calculated based on the distance of the source and destination node coordinates, and each nodes are directly connected to one another. Ipv4Underlay emulates real-life networks and contain hierarchies of nodes, routers, and backbones. The network type is set with the first-tier parameter network. Network modules can be accessed under the names of !SimpleNetwork for !SimpleUnderlay and Ipv4Network for Ipv4Underlay. The module in charge of building the network is called underlayConfigurator. |
370 | | |
371 | | Now, once the network type has been set, now we need to declare the node classes. We can declare each node to be made of the same components and attributes, or create different classes each with its own attributes. Each class is represented by a churnGenerator. For example, we can create a network with one type of node: |
372 | | |
373 | | {{{ |
374 | | [Run 1] |
375 | | |
376 | | network = SimpleNetwork |
377 | | |
378 | | # The following applies to SimpleNetwork |
379 | | *.underlayConfigurator.churnGeneratorTypes = "LifetimeChurn" // one node type |
380 | | |
381 | | # Since we only have one churn generator, the following applies to SimpleNetwork.churnGenerator[0] |
382 | | **.numTiers = 1 // only one application tier |
383 | | **.tier1Type = "MyAppModule" // module name of the application tier |
384 | | **.overlayType = "MyOverlay" // module name of the overlay |
385 | | **.lifetimeMean = 10000 // mean session time in seconds |
386 | | **.targetOverlayTerminalNum=10 // target number of nodes |
387 | | }}} |
388 | | |
389 | | The following applies for a network with 2 node classes: a server and a client. All other values are the default ones set in default.ini. |
390 | | |
391 | | {{{ |
392 | | [Run 2] |
393 | | |
394 | | *.underlayConfigurator.churnGeneratorTypes = "LifetimeChurn NoChurn" // two churn generators |
395 | | |
396 | | # First churn |
397 | | *.churnGenerator[0].tier1Type = "MyClientModule" // module name of the application tier |
398 | | *.churnGenerator[0].overlayType = "MyOverlay" // module name of the overlay |
399 | | |
400 | | # Second churn |
401 | | *.churnGenerator[1].tier1Type = "MyServerModule" // module name of the application tier |
402 | | *.churnGenerator[1].overlayType = "MyOverlay" // module name of the overlay |
403 | | }}} |
404 | | |
405 | | == 5. Compiling and running == |
406 | | |
407 | | In order for !OverSim to find your files you need to to make the following changes: |
408 | | Edit makemakefiles in the root folder, and add an include to your directory in ALL_OVERSIM_INCLUDES. |
409 | | Change the “all:” section accordingly by adding your folder to the list. |
410 | | |
411 | | Now run {{{ ./makemake }}} and {{{ make }}} in the root folder. That should compile your new modules. |
412 | | |
413 | | In order to run it, you need to setup your simulation run in omnetpp.ini as explained in Section 4. Make sure that you selected default values for all parameters in default.ini, or you'll be prompted for a value when the simulation begins - for each instance of the parameter. To start !OverSim, enter the directory Simulations and run : |
414 | | {{{ |
415 | | ../bin/OverSim [-f customConfigFile] [-r runNumber] |
416 | | }}} |
417 | | |
418 | | If you don't select a run number, if the GUI is enabled, you'll be prompted for a run number. If not, all the runs in omnetpp.ini (or the given custom config file) will be run. |
| 575 | Each set of parameters for a simulation environment is called a configuration. A simulation file can contain any amounts of configurations, each with a different name. In the same way, each configuration can contain any amount of overlay parameters as needed. |
| 576 | |
| 577 | Since many configurations might specify the same value for a given parameter, we can set a default value in the General section. For example, let's set the value of the base network as SimpleUnderlayNetwork. |
| 578 | |
| 579 | {{{ |
| 580 | [General] |
| 581 | sim-time-limit = 1000s // set all simulations to last 1000 seconds |
| 582 | network = "oversim.underlay.simpleunderlay.SimpleUnderlayNetwork" // this is a module path! do not confuse with parameter paths. |
| 583 | }}} |
| 584 | |
| 585 | Now, unless specified otherwise, all configurations will use SimpleUnderlayNetwork as their network. Notice that the value of "network" is a module path, which represents the location of the ned file, and not a parameter path. |
| 586 | |
| 587 | Further on networks, there are two base networks: SimpleUnderlay and Ipv4Underlay. !SimpleUnderlay is a simplified flat network where each node is assigned coordinates, packet latencies are calculated based on the distance of the source and destination node coordinates, and each nodes are directly connected to one another. Ipv4Underlay emulates real-life networks and contain hierarchies of nodes, routers, and backbones. Network modules can be accessed under the names of !SimpleUnderlayNetwork for !SimpleUnderlay and Ipv4Network for Ipv4Underlay. |
| 588 | |
| 589 | Now, once the network type has been set, we can set the parameter values that we'll need. For example, let's create a configuration called "Example". There are two basic parts that should be configured: the churn type, and the node parameters. |
| 590 | |
| 591 | The churn type tells OverSim how often it should add or delete nodes from the network. There are many churn types (e.g. NoChurn for a static network, LifetimeChurn for creating nodes with a given lifetime distribution, and so on); for a complete list see the ned files in <OverSim>/src/common. In our example, we'll use the less interesting NoChurn, which disables churn in our network. There are plenty of examples in <OverSim>/simulations/omnetpp.ini for all churns. |
| 592 | |
| 593 | The second part are the node parameters. Two node parameters are obligatory: tier1Type (to specify the application module), and overlayType (for the overlay module). Further parameters can be specified for these application and overlay modules. For this example, we'll use the module MyOverlayModules, which we declared in a previous chapter. |
| 594 | |
| 595 | {{{ |
| 596 | [Config ExampleConf] |
| 597 | description = MyOverlay test (SimpleUnderlayNetwork) |
| 598 | |
| 599 | // Configure the churn |
| 600 | *.underlayConfigurator.churnGeneratorTypes = "oversim.common.NoChurn" |
| 601 | **.targetOverlayTerminalNum = 10 |
| 602 | |
| 603 | // Node parameters |
| 604 | **.overlayType = "oversim.overlay.myoverlay.MyOverlayModules" |
| 605 | **.tier1Type = "oversim.applications.myapplication.MyApplicationModules" |
| 606 | |
| 607 | **.myOverlay.enableDrops = true |
| 608 | **.myOverlay.dropChance = 0 |
| 609 | |
| 610 | **.myApplication.sendPeriod = 1s |
| 611 | **.myApplication.numToSend = 1 |
| 612 | **.myApplication.largestKey = 10 |
| 613 | |
| 614 | }}} |
| 615 | |
| 616 | Like modules, configurations can also use inheritance. A configuration that inherits from a parent configuration receives all the parameters that were set there, but can change them if needed. For example, let's make two configurations that inherit from ExampleConf: |
| 617 | |
| 618 | {{{ |
| 619 | |
| 620 | [Config ExampleA] |
| 621 | extends = ExampleConf |
| 622 | **.myOverlay.dropChance = 0.1 |
| 623 | **.myApplication.sendPeriod = 1s |
| 624 | |
| 625 | [Config ExampleB] |
| 626 | extends = ExampleConf |
| 627 | **.myOverlay.dropChance = 0.5 |
| 628 | **.myApplication.sendPeriod = 2s |
| 629 | |
| 630 | }}} |
| 631 | |
| 632 | Now, we see that both configurations are equal except for their different values for dropChance and sendPeriod. What happens if we need more simulations that have different values for both parameters, but retain the rest? For that, we can use runs. |
| 633 | |
| 634 | Let's, for example, create a configuration from ExampleConf, but where dropChance takes the values of 0.1, 0.3, 0.5, and 0.7. |
| 635 | |
| 636 | {{{ |
| 637 | |
| 638 | [Config ExampleC] |
| 639 | extends = Example |
| 640 | **.myOverlay.dropChance = ${Drop=0.1, 0.3, 0.5, 0.7} |
| 641 | **.myApplication.sendPeriod = 1s |
| 642 | |
| 643 | }}} |
| 644 | |
| 645 | Here, we have a single configuration, ExampleC, with 4 runs: one for when dropChance is 0.1, the second for when dropChance is 0.3, and so on. In order to identify which dropChance we'll be using for a given run, we'll define a variable called Drop, which will contain the dropChance value. This is specified by the "Drop=" chain inside the dropChance definition. This variable can be used for other purposes, specified in the !User Manual. |
| 646 | |
| 647 | The results from each run will be saved in a different file (ExampleC-0.sca, ExampleC-1.sca, and so on for the scalar data). The parameter value that was taken for each file is saved within it (together with the variable), to avoid confusion. |
| 648 | |
| 649 | Additionaly, many parameters can have variable values, for example, we can set sendPeriod as follows: |
| 650 | |
| 651 | {{{ |
| 652 | |
| 653 | ... |
| 654 | **.application.sendPeriod = ${Period=1s, 2s, 5s} |
| 655 | |
| 656 | }}} |
| 657 | |
| 658 | In this case, we'll have 12 runs, once for each combination of dropChance and sendPeriod. Furthermore, each run can be repeated as many times as desired. |
| 659 | |
| 660 | {{{ |
| 661 | |
| 662 | repeat = 3 |
| 663 | |
| 664 | }}} |
| 665 | |
| 666 | With this, we'll have each of the 12 runs repeated 3 times, for a total of 36 runs. It is recommended to simplify your configurations into a single one by using runs, when possible. |
| 667 | |
| 668 | |
| 669 | == 6. Compiling and Running == |
| 670 | |
| 671 | Unlike previous versions of !OverSim, now the framework will automatically find any new "cpp" and "ned" files and compile them. |
| 672 | |
| 673 | Now we need to build !OverSim. There are two types of builds: debug (no optimizations, include debug information) and release (optimizations, with reduced debug information). Go to the root folder and type one of the following: |
| 674 | |
| 675 | make MODE=release # release mode |
| 676 | make MODE=debug # debug mode |
| 677 | make # also sets debug mode |
| 678 | |
| 679 | That should compile your new modules. To clear all compiled files, use make clean as usual. |
| 680 | |
| 681 | In order to run it, you need to setup your configuration in omnetpp.ini as explained in Section 4. Make sure that you selected default values for all parameters in default.ini, or you'll be prompted for a value when the simulation begins (for each module that uses that parameter!). |
| 682 | |
| 683 | There are two ways to start !OverSim: by using the GUI (Tkenv) or through the command line (Cmdenv). To run !OverSim, enter the directory <OverSim>/simulations and run : |
| 684 | |
| 685 | {{{ |
| 686 | ../src/OverSim [-u Tkenv | CmdEnv] [-f customConfigFile] -c configName [-r runNumber] |
| 687 | }}} |
| 688 | |
| 689 | The configuration is optional if the GUI is enabled, but you'll be prompted later for which configuration to run. If you don't specify a configuration for the command line (or a non-existing one), the simulator will fail silently and run a default simulation. |
| 690 | |
| 691 | |
| 692 | == 7. Remote Procedure Calls == |
| 693 | |
| 694 | Remote Procedure Calls (RPCs) are a useful paradigm for when an application wants to execute a function, but where the context and / or the code are in another host, yet in a transparent way. Continuing our MyOverlay example, let's create an RPC function that retrieves the neighbors from another node. |
| 695 | |
| 696 | The MyOverlay header would look this way: |
| 697 | |
| 698 | {{{ |
| 699 | class MyOverlay : public BaseOverlay { |
| 700 | ... |
| 701 | // The function that will be called from outside |
| 702 | |
| 703 | void getNeighbors(const OverlayKey& neighborKey); // asynchronously request the neighbors of neighborKey |
| 704 | |
| 705 | // Callback functions (that will be overwritten) |
| 706 | |
| 707 | virtual void callbackNeighbors( |
| 708 | const NodeHandle& neighborKey, |
| 709 | const NodeHandle& prevNeighbor, |
| 710 | const NodeHandle& nextNeighbor); // function to call to respond about the queried neighbors |
| 711 | virtual void callbackTimeout( |
| 712 | const OverlayKey &neighborKey); // function to call if the query times out |
| 713 | |
| 714 | // Internal handling of RPCs |
| 715 | |
| 716 | bool handleRpcCall(BaseCallMessage *msg); // called when we receive an RPC from another node |
| 717 | void handleRpcResponse(BaseResponseMessage* msg, // called when we receive an RPC response from another node |
| 718 | cPolymorphic* context, |
| 719 | int rpcId, |
| 720 | simtime_t rtt); |
| 721 | void handleRpcTimeout(BaseCallMessage* msg, // called when an RPC times out |
| 722 | const TransportAddress& dest, |
| 723 | cPolymorphic* context, int rpcId, |
| 724 | const OverlayKey&); |
| 725 | |
| 726 | |
| 727 | }; |
| 728 | }}} |
| 729 | |
| 730 | The RPC function will query another node about its neighbors. To communicate with that node, we'll need a couple of custom messages. |
| 731 | |
| 732 | The first message is the Call message, and inherits from BaseCallMessage. The address of the calling node is stored in BaseCallMessage, so it is not necessary to add it. However, we'll store the destination key, so we can identify the message quickly in case it times out. Note that the name of Call messages should end in "Call". |
| 733 | The second message is the Response message, and inherits from BaseResponseMessage. It will contain the key of the responding node, together with both of its neighbors. Note that the name of Response messages should end in "Response". |
| 734 | |
| 735 | |
| 736 | Here's how they look: |
| 737 | |
| 738 | {{{ |
| 739 | |
| 740 | cplusplus {{ |
| 741 | #include <NodeHandle.h> |
| 742 | #include <OverlayKey.h> |
| 743 | #include <CommonMessages_m.h> |
| 744 | }} |
| 745 | class noncobject NodeHandle; |
| 746 | class noncobject OverlayKey; |
| 747 | class BaseCallMessage; |
| 748 | class BaseResponseMessage; |
| 749 | |
| 750 | |
| 751 | packet MyNeighborCall extends BaseCallMessage |
| 752 | { |
| 753 | OverlayKey destinationKey; |
| 754 | }; |
| 755 | |
| 756 | packet MyNeighborResponse extends BaseResponseMessage |
| 757 | { |
| 758 | NodeHandle respondingNode; |
| 759 | NodeHandle prevNeighbor; |
| 760 | NodeHandle nextNeighbor; |
| 761 | }; |
| 762 | |
| 763 | }}} |
| 764 | |
| 765 | |
| 766 | First, we start with the getNeighbors() function, which creates the RPC message. |
| 767 | |
| 768 | {{{ |
| 769 | void MyOverlay::getNeighbors(const OverlayKey &neighborKey) { |
| 770 | MyNeighborCall *msg = new MyNeighborCall(); |
| 771 | msg->setDestinationKey(neighborKey); |
| 772 | |
| 773 | // The function we'll be using to send an RPC is sendRouteRpcCall. |
| 774 | // The first value is to which tier we'll be talking. Can be either OVERLAY_COMP, TIER1_COMP, TIER2_COMP, and so on. |
| 775 | // The second parameter is the node to which we'll send the message. Can be either an OverlayKey or a TransportAddress. |
| 776 | // The third parameter is the message. |
| 777 | sendRouteRpcCall(OVERLAY_COMP, neighborKey, msg); |
| 778 | } |
| 779 | }}} |
| 780 | |
| 781 | Now, let's see how the RPC calls are implemented: |
| 782 | |
| 783 | {{{ |
| 784 | |
| 785 | // Handle an incoming Call message |
| 786 | // Only delete msg if the RPC is handled here, and you won't respond using sendRpcResponse! |
| 787 | |
| 788 | bool MyOverlay::handleRpcCall(BaseCallMessage *msg) { |
| 789 | |
| 790 | // There are many macros to simplify the handling of RPCs. The full list is in <OverSim>/src/common/RpcMacros.h. |
| 791 | |
| 792 | // start a switch |
| 793 | RPC_SWITCH_START(msg); |
| 794 | |
| 795 | // enters the following block if the message is of type MyNeighborCall (note the shortened parameter!) |
| 796 | RPC_ON_CALL(MyNeighbor) { |
| 797 | MyNeighborCall *mrpc = (MyNeighborCall*)msg; // get Call message |
| 798 | |
| 799 | MyNeighborResponse *rrpc = new MyNeighborResponse(); // create response |
| 800 | rrpc->setRespondingNode(thisNode); |
| 801 | rrpc->setPrevNeighbor(prevNode); |
| 802 | rrpc->setNextNeighbor(nextNode); |
| 803 | |
| 804 | // now send the response. sendRpcResponse can automatically tell where to send it to. |
| 805 | // note that sendRpcResponse will delete mrpc (aka msg)! |
| 806 | sendRpcResponse(mrpc, rrpc); |
| 807 | |
| 808 | RPC_HANDLED = true; // set to true, since we did handle this RPC (default is false) |
| 809 | } |
| 810 | |
| 811 | // end the switch |
| 812 | RPC_SWITCH_END(); |
| 813 | |
| 814 | // return whether we handled the message or not. |
| 815 | // don't delete unhandled messages! |
| 816 | return RPC_HANDLED; |
| 817 | } |
| 818 | |
| 819 | // Called when an RPC we sent has timed out. |
| 820 | // Don't delete msg here! |
| 821 | |
| 822 | void MyOverlay::handleRpcTimeout(BaseCallMessage* msg, |
| 823 | const TransportAddress& dest, |
| 824 | cPolymorphic* context, int rpcId, |
| 825 | const OverlayKey&) |
| 826 | { |
| 827 | // Same macros as in handleRpc |
| 828 | |
| 829 | // start a switch |
| 830 | RPC_SWITCH_START(msg); |
| 831 | |
| 832 | // enters the following block if the message is of type MyNeighborCall (note the shortened parameter!) |
| 833 | RPC_ON_CALL(MyNeighbor) { |
| 834 | MyNeighborCall *mrpc = (MyNeighborCall*)msg; // get Call message |
| 835 | callbackTimeout(mrpc->getDestinationKey()); // call our interface function |
| 836 | } |
| 837 | // end the switch |
| 838 | RPC_SWITCH_END(); |
| 839 | } |
| 840 | |
| 841 | // Called when we receive an RPC response from another node. |
| 842 | // Don't delete msg here! |
| 843 | |
| 844 | void MyOverlay::handleRpcResponse(BaseResponseMessage* msg, |
| 845 | cPolymorphic* context, |
| 846 | int rpcId, |
| 847 | simtime_t rtt) |
| 848 | { |
| 849 | // The macros are here similar. Just use RPC_ON_RESPONSE instead of RPC_ON_CALL. |
| 850 | |
| 851 | // start a switch |
| 852 | RPC_SWITCH_START(msg); |
| 853 | |
| 854 | // enters the following block if the message is of type MyNeighborResponse (note the shortened parameter!) |
| 855 | RPC_ON_RESPONSE(MyNeighbor) { |
| 856 | MyNeighborResponse *mrpc = (MyNeighborResponse*)msg; // get Response message |
| 857 | callbackNeighbors(mrpc->getRespondingNode(), |
| 858 | mrpc->getPrevNeighbor(), |
| 859 | mrpc->getNextNeighbor()); // call our interface function |
| 860 | } |
| 861 | // end the switch |
| 862 | RPC_SWITCH_END(); |
| 863 | } |
| 864 | |
| 865 | }}} |
| 866 | |