KcpPeer.cs 33 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746
  1. // Kcp Peer, similar to UDP Peer but wrapped with reliability, channels,
  2. // timeouts, authentication, state, etc.
  3. //
  4. // still IO agnostic to work with udp, nonalloc, relays, native, etc.
  5. using System;
  6. using System.Diagnostics;
  7. using System.Net.Sockets;
  8. namespace kcp2k
  9. {
  10. public abstract class KcpPeer
  11. {
  12. // kcp reliability algorithm
  13. internal Kcp kcp;
  14. // security cookie to prevent UDP spoofing.
  15. // credits to IncludeSec for disclosing the issue.
  16. //
  17. // server passes the expected cookie to the client's KcpPeer.
  18. // KcpPeer sends cookie to the connected client.
  19. // KcpPeer only accepts packets which contain the cookie.
  20. // => cookie can be a random number, but it needs to be cryptographically
  21. // secure random that can't be easily predicted.
  22. // => cookie can be hash(ip, port) BUT only if salted to be not predictable
  23. internal uint cookie;
  24. // state: connected as soon as we create the peer.
  25. // leftover from KcpConnection. remove it after refactoring later.
  26. protected KcpState state = KcpState.Connected;
  27. // If we don't receive anything these many milliseconds
  28. // then consider us disconnected
  29. public const int DEFAULT_TIMEOUT = 10000;
  30. public int timeout;
  31. uint lastReceiveTime;
  32. // internal time.
  33. // StopWatch offers ElapsedMilliSeconds and should be more precise than
  34. // Unity's time.deltaTime over long periods.
  35. readonly Stopwatch watch = new Stopwatch();
  36. // buffer to receive kcp's processed messages (avoids allocations).
  37. // IMPORTANT: this is for KCP messages. so it needs to be of size:
  38. // 1 byte header + MaxMessageSize content
  39. readonly byte[] kcpMessageBuffer;// = new byte[1 + ReliableMaxMessageSize];
  40. // send buffer for handing user messages to kcp for processing.
  41. // (avoids allocations).
  42. // IMPORTANT: needs to be of size:
  43. // 1 byte header + MaxMessageSize content
  44. readonly byte[] kcpSendBuffer;// = new byte[1 + ReliableMaxMessageSize];
  45. // raw send buffer is exactly MTU.
  46. readonly byte[] rawSendBuffer;
  47. // send a ping occasionally so we don't time out on the other end.
  48. // for example, creating a character in an MMO could easily take a
  49. // minute of no data being sent. which doesn't mean we want to time out.
  50. // same goes for slow paced card games etc.
  51. public const int PING_INTERVAL = 1000;
  52. uint lastPingTime;
  53. // if we send more than kcp can handle, we will get ever growing
  54. // send/recv buffers and queues and minutes of latency.
  55. // => if a connection can't keep up, it should be disconnected instead
  56. // to protect the server under heavy load, and because there is no
  57. // point in growing to gigabytes of memory or minutes of latency!
  58. // => 2k isn't enough. we reach 2k when spawning 4k monsters at once
  59. // easily, but it does recover over time.
  60. // => 10k seems safe.
  61. //
  62. // note: we have a ChokeConnectionAutoDisconnects test for this too!
  63. internal const int QueueDisconnectThreshold = 10000;
  64. // getters for queue and buffer counts, used for debug info
  65. public int SendQueueCount => kcp.snd_queue.Count;
  66. public int ReceiveQueueCount => kcp.rcv_queue.Count;
  67. public int SendBufferCount => kcp.snd_buf.Count;
  68. public int ReceiveBufferCount => kcp.rcv_buf.Count;
  69. // we need to subtract the channel and cookie bytes from every
  70. // MaxMessageSize calculation.
  71. // we also need to tell kcp to use MTU-1 to leave space for the byte.
  72. public const int CHANNEL_HEADER_SIZE = 1;
  73. public const int COOKIE_HEADER_SIZE = 4;
  74. public const int METADATA_SIZE = CHANNEL_HEADER_SIZE + COOKIE_HEADER_SIZE;
  75. // reliable channel (= kcp) MaxMessageSize so the outside knows largest
  76. // allowed message to send. the calculation in Send() is not obvious at
  77. // all, so let's provide the helper here.
  78. //
  79. // kcp does fragmentation, so max message is way larger than MTU.
  80. //
  81. // -> runtime MTU changes are disabled: mss is always MTU_DEF-OVERHEAD
  82. // -> Send() checks if fragment count < rcv_wnd, so we use rcv_wnd - 1.
  83. // NOTE that original kcp has a bug where WND_RCV default is used
  84. // instead of configured rcv_wnd, limiting max message size to 144 KB
  85. // https://github.com/skywind3000/kcp/pull/291
  86. // we fixed this in kcp2k.
  87. // -> we add 1 byte KcpHeader enum to each message, so -1
  88. //
  89. // IMPORTANT: max message is MTU * rcv_wnd, in other words it completely
  90. // fills the receive window! due to head of line blocking,
  91. // all other messages have to wait while a maxed size message
  92. // is being delivered.
  93. // => in other words, DO NOT use max size all the time like
  94. // for batching.
  95. // => sending UNRELIABLE max message size most of the time is
  96. // best for performance (use that one for batching!)
  97. static int ReliableMaxMessageSize_Unconstrained(int mtu, uint rcv_wnd) =>
  98. (mtu - Kcp.OVERHEAD - METADATA_SIZE) * ((int)rcv_wnd - 1) - 1;
  99. // kcp encodes 'frg' as 1 byte.
  100. // max message size can only ever allow up to 255 fragments.
  101. // WND_RCV gives 127 fragments.
  102. // WND_RCV * 2 gives 255 fragments.
  103. // so we can limit max message size by limiting rcv_wnd parameter.
  104. public static int ReliableMaxMessageSize(int mtu, uint rcv_wnd) =>
  105. ReliableMaxMessageSize_Unconstrained(mtu, Math.Min(rcv_wnd, Kcp.FRG_MAX));
  106. // unreliable max message size is simply MTU - channel header size
  107. public static int UnreliableMaxMessageSize(int mtu) =>
  108. mtu - METADATA_SIZE;
  109. // maximum send rate per second can be calculated from kcp parameters
  110. // source: https://translate.google.com/translate?sl=auto&tl=en&u=https://wetest.qq.com/lab/view/391.html
  111. //
  112. // KCP can send/receive a maximum of WND*MTU per interval.
  113. // multiple by 1000ms / interval to get the per-second rate.
  114. //
  115. // example:
  116. // WND(32) * MTU(1400) = 43.75KB
  117. // => 43.75KB * 1000 / INTERVAL(10) = 4375KB/s
  118. //
  119. // returns bytes/second!
  120. public uint MaxSendRate => kcp.snd_wnd * kcp.mtu * 1000 / kcp.interval;
  121. public uint MaxReceiveRate => kcp.rcv_wnd * kcp.mtu * 1000 / kcp.interval;
  122. // calculate max message sizes based on mtu and wnd only once
  123. public readonly int unreliableMax;
  124. public readonly int reliableMax;
  125. // SetupKcp creates and configures a new KCP instance.
  126. // => useful to start from a fresh state every time the client connects
  127. // => NoDelay, interval, wnd size are the most important configurations.
  128. // let's force require the parameters so we don't forget it anywhere.
  129. protected KcpPeer(KcpConfig config, uint cookie)
  130. {
  131. // initialize variable state in extra function so we can reuse it
  132. // when reconnecting to reset state
  133. Reset(config);
  134. // set the cookie after resetting state so it's not overwritten again.
  135. // with log message for debugging in case of cookie issues.
  136. this.cookie = cookie;
  137. Log.Info($"{GetType()}: created with cookie={cookie}");
  138. // create mtu sized send buffer
  139. rawSendBuffer = new byte[config.Mtu];
  140. // calculate max message sizes once
  141. unreliableMax = UnreliableMaxMessageSize(config.Mtu);
  142. reliableMax = ReliableMaxMessageSize(config.Mtu, config.ReceiveWindowSize);
  143. // create message buffers AFTER window size is set
  144. // see comments on buffer definition for the "+1" part
  145. kcpMessageBuffer = new byte[1 + reliableMax];
  146. kcpSendBuffer = new byte[1 + reliableMax];
  147. }
  148. // Reset all state once.
  149. // useful for KcpClient to reconned with a fresh kcp state.
  150. protected void Reset(KcpConfig config)
  151. {
  152. // reset state
  153. cookie = 0;
  154. state = KcpState.Connected;
  155. lastReceiveTime = 0;
  156. lastPingTime = 0;
  157. watch.Restart(); // start at 0 each time
  158. // set up kcp over reliable channel (that's what kcp is for)
  159. kcp = new Kcp(0, RawSendReliable);
  160. // set nodelay.
  161. // note that kcp uses 'nocwnd' internally so we negate the parameter
  162. kcp.SetNoDelay(config.NoDelay ? 1u : 0u, config.Interval, config.FastResend, !config.CongestionWindow);
  163. kcp.SetWindowSize(config.SendWindowSize, config.ReceiveWindowSize);
  164. // IMPORTANT: high level needs to add 1 channel byte to each raw
  165. // message. so while Kcp.MTU_DEF is perfect, we actually need to
  166. // tell kcp to use MTU-1 so we can still put the header into the
  167. // message afterwards.
  168. kcp.SetMtu((uint)config.Mtu - METADATA_SIZE);
  169. // set maximum retransmits (aka dead_link)
  170. kcp.dead_link = config.MaxRetransmits;
  171. timeout = config.Timeout;
  172. }
  173. // callbacks ///////////////////////////////////////////////////////////
  174. // events are abstract, guaranteed to be implemented.
  175. // this ensures they are always initialized when used.
  176. // fixes https://github.com/MirrorNetworking/Mirror/issues/3337 and more
  177. protected abstract void OnAuthenticated();
  178. protected abstract void OnData(ArraySegment<byte> message, KcpChannel channel);
  179. protected abstract void OnDisconnected();
  180. // error callback instead of logging.
  181. // allows libraries to show popups etc.
  182. // (string instead of Exception for ease of use and to avoid user panic)
  183. protected abstract void OnError(ErrorCode error, string message);
  184. protected abstract void RawSend(ArraySegment<byte> data);
  185. ////////////////////////////////////////////////////////////////////////
  186. void HandleTimeout(uint time)
  187. {
  188. // note: we are also sending a ping regularly, so timeout should
  189. // only ever happen if the connection is truly gone.
  190. if (time >= lastReceiveTime + timeout)
  191. {
  192. // pass error to user callback. no need to log it manually.
  193. // GetType() shows Server/ClientConn instead of just Connection.
  194. OnError(ErrorCode.Timeout, $"{GetType()}: Connection timed out after not receiving any message for {timeout}ms. Disconnecting.");
  195. Disconnect();
  196. }
  197. }
  198. void HandleDeadLink()
  199. {
  200. // kcp has 'dead_link' detection. might as well use it.
  201. if (kcp.state == -1)
  202. {
  203. // pass error to user callback. no need to log it manually.
  204. // GetType() shows Server/ClientConn instead of just Connection.
  205. OnError(ErrorCode.Timeout, $"{GetType()}: dead_link detected: a message was retransmitted {kcp.dead_link} times without ack. Disconnecting.");
  206. Disconnect();
  207. }
  208. }
  209. // send a ping occasionally in order to not time out on the other end.
  210. void HandlePing(uint time)
  211. {
  212. // enough time elapsed since last ping?
  213. if (time >= lastPingTime + PING_INTERVAL)
  214. {
  215. // ping again and reset time
  216. //Log.Debug("KCP: sending ping...");
  217. SendPing();
  218. lastPingTime = time;
  219. }
  220. }
  221. void HandleChoked()
  222. {
  223. // disconnect connections that can't process the load.
  224. // see QueueSizeDisconnect comments.
  225. // => include all of kcp's buffers and the unreliable queue!
  226. int total = kcp.rcv_queue.Count + kcp.snd_queue.Count +
  227. kcp.rcv_buf.Count + kcp.snd_buf.Count;
  228. if (total >= QueueDisconnectThreshold)
  229. {
  230. // pass error to user callback. no need to log it manually.
  231. // GetType() shows Server/ClientConn instead of just Connection.
  232. OnError(ErrorCode.Congestion,
  233. $"{GetType()}: disconnecting connection because it can't process data fast enough.\n" +
  234. $"Queue total {total}>{QueueDisconnectThreshold}. rcv_queue={kcp.rcv_queue.Count} snd_queue={kcp.snd_queue.Count} rcv_buf={kcp.rcv_buf.Count} snd_buf={kcp.snd_buf.Count}\n" +
  235. $"* Try to Enable NoDelay, decrease INTERVAL, disable Congestion Window (= enable NOCWND!), increase SEND/RECV WINDOW or compress data.\n" +
  236. $"* Or perhaps the network is simply too slow on our end, or on the other end.");
  237. // let's clear all pending sends before disconnting with 'Bye'.
  238. // otherwise a single Flush in Disconnect() won't be enough to
  239. // flush thousands of messages to finally deliver 'Bye'.
  240. // this is just faster and more robust.
  241. kcp.snd_queue.Clear();
  242. Disconnect();
  243. }
  244. }
  245. // reads the next reliable message type & content from kcp.
  246. // -> to avoid buffering, unreliable messages call OnData directly.
  247. bool ReceiveNextReliable(out KcpHeader header, out ArraySegment<byte> message)
  248. {
  249. message = default;
  250. header = KcpHeader.Disconnect;
  251. int msgSize = kcp.PeekSize();
  252. if (msgSize <= 0) return false;
  253. // only allow receiving up to buffer sized messages.
  254. // otherwise we would get BlockCopy ArgumentException anyway.
  255. if (msgSize > kcpMessageBuffer.Length)
  256. {
  257. // we don't allow sending messages > Max, so this must be an
  258. // attacker. let's disconnect to avoid allocation attacks etc.
  259. // pass error to user callback. no need to log it manually.
  260. OnError(ErrorCode.InvalidReceive, $"{GetType()}: possible allocation attack for msgSize {msgSize} > buffer {kcpMessageBuffer.Length}. Disconnecting the connection.");
  261. Disconnect();
  262. return false;
  263. }
  264. // receive from kcp
  265. int received = kcp.Receive(kcpMessageBuffer, msgSize);
  266. if (received < 0)
  267. {
  268. // if receive failed, close everything
  269. // pass error to user callback. no need to log it manually.
  270. // GetType() shows Server/ClientConn instead of just Connection.
  271. OnError(ErrorCode.InvalidReceive, $"{GetType()}: Receive failed with error={received}. closing connection.");
  272. Disconnect();
  273. return false;
  274. }
  275. // extract header & content without header
  276. header = (KcpHeader)kcpMessageBuffer[0];
  277. message = new ArraySegment<byte>(kcpMessageBuffer, 1, msgSize - 1);
  278. lastReceiveTime = (uint)watch.ElapsedMilliseconds;
  279. return true;
  280. }
  281. void TickIncoming_Connected(uint time)
  282. {
  283. // detect common events & ping
  284. HandleTimeout(time);
  285. HandleDeadLink();
  286. HandlePing(time);
  287. HandleChoked();
  288. // any reliable kcp message received?
  289. if (ReceiveNextReliable(out KcpHeader header, out ArraySegment<byte> message))
  290. {
  291. // message type FSM. no default so we never miss a case.
  292. switch (header)
  293. {
  294. case KcpHeader.Hello:
  295. {
  296. // we were waiting for a Hello message.
  297. // it proves that the other end speaks our protocol.
  298. // log with previously parsed cookie
  299. Log.Info($"{GetType()}: received hello with cookie={cookie}");
  300. state = KcpState.Authenticated;
  301. OnAuthenticated();
  302. break;
  303. }
  304. case KcpHeader.Ping:
  305. {
  306. // ping keeps kcp from timing out. do nothing.
  307. break;
  308. }
  309. case KcpHeader.Data:
  310. case KcpHeader.Disconnect:
  311. {
  312. // everything else is not allowed during handshake!
  313. // pass error to user callback. no need to log it manually.
  314. // GetType() shows Server/ClientConn instead of just Connection.
  315. OnError(ErrorCode.InvalidReceive, $"{GetType()}: received invalid header {header} while Connected. Disconnecting the connection.");
  316. Disconnect();
  317. break;
  318. }
  319. }
  320. }
  321. }
  322. void TickIncoming_Authenticated(uint time)
  323. {
  324. // detect common events & ping
  325. HandleTimeout(time);
  326. HandleDeadLink();
  327. HandlePing(time);
  328. HandleChoked();
  329. // process all received messages
  330. while (ReceiveNextReliable(out KcpHeader header, out ArraySegment<byte> message))
  331. {
  332. // message type FSM. no default so we never miss a case.
  333. switch (header)
  334. {
  335. case KcpHeader.Hello:
  336. {
  337. // should never receive another hello after auth
  338. // GetType() shows Server/ClientConn instead of just Connection.
  339. Log.Warning($"{GetType()}: received invalid header {header} while Authenticated. Disconnecting the connection.");
  340. Disconnect();
  341. break;
  342. }
  343. case KcpHeader.Data:
  344. {
  345. // call OnData IF the message contained actual data
  346. if (message.Count > 0)
  347. {
  348. //Log.Warning($"Kcp recv msg: {BitConverter.ToString(message.Array, message.Offset, message.Count)}");
  349. OnData(message, KcpChannel.Reliable);
  350. }
  351. // empty data = attacker, or something went wrong
  352. else
  353. {
  354. // pass error to user callback. no need to log it manually.
  355. // GetType() shows Server/ClientConn instead of just Connection.
  356. OnError(ErrorCode.InvalidReceive, $"{GetType()}: received empty Data message while Authenticated. Disconnecting the connection.");
  357. Disconnect();
  358. }
  359. break;
  360. }
  361. case KcpHeader.Ping:
  362. {
  363. // ping keeps kcp from timing out. do nothing.
  364. break;
  365. }
  366. case KcpHeader.Disconnect:
  367. {
  368. // disconnect might happen
  369. // GetType() shows Server/ClientConn instead of just Connection.
  370. Log.Info($"{GetType()}: received disconnect message");
  371. Disconnect();
  372. break;
  373. }
  374. }
  375. }
  376. }
  377. public virtual void TickIncoming()
  378. {
  379. uint time = (uint)watch.ElapsedMilliseconds;
  380. try
  381. {
  382. switch (state)
  383. {
  384. case KcpState.Connected:
  385. {
  386. TickIncoming_Connected(time);
  387. break;
  388. }
  389. case KcpState.Authenticated:
  390. {
  391. TickIncoming_Authenticated(time);
  392. break;
  393. }
  394. case KcpState.Disconnected:
  395. {
  396. // do nothing while disconnected
  397. break;
  398. }
  399. }
  400. }
  401. // TODO KcpConnection is IO agnostic. move this to outside later.
  402. catch (SocketException exception)
  403. {
  404. // this is ok, the connection was closed
  405. // pass error to user callback. no need to log it manually.
  406. // GetType() shows Server/ClientConn instead of just Connection.
  407. OnError(ErrorCode.ConnectionClosed, $"{GetType()}: Disconnecting because {exception}. This is fine.");
  408. Disconnect();
  409. }
  410. catch (ObjectDisposedException exception)
  411. {
  412. // fine, socket was closed
  413. // pass error to user callback. no need to log it manually.
  414. // GetType() shows Server/ClientConn instead of just Connection.
  415. OnError(ErrorCode.ConnectionClosed, $"{GetType()}: Disconnecting because {exception}. This is fine.");
  416. Disconnect();
  417. }
  418. catch (Exception exception)
  419. {
  420. // unexpected
  421. // pass error to user callback. no need to log it manually.
  422. // GetType() shows Server/ClientConn instead of just Connection.
  423. OnError(ErrorCode.Unexpected, $"{GetType()}: unexpected Exception: {exception}");
  424. Disconnect();
  425. }
  426. }
  427. public virtual void TickOutgoing()
  428. {
  429. uint time = (uint)watch.ElapsedMilliseconds;
  430. try
  431. {
  432. switch (state)
  433. {
  434. case KcpState.Connected:
  435. case KcpState.Authenticated:
  436. {
  437. // update flushes out messages
  438. kcp.Update(time);
  439. break;
  440. }
  441. case KcpState.Disconnected:
  442. {
  443. // do nothing while disconnected
  444. break;
  445. }
  446. }
  447. }
  448. // TODO KcpConnection is IO agnostic. move this to outside later.
  449. catch (SocketException exception)
  450. {
  451. // this is ok, the connection was closed
  452. // pass error to user callback. no need to log it manually.
  453. // GetType() shows Server/ClientConn instead of just Connection.
  454. OnError(ErrorCode.ConnectionClosed, $"{GetType()}: Disconnecting because {exception}. This is fine.");
  455. Disconnect();
  456. }
  457. catch (ObjectDisposedException exception)
  458. {
  459. // fine, socket was closed
  460. // pass error to user callback. no need to log it manually.
  461. // GetType() shows Server/ClientConn instead of just Connection.
  462. OnError(ErrorCode.ConnectionClosed, $"{GetType()}: Disconnecting because {exception}. This is fine.");
  463. Disconnect();
  464. }
  465. catch (Exception exception)
  466. {
  467. // unexpected
  468. // pass error to user callback. no need to log it manually.
  469. // GetType() shows Server/ClientConn instead of just Connection.
  470. OnError(ErrorCode.Unexpected, $"{GetType()}: unexpected exception: {exception}");
  471. Disconnect();
  472. }
  473. }
  474. protected void OnRawInputReliable(ArraySegment<byte> message)
  475. {
  476. // input into kcp, but skip channel byte
  477. int input = kcp.Input(message.Array, message.Offset, message.Count);
  478. if (input != 0)
  479. {
  480. // GetType() shows Server/ClientConn instead of just Connection.
  481. Log.Warning($"{GetType()}: Input failed with error={input} for buffer with length={message.Count - 1}");
  482. }
  483. }
  484. protected void OnRawInputUnreliable(ArraySegment<byte> message)
  485. {
  486. // ideally we would queue all unreliable messages and
  487. // then process them in ReceiveNext() together with the
  488. // reliable messages, but:
  489. // -> queues/allocations/pools are slow and complex.
  490. // -> DOTSNET 10k is actually slower if we use pooled
  491. // unreliable messages for transform messages.
  492. //
  493. // DOTSNET 10k benchmark:
  494. // reliable-only: 170 FPS
  495. // unreliable queued: 130-150 FPS
  496. // unreliable direct: 183 FPS(!)
  497. //
  498. // DOTSNET 50k benchmark:
  499. // reliable-only: FAILS (queues keep growing)
  500. // unreliable direct: 18-22 FPS(!)
  501. //
  502. // -> all unreliable messages are DATA messages anyway.
  503. // -> let's skip the magic and call OnData directly if
  504. // the current state allows it.
  505. if (state == KcpState.Authenticated)
  506. {
  507. OnData(message, KcpChannel.Unreliable);
  508. // set last receive time to avoid timeout.
  509. // -> we do this in ANY case even if not enabled.
  510. // a message is a message.
  511. // -> we set last receive time for both reliable and
  512. // unreliable messages. both count.
  513. // otherwise a connection might time out even
  514. // though unreliable were received, but no
  515. // reliable was received.
  516. lastReceiveTime = (uint)watch.ElapsedMilliseconds;
  517. }
  518. else
  519. {
  520. // it's common to receive unreliable messages before being
  521. // authenticated, for example:
  522. // - random internet noise
  523. // - game server may send an unreliable message after authenticating,
  524. // and the unreliable message arrives on the client before the
  525. // 'auth_ok' message. this can be avoided by sending a final
  526. // 'ready' message after being authenticated, but this would
  527. // add another 'round trip time' of latency to the handshake.
  528. //
  529. // it's best to simply ignore invalid unreliable messages here.
  530. // Log.Info($"{GetType()}: received unreliable message while not authenticated.");
  531. }
  532. }
  533. // raw send called by kcp
  534. void RawSendReliable(byte[] data, int length)
  535. {
  536. // write channel header
  537. // from 0, with 1 byte
  538. rawSendBuffer[0] = (byte)KcpChannel.Reliable;
  539. // write handshake cookie to protect against UDP spoofing.
  540. // from 1, with 4 bytes
  541. Utils.Encode32U(rawSendBuffer, 1, cookie); // allocation free
  542. // write data
  543. // from 5, with N bytes
  544. Buffer.BlockCopy(data, 0, rawSendBuffer, 1+4, length);
  545. // IO send
  546. ArraySegment<byte> segment = new ArraySegment<byte>(rawSendBuffer, 0, length + 1+4);
  547. RawSend(segment);
  548. }
  549. void SendReliable(KcpHeader header, ArraySegment<byte> content)
  550. {
  551. // 1 byte header + content needs to fit into send buffer
  552. if (1 + content.Count > kcpSendBuffer.Length) // TODO
  553. {
  554. // otherwise content is larger than MaxMessageSize. let user know!
  555. // GetType() shows Server/ClientConn instead of just Connection.
  556. OnError(ErrorCode.InvalidSend, $"{GetType()}: Failed to send reliable message of size {content.Count} because it's larger than ReliableMaxMessageSize={reliableMax}");
  557. return;
  558. }
  559. // write channel header
  560. kcpSendBuffer[0] = (byte)header;
  561. // write data (if any)
  562. if (content.Count > 0)
  563. Buffer.BlockCopy(content.Array, content.Offset, kcpSendBuffer, 1, content.Count);
  564. // send to kcp for processing
  565. int sent = kcp.Send(kcpSendBuffer, 0, 1 + content.Count);
  566. if (sent < 0)
  567. {
  568. // GetType() shows Server/ClientConn instead of just Connection.
  569. OnError(ErrorCode.InvalidSend, $"{GetType()}: Send failed with error={sent} for content with length={content.Count}");
  570. }
  571. }
  572. void SendUnreliable(ArraySegment<byte> message)
  573. {
  574. // message size needs to be <= unreliable max size
  575. if (message.Count > unreliableMax)
  576. {
  577. // otherwise content is larger than MaxMessageSize. let user know!
  578. // GetType() shows Server/ClientConn instead of just Connection.
  579. Log.Error($"{GetType()}: Failed to send unreliable message of size {message.Count} because it's larger than UnreliableMaxMessageSize={unreliableMax}");
  580. return;
  581. }
  582. // write channel header
  583. // from 0, with 1 byte
  584. rawSendBuffer[0] = (byte)KcpChannel.Unreliable;
  585. // write handshake cookie to protect against UDP spoofing.
  586. // from 1, with 4 bytes
  587. Utils.Encode32U(rawSendBuffer, 1, cookie); // allocation free
  588. // write data
  589. // from 5, with N bytes
  590. Buffer.BlockCopy(message.Array, message.Offset, rawSendBuffer, 1 + 4, message.Count);
  591. // IO send
  592. ArraySegment<byte> segment = new ArraySegment<byte>(rawSendBuffer, 0, message.Count + 1 + 4);
  593. RawSend(segment);
  594. }
  595. // server & client need to send handshake at different times, so we need
  596. // to expose the function.
  597. // * client should send it immediately.
  598. // * server should send it as reply to client's handshake, not before
  599. // (server should not reply to random internet messages with handshake)
  600. // => handshake info needs to be delivered, so it goes over reliable.
  601. public void SendHello()
  602. {
  603. // send an empty message with 'Hello' header.
  604. // cookie is automatically included in all messages.
  605. // GetType() shows Server/ClientConn instead of just Connection.
  606. Log.Info($"{GetType()}: sending handshake to other end with cookie={cookie}");
  607. SendReliable(KcpHeader.Hello, default);
  608. }
  609. public void SendData(ArraySegment<byte> data, KcpChannel channel)
  610. {
  611. // sending empty segments is not allowed.
  612. // nobody should ever try to send empty data.
  613. // it means that something went wrong, e.g. in Mirror/DOTSNET.
  614. // let's make it obvious so it's easy to debug.
  615. if (data.Count == 0)
  616. {
  617. // pass error to user callback. no need to log it manually.
  618. // GetType() shows Server/ClientConn instead of just Connection.
  619. OnError(ErrorCode.InvalidSend, $"{GetType()}: tried sending empty message. This should never happen. Disconnecting.");
  620. Disconnect();
  621. return;
  622. }
  623. switch (channel)
  624. {
  625. case KcpChannel.Reliable:
  626. SendReliable(KcpHeader.Data, data);
  627. break;
  628. case KcpChannel.Unreliable:
  629. SendUnreliable(data);
  630. break;
  631. }
  632. }
  633. // ping goes through kcp to keep it from timing out, so it goes over the
  634. // reliable channel.
  635. void SendPing() => SendReliable(KcpHeader.Ping, default);
  636. // disconnect info needs to be delivered, so it goes over reliable
  637. void SendDisconnect() => SendReliable(KcpHeader.Disconnect, default);
  638. // disconnect this connection
  639. public virtual void Disconnect()
  640. {
  641. // only if not disconnected yet
  642. if (state == KcpState.Disconnected)
  643. return;
  644. // send a disconnect message
  645. try
  646. {
  647. SendDisconnect();
  648. kcp.Flush();
  649. }
  650. // TODO KcpConnection is IO agnostic. move this to outside later.
  651. catch (SocketException)
  652. {
  653. // this is ok, the connection was already closed
  654. }
  655. catch (ObjectDisposedException)
  656. {
  657. // this is normal when we stop the server
  658. // the socket is stopped so we can't send anything anymore
  659. // to the clients
  660. // the clients will eventually timeout and realize they
  661. // were disconnected
  662. }
  663. // set as Disconnected, call event
  664. // GetType() shows Server/ClientConn instead of just Connection.
  665. Log.Info($"{GetType()}: Disconnected.");
  666. state = KcpState.Disconnected;
  667. OnDisconnected();
  668. }
  669. }
  670. }