Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions kcp2k/kcp2k/highlevel/Common.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,30 @@ public static bool ResolveHostname(string hostname, out IPAddress[] addresses)
}
}

// if connections drop under heavy load, increase to OS limit.
// if still not enough, increase the OS limit.
public static void ConfigureSocketBuffers(ISocket socket, int recvBufferSize, int sendBufferSize)
{
// log initial size for comparison.
// remember initial size for log comparison
int initialReceive = socket.ReceiveBufferSize;
int initialSend = socket.SendBufferSize;

// set to configured size
try
{
socket.ReceiveBufferSize = recvBufferSize;
socket.SendBufferSize = sendBufferSize;
}
catch (SocketException)
{
Log.Warning($"[KCP] failed to set Socket RecvBufSize = {recvBufferSize} SendBufSize = {sendBufferSize}");
}


Log.Info($"[KCP] RecvBuf = {initialReceive}=>{socket.ReceiveBufferSize} ({socket.ReceiveBufferSize/initialReceive}x) SendBuf = {initialSend}=>{socket.SendBufferSize} ({socket.SendBufferSize/initialSend}x)");
}

// if connections drop under heavy load, increase to OS limit.
// if still not enough, increase the OS limit.
public static void ConfigureSocketBuffers(Socket socket, int recvBufferSize, int sendBufferSize)
Expand Down
75 changes: 0 additions & 75 deletions kcp2k/kcp2k/highlevel/Extensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -45,39 +45,6 @@ public static bool SendToNonBlocking(this Socket socket, ArraySegment<byte> data
}
}

// non-blocking UDP send.
// allows for reuse when overwriting KcpServer/Client (i.e. for relays).
// => wrapped with Poll to avoid WouldBlock allocating new SocketException.
// => wrapped with try-catch to ignore WouldBlock exception.
// make sure to set socket.Blocking = false before using this!
public static bool SendNonBlocking(this Socket socket, ArraySegment<byte> data)
{
try
{
// when using non-blocking sockets, SendTo may return WouldBlock.
// in C#, WouldBlock throws a SocketException, which is expected.
// unfortunately, creating the SocketException allocates in C#.
// let's poll first to avoid the WouldBlock allocation.
// note that this entirely to avoid allocations.
// non-blocking UDP doesn't need Poll in other languages.
// and the code still works without the Poll call.
if (!socket.Poll(0, SelectMode.SelectWrite)) return false;

// SendTo allocates. we used bound Send.
socket.Send(data.Array, data.Offset, data.Count, SocketFlags.None);
return true;
}
catch (SocketException e)
{
// for non-blocking sockets, SendTo may throw WouldBlock.
// in that case, simply drop the message. it's UDP, it's fine.
if (e.SocketErrorCode == SocketError.WouldBlock) return false;

// otherwise it's a real socket error. throw it.
throw;
}
}

// non-blocking UDP receive.
// allows for reuse when overwriting KcpServer/Client (i.e. for relays).
// => wrapped with Poll to avoid WouldBlock allocating new SocketException.
Expand Down Expand Up @@ -120,47 +87,5 @@ public static bool ReceiveFromNonBlocking(this Socket socket, byte[] recvBuffer,
throw;
}
}

// non-blocking UDP receive.
// allows for reuse when overwriting KcpServer/Client (i.e. for relays).
// => wrapped with Poll to avoid WouldBlock allocating new SocketException.
// => wrapped with try-catch to ignore WouldBlock exception.
// make sure to set socket.Blocking = false before using this!
public static bool ReceiveNonBlocking(this Socket socket, byte[] recvBuffer, out ArraySegment<byte> data)
{
data = default;

try
{
// when using non-blocking sockets, ReceiveFrom may return WouldBlock.
// in C#, WouldBlock throws a SocketException, which is expected.
// unfortunately, creating the SocketException allocates in C#.
// let's poll first to avoid the WouldBlock allocation.
// note that this entirely to avoid allocations.
// non-blocking UDP doesn't need Poll in other languages.
// and the code still works without the Poll call.
if (!socket.Poll(0, SelectMode.SelectRead)) return false;

// ReceiveFrom allocates. we used bound Receive.
// returns amount of bytes written into buffer.
// throws SocketException if datagram was larger than buffer.
// https://learn.microsoft.com/en-us/dotnet/api/system.net.sockets.socket.receive?view=net-6.0
//
// throws SocketException if datagram was larger than buffer.
// https://learn.microsoft.com/en-us/dotnet/api/system.net.sockets.socket.receive?view=net-6.0
int size = socket.Receive(recvBuffer, 0, recvBuffer.Length, SocketFlags.None);
data = new ArraySegment<byte>(recvBuffer, 0, size);
return true;
}
catch (SocketException e)
{
// for non-blocking sockets, Receive throws WouldBlock if there is
// no message to read. that's okay. only log for other errors.
if (e.SocketErrorCode == SocketError.WouldBlock) return false;

// otherwise it's a real socket error. throw it.
throw;
}
}
}
}
17 changes: 17 additions & 0 deletions kcp2k/kcp2k/highlevel/ISocket.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
using System;
using System.Net;

namespace kcp2k
{
public interface ISocket
{
EndPoint LocalEndPoint { get; }
bool Blocking { get; set; }
int ReceiveBufferSize { get; set; }
int SendBufferSize { get; set; }
void Connect(EndPoint remoteEP);
void Close();
bool SendNonBlocking(ArraySegment<byte> data);
bool ReceiveNonBlocking(byte[] recvBuffer, out ArraySegment<byte> data);
}
}
32 changes: 22 additions & 10 deletions kcp2k/kcp2k/highlevel/KcpClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ namespace kcp2k
public class KcpClient : KcpPeer
{
// IO
protected Socket socket;
protected ISocket socket;
public EndPoint remoteEndPoint;

// expose local endpoint for users / relays / nat traversal etc.
Expand Down Expand Up @@ -43,12 +43,14 @@ public class KcpClient : KcpPeer
bool active = false; // active between when connect() and disconnect() are called
public bool connected;

public KcpClient(Action OnConnected,
Action<ArraySegment<byte>, KcpChannel> OnData,
Action OnDisconnected,
Action<ErrorCode, string> OnError,
KcpConfig config)
: base(config, 0) // client has no cookie yet
public KcpClient(
Action OnConnected,
Action<ArraySegment<byte>, KcpChannel> OnData,
Action OnDisconnected,
Action<ErrorCode, string> OnError,
KcpConfig config
)
: base(config, 0) // client has no cookie yet
{
// initialize callbacks first to ensure they can be used safely.
OnConnectedCallback = OnConnected;
Expand Down Expand Up @@ -96,6 +98,7 @@ public void Connect(string address, ushort port)
return;
}

#if UNITY_EDITOR || !UNITY_WEBGL
// resolve host name before creating peer.
// fixes: https://github.com/MirrorNetworking/Mirror/issues/3361
if (!Common.ResolveHostname(address, out IPAddress[] addresses))
Expand All @@ -105,18 +108,27 @@ public void Connect(string address, ushort port)
OnDisconnectedCallback();
return;
}

#endif
// create fresh peer for each new session
// client doesn't need secure cookie.
Reset(config);

Log.Info($"[KCP] Client: connect to {address}:{port}");

#if UNITY_EDITOR || !UNITY_WEBGL
// create socket
remoteEndPoint = new IPEndPoint(addresses[0], port);
socket = new Socket(remoteEndPoint.AddressFamily, SocketType.Dgram, ProtocolType.Udp);
socket = new KcpSocket(
remoteEndPoint.AddressFamily,
SocketType.Dgram,
ProtocolType.Udp
);
#elif UNITY_WEBGL && WECHATMINIGAME
socket = new WXSocket(address, port);
#endif
active = true;

#if UNITY_EDITOR || !UNITY_WEBGL
// recv & send are called from main thread.
// need to ensure this never blocks.
// even a 1ms block per connection would stop us from scaling.
Expand All @@ -127,7 +139,7 @@ public void Connect(string address, ushort port)

// bind to endpoint so we can use send/recv instead of sendto/recvfrom.
socket.Connect(remoteEndPoint);

#endif
// immediately send a hello message to the server.
// server will call OnMessage and add the new connection.
// note that this still has cookie=0 until we receive the server's hello.
Expand Down
98 changes: 98 additions & 0 deletions kcp2k/kcp2k/highlevel/KcpSocket.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
using System;
using System.Net;
using System.Net.Sockets;

namespace kcp2k
{
public class KcpSocket : Socket, ISocket
{
public KcpSocket(AddressFamily addressFamily, SocketType socketType, ProtocolType protocolType) : base(addressFamily, socketType, protocolType)
{
}

public KcpSocket(SocketInformation socketInformation) : base(socketInformation)
{
}

public KcpSocket(SocketType socketType, ProtocolType protocolType) : base(socketType, protocolType)
{
}

// non-blocking UDP send.
// allows for reuse when overwriting KcpServer/Client (i.e. for relays).
// => wrapped with Poll to avoid WouldBlock allocating new SocketException.
// => wrapped with try-catch to ignore WouldBlock exception.
// make sure to set socket.Blocking = false before using this!
public bool SendNonBlocking(ArraySegment<byte> data)
{
try
{
// when using non-blocking sockets, SendTo may return WouldBlock.
// in C#, WouldBlock throws a SocketException, which is expected.
// unfortunately, creating the SocketException allocates in C#.
// let's poll first to avoid the WouldBlock allocation.
// note that this entirely to avoid allocations.
// non-blocking UDP doesn't need Poll in other languages.
// and the code still works without the Poll call.
if (!base.Poll(0, SelectMode.SelectWrite)) return false;

// SendTo allocates. we used bound Send.
base.Send(data.Array, data.Offset, data.Count, SocketFlags.None);
//Log.Info($"[KcpSocket] SendNonBlocking, data: {BitConverter.ToString(data.Array!, data.Offset, data.Count)}");
return true;
}
catch (SocketException e)
{
// for non-blocking sockets, SendTo may throw WouldBlock.
// in that case, simply drop the message. it's UDP, it's fine.
if (e.SocketErrorCode == SocketError.WouldBlock) return false;

// otherwise it's a real socket error. throw it.
throw;
}
}

// non-blocking UDP receive.
// allows for reuse when overwriting KcpServer/Client (i.e. for relays).
// => wrapped with Poll to avoid WouldBlock allocating new SocketException.
// => wrapped with try-catch to ignore WouldBlock exception.
// make sure to set socket.Blocking = false before using this!
public bool ReceiveNonBlocking(byte[] recvBuffer, out ArraySegment<byte> data)
{
data = default;

try
{
// when using non-blocking sockets, ReceiveFrom may return WouldBlock.
// in C#, WouldBlock throws a SocketException, which is expected.
// unfortunately, creating the SocketException allocates in C#.
// let's poll first to avoid the WouldBlock allocation.
// note that this entirely to avoid allocations.
// non-blocking UDP doesn't need Poll in other languages.
// and the code still works without the Poll call.
if (!base.Poll(0, SelectMode.SelectRead)) return false;

// ReceiveFrom allocates. we used bound Receive.
// returns amount of bytes written into buffer.
// throws SocketException if datagram was larger than buffer.
// https://learn.microsoft.com/en-us/dotnet/api/system.net.sockets.socket.receive?view=net-6.0
//
// throws SocketException if datagram was larger than buffer.
// https://learn.microsoft.com/en-us/dotnet/api/system.net.sockets.socket.receive?view=net-6.0
int size = base.Receive(recvBuffer, 0, recvBuffer.Length, SocketFlags.None);
//if (size > 0) Log.Info($"[KcpSocket] ReceiveNonBlocking, data: {BitConverter.ToString(recvBuffer, 0, size)}");
data = new ArraySegment<byte>(recvBuffer, 0, size);
return true;
}
catch (SocketException e)
{
// for non-blocking sockets, Receive throws WouldBlock if there is
// no message to read. that's okay. only log for other errors.
if (e.SocketErrorCode == SocketError.WouldBlock) return false;

// otherwise it's a real socket error. throw it.
throw;
}
}
}
}
Loading