mirror of
https://github.com/MirrorNetworking/Mirror.git
synced 2024-11-18 02:50:32 +00:00
kcp2k V1.0
This commit is contained in:
parent
85be663b13
commit
ceb5dceacd
@ -268,8 +268,8 @@ GameObject:
|
||||
- component: {fileID: 1282001518}
|
||||
- component: {fileID: 1282001520}
|
||||
- component: {fileID: 1282001519}
|
||||
- component: {fileID: 1282001521}
|
||||
- component: {fileID: 1282001522}
|
||||
- component: {fileID: 1282001521}
|
||||
m_Layer: 0
|
||||
m_Name: NetworkManager
|
||||
m_TagString: Untagged
|
||||
@ -347,7 +347,7 @@ MonoBehaviour:
|
||||
m_GameObject: {fileID: 1282001517}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: c7424c1070fad4ba2a7a96b02fbeb4bb, type: 3}
|
||||
m_Script: {fileID: 11500000, guid: 6b0fecffa3f624585964b0d0eb21b18e, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier:
|
||||
OnClientConnected:
|
||||
@ -374,12 +374,9 @@ MonoBehaviour:
|
||||
OnServerDisconnected:
|
||||
m_PersistentCalls:
|
||||
m_Calls: []
|
||||
port: 7777
|
||||
Port: 7777
|
||||
NoDelay: 1
|
||||
serverMaxMessageSize: 16384
|
||||
serverMaxReceivesPerTick: 10000
|
||||
clientMaxMessageSize: 16384
|
||||
clientMaxReceivesPerTick: 1000
|
||||
Interval: 10
|
||||
--- !u!114 &1282001522
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
|
8
Assets/Mirror/Runtime/Transport/KCP.meta
Normal file
8
Assets/Mirror/Runtime/Transport/KCP.meta
Normal file
@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8f38345a2b94c4b5ca63a775eaad5584
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
16
Assets/Mirror/Runtime/Transport/KCP/KCP.asmdef
Executable file
16
Assets/Mirror/Runtime/Transport/KCP/KCP.asmdef
Executable file
@ -0,0 +1,16 @@
|
||||
{
|
||||
"name": "KCP",
|
||||
"references": [
|
||||
"GUID:30817c1a0e6d646d99c048fc403f5979",
|
||||
"GUID:f51ebe6a0ceec4240a699833d6309b23"
|
||||
],
|
||||
"includePlatforms": [],
|
||||
"excludePlatforms": [],
|
||||
"allowUnsafeCode": true,
|
||||
"overrideReferences": false,
|
||||
"precompiledReferences": [],
|
||||
"autoReferenced": true,
|
||||
"defineConstraints": [],
|
||||
"versionDefines": [],
|
||||
"noEngineReferences": false
|
||||
}
|
7
Assets/Mirror/Runtime/Transport/KCP/KCP.asmdef.meta
Normal file
7
Assets/Mirror/Runtime/Transport/KCP/KCP.asmdef.meta
Normal file
@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f34a216d4b31d4eb4872b2f30c5a6a11
|
||||
AssemblyDefinitionImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
8
Assets/Mirror/Runtime/Transport/KCP/MirrorTransport.meta
Normal file
8
Assets/Mirror/Runtime/Transport/KCP/MirrorTransport.meta
Normal file
@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7bdb797750d0a490684410110bf48192
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -0,0 +1,321 @@
|
||||
#if MIRROR
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Net.Sockets;
|
||||
using UnityEngine;
|
||||
using kcp2k;
|
||||
|
||||
namespace Mirror.KCP
|
||||
{
|
||||
public class KcpTransport : Transport
|
||||
{
|
||||
// common
|
||||
[Header("Transport Configuration")]
|
||||
public ushort Port = 7777;
|
||||
[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.")]
|
||||
public uint Interval = 10;
|
||||
readonly byte[] buffer = new byte[Kcp.MTU_DEF];
|
||||
|
||||
// server
|
||||
Socket serverSocket;
|
||||
EndPoint serverNewClientEP = new IPEndPoint(IPAddress.IPv6Any, 0);
|
||||
// connections <connectionId, connection> where connectionId is EndPoint.GetHashCode
|
||||
Dictionary<int, KcpServerConnection> connections = new Dictionary<int, KcpServerConnection>();
|
||||
|
||||
// client
|
||||
KcpClientConnection clientConnection;
|
||||
bool clientConnected;
|
||||
|
||||
void Awake()
|
||||
{
|
||||
Debug.Log("KcpTransport initialized!");
|
||||
}
|
||||
|
||||
// all except WebGL
|
||||
public override bool Available() =>
|
||||
Application.platform != RuntimePlatform.WebGLPlayer;
|
||||
|
||||
// use same Kcp configuration on server and client
|
||||
void ConfigureKcpConnection(KcpConnection connection)
|
||||
{
|
||||
// TODO consider lower interval IF interval matters in nodelay mode
|
||||
|
||||
// we did this in previous test
|
||||
connection.kcp.SetNoDelay(1, 10, 2, true);
|
||||
|
||||
// this works for 4k:
|
||||
//connection.kcp.SetWindowSize(128, 128);
|
||||
// this works for 10k:
|
||||
connection.kcp.SetWindowSize(512, 512);
|
||||
// this works for 20k:
|
||||
//connection.kcp.SetWindowSize(8192, 8192);
|
||||
}
|
||||
|
||||
// client
|
||||
public override bool ClientConnected() => clientConnection != null;
|
||||
public override void ClientConnect(string address)
|
||||
{
|
||||
if (clientConnected)
|
||||
{
|
||||
Debug.LogWarning("KCP: client already connected!");
|
||||
return;
|
||||
}
|
||||
|
||||
clientConnection = new KcpClientConnection();
|
||||
// setup events
|
||||
clientConnection.OnConnected += () =>
|
||||
{
|
||||
Debug.Log($"KCP: OnClientConnected");
|
||||
clientConnected = true;
|
||||
OnClientConnected.Invoke();
|
||||
};
|
||||
clientConnection.OnData += (message) =>
|
||||
{
|
||||
//Debug.Log($"KCP: OnClientDataReceived({BitConverter.ToString(message.Array, message.Offset, message.Count)})");
|
||||
OnClientDataReceived.Invoke(message);
|
||||
};
|
||||
clientConnection.OnDisconnected += () =>
|
||||
{
|
||||
Debug.Log($"KCP: OnClientDisconnected");
|
||||
clientConnected = false;
|
||||
OnClientDisconnected.Invoke();
|
||||
};
|
||||
|
||||
// connect
|
||||
clientConnection.Connect(address, Port, NoDelay, Interval);
|
||||
|
||||
// configure connection for max scale
|
||||
ConfigureKcpConnection(clientConnection);
|
||||
}
|
||||
public override bool ClientSend(int channelId, ArraySegment<byte> segment)
|
||||
{
|
||||
if (clientConnection != null)
|
||||
{
|
||||
clientConnection.Send(segment);
|
||||
return true;
|
||||
}
|
||||
Debug.LogWarning("KCP: can't send because client not connected!");
|
||||
return false;
|
||||
}
|
||||
|
||||
public override void ClientDisconnect()
|
||||
{
|
||||
// only if connected
|
||||
// otherwise we end up in a deadlock because of an open Mirror bug:
|
||||
// https://github.com/vis2k/Mirror/issues/2353
|
||||
if (clientConnected)
|
||||
{
|
||||
clientConnection?.Disconnect();
|
||||
clientConnection = null;
|
||||
}
|
||||
}
|
||||
|
||||
HashSet<int> connectionsToRemove = new HashSet<int>();
|
||||
void UpdateServer()
|
||||
{
|
||||
while (serverSocket != null && serverSocket.Poll(0, SelectMode.SelectRead))
|
||||
{
|
||||
int msgLength = serverSocket.ReceiveFrom(buffer, 0, buffer.Length, SocketFlags.None, ref serverNewClientEP);
|
||||
//Debug.Log($"KCP: server raw recv {msgLength} bytes = {BitConverter.ToString(buffer, 0, msgLength)}");
|
||||
|
||||
// calculate connectionId from endpoint
|
||||
int connectionId = serverNewClientEP.GetHashCode();
|
||||
|
||||
// is this a new connection?
|
||||
if (!connections.TryGetValue(connectionId, out KcpServerConnection connection))
|
||||
{
|
||||
// add it to a queue
|
||||
connection = new KcpServerConnection(serverSocket, serverNewClientEP, NoDelay, Interval);
|
||||
|
||||
// configure connection for max scale
|
||||
ConfigureKcpConnection(connection);
|
||||
|
||||
//acceptedConnections.Writer.TryWrite(connection);
|
||||
connections.Add(connectionId, connection);
|
||||
Debug.Log($"KCP: server added connection {serverNewClientEP}");
|
||||
|
||||
// setup connected event
|
||||
connection.OnConnected += () =>
|
||||
{
|
||||
// call mirror event
|
||||
Debug.Log($"KCP: OnServerConnected({connectionId})");
|
||||
OnServerConnected.Invoke(connectionId);
|
||||
};
|
||||
|
||||
// setup data event
|
||||
connection.OnData += (message) =>
|
||||
{
|
||||
// call mirror event
|
||||
//Debug.Log($"KCP: OnServerDataReceived({connectionId}, {BitConverter.ToString(message.Array, message.Offset, message.Count)})");
|
||||
OnServerDataReceived.Invoke(connectionId, message);
|
||||
};
|
||||
|
||||
// setup disconnected event
|
||||
connection.OnDisconnected += () =>
|
||||
{
|
||||
// flag for removal
|
||||
// (can't remove directly because connection is updated
|
||||
// and event is called while iterating all connections)
|
||||
connectionsToRemove.Add(connectionId);
|
||||
|
||||
// call mirror event
|
||||
Debug.Log($"KCP: OnServerDisconnected({connectionId})");
|
||||
OnServerDisconnected.Invoke(connectionId);
|
||||
};
|
||||
|
||||
// send handshake
|
||||
connection.Handshake();
|
||||
}
|
||||
|
||||
connection.RawInput(buffer, msgLength);
|
||||
}
|
||||
|
||||
// tick all server connections
|
||||
foreach (KcpServerConnection connection in connections.Values)
|
||||
{
|
||||
connection.Tick();
|
||||
connection.Receive();
|
||||
}
|
||||
|
||||
// remove disconnected connections
|
||||
// (can't do it in connection.OnDisconnected because Tick is called
|
||||
// while iterating connections)
|
||||
foreach (int connectionId in connectionsToRemove)
|
||||
{
|
||||
connections.Remove(connectionId);
|
||||
}
|
||||
connectionsToRemove.Clear();
|
||||
}
|
||||
|
||||
void UpdateClient()
|
||||
{
|
||||
// tick client connection
|
||||
if (clientConnection != null)
|
||||
{
|
||||
clientConnection.Tick();
|
||||
// recv on socket
|
||||
clientConnection.RawReceive();
|
||||
// recv on kcp
|
||||
clientConnection.Receive();
|
||||
}
|
||||
}
|
||||
|
||||
// IMPORTANT: set script execution order to >1000 to call Transport's
|
||||
// LateUpdate after all others. Fixes race condition where
|
||||
// e.g. in uSurvival Transport would apply Cmds before
|
||||
// ShoulderRotation.LateUpdate, resulting in projectile
|
||||
// spawns at the point before shoulder rotation.
|
||||
public void LateUpdate()
|
||||
{
|
||||
// note: we need to check enabled in case we set it to false
|
||||
// when LateUpdate already started.
|
||||
// (https://github.com/vis2k/Mirror/pull/379)
|
||||
if (!enabled)
|
||||
return;
|
||||
|
||||
UpdateServer();
|
||||
UpdateClient();
|
||||
}
|
||||
|
||||
// server
|
||||
public override bool ServerActive() => serverSocket != null;
|
||||
public override void ServerStart()
|
||||
{
|
||||
// only start once
|
||||
if (serverSocket != null)
|
||||
{
|
||||
Debug.LogWarning("KCP: server already started!");
|
||||
}
|
||||
|
||||
// listen
|
||||
serverSocket = new Socket(AddressFamily.InterNetworkV6, SocketType.Dgram, ProtocolType.Udp);
|
||||
serverSocket.DualMode = true;
|
||||
serverSocket.Bind(new IPEndPoint(IPAddress.IPv6Any, Port));
|
||||
}
|
||||
public override bool ServerSend(int connectionId, int channelId, ArraySegment<byte> segment)
|
||||
{
|
||||
if (connections.TryGetValue(connectionId, out KcpServerConnection connection))
|
||||
{
|
||||
connection.Send(segment);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
public override bool ServerDisconnect(int connectionId)
|
||||
{
|
||||
if (connections.TryGetValue(connectionId, out KcpServerConnection connection))
|
||||
{
|
||||
connection.Disconnect();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
public override string ServerGetClientAddress(int connectionId)
|
||||
{
|
||||
if (connections.TryGetValue(connectionId, out KcpServerConnection connection))
|
||||
{
|
||||
return (connection.GetRemoteEndPoint() as IPEndPoint).Address.ToString();
|
||||
}
|
||||
return "";
|
||||
}
|
||||
public override void ServerStop()
|
||||
{
|
||||
serverSocket?.Close();
|
||||
serverSocket = null;
|
||||
}
|
||||
|
||||
// common
|
||||
public override void Shutdown() {}
|
||||
|
||||
// MTU
|
||||
public override ushort GetMaxPacketSize() => Kcp.MTU_DEF;
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return "KCP";
|
||||
}
|
||||
|
||||
int GetTotalSendQueue() =>
|
||||
connections.Values.Sum(conn => conn.kcp.snd_queue.Count);
|
||||
int GetTotalReceiveQueue() =>
|
||||
connections.Values.Sum(conn => conn.kcp.rcv_queue.Count);
|
||||
int GetTotalSendBuffer() =>
|
||||
connections.Values.Sum(conn => conn.kcp.snd_buf.Count);
|
||||
int GetTotalReceiveBuffer() =>
|
||||
connections.Values.Sum(conn => conn.kcp.rcv_buf.Count);
|
||||
|
||||
void OnGUI()
|
||||
{
|
||||
GUILayout.BeginArea(new Rect(5, 100, 300, 300));
|
||||
|
||||
if (ServerActive())
|
||||
{
|
||||
GUILayout.BeginVertical("Box");
|
||||
GUILayout.Label("SERVER");
|
||||
GUILayout.Label(" SendQueue: " + GetTotalSendQueue());
|
||||
GUILayout.Label(" ReceiveQueue: " + GetTotalReceiveQueue());
|
||||
GUILayout.Label(" SendBuffer: " + GetTotalSendBuffer());
|
||||
GUILayout.Label(" ReceiveBuffer: " + GetTotalReceiveBuffer());
|
||||
GUILayout.EndVertical();
|
||||
}
|
||||
|
||||
if (ClientConnected())
|
||||
{
|
||||
GUILayout.BeginVertical("Box");
|
||||
GUILayout.Label("CLIENT");
|
||||
GUILayout.Label(" SendQueue: " + clientConnection.kcp.snd_queue.Count);
|
||||
GUILayout.Label(" ReceiveQueue: " + clientConnection.kcp.rcv_queue.Count);
|
||||
GUILayout.Label(" SendBuffer: " + clientConnection.kcp.snd_buf.Count);
|
||||
GUILayout.Label(" ReceiveBuffer: " + clientConnection.kcp.rcv_buf.Count);
|
||||
GUILayout.EndVertical();
|
||||
}
|
||||
|
||||
GUILayout.EndArea();
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 6b0fecffa3f624585964b0d0eb21b18e
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
8
Assets/Mirror/Runtime/Transport/KCP/kcp2k.meta
Normal file
8
Assets/Mirror/Runtime/Transport/KCP/kcp2k.meta
Normal file
@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b6fcfb8bb66ef4cac9bb90b51a0e932f
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
24
Assets/Mirror/Runtime/Transport/KCP/kcp2k/LICENSE
Executable file
24
Assets/Mirror/Runtime/Transport/KCP/kcp2k/LICENSE
Executable file
@ -0,0 +1,24 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2016 limpo1989
|
||||
Copyright (c) 2020 Paul Pacheco
|
||||
Copyright (c) 2020 Lymdun
|
||||
Copyright (c) 2020 vis2k
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
7
Assets/Mirror/Runtime/Transport/KCP/kcp2k/LICENSE.meta
Normal file
7
Assets/Mirror/Runtime/Transport/KCP/kcp2k/LICENSE.meta
Normal file
@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9a3e8369060cf4e94ac117603de47aa6
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
6
Assets/Mirror/Runtime/Transport/KCP/kcp2k/VERSION
Executable file
6
Assets/Mirror/Runtime/Transport/KCP/kcp2k/VERSION
Executable file
@ -0,0 +1,6 @@
|
||||
V1.0
|
||||
- Kcp.cs now mirrors original Kcp.c behaviour
|
||||
(this fixes dozens of bugs)
|
||||
|
||||
V0.1
|
||||
- initial kcp-csharp based version
|
7
Assets/Mirror/Runtime/Transport/KCP/kcp2k/VERSION.meta
Normal file
7
Assets/Mirror/Runtime/Transport/KCP/kcp2k/VERSION.meta
Normal file
@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ed3f2cf1bbf1b4d53a6f2c103d311f71
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
8
Assets/Mirror/Runtime/Transport/KCP/kcp2k/highlevel.meta
Normal file
8
Assets/Mirror/Runtime/Transport/KCP/kcp2k/highlevel.meta
Normal file
@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 50a0273b27ec14f789a8e3c676e0bff4
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -0,0 +1,73 @@
|
||||
using UnityEngine;
|
||||
using System.Net;
|
||||
using System.Net.Sockets;
|
||||
|
||||
namespace kcp2k
|
||||
{
|
||||
public class KcpClientConnection : KcpConnection
|
||||
{
|
||||
readonly byte[] buffer = new byte[1500];
|
||||
|
||||
/// <summary>
|
||||
/// Client connection, does not share the UDP client with anyone
|
||||
/// so we can set up our own read loop
|
||||
/// </summary>
|
||||
/// <param name="host"></param>
|
||||
/// <param name="port"></param>
|
||||
public KcpClientConnection() : base()
|
||||
{
|
||||
}
|
||||
|
||||
public void Connect(string host, ushort port, bool noDelay, uint interval = Kcp.INTERVAL)
|
||||
{
|
||||
Debug.Log($"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);
|
||||
socket.Connect(remoteEndpoint);
|
||||
SetupKcp(noDelay, interval);
|
||||
|
||||
RawReceive();
|
||||
|
||||
Handshake();
|
||||
}
|
||||
|
||||
// TODO call from transport update
|
||||
public void RawReceive()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (socket != null)
|
||||
{
|
||||
while (socket.Poll(0, SelectMode.SelectRead))
|
||||
{
|
||||
int msgLength = socket.ReceiveFrom(buffer, ref remoteEndpoint);
|
||||
//Debug.Log($"KCP: client raw recv {msgLength} bytes = {BitConverter.ToString(buffer, 0, msgLength)}");
|
||||
RawInput(buffer, msgLength);
|
||||
}
|
||||
|
||||
// wait a few MS to poll again
|
||||
//await UniTask.Delay(2);
|
||||
}
|
||||
}
|
||||
catch (SocketException)
|
||||
{
|
||||
// this is fine, the socket might have been closed in the other end
|
||||
}
|
||||
}
|
||||
|
||||
protected override void Dispose()
|
||||
{
|
||||
socket.Close();
|
||||
socket = null;
|
||||
}
|
||||
|
||||
protected override void RawSend(byte[] data, int length)
|
||||
{
|
||||
socket.Send(data, length, SocketFlags.None);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 96512e74aa8214a6faa8a412a7a07877
|
||||
timeCreated: 1602601237
|
@ -0,0 +1,282 @@
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Net;
|
||||
using System.Net.Sockets;
|
||||
using Unity.Collections.LowLevel.Unsafe;
|
||||
using Debug = UnityEngine.Debug;
|
||||
|
||||
namespace kcp2k
|
||||
{
|
||||
public abstract class KcpConnection
|
||||
{
|
||||
protected Socket socket;
|
||||
protected EndPoint remoteEndpoint;
|
||||
internal Kcp kcp;
|
||||
volatile bool open;
|
||||
|
||||
public event Action OnConnected;
|
||||
public event Action<ArraySegment<byte>> OnData;
|
||||
public event Action OnDisconnected;
|
||||
|
||||
// If we don't receive anything these many milliseconds
|
||||
// then consider us disconnected
|
||||
public const int TIMEOUT = 3000;
|
||||
|
||||
// internal time.
|
||||
// StopWatch offers ElapsedMilliSeconds and should be more precise than
|
||||
// Unity's time.deltaTime over long periods.
|
||||
readonly Stopwatch refTime = new Stopwatch();
|
||||
|
||||
// recv buffer to avoid allocations
|
||||
byte[] buffer = new byte[Kcp.MTU_DEF];
|
||||
|
||||
volatile uint lastReceived;
|
||||
|
||||
internal static readonly ArraySegment<byte> Hello = new ArraySegment<byte>(new byte[] { 0 });
|
||||
private static readonly ArraySegment<byte> Goodby = new ArraySegment<byte>(new byte[] { 1 });
|
||||
|
||||
// a connection is authenticated after sending the correct handshake.
|
||||
// useful to protect against random data from the internet.
|
||||
bool authenticated;
|
||||
|
||||
protected KcpConnection()
|
||||
{
|
||||
}
|
||||
|
||||
// NoDelay & interval are the most important configurations.
|
||||
// let's force require the parameters so we don't forget it anywhere.
|
||||
protected void SetupKcp(bool noDelay, uint interval = Kcp.INTERVAL)
|
||||
{
|
||||
kcp = new Kcp(0, RawSend);
|
||||
kcp.SetNoDelay(noDelay ? 1u : 0u, interval);
|
||||
refTime.Start();
|
||||
open = true;
|
||||
|
||||
Tick();
|
||||
}
|
||||
|
||||
public void Tick()
|
||||
{
|
||||
try
|
||||
{
|
||||
uint time = (uint)refTime.ElapsedMilliseconds;
|
||||
|
||||
// TODO MirorrNG KCP used to set lastReceived here. but this
|
||||
// would make the below time check always true.
|
||||
// should we set lastReceived after updating instead?
|
||||
//lastReceived = time;
|
||||
|
||||
if (open && time < lastReceived + TIMEOUT)
|
||||
{
|
||||
kcp.Update(time);
|
||||
|
||||
// check can be used to skip updates IF:
|
||||
// - time < what check returned
|
||||
// - AND send / recv haven't been called in that time
|
||||
// (see Check() comments)
|
||||
//
|
||||
// for now, let's just always update and not call check.
|
||||
//uint check = kcp.Check();
|
||||
}
|
||||
}
|
||||
catch (SocketException)
|
||||
{
|
||||
// this is ok, the connection was closed
|
||||
open = false;
|
||||
}
|
||||
catch (ObjectDisposedException)
|
||||
{
|
||||
// fine, socket was closed, no more ticking needed
|
||||
open = false;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
open = false;
|
||||
Debug.LogException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
public void RawInput(byte[] buffer, int msgLength)
|
||||
{
|
||||
int input = kcp.Input(buffer, msgLength);
|
||||
if (input == 0)
|
||||
{
|
||||
lastReceived = (uint)refTime.ElapsedMilliseconds;
|
||||
}
|
||||
else Debug.LogWarning($"Input failed with error={input} for buffer with length={msgLength}");
|
||||
}
|
||||
|
||||
protected abstract void RawSend(byte[] data, int length);
|
||||
|
||||
public void Send(ArraySegment<byte> data)
|
||||
{
|
||||
// only allow sending up to MaxMessageSize sized messages.
|
||||
// other end won't process bigger messages anyway.
|
||||
if (data.Count <= Kcp.MTU_DEF)
|
||||
{
|
||||
int sent = kcp.Send(data.Array, data.Offset, data.Count);
|
||||
if (sent < 0)
|
||||
{
|
||||
Debug.LogWarning($"Send failed with error={sent} for segment with length={data.Count}");
|
||||
}
|
||||
}
|
||||
else Debug.LogError($"Failed to send message of size {data.Count} because it's larger than MaxMessageSize={Kcp.MTU_DEF}");
|
||||
}
|
||||
|
||||
protected virtual void Dispose()
|
||||
{
|
||||
}
|
||||
|
||||
// ArraySegment content comparison without Linq
|
||||
public static unsafe bool SegmentsEqual(ArraySegment<byte> a, ArraySegment<byte> b)
|
||||
{
|
||||
if (a.Count == b.Count)
|
||||
{
|
||||
fixed (byte* aPtr = &a.Array[a.Offset],
|
||||
bPtr = &b.Array[b.Offset])
|
||||
{
|
||||
return UnsafeUtility.MemCmp(aPtr, bPtr, a.Count) == 0;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// reads a message from connection
|
||||
/// </summary>
|
||||
/// <param name="buffer">buffer where the message will be written</param>
|
||||
/// <returns>true if we got a message, false if we got disconnected</returns>
|
||||
public void Receive()
|
||||
{
|
||||
if (!open)
|
||||
{
|
||||
OnDisconnected?.Invoke();
|
||||
Debug.LogWarning("DISCO a");
|
||||
return;
|
||||
}
|
||||
|
||||
// read as long as we have things to read
|
||||
int msgSize;
|
||||
while ((msgSize = kcp.PeekSize()) > 0)
|
||||
{
|
||||
// only allow receiving up to MaxMessageSize sized messages.
|
||||
// otherwise we would get BlockCopy ArgumentException anyway.
|
||||
if (msgSize <= Kcp.MTU_DEF)
|
||||
{
|
||||
int received = kcp.Receive(buffer, msgSize);
|
||||
if (received >= 0)
|
||||
{
|
||||
ArraySegment<byte> dataSegment = new ArraySegment<byte>(buffer, 0, msgSize);
|
||||
|
||||
// not authenticated yet?
|
||||
if (!authenticated)
|
||||
{
|
||||
// handshake message?
|
||||
if (SegmentsEqual(dataSegment, Hello))
|
||||
{
|
||||
// we are only connected if we received the handshake.
|
||||
// not just after receiving any first data.
|
||||
authenticated = true;
|
||||
//Debug.Log("KCP: received handshake");
|
||||
OnConnected?.Invoke();
|
||||
}
|
||||
// otherwise it's random data from the internet, not
|
||||
// from a legitimate player.
|
||||
else
|
||||
{
|
||||
Debug.LogWarning("KCP: received random data before handshake. Disconnecting the connection.");
|
||||
open = false;
|
||||
OnDisconnected?.Invoke();
|
||||
break;
|
||||
}
|
||||
}
|
||||
// authenticated.
|
||||
else
|
||||
{
|
||||
// disconnect message?
|
||||
if (SegmentsEqual(dataSegment, Goodby))
|
||||
{
|
||||
// if we receive a disconnect message, then close everything
|
||||
//Debug.Log("KCP: received disconnect message");
|
||||
open = false;
|
||||
OnDisconnected?.Invoke();
|
||||
break;
|
||||
}
|
||||
// otherwise regular message
|
||||
else
|
||||
{
|
||||
// only accept regular messages
|
||||
//Debug.LogWarning($"Kcp recv msg: {BitConverter.ToString(buffer, 0, msgSize)}");
|
||||
OnData?.Invoke(dataSegment);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// if receive failed, close everything
|
||||
Debug.LogWarning($"Receive failed with error={received}. closing connection.");
|
||||
open = false;
|
||||
OnDisconnected?.Invoke();
|
||||
break;
|
||||
}
|
||||
}
|
||||
// we don't allow sending messages > Max, so this must be an
|
||||
// attacker. let's disconnect to avoid allocation attacks etc.
|
||||
else
|
||||
{
|
||||
Debug.LogWarning($"KCP: possible allocation attack for msgSize {msgSize} > max {Kcp.MTU_DEF}. Disconnecting the connection.");
|
||||
open = false;
|
||||
OnDisconnected?.Invoke();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Handshake()
|
||||
{
|
||||
// send a greeting and see if the server replies
|
||||
Debug.LogWarning("KcpConnection: sending Handshake to other end!");
|
||||
Send(Hello);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Disconnect this connection
|
||||
/// </summary>
|
||||
public virtual void Disconnect()
|
||||
{
|
||||
// send a disconnect message and disconnect
|
||||
if (open && socket.Connected)
|
||||
{
|
||||
try
|
||||
{
|
||||
Send(Goodby);
|
||||
kcp.Flush();
|
||||
|
||||
// call OnDisconnected event, even if we manually
|
||||
// disconnected
|
||||
OnDisconnected?.Invoke();
|
||||
}
|
||||
catch (SocketException)
|
||||
{
|
||||
// this is ok, the connection was already closed
|
||||
}
|
||||
catch (ObjectDisposedException)
|
||||
{
|
||||
// this is normal when we stop the server
|
||||
// the socket is stopped so we can't send anything anymore
|
||||
// to the clients
|
||||
|
||||
// the clients will eventually timeout and realize they
|
||||
// were disconnected
|
||||
}
|
||||
}
|
||||
open = false;
|
||||
|
||||
// EOF is now available
|
||||
//dataAvailable?.TrySetResult();
|
||||
}
|
||||
|
||||
// get remote endpoint
|
||||
public EndPoint GetRemoteEndPoint() => remoteEndpoint;
|
||||
}
|
||||
}
|
@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 3915c7c62b72d4dc2a9e4e76c94fc484
|
||||
timeCreated: 1602600432
|
@ -0,0 +1,20 @@
|
||||
using System.Net;
|
||||
using System.Net.Sockets;
|
||||
|
||||
namespace kcp2k
|
||||
{
|
||||
public class KcpServerConnection : KcpConnection
|
||||
{
|
||||
public KcpServerConnection(Socket socket, EndPoint remoteEndpoint, bool noDelay, uint interval = Kcp.INTERVAL)
|
||||
{
|
||||
this.socket = socket;
|
||||
this.remoteEndpoint = remoteEndpoint;
|
||||
SetupKcp(noDelay, interval);
|
||||
}
|
||||
|
||||
protected override void RawSend(byte[] data, int length)
|
||||
{
|
||||
socket.SendTo(data, 0, length, SocketFlags.None, remoteEndpoint);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 80a9b1ce9a6f14abeb32bfa9921d097b
|
||||
timeCreated: 1602601483
|
8
Assets/Mirror/Runtime/Transport/KCP/kcp2k/kcp.meta
Normal file
8
Assets/Mirror/Runtime/Transport/KCP/kcp2k/kcp.meta
Normal file
@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: da30be07fda954844b621fec7c727b6a
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -0,0 +1,3 @@
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
[assembly: InternalsVisibleTo("kcp2k.Tests")]
|
@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: aec6a15ac7bd43129317ea1f01f19782
|
||||
timeCreated: 1602665988
|
36
Assets/Mirror/Runtime/Transport/KCP/kcp2k/kcp/ByteBuffer.cs
Executable file
36
Assets/Mirror/Runtime/Transport/KCP/kcp2k/kcp/ByteBuffer.cs
Executable file
@ -0,0 +1,36 @@
|
||||
// byte[] buffer with Position, resizes automatically.
|
||||
// There is no size limit because we will only use it with ~MTU sized arrays.
|
||||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace kcp2k
|
||||
{
|
||||
public class ByteBuffer
|
||||
{
|
||||
public int Position;
|
||||
internal const int InitialCapacity = 1200;
|
||||
public byte[] RawBuffer = new byte[InitialCapacity];
|
||||
|
||||
// resize to 'value' capacity if needed
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
void EnsureCapacity(int value)
|
||||
{
|
||||
if (RawBuffer.Length < value)
|
||||
{
|
||||
int capacity = Math.Max(value, RawBuffer.Length * 2);
|
||||
Array.Resize(ref RawBuffer, capacity);
|
||||
}
|
||||
}
|
||||
|
||||
// write bytes at offset
|
||||
public void WriteBytes(byte[] bytes, int offset, int count)
|
||||
{
|
||||
if (offset >= 0 && count > 0)
|
||||
{
|
||||
EnsureCapacity(Position + count);
|
||||
Buffer.BlockCopy(bytes, offset, RawBuffer, Position, count);
|
||||
Position += count;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
11
Assets/Mirror/Runtime/Transport/KCP/kcp2k/kcp/ByteBuffer.cs.meta
Executable file
11
Assets/Mirror/Runtime/Transport/KCP/kcp2k/kcp/ByteBuffer.cs.meta
Executable file
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a9e4197a65c254ec0bf52ae9be1b340b
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
1021
Assets/Mirror/Runtime/Transport/KCP/kcp2k/kcp/Kcp.cs
Executable file
1021
Assets/Mirror/Runtime/Transport/KCP/kcp2k/kcp/Kcp.cs
Executable file
File diff suppressed because it is too large
Load Diff
11
Assets/Mirror/Runtime/Transport/KCP/kcp2k/kcp/Kcp.cs.meta
Executable file
11
Assets/Mirror/Runtime/Transport/KCP/kcp2k/kcp/Kcp.cs.meta
Executable file
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a59b1cae10a334faf807432ab472f212
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
84
Assets/Mirror/Runtime/Transport/KCP/kcp2k/kcp/Segment.cs
Executable file
84
Assets/Mirror/Runtime/Transport/KCP/kcp2k/kcp/Segment.cs
Executable file
@ -0,0 +1,84 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace kcp2k
|
||||
{
|
||||
// KCP Segment Definition
|
||||
internal class Segment
|
||||
{
|
||||
internal uint conv; // conversation
|
||||
internal uint cmd; // command, e.g. Kcp.CMD_ACK etc.
|
||||
internal uint frg; // fragment
|
||||
internal uint wnd; // window
|
||||
internal uint ts; // timestamp
|
||||
internal uint sn; // serial number
|
||||
internal uint una;
|
||||
internal uint resendts; // resend timestamp
|
||||
internal int rto;
|
||||
internal uint fastack;
|
||||
internal uint xmit;
|
||||
internal ByteBuffer data;
|
||||
|
||||
// pool ////////////////////////////////////////////////////////////////
|
||||
internal static readonly Stack<Segment> Pool = new Stack<Segment>(32);
|
||||
|
||||
public static Segment Take()
|
||||
{
|
||||
if (Pool.Count > 0)
|
||||
{
|
||||
Segment seg = Pool.Pop();
|
||||
return seg;
|
||||
}
|
||||
return new Segment();
|
||||
}
|
||||
|
||||
public static void Return(Segment seg)
|
||||
{
|
||||
seg.Reset();
|
||||
Pool.Push(seg);
|
||||
}
|
||||
////////////////////////////////////////////////////////////////////////
|
||||
internal Segment()
|
||||
{
|
||||
// allocate the ByteBuffer once.
|
||||
// note that we don't need to pool ByteBuffer, because Segment is
|
||||
// already pooled.
|
||||
data = new ByteBuffer();
|
||||
}
|
||||
|
||||
// ikcp_encode_seg
|
||||
// encode a segment into buffer
|
||||
internal int Encode(byte[] ptr, int offset)
|
||||
{
|
||||
int offset_ = offset;
|
||||
offset += Utils.Encode32U(ptr, offset, conv);
|
||||
offset += Utils.Encode8u(ptr, offset, (byte)cmd);
|
||||
offset += Utils.Encode8u(ptr, offset, (byte)frg);
|
||||
offset += Utils.Encode16U(ptr, offset, (ushort)wnd);
|
||||
offset += Utils.Encode32U(ptr, offset, ts);
|
||||
offset += Utils.Encode32U(ptr, offset, sn);
|
||||
offset += Utils.Encode32U(ptr, offset, una);
|
||||
offset += Utils.Encode32U(ptr, offset, (uint)data.Position);
|
||||
|
||||
return offset - offset_;
|
||||
}
|
||||
|
||||
// reset to return a fresh segment to the pool
|
||||
internal void Reset()
|
||||
{
|
||||
conv = 0;
|
||||
cmd = 0;
|
||||
frg = 0;
|
||||
wnd = 0;
|
||||
ts = 0;
|
||||
sn = 0;
|
||||
una = 0;
|
||||
rto = 0;
|
||||
xmit = 0;
|
||||
resendts = 0;
|
||||
fastack = 0;
|
||||
|
||||
// keep buffer for next pool usage, but reset position
|
||||
data.Position = 0;
|
||||
}
|
||||
}
|
||||
}
|
11
Assets/Mirror/Runtime/Transport/KCP/kcp2k/kcp/Segment.cs.meta
Executable file
11
Assets/Mirror/Runtime/Transport/KCP/kcp2k/kcp/Segment.cs.meta
Executable file
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: fc58706a05dd3442c8fde858d5266855
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
68
Assets/Mirror/Runtime/Transport/KCP/kcp2k/kcp/Utils.cs
Executable file
68
Assets/Mirror/Runtime/Transport/KCP/kcp2k/kcp/Utils.cs
Executable file
@ -0,0 +1,68 @@
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace kcp2k
|
||||
{
|
||||
public static class Utils
|
||||
{
|
||||
// encode 8 bits unsigned int
|
||||
public static int Encode8u(byte[] p, int offset, byte c)
|
||||
{
|
||||
p[0 + offset] = c;
|
||||
return 1;
|
||||
}
|
||||
|
||||
// decode 8 bits unsigned int
|
||||
public static int Decode8u(byte[] p, int offset, ref byte c)
|
||||
{
|
||||
c = p[0 + offset];
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* encode 16 bits unsigned int (lsb) */
|
||||
public static int Encode16U(byte[] p, int offset, ushort w)
|
||||
{
|
||||
p[0 + offset] = (byte)(w >> 0);
|
||||
p[1 + offset] = (byte)(w >> 8);
|
||||
return 2;
|
||||
}
|
||||
|
||||
/* decode 16 bits unsigned int (lsb) */
|
||||
public static int Decode16U(byte[] p, int offset, ref ushort c)
|
||||
{
|
||||
ushort result = 0;
|
||||
result |= p[0 + offset];
|
||||
result |= (ushort)(p[1 + offset] << 8);
|
||||
c = result;
|
||||
return 2;
|
||||
}
|
||||
|
||||
/* encode 32 bits unsigned int (lsb) */
|
||||
public static int Encode32U(byte[] p, int offset, uint l)
|
||||
{
|
||||
p[0 + offset] = (byte)(l >> 0);
|
||||
p[1 + offset] = (byte)(l >> 8);
|
||||
p[2 + offset] = (byte)(l >> 16);
|
||||
p[3 + offset] = (byte)(l >> 24);
|
||||
return 4;
|
||||
}
|
||||
|
||||
/* decode 32 bits unsigned int (lsb) */
|
||||
public static int Decode32U(byte[] p, int offset, ref uint c)
|
||||
{
|
||||
uint result = 0;
|
||||
result |= p[0 + offset];
|
||||
result |= (uint)(p[1 + offset] << 8);
|
||||
result |= (uint)(p[2 + offset] << 16);
|
||||
result |= (uint)(p[3 + offset] << 24);
|
||||
c = result;
|
||||
return 4;
|
||||
}
|
||||
|
||||
// timediff was a macro in original Kcp. let's inline it if possible.
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static int TimeDiff(uint later, uint earlier)
|
||||
{
|
||||
return (int)(later - earlier);
|
||||
}
|
||||
}
|
||||
}
|
11
Assets/Mirror/Runtime/Transport/KCP/kcp2k/kcp/Utils.cs.meta
Executable file
11
Assets/Mirror/Runtime/Transport/KCP/kcp2k/kcp/Utils.cs.meta
Executable file
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ef959eb716205bd48b050f010a9a35ae
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
Loading…
Reference in New Issue
Block a user