KcpConnection.cs 29 KB

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