mirror of
https://github.com/MirrorNetworking/Mirror.git
synced 2024-11-18 11:00:32 +00:00
fix: kcp2k V1.12
- where-allocation removed. will be optional in the future. - Tests: don't depend on Unity anymore - fix: #26 - Kcp now catches exception if host couldn't be resolved, and calls OnDisconnected to let the user now. - fix: KcpServer.DualMode is now configurable in the constructor instead of using #if UNITY_SWITCH. makes it run on all other non dual mode platforms too.
This commit is contained in:
parent
69116bfeb2
commit
d66d228079
@ -16,6 +16,8 @@ public class KcpTransport : Transport
|
||||
// common
|
||||
[Header("Transport Configuration")]
|
||||
public ushort Port = 7777;
|
||||
[Tooltip("DualMode listens to IPv6 and IPv4 simultaneously. Disable if the platform only supports IPv4.")]
|
||||
public bool DualMode = true;
|
||||
[Tooltip("NoDelay is recommended to reduce latency. This also scales better without buffers getting full.")]
|
||||
public bool NoDelay = true;
|
||||
[Tooltip("KCP internal update interval. 100ms is KCP default, but a lower interval is recommended to minimize latency and to scale to more networked entities.")]
|
||||
@ -69,6 +71,7 @@ void Awake()
|
||||
(connectionId) => OnServerConnected.Invoke(connectionId),
|
||||
(connectionId, message) => OnServerDataReceived.Invoke(connectionId, message, Channels.Reliable),
|
||||
(connectionId) => OnServerDisconnected.Invoke(connectionId),
|
||||
DualMode,
|
||||
NoDelay,
|
||||
Interval,
|
||||
FastResend,
|
||||
|
@ -1,6 +1,12 @@
|
||||
V1.11 [2021-06-01]
|
||||
- perf: where-allocation (https://github.com/vis2k/where-allocation):
|
||||
nearly removes all socket.SendTo/ReceiveFrom allocations
|
||||
V1.12 [2021-07-16]
|
||||
- where-allocation removed. will be optional in the future.
|
||||
- Tests: don't depend on Unity anymore
|
||||
- fix: #26 - Kcp now catches exception if host couldn't be resolved, and calls
|
||||
OnDisconnected to let the user now.
|
||||
- fix: KcpServer.DualMode is now configurable in the constructor instead of
|
||||
using #if UNITY_SWITCH. makes it run on all other non dual mode platforms too.
|
||||
|
||||
V1.11 rollback [2021-06-01]
|
||||
- perf: Segment MemoryStream initial capacity set to MTU to avoid early runtime
|
||||
resizing/allocations
|
||||
|
||||
|
@ -1,6 +1,5 @@
|
||||
using System.Net;
|
||||
using System.Net.Sockets;
|
||||
using WhereAllocation;
|
||||
|
||||
namespace kcp2k
|
||||
{
|
||||
@ -13,30 +12,41 @@ public class KcpClientConnection : KcpConnection
|
||||
// => we need the MTU to fit channel + message!
|
||||
readonly byte[] rawReceiveBuffer = new byte[Kcp.MTU_DEF];
|
||||
|
||||
// where-allocation
|
||||
IPEndPointNonAlloc reusableEP;
|
||||
// helper function to resolve host to IPAddress
|
||||
public static bool ResolveHostname(string hostname, out IPAddress[] addresses)
|
||||
{
|
||||
try
|
||||
{
|
||||
addresses = Dns.GetHostAddresses(hostname);
|
||||
return addresses.Length >= 1;
|
||||
}
|
||||
catch (SocketException)
|
||||
{
|
||||
Log.Info($"Failed to resolve host: {hostname}");
|
||||
addresses = null;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public void Connect(string host, ushort port, 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)
|
||||
{
|
||||
Log.Info($"KcpClient: connect to {host}:{port}");
|
||||
IPAddress[] ipAddress = Dns.GetHostAddresses(host);
|
||||
if (ipAddress.Length < 1)
|
||||
throw new SocketException((int)SocketError.HostNotFound);
|
||||
|
||||
remoteEndpoint = new IPEndPoint(ipAddress[0], port);
|
||||
socket = new Socket(remoteEndpoint.AddressFamily, SocketType.Dgram, ProtocolType.Udp);
|
||||
// try resolve host name
|
||||
if (ResolveHostname(host, out IPAddress[] addresses))
|
||||
{
|
||||
remoteEndpoint = new IPEndPoint(addresses[0], port);
|
||||
socket = new Socket(remoteEndpoint.AddressFamily, SocketType.Dgram, ProtocolType.Udp);
|
||||
socket.Connect(remoteEndpoint);
|
||||
SetupKcp(noDelay, interval, fastResend, congestionWindow, sendWindowSize, receiveWindowSize, timeout);
|
||||
|
||||
// create reusableEP with same address family as remoteEndPoint.
|
||||
// otherwise ReceiveFrom_NonAlloc couldn't use it.
|
||||
reusableEP = new IPEndPointNonAlloc(ipAddress[0], port);
|
||||
// client should send handshake to server as very first message
|
||||
SendHandshake();
|
||||
|
||||
socket.Connect(remoteEndpoint);
|
||||
SetupKcp(noDelay, interval, fastResend, congestionWindow, sendWindowSize, receiveWindowSize, timeout);
|
||||
|
||||
// client should send handshake to server as very first message
|
||||
SendHandshake();
|
||||
|
||||
RawReceive();
|
||||
RawReceive();
|
||||
}
|
||||
// otherwise call OnDisconnected to let the user know.
|
||||
else OnDisconnected();
|
||||
}
|
||||
|
||||
// call from transport update
|
||||
@ -48,8 +58,7 @@ public void RawReceive()
|
||||
{
|
||||
while (socket.Poll(0, SelectMode.SelectRead))
|
||||
{
|
||||
// where-allocation: receive nonalloc.
|
||||
int msgLength = socket.ReceiveFrom_NonAlloc(rawReceiveBuffer, reusableEP);
|
||||
int msgLength = socket.ReceiveFrom(rawReceiveBuffer, ref remoteEndpoint);
|
||||
// IMPORTANT: detect if buffer was too small for the
|
||||
// received msgLength. otherwise the excess
|
||||
// data would be silently lost.
|
||||
|
@ -4,7 +4,6 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Net;
|
||||
using System.Net.Sockets;
|
||||
using WhereAllocation;
|
||||
|
||||
namespace kcp2k
|
||||
{
|
||||
@ -16,6 +15,9 @@ public class KcpServer
|
||||
public Action<int> OnDisconnected;
|
||||
|
||||
// configuration
|
||||
// DualMode uses both IPv6 and IPv4. not all platforms support it.
|
||||
// (Nintendo Switch, etc.)
|
||||
public bool DualMode;
|
||||
// NoDelay is recommended to reduce latency. This also scales better
|
||||
// without buffers getting full.
|
||||
public bool NoDelay;
|
||||
@ -42,14 +44,8 @@ public class KcpServer
|
||||
|
||||
// state
|
||||
Socket socket;
|
||||
#if UNITY_SWITCH
|
||||
// switch does not support ipv6
|
||||
//EndPoint newClientEP = new IPEndPoint(IPAddress.Any, 0);
|
||||
IPEndPointNonAlloc reusableClientEP = new IPEndPointNonAlloc(IPAddress.Any, 0); // where-allocation
|
||||
#else
|
||||
//EndPoint newClientEP = new IPEndPoint(IPAddress.IPv6Any, 0);
|
||||
IPEndPointNonAlloc reusableClientEP = new IPEndPointNonAlloc(IPAddress.IPv6Any, 0); // where-allocation
|
||||
#endif
|
||||
EndPoint newClientEP;
|
||||
|
||||
// IMPORTANT: raw receive buffer always needs to be of 'MTU' size, even
|
||||
// if MaxMessageSize is larger. kcp always sends in MTU
|
||||
// segments and having a buffer smaller than MTU would
|
||||
@ -63,6 +59,7 @@ public class KcpServer
|
||||
public KcpServer(Action<int> OnConnected,
|
||||
Action<int, ArraySegment<byte>> OnData,
|
||||
Action<int> OnDisconnected,
|
||||
bool DualMode,
|
||||
bool NoDelay,
|
||||
uint Interval,
|
||||
int FastResend = 0,
|
||||
@ -74,6 +71,7 @@ public KcpServer(Action<int> OnConnected,
|
||||
this.OnConnected = OnConnected;
|
||||
this.OnData = OnData;
|
||||
this.OnDisconnected = OnDisconnected;
|
||||
this.DualMode = DualMode;
|
||||
this.NoDelay = NoDelay;
|
||||
this.Interval = Interval;
|
||||
this.FastResend = FastResend;
|
||||
@ -81,6 +79,11 @@ public KcpServer(Action<int> OnConnected,
|
||||
this.SendWindowSize = SendWindowSize;
|
||||
this.ReceiveWindowSize = ReceiveWindowSize;
|
||||
this.Timeout = Timeout;
|
||||
|
||||
// create newClientEP either IPv4 or IPv6
|
||||
newClientEP = DualMode
|
||||
? new IPEndPoint(IPAddress.IPv6Any, 0)
|
||||
: new IPEndPoint(IPAddress.Any, 0);
|
||||
}
|
||||
|
||||
public bool IsActive() => socket != null;
|
||||
@ -94,15 +97,19 @@ public void Start(ushort port)
|
||||
}
|
||||
|
||||
// listen
|
||||
#if UNITY_SWITCH
|
||||
// Switch does not support ipv6
|
||||
socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
|
||||
socket.Bind(new IPEndPoint(IPAddress.Any, port));
|
||||
#else
|
||||
socket = new Socket(AddressFamily.InterNetworkV6, SocketType.Dgram, ProtocolType.Udp);
|
||||
socket.DualMode = true;
|
||||
socket.Bind(new IPEndPoint(IPAddress.IPv6Any, port));
|
||||
#endif
|
||||
if (DualMode)
|
||||
{
|
||||
// IPv6 socket with DualMode
|
||||
socket = new Socket(AddressFamily.InterNetworkV6, SocketType.Dgram, ProtocolType.Udp);
|
||||
socket.DualMode = true;
|
||||
socket.Bind(new IPEndPoint(IPAddress.IPv6Any, port));
|
||||
}
|
||||
else
|
||||
{
|
||||
// IPv4 socket
|
||||
socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
|
||||
socket.Bind(new IPEndPoint(IPAddress.Any, port));
|
||||
}
|
||||
}
|
||||
|
||||
public void Send(int connectionId, ArraySegment<byte> segment, KcpChannel channel)
|
||||
@ -143,13 +150,9 @@ public void TickIncoming()
|
||||
// receive from calls newClientEP.Create(socketAddr).
|
||||
// IPEndPoint.Create always returns a new IPEndPoint.
|
||||
// https://github.com/mono/mono/blob/f74eed4b09790a0929889ad7fc2cf96c9b6e3757/mcs/class/System/System.Net.Sockets/Socket.cs#L1761
|
||||
//int msgLength = socket.ReceiveFrom(rawReceiveBuffer, 0, rawReceiveBuffer.Length, SocketFlags.None, ref newClientEP);
|
||||
int msgLength = socket.ReceiveFrom(rawReceiveBuffer, 0, rawReceiveBuffer.Length, SocketFlags.None, ref newClientEP);
|
||||
//Log.Info($"KCP: server raw recv {msgLength} bytes = {BitConverter.ToString(buffer, 0, msgLength)}");
|
||||
|
||||
// where-allocation nonalloc ReceiveFrom.
|
||||
int msgLength = socket.ReceiveFrom_NonAlloc(rawReceiveBuffer, 0, rawReceiveBuffer.Length, SocketFlags.None, reusableClientEP);
|
||||
SocketAddress remoteAddress = reusableClientEP.temp;
|
||||
|
||||
// calculate connectionId from endpoint
|
||||
// NOTE: IPEndPoint.GetHashCode() allocates.
|
||||
// it calls m_Address.GetHashCode().
|
||||
@ -159,10 +162,7 @@ public void TickIncoming()
|
||||
//
|
||||
// => using only newClientEP.Port wouldn't work, because
|
||||
// different connections can have the same port.
|
||||
//int connectionId = newClientEP.GetHashCode();
|
||||
|
||||
// where-allocation nonalloc GetHashCode
|
||||
int connectionId = remoteAddress.GetHashCode();
|
||||
int connectionId = newClientEP.GetHashCode();
|
||||
|
||||
// IMPORTANT: detect if buffer was too small for the received
|
||||
// msgLength. otherwise the excess data would be
|
||||
@ -173,19 +173,8 @@ public void TickIncoming()
|
||||
// is this a new connection?
|
||||
if (!connections.TryGetValue(connectionId, out KcpServerConnection connection))
|
||||
{
|
||||
// IPEndPointNonAlloc is reused all the time.
|
||||
// we can't store that as the connection's endpoint.
|
||||
// we need a new copy!
|
||||
IPEndPoint newClientEP = reusableClientEP.DeepCopyIPEndPoint();
|
||||
|
||||
// for allocation free sending, we also need another
|
||||
// IPEndPointNonAlloc...
|
||||
IPEndPointNonAlloc reusableSendEP = new IPEndPointNonAlloc(newClientEP.Address, newClientEP.Port);
|
||||
|
||||
// create a new KcpConnection
|
||||
// -> where-allocation IPEndPointNonAlloc is reused.
|
||||
// need to create a new one from the temp address.
|
||||
connection = new KcpServerConnection(socket, newClientEP, reusableSendEP, NoDelay, Interval, FastResend, CongestionWindow, SendWindowSize, ReceiveWindowSize, Timeout);
|
||||
connection = new KcpServerConnection(socket, newClientEP, NoDelay, Interval, FastResend, CongestionWindow, SendWindowSize, ReceiveWindowSize, Timeout);
|
||||
|
||||
// DO NOT add to connections yet. only if the first message
|
||||
// is actually the kcp handshake. otherwise it's either:
|
||||
|
@ -1,25 +1,20 @@
|
||||
using System.Net;
|
||||
using System.Net.Sockets;
|
||||
using WhereAllocation;
|
||||
|
||||
namespace kcp2k
|
||||
{
|
||||
public class KcpServerConnection : KcpConnection
|
||||
{
|
||||
IPEndPointNonAlloc reusableSendEndPoint;
|
||||
|
||||
public KcpServerConnection(Socket socket, EndPoint remoteEndPoint, IPEndPointNonAlloc reusableSendEndPoint, 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)
|
||||
public KcpServerConnection(Socket socket, EndPoint remoteEndpoint, 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)
|
||||
{
|
||||
this.socket = socket;
|
||||
this.remoteEndpoint = remoteEndPoint;
|
||||
this.reusableSendEndPoint = reusableSendEndPoint;
|
||||
this.remoteEndpoint = remoteEndpoint;
|
||||
SetupKcp(noDelay, interval, fastResend, congestionWindow, sendWindowSize, receiveWindowSize, timeout);
|
||||
}
|
||||
|
||||
protected override void RawSend(byte[] data, int length)
|
||||
{
|
||||
// where-allocation nonalloc
|
||||
socket.SendTo_NonAlloc(data, 0, length, SocketFlags.None, reusableSendEndPoint);
|
||||
socket.SendTo(data, 0, length, SocketFlags.None, remoteEndpoint);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user