diff --git a/src/Discord.Net.Providers.UDPClient/Discord.Net.Providers.UdpClient.csproj b/src/Discord.Net.Providers.UDPClient/Discord.Net.Providers.UdpClient.csproj
new file mode 100644
index 000000000..64225a50d
--- /dev/null
+++ b/src/Discord.Net.Providers.UDPClient/Discord.Net.Providers.UdpClient.csproj
@@ -0,0 +1,27 @@
+
+
+ An optional UDP client provider for Discord.Net using System.Net.UdpClient
+ 1.0.0-beta2
+ net45
+ true
+ Discord.Net.Providers.UDPClient
+ discord;discordapp
+ https://github.com/RogueException/Discord.Net
+ http://opensource.org/licenses/MIT
+ git
+ git://github.com/RogueException/Discord.Net
+ Discord.Providers.UDPClient
+
+
+
+
+
+
+
+
+
+ $(NoWarn);CS1573;CS1591
+ true
+ true
+
+
\ No newline at end of file
diff --git a/src/Discord.Net.Providers.UDPClient/UDPClient.cs b/src/Discord.Net.Providers.UDPClient/UDPClient.cs
new file mode 100644
index 000000000..459feb335
--- /dev/null
+++ b/src/Discord.Net.Providers.UDPClient/UDPClient.cs
@@ -0,0 +1,128 @@
+using Discord.Net.Udp;
+using System;
+using System.Net;
+using System.Threading;
+using System.Threading.Tasks;
+using UdpSocket = System.Net.Sockets.UdpClient;
+
+namespace Discord.Net.Providers.UDPClient
+{
+ internal class UDPClient : IUdpSocket, IDisposable
+ {
+ public event Func ReceivedDatagram;
+
+ private readonly SemaphoreSlim _lock;
+ private UdpSocket _udp;
+ private IPEndPoint _destination;
+ private CancellationTokenSource _cancelTokenSource;
+ private CancellationToken _cancelToken, _parentToken;
+ private Task _task;
+ private bool _isDisposed;
+
+ public UDPClient()
+ {
+ _lock = new SemaphoreSlim(1, 1);
+ }
+ private void Dispose(bool disposing)
+ {
+ if (!_isDisposed)
+ {
+ if (disposing)
+ StopInternalAsync(true).GetAwaiter().GetResult();
+ _isDisposed = true;
+ }
+ }
+ public void Dispose()
+ {
+ Dispose(true);
+ }
+
+
+ public async Task StartAsync()
+ {
+ await _lock.WaitAsync().ConfigureAwait(false);
+ try
+ {
+ await StartInternalAsync(_cancelToken).ConfigureAwait(false);
+ }
+ finally
+ {
+ _lock.Release();
+ }
+ }
+ public async Task StartInternalAsync(CancellationToken cancelToken)
+ {
+ await StopInternalAsync().ConfigureAwait(false);
+
+ _cancelTokenSource = new CancellationTokenSource();
+ _cancelToken = CancellationTokenSource.CreateLinkedTokenSource(_parentToken, _cancelTokenSource.Token).Token;
+
+ _udp = new UdpSocket();
+
+ _task = RunAsync(_cancelToken);
+ }
+ public async Task StopAsync()
+ {
+ await _lock.WaitAsync().ConfigureAwait(false);
+ try
+ {
+ await StopInternalAsync().ConfigureAwait(false);
+ }
+ finally
+ {
+ _lock.Release();
+ }
+ }
+ public async Task StopInternalAsync(bool isDisposing = false)
+ {
+ try { _cancelTokenSource.Cancel(false); } catch { }
+
+ if (!isDisposing)
+ await (_task ?? Task.Delay(0)).ConfigureAwait(false);
+
+ if (_udp != null)
+ {
+ try { _udp.Close(); }
+ catch { }
+ _udp = null;
+ }
+ }
+
+ public void SetDestination(string host, int port)
+ {
+ var entry = Dns.GetHostEntryAsync(host).GetAwaiter().GetResult();
+ _destination = new IPEndPoint(entry.AddressList[0], port);
+ }
+ public void SetCancelToken(CancellationToken cancelToken)
+ {
+ _parentToken = cancelToken;
+ _cancelToken = CancellationTokenSource.CreateLinkedTokenSource(_parentToken, _cancelTokenSource.Token).Token;
+ }
+
+ public async Task SendAsync(byte[] data, int index, int count)
+ {
+ if (index != 0) //Should never happen?
+ {
+ var newData = new byte[count];
+ Buffer.BlockCopy(data, index, newData, 0, count);
+ data = newData;
+ }
+ await _udp.SendAsync(data, count, _destination).ConfigureAwait(false);
+ }
+
+ private async Task RunAsync(CancellationToken cancelToken)
+ {
+ var closeTask = Task.Delay(-1, cancelToken);
+ while (!cancelToken.IsCancellationRequested)
+ {
+ var receiveTask = _udp.ReceiveAsync();
+ var task = await Task.WhenAny(closeTask, receiveTask).ConfigureAwait(false);
+ if (task == closeTask)
+ break;
+
+ var result = receiveTask.Result;
+ await ReceivedDatagram(result.Buffer, 0, result.Buffer.Length).ConfigureAwait(false);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Discord.Net.Providers.UDPClient/UDPClientProvider.cs b/src/Discord.Net.Providers.UDPClient/UDPClientProvider.cs
new file mode 100644
index 000000000..6bdf9eb63
--- /dev/null
+++ b/src/Discord.Net.Providers.UDPClient/UDPClientProvider.cs
@@ -0,0 +1,9 @@
+using Discord.Net.Udp;
+
+namespace Discord.Net.Providers.UDPClient
+{
+ public static class UDPClientProvider
+ {
+ public static readonly UdpSocketProvider Instance = () => new UDPClient();
+ }
+}
diff --git a/src/Discord.Net.Providers.UDPClient/project.json b/src/Discord.Net.Providers.UDPClient/project.json
new file mode 100644
index 000000000..59d8509ad
--- /dev/null
+++ b/src/Discord.Net.Providers.UDPClient/project.json
@@ -0,0 +1,37 @@
+{
+ "version": "1.0.0-*",
+ "description": "An optional UDP client provider for Discord.Net using System.Net.UdpClient.",
+ "authors": [ "RogueException" ],
+
+ "packOptions": {
+ "tags": [ "discord", "discordapp" ],
+ "licenseUrl": "http://opensource.org/licenses/MIT",
+ "projectUrl": "https://github.com/RogueException/Discord.Net",
+ "repository": {
+ "type": "git",
+ "url": "git://github.com/RogueException/Discord.Net"
+ }
+ },
+
+ "configurations": {
+ "Release": {
+ "buildOptions": {
+ "define": [ "RELEASE" ],
+ "nowarn": [ "CS1573", "CS1591" ],
+ "optimize": true,
+ "warningsAsErrors": true,
+ "xmlDoc": true
+ }
+ }
+ },
+
+ "dependencies": {
+ "Discord.Net.Core": {
+ "target": "project"
+ }
+ },
+
+ "frameworks": {
+ "net45": {}
+ }
+}
\ No newline at end of file