creatingpackets.html 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363
  1. <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
  2. <html><head><title>Creating Packets</title>
  3. <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1">
  4. <link href="RaknetManual.css" rel="stylesheet" type="text/css">
  5. <meta name="title" content="RakNet - Advanced multiplayer game networking API">
  6. </head>
  7. <body leftmargin="0" topmargin="0" style="background-color: rgb(255, 255, 255);" alink="#003399" link="#003399" marginheight="0" marginwidth="0" vlink="#003399">
  8. <img src="RakNet_Icon_Final-copy.jpg" alt="Oculus VR, Inc." width="150" height="150"><br>
  9. <br>
  10. <table border="0" width="100%">
  11. <tbody>
  12. <tr>
  13. <td bgcolor="#2c5d92" class="RakNetWhiteHeader">
  14. <img src="spacer.gif" height="1" width="8">Creating
  15. Packets</td>
  16. </tr>
  17. </tbody>
  18. </table>
  19. <table border="0" cellpadding="10" cellspacing="0" width="100%">
  20. <tbody>
  21. <tr>
  22. <td><span class="RakNetBlueHeader">How
  23. to encode your game data into packets</span><br>
  24. <br>
  25. Systems
  26. that run RakNet, and in fact all systems on the internet, communicate
  27. through what is commonly known as packets. Or more accurately in the
  28. case of UDP, datagrams. Each datagram is created by RakNet and contains
  29. one or more messages. Messages could be created by you, such as
  30. position or health, or sometimes are created by RakNet internally, such
  31. as pings. By convention, the first byte of messages contain a numerical
  32. identifier from 0 to 255 which is used to indicate what type of message
  33. it is. RakNet already has a large set of messages it uses internally,
  34. or for plugins. These can be viewed in the file MessageIdentifiers.h.<br>
  35. <br>
  36. For this example, lets set the position of a timed mine in the gameworld.
  37. We'll need the following data:<br>
  38. <ul>
  39. <li>The position of the mine, which is 3 floats. float x,
  40. float y,
  41. float z. You may have your own vector type which you can use intead. </li>
  42. <li>Some way to refer to the mine that all systems agree
  43. on. The <a href="networkidobject.html">NetworkIDObject</a>
  44. class is perfect for that. Lets assume class Mine inherits from
  45. NetworkIDObject. Then all we have to store is get the NetworkID of the
  46. mine (for more information see <a href="receivingpackets.html">Receiving
  47. Packets</a>, <a href="sendingpackets.html">Sending
  48. Packets</a>. </li>
  49. <li>Who owns the mine. That way if someone steps on it we
  50. know who
  51. to give credit to. The built in reference to players, SystemAddress, is
  52. perfect.. You can use GetExternalID() to get the SystemAddress. </li>
  53. <li>When the mine was placed. Lets say after 10 seconds
  54. the mine
  55. is automatically disintegrated, so it's important that we get the time
  56. correct so the mine doesn't disintegrate at different times on
  57. different computers. Fortunately the RakNet has the built-in ability to
  58. handle this using <a href="timestamping.html">Timestamping</a>.</li>
  59. </ul>
  60. <span class="RakNetBlueHeader">Use
  61. a structure or a bitstream?</span><br>
  62. <br>
  63. Ultimately, anytime you send data you will send a stream of characters.
  64. There are two easy ways to encode your data into this. One is to create
  65. a structure and cast it to a (char*) the other is to use the built-in <a href="bitstreams.html">BitStream</a> class.<br>
  66. <br>
  67. The advantage of creating a structure and casting is that it is very
  68. easy to change the structure and to see what data you are actually
  69. sending. Since both the sender and the recipient can share the same
  70. source file defining the structure, you avoid casting mistakes. There
  71. is also no risk of getting the data out of order, or using the wrong
  72. types. The disadvantage of creating a structure is that you often have
  73. to change and recompile many files to do so. You lose the
  74. compression you can automatically perform with the bitstream class. And RakNet cannot automatically endian-swap the structure members.<br>
  75. <br>
  76. The advantage of using a bitstream is that you don't have to change any
  77. external files to use it. Simply create the bitstream, write the data
  78. you want in whatever order you want, and send it. You can use the
  79. 'compressed' version of the read and write methods to write using fewer
  80. bits and it will write bools using only one bit. You can write
  81. data out dynamically, writing certain values if certain conditions are
  82. true or false. BitStream will automatically endian-swap members written with Serialize(), Write(), or Read(). The disadvantage of a bitstream is you are now
  83. susceptible to make mistakes. You can read data in a way that does not
  84. complement how you wrote it - the wrong order, a wrong data type, or
  85. other mistakes.<br>
  86. <br>
  87. We will cover both ways of creating packets here.<br></td>
  88. </tr>
  89. </tbody>
  90. </table>
  91. <table border="0" width="100%">
  92. <tbody>
  93. <tr>
  94. <td bgcolor="#2c5d92" class="RakNetWhiteHeader">
  95. <img src="spacer.gif" height="1" width="8">Creating
  96. Packets with structs</td>
  97. </tr>
  98. </tbody>
  99. </table>
  100. <table border="0" cellpadding="10" cellspacing="0" width="100%">
  101. <tbody>
  102. <tr>
  103. <td>
  104. <p><br>
  105. As I&#8217;ve probably mentioned earlier, RakNet uses a convention on how
  106. packets (Packet) types are identified.
  107. The first byte of the data field is a single byte enumeration that
  108. specifies type, followed by the transmitted data.
  109. In packets that include a time stamp, the first byte contains
  110. ID_TIMESTAMP, the following 4 bytes the actual time stamp value, then
  111. the byte that identifies the packet, and only then the actual data
  112. transmitted. <br>
  113. <br>
  114. <span class="RakNetBlueHeader">Without
  115. Timestamping</span>
  116. <span class="RakNetCode"><br>
  117. </span></p>
  118. <p class="RakNetCode">#pragma pack(push, 1)<br>
  119. struct structName<br>
  120. {<br>
  121. unsigned char typeId; // Your type here<br>
  122. // Your data here<br>
  123. };<br>
  124. #pragma pack(pop)</p>
  125. <p>Noticed
  126. the #pragma pack(push,1) and #pragma pack(pop) ? These force your
  127. compiler (in this case VC++), to pack the structure as byte-aligned.
  128. Check your compiler documentation to learn more.</p>
  129. <p class="RakNetBlueHeader">With Timestamping</p>
  130. <p class="RakNetCode"> #pragma pack(push, 1)<br>
  131. struct structName<br>
  132. {<br>
  133. unsigned char useTimeStamp; // Assign ID_TIMESTAMP to this<br>
  134. RakNet::Time timeStamp; // Put the system time in here returned by RakNet::GetTime() or some other method that returns a similar value<br>
  135. unsigned char typeId; // Your type here<br>
  136. // Your data here<br>
  137. };<br>
  138. #pragma pack(pop)</p>
  139. <p>Note that when sending structures, RakNet assumes the timeStamp is in network order. You would have to use the function BitStream::EndianSwapBytes() on the timeStamp field to make this happen. To then read the timestamp on the receiving system, use if (bitStream-&gt;DoEndianSwap()) bitStream-&gt;ReverseBytes(timeStamp, sizeof(timeStamp). This step is not necessary if using BitStreams.</p>
  140. <p> Fill out your packet. For our timed mine, we want the form that uses
  141. timestamping. So the end result should look like the following...</p>
  142. <span class="RakNetCode"> #pragma pack(push, 1)<br>
  143. struct structName<br>
  144. {<br>
  145. unsigned char useTimeStamp; // Assign ID_TIMESTAMP to this<br>
  146. RakNet::Time timeStamp; // Put the system time in here returned by RakNet::GetTime()<br>
  147. unsigned char typeId; // You should put here an enum you defined after the last one defined in MessageIdentifiers.h, lets say ID_SET_TIMED_MINE<br>
  148. float x,y,z; // Mine position<br>
  149. NetworkID networkId; // NetworkID of the mine, used as a common method to refer to the mine on different computers<br>
  150. SystemAddress systemAddress; // The SystenAddress of the player that owns the mine<br>
  151. };<br>
  152. #pragma pack(pop)</span>
  153. <p>
  154. As I wrote in the comment above, we have to define enums for our own
  155. packets types, so when the data stream arrives in a Receive call we
  156. know what we are looking at. You should define your enums as starting
  157. at ID_USER_PACKET_ENUM, like this:</p>
  158. <p><span class="RakNetCode"><br>
  159. // Define our custom packet ID's<br>
  160. enum {<br>
  161. ID_SET_TIMED_MINE = ID_USER_PACKET_ENUM,<br>
  162. // More enums....<br>
  163. };<br>
  164. </span><br>
  165. NOTE THAT YOU CANNOT INCLUDE POINTERS DIRECTLY OR
  166. INDIRECTLY IN THE STRUCTS.<br>
  167. <br>
  168. It seems to be a fairly common mistake that people include a pointer or
  169. a class with a pointer in the struct and think that the data pointed to
  170. by the pointer will be sent over the network. This is not the case -
  171. all it would send is the pointer address<br>
  172. <br>
  173. Nested Structures<br>
  174. <br>
  175. There is no problem with nesting structures. Just keep in mind that the
  176. first byte is always what determines the packet type.<br>
  177. <span class="RakNetCode"><br>
  178. #pragma pack(push, 1)<br>
  179. struct A<br>
  180. {<br>
  181. unsigned char typeId; // ID_A<br>
  182. };<br>
  183. struct B<br>
  184. {<br>
  185. unsigned char typeId; // ID_A<br>
  186. };<br>
  187. struct C // Struct C is of type ID_A<br>
  188. {<br>
  189. A a;<br>
  190. B b;<br>
  191. }<br>
  192. struct D // Struct D is of type ID_B<br>
  193. {<br>
  194. B b;<br>
  195. A a;<br>
  196. }<br>
  197. #pragma pack(pop)</span><font class="G10" color="#111122" face="Geneva, Verdana, Arial, Helvetica, sans-serif" size="1"><br>
  198. </p>
  199. <p></p></td>
  200. </tr>
  201. </tbody>
  202. </table>
  203. <table border="0" width="100%">
  204. <tbody>
  205. <tr>
  206. <td bgcolor="#2c5d92"><font color="#ffffff" face="Arial, Helvetica, sans-serif" size="3"><strong><span class="RakNetWhiteHeader">&nbsp;Creating
  207. Packets with Bitstreams</span></td>
  208. </tr>
  209. </tbody>
  210. </table>
  211. <table border="0" cellpadding="10" cellspacing="0" width="100%">
  212. <tbody>
  213. <tr>
  214. <td><p><span class="RakNetBlueHeader">Write
  215. less data with bitstreams
  216. </span><br>
  217. <br>
  218. Lets take our mine example above and use a bitstream to write it out
  219. instead. We have all the same data as before.<br>
  220. <br>
  221. <span class="RakNetCode"> MessageID useTimeStamp; // Assign this to ID_TIMESTAMP<br>
  222. RakNet::Time timeStamp; // Put the system time in here returned by RakNet::GetTime()<br>
  223. MessageID typeId; // This will be assigned to a type I've added after ID_USER_PACKET_ENUM, lets say ID_SET_TIMED_MINE<br>
  224. useTimeStamp = ID_TIMESTAMP;<br>
  225. timeStamp = RakNet::GetTime();<br>
  226. typeId=ID_SET_TIMED_MINE;<br>
  227. Bitstream myBitStream;<br>
  228. myBitStream.Write(useTimeStamp);<br>
  229. myBitStream.Write(timeStamp);<br>
  230. myBitStream.Write(typeId);<br>
  231. // Assume we have a Mine* mine object<br>
  232. myBitStream.Write(mine-&gt;GetPosition().x);<br>
  233. myBitStream.Write(mine-&gt;GetPosition().y);<br>
  234. myBitStream.Write(mine-&gt;GetPosition().z);<br>
  235. myBitStream.Write(mine-&gt;GetNetworkID()); // In the struct this is NetworkID networkId<br>
  236. myBitStream.Write(mine-&gt;GetOwner()); // In the struct this is SystemAddress systemAddress<br>
  237. </span><br>
  238. If we were to send myBitStream to RakPeerInterface::Send it would be
  239. identical internally to a casted struct at this point. Now lets try
  240. some improvements. Lets assume that a good deal of the time mines are
  241. at 0,0,0 for some reason. We can then do this instead:<br>
  242. <span class="RakNetCode"><br>
  243. unsigned char useTimeStamp; // Assign this to ID_TIMESTAMP<br>
  244. RakNet::Time timeStamp; // Put the system time in here returned by RakNet::GetTime()<br>
  245. unsigned char typeId; // This will be assigned to a type I've added after ID_USER_PACKET_ENUM, lets say ID_SET_TIMED_MINE<br>
  246. useTimeStamp = ID_TIMESTAMP;<br>
  247. timeStamp = RakNet::GetTime();<br>
  248. typeId=ID_SET_TIMED_MINE;<br>
  249. Bitstream myBitStream;<br>
  250. myBitStream.Write(useTimeStamp);<br>
  251. myBitStream.Write(timeStamp);<br>
  252. myBitStream.Write(typeId);<br>
  253. // Assume we have a Mine* mine object<br>
  254. // If the mine is at 0,0,0, use 1 bit to represent this<br>
  255. if (mine-&gt;GetPosition().x==0.0f &amp;&amp; mine-&gt;GetPosition().y==0.0f &amp;&amp; mine-&gt;GetPosition().z==0.0f)<br>
  256. {<br>
  257. myBitStream.Write(true);<br>
  258. }<br>
  259. else<br>
  260. {<br>
  261. myBitStream.Write(false);<br>
  262. myBitStream.Write(mine-&gt;GetPosition().x);<br>
  263. myBitStream.Write(mine-&gt;GetPosition().y);<br>
  264. myBitStream.Write(mine-&gt;GetPosition().z);<br>
  265. }<br>
  266. myBitStream.Write(mine-&gt;GetNetworkID()); // In the struct this is NetworkID networkId<br>
  267. myBitStream.Write(mine-&gt;GetOwner()); // In the struct this is SystemAddress systemAddress<br>
  268. </span><br>
  269. This can potentially save us sending 3 floats over the network, at the
  270. cost of 1 bit.<br>
  271. <br>
  272. <span class="RakNetBlueHeader">Common
  273. mistake!</span><br>
  274. <br style="font-weight: bold;">
  275. When
  276. writing the first byte to a bitstream, be sure to cast it to
  277. (MessageID) or (unsigned char). If you just write the enumeration
  278. directly you will be writing a full integer (4 bytes).<span style="font-style: italic;"><br>
  279. </span><br>
  280. Right:<br>
  281. <span class="RakNetCode">bitStream-&gt;Write((MessageID)ID_SET_TIMED_MINE); </span></p>
  282. <p> Wrong: <br>
  283. <span class="RakNetCode">bitStream-&gt;Write(ID_SET_TIMED_MINE);</span></p>
  284. <p> In the second case, RakNet will see the first byte is 0, which is
  285. reserved internally to ID_INTERNAL_PING, and you will never get it.</p>
  286. <p><font class="G10" color="#666666" size="2"><br>
  287. <strong class="RakNetBlueHeader">Writing
  288. strings</strong>
  289. <br>
  290. <br>
  291. It is possible to write strings using the array overload of the
  292. BitStream. One way to do it would be to write the length, then the data
  293. such as:</p>
  294. <p><span class="RakNetCode">void WriteStringToBitStream(char *myString, BitStream *output)<br>
  295. {<br>
  296. output-&gt;Write((unsigned short) strlen(myString));<br>
  297. output-&gt;Write(myString, strlen(myString);<br>
  298. }</span></p>
  299. <p>Decoding
  300. is similar. However, that is not very efficient. RakNet comes with a
  301. built in StringCompressor called... stringCompressor. It is a global
  302. instance. With it, WriteStringToBitStream becomes:</p>
  303. <p><span class="RakNetCode">void WriteStringToBitStream(char *myString, BitStream *output)<br>
  304. {<br>
  305. stringCompressor-&gt;EncodeString(myString, 256, output);<br>
  306. }</span></p>
  307. <p>Not
  308. only does it encode the string, so the string can not easily be read by
  309. packet sniffers, but it compresses it as well. To decode the string you
  310. would use:</p>
  311. <p><span class="RakNetCode">void WriteBitStreamToString(char *myString, BitStream *input)<br>
  312. {<br>
  313. stringCompressor-&gt;DecodeString(myString, 256, input);<br>
  314. }</span></p>
  315. <p>The
  316. 256 in this case is the maximum number of bytes to write and read. In
  317. EncodeString, if your string is less than 256 it will write the entire
  318. string. If it is greater than 256 characters it will truncate it such
  319. that it would decode to an array with 256 characters, including the
  320. null terminator. </p>
  321. <p>RakNet also has a string class, RakNet::RakString, found in RakString.h</p>
  322. <p class="RakNetCode">RakNet::RakString rakString(&quot;The value is %i&quot;, myInt);</p>
  323. <p><span class="RakNetCode">bitStream-&gt;write(rakString);</span><br>
  324. <br>
  325. RakString is approximately 3X faster than std::string.</p>
  326. <p>Use RakWString for Unicode support.</p>
  327. <p><i>Programmer's notes:</i><br>
  328. <br>
  329. 1. You can also write structs
  330. directly into a Bitstream simply by casting it to a (char*). It will
  331. copy your structs using memcpy. As with structs, it will not
  332. dereference pointers so you should not write pointers into the
  333. bitstream.<br>
  334. 2.
  335. If you use a string very commonly, you can use the StringTable class
  336. instead. It works the same way as StringCompressor, but can send two
  337. bytes to represent a known string.<font class="G10" color="#666666" size="2"><font class="G10" color="#666666" size="2"><font class="G10" color="#666666" size="2"><font class="G10" color="#666666" face="Geneva, Verdana, Arial, Helvetica, sans-serif" size="2"><font class="G10" color="#666666" face="Geneva, Verdana, Arial, Helvetica, sans-serif" size="2"><font class="G10" color="#666666" face="Geneva, Verdana, Arial, Helvetica, sans-serif" size="2"><br>
  338. </p></td>
  339. </tr>
  340. </tbody>
  341. </table>
  342. <table border="0" width="100%">
  343. <tbody>
  344. <tr>
  345. <td bgcolor="#2c5d92" class="RakNetWhiteHeader"><strong>&nbsp;See Also</td>
  346. </tr>
  347. </tbody>
  348. </table>
  349. <table border="0" cellpadding="10" cellspacing="0" width="100%">
  350. <tbody>
  351. <tr>
  352. <td><font class="G10" color="#111122" face="Geneva, Verdana, Arial, Helvetica, sans-serif" size="1"><a href="index.html">Index</a><br>
  353. <a href="sendingpackets.html">Sending Packets</a><br>
  354. <a href="receivingpackets.html">Receiving Packets</a><br>
  355. <a href="timestamping.html">Timestamping</a><br>
  356. </td>
  357. </tr>
  358. </tbody>
  359. </table>
  360. </body></html>
粤ICP备19079148号