Files
EonaCat.Network/EonaCat.Network/System/Sockets/Tcp/SocketTcpClient.cs
2024-04-24 20:07:45 +02:00

172 lines
5.9 KiB
C#

using System;
using System.IO;
using System.Net;
using System.Net.Security;
using System.Net.Sockets;
using System.Security.Authentication;
using System.Security.Cryptography.X509Certificates;
using System.Threading.Tasks;
namespace EonaCat.Network
{
public class SocketTcpClient
{
private const int BUFFER_SIZE = 4096;
private Socket _socket;
private bool _isIPv6;
public bool IsIPv6 => _isIPv6;
public event Action<RemoteInfo> OnConnect;
public event Action<RemoteInfo> OnReceive;
public event Action<RemoteInfo> OnDisconnect;
public event Action<Exception, string> OnError;
public async Task ConnectAsync(string ipAddress, int port, bool useSsl = false, SslOptions sslOptions = null)
{
if (!IPAddress.TryParse(ipAddress, out var ip))
{
throw new ArgumentException("Invalid ipAddress given");
}
await CreateSocketTcpClientAsync(ip, port, useSsl, sslOptions);
}
private async Task CreateSocketTcpClientAsync(IPAddress ipAddress, int port, bool useSsl, SslOptions sslOptions)
{
_isIPv6 = ipAddress.AddressFamily == AddressFamily.InterNetworkV6;
_socket = new Socket(_isIPv6 ? AddressFamily.InterNetworkV6 : AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
try
{
await _socket.ConnectAsync(ipAddress, port).ConfigureAwait(false);
OnConnect?.Invoke(new RemoteInfo { IsTcp = true, IsIPv6 = _isIPv6 });
if (useSsl)
{
var sslStream = new SslStream(new NetworkStream(_socket), false, ValidateRemoteCertificate, null);
try
{
await sslStream.AuthenticateAsClientAsync(ipAddress.ToString(), sslOptions.ClientCertificates, sslOptions.SslProtocol, sslOptions.CheckCertificateRevocation);
_ = StartReceivingSslAsync(sslStream);
}
catch (AuthenticationException ex)
{
OnError?.Invoke(ex, $"AuthenticationException: {ex.Message}");
Disconnect();
}
}
else
{
_ = StartReceivingAsync();
}
}
catch (SocketException ex)
{
OnError?.Invoke(ex, $"SocketException: {ex.Message}");
Disconnect();
}
}
private async Task StartReceivingAsync()
{
var buffer = new byte[BUFFER_SIZE];
while (_socket.Connected)
{
try
{
var received = await _socket.ReceiveAsync(new ArraySegment<byte>(buffer), SocketFlags.None).ConfigureAwait(false);
if (received > 0)
{
var data = new byte[received];
Buffer.BlockCopy(buffer, 0, data, 0, received);
OnReceive?.Invoke(new RemoteInfo
{
IsTcp = true,
Data = data,
EndPoint = _socket.RemoteEndPoint,
IsIPv6 = _isIPv6
});
}
else
{
break;
}
}
catch (SocketException ex)
{
OnError?.Invoke(ex, $"SocketException: {ex.Message}");
break;
}
}
OnDisconnect?.Invoke(new RemoteInfo
{
IsTcp = true,
EndPoint = _socket.RemoteEndPoint,
IsIPv6 = _isIPv6
});
}
private async Task StartReceivingSslAsync(SslStream sslStream)
{
var buffer = new byte[BUFFER_SIZE];
while (_socket.Connected)
{
try
{
var received = await sslStream.ReadAsync(buffer, 0, buffer.Length).ConfigureAwait(false);
if (received > 0)
{
var data = new byte[received];
Buffer.BlockCopy(buffer, 0, data, 0, received);
OnReceive?.Invoke(new RemoteInfo
{
IsTcp = true,
Data = data,
EndPoint = _socket.RemoteEndPoint,
IsIPv6 = _isIPv6
});
}
else
{
break;
}
}
catch (IOException ex)
{
OnError?.Invoke(ex, $"IOException: {ex.Message}");
break;
}
catch (AuthenticationException ex)
{
OnError?.Invoke(ex, $"AuthenticationException: {ex.Message}");
break;
}
}
OnDisconnect?.Invoke(new RemoteInfo
{
IsTcp = true,
EndPoint = _socket.RemoteEndPoint,
IsIPv6 = _isIPv6
});
}
public async Task SendAsync(byte[] data)
{
await _socket.SendAsync(new ArraySegment<byte>(data), SocketFlags.None).ConfigureAwait(false);
}
public void Disconnect()
{
_socket.Close();
}
private bool ValidateRemoteCertificate(object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors)
{
return sslPolicyErrors == SslPolicyErrors.None;
}
}
}