EdgegapKcpServer.cs 7.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203
  1. using System;
  2. using System.Net;
  3. using System.Net.Sockets;
  4. using Mirror;
  5. using UnityEngine;
  6. using kcp2k;
  7. namespace Edgegap
  8. {
  9. public class EdgegapKcpServer : KcpServer
  10. {
  11. // need buffer larger than KcpClient.rawReceiveBuffer to add metadata
  12. readonly byte[] relayReceiveBuffer;
  13. // authentication
  14. public uint userId;
  15. public uint sessionId;
  16. public ConnectionState state = ConnectionState.Disconnected;
  17. // server is an UDP client talking to relay
  18. protected Socket relaySocket;
  19. public EndPoint remoteEndPoint;
  20. // ping
  21. double lastPingTime;
  22. // custom 'active'. while connected to relay
  23. bool relayActive;
  24. public EdgegapKcpServer(
  25. Action<int> OnConnected,
  26. Action<int, ArraySegment<byte>, KcpChannel> OnData,
  27. Action<int> OnDisconnected,
  28. Action<int, ErrorCode, string> OnError,
  29. KcpConfig config)
  30. // TODO don't call base. don't listen to local UdpServer at all?
  31. : base(OnConnected, OnData, OnDisconnected, OnError, config)
  32. {
  33. relayReceiveBuffer = new byte[config.Mtu + Protocol.Overhead];
  34. }
  35. public override bool IsActive() => relayActive;
  36. // custom start function with relay parameters; connects udp client.
  37. public void Start(string relayAddress, ushort relayPort, uint userId, uint sessionId)
  38. {
  39. // reset last state
  40. state = ConnectionState.Checking;
  41. this.userId = userId;
  42. this.sessionId = sessionId;
  43. // try resolve host name
  44. if (!Common.ResolveHostname(relayAddress, out IPAddress[] addresses))
  45. {
  46. OnError(0, ErrorCode.DnsResolve, $"Failed to resolve host: {relayAddress}");
  47. return;
  48. }
  49. // create socket
  50. remoteEndPoint = new IPEndPoint(addresses[0], relayPort);
  51. relaySocket = new Socket(remoteEndPoint.AddressFamily, SocketType.Dgram, ProtocolType.Udp);
  52. relaySocket.Blocking = false;
  53. // configure buffer sizes
  54. Common.ConfigureSocketBuffers(relaySocket, config.RecvBufferSize, config.SendBufferSize);
  55. // bind to endpoint for Send/Receive instead of SendTo/ReceiveFrom
  56. relaySocket.Connect(remoteEndPoint);
  57. relayActive = true;
  58. }
  59. public override void Stop()
  60. {
  61. relayActive = false;
  62. }
  63. protected override bool RawReceiveFrom(out ArraySegment<byte> segment, out int connectionId)
  64. {
  65. segment = default;
  66. connectionId = 0;
  67. if (relaySocket == null) return false;
  68. try
  69. {
  70. // TODO need separate buffer. don't write into result yet. only payload
  71. if (relaySocket.ReceiveNonBlocking(relayReceiveBuffer, out ArraySegment<byte> content))
  72. {
  73. using (NetworkReaderPooled reader = NetworkReaderPool.Get(content))
  74. {
  75. // parse message type
  76. if (reader.Remaining == 0)
  77. {
  78. Debug.LogWarning($"EdgegapServer: message of {content.Count} is too small to parse header.");
  79. return false;
  80. }
  81. byte messageType = reader.ReadByte();
  82. // handle message type
  83. switch (messageType)
  84. {
  85. case (byte)MessageType.Ping:
  86. {
  87. // parse state
  88. if (reader.Remaining < 1) return false;
  89. ConnectionState last = state;
  90. state = (ConnectionState)reader.ReadByte();
  91. // log state changes for debugging.
  92. if (state != last) Debug.Log($"EdgegapServer: state updated to: {state}");
  93. // return true indicates Mirror to keep checking
  94. // for further messages.
  95. return true;
  96. }
  97. case (byte)MessageType.Data:
  98. {
  99. // parse connectionId and payload
  100. if (reader.Remaining <= 4)
  101. {
  102. Debug.LogWarning($"EdgegapServer: message of {content.Count} is too small to parse connId.");
  103. return false;
  104. }
  105. connectionId = reader.ReadInt();
  106. segment = reader.ReadBytesSegment(reader.Remaining);
  107. // Debug.Log($"EdgegapServer: receiving from connId={connectionId}: {segment.ToHexString()}");
  108. return true;
  109. }
  110. // wrong message type. return false, don't throw.
  111. default: return false;
  112. }
  113. }
  114. }
  115. }
  116. catch (SocketException e)
  117. {
  118. Log.Info($"EdgegapServer: looks like the other end has closed the connection. This is fine: {e}");
  119. }
  120. return false;
  121. }
  122. protected override void RawSend(int connectionId, ArraySegment<byte> data)
  123. {
  124. using (NetworkWriterPooled writer = NetworkWriterPool.Get())
  125. {
  126. // Debug.Log($"EdgegapServer: sending to connId={connectionId}: {data.ToHexString()}");
  127. writer.WriteUInt(userId);
  128. writer.WriteUInt(sessionId);
  129. writer.WriteByte((byte)MessageType.Data);
  130. writer.WriteInt(connectionId);
  131. writer.WriteBytes(data.Array, data.Offset, data.Count);
  132. ArraySegment<byte> message = writer;
  133. try
  134. {
  135. relaySocket.SendNonBlocking(message);
  136. }
  137. catch (SocketException e)
  138. {
  139. Log.Error($"KcpRleayServer: RawSend failed: {e}");
  140. }
  141. }
  142. }
  143. void SendPing()
  144. {
  145. using (NetworkWriterPooled writer = NetworkWriterPool.Get())
  146. {
  147. writer.WriteUInt(userId);
  148. writer.WriteUInt(sessionId);
  149. writer.WriteByte((byte)MessageType.Ping);
  150. ArraySegment<byte> message = writer;
  151. try
  152. {
  153. relaySocket.SendNonBlocking(message);
  154. }
  155. catch (SocketException e)
  156. {
  157. Debug.LogWarning($"EdgegapServer: failed to ping. perhaps the relay isn't running? {e}");
  158. }
  159. }
  160. }
  161. public override void TickOutgoing()
  162. {
  163. if (relayActive)
  164. {
  165. // ping every interval for keepalive & handshake
  166. if (NetworkTime.localTime >= lastPingTime + Protocol.PingInterval)
  167. {
  168. SendPing();
  169. lastPingTime = NetworkTime.localTime;
  170. }
  171. }
  172. // base processing
  173. base.TickOutgoing();
  174. }
  175. }
  176. }