2024-10-17 17:23:05 +03:00

204 lines
7.4 KiB
C#

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();
}
}
}