123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203 |
- using System;
- using System.Net;
- using System.Net.Sockets;
- using Mirror;
- using UnityEngine;
- using kcp2k;
- namespace Edgegap
- {
- public class EdgegapKcpServer : KcpServer
- {
- // need buffer larger than KcpClient.rawReceiveBuffer to add metadata
- readonly byte[] relayReceiveBuffer;
- // authentication
- public uint userId;
- public uint sessionId;
- public ConnectionState state = ConnectionState.Disconnected;
- // server is an UDP client talking to relay
- protected Socket relaySocket;
- public EndPoint remoteEndPoint;
- // ping
- double lastPingTime;
- // custom 'active'. while connected to relay
- bool relayActive;
- public EdgegapKcpServer(
- Action<int> OnConnected,
- Action<int, ArraySegment<byte>, KcpChannel> OnData,
- Action<int> OnDisconnected,
- Action<int, ErrorCode, string> OnError,
- KcpConfig config)
- // TODO don't call base. don't listen to local UdpServer at all?
- : base(OnConnected, OnData, OnDisconnected, OnError, config)
- {
- relayReceiveBuffer = new byte[config.Mtu + Protocol.Overhead];
- }
- public override bool IsActive() => relayActive;
- // custom start function with relay parameters; connects udp client.
- public void Start(string relayAddress, ushort relayPort, uint userId, uint sessionId)
- {
- // reset last state
- state = ConnectionState.Checking;
- this.userId = userId;
- this.sessionId = sessionId;
- // try resolve host name
- if (!Common.ResolveHostname(relayAddress, out IPAddress[] addresses))
- {
- OnError(0, ErrorCode.DnsResolve, $"Failed to resolve host: {relayAddress}");
- return;
- }
- // create socket
- remoteEndPoint = new IPEndPoint(addresses[0], relayPort);
- relaySocket = new Socket(remoteEndPoint.AddressFamily, SocketType.Dgram, ProtocolType.Udp);
- relaySocket.Blocking = false;
- // configure buffer sizes
- Common.ConfigureSocketBuffers(relaySocket, config.RecvBufferSize, config.SendBufferSize);
- // bind to endpoint for Send/Receive instead of SendTo/ReceiveFrom
- relaySocket.Connect(remoteEndPoint);
- relayActive = true;
- }
- public override void Stop()
- {
- relayActive = false;
- }
- protected override bool RawReceiveFrom(out ArraySegment<byte> segment, out int connectionId)
- {
- segment = default;
- connectionId = 0;
- if (relaySocket == null) return false;
- try
- {
- // TODO need separate buffer. don't write into result yet. only payload
- if (relaySocket.ReceiveNonBlocking(relayReceiveBuffer, out ArraySegment<byte> content))
- {
- using (NetworkReaderPooled reader = NetworkReaderPool.Get(content))
- {
- // parse message type
- if (reader.Remaining == 0)
- {
- Debug.LogWarning($"EdgegapServer: message of {content.Count} is too small to parse header.");
- return false;
- }
- byte messageType = reader.ReadByte();
- // handle message type
- switch (messageType)
- {
- case (byte)MessageType.Ping:
- {
- // parse state
- if (reader.Remaining < 1) return false;
- ConnectionState last = state;
- state = (ConnectionState)reader.ReadByte();
- // log state changes for debugging.
- if (state != last) Debug.Log($"EdgegapServer: state updated to: {state}");
- // return true indicates Mirror to keep checking
- // for further messages.
- return true;
- }
- case (byte)MessageType.Data:
- {
- // parse connectionId and payload
- if (reader.Remaining <= 4)
- {
- Debug.LogWarning($"EdgegapServer: message of {content.Count} is too small to parse connId.");
- return false;
- }
- connectionId = reader.ReadInt();
- segment = reader.ReadBytesSegment(reader.Remaining);
- // Debug.Log($"EdgegapServer: receiving from connId={connectionId}: {segment.ToHexString()}");
- return true;
- }
- // wrong message type. return false, don't throw.
- default: return false;
- }
- }
- }
- }
- catch (SocketException e)
- {
- Log.Info($"EdgegapServer: looks like the other end has closed the connection. This is fine: {e}");
- }
- return false;
- }
- protected override void RawSend(int connectionId, ArraySegment<byte> data)
- {
- using (NetworkWriterPooled writer = NetworkWriterPool.Get())
- {
- // Debug.Log($"EdgegapServer: sending to connId={connectionId}: {data.ToHexString()}");
- writer.WriteUInt(userId);
- writer.WriteUInt(sessionId);
- writer.WriteByte((byte)MessageType.Data);
- writer.WriteInt(connectionId);
- writer.WriteBytes(data.Array, data.Offset, data.Count);
- ArraySegment<byte> message = writer;
- try
- {
- relaySocket.SendNonBlocking(message);
- }
- catch (SocketException e)
- {
- Log.Error($"KcpRleayServer: RawSend failed: {e}");
- }
- }
- }
- void SendPing()
- {
- using (NetworkWriterPooled writer = NetworkWriterPool.Get())
- {
- writer.WriteUInt(userId);
- writer.WriteUInt(sessionId);
- writer.WriteByte((byte)MessageType.Ping);
- ArraySegment<byte> message = writer;
- try
- {
- relaySocket.SendNonBlocking(message);
- }
- catch (SocketException e)
- {
- Debug.LogWarning($"EdgegapServer: failed to ping. perhaps the relay isn't running? {e}");
- }
- }
- }
- public override void TickOutgoing()
- {
- if (relayActive)
- {
- // ping every interval for keepalive & handshake
- if (NetworkTime.localTime >= lastPingTime + Protocol.PingInterval)
- {
- SendPing();
- lastPingTime = NetworkTime.localTime;
- }
- }
- // base processing
- base.TickOutgoing();
- }
- }
- }
|