// This file is provided under The MIT License as part of RiptideNetworking.
// Copyright (c) Tom Weiland
// For additional information please see the included LICENSE.md file or view it on GitHub:
// https://github.com/RiptideNetworking/Riptide/blob/main/LICENSE.md
using System;
using System.Linq;
using System.Net;
using System.Net.Sockets;
namespace Riptide.Transports.Tcp
{
/// A client which can connect to a .
public class TcpClient : TcpPeer, IClient
{
///
public event EventHandler Connected;
///
public event EventHandler ConnectionFailed;
///
public event EventHandler DataReceived;
/// The connection to the server.
private TcpConnection tcpConnection;
///
/// Expects the host address to consist of an IP and port, separated by a colon. For example: 127.0.0.1:7777.
public bool Connect(string hostAddress, out Connection connection, out string connectError)
{
connectError = $"Invalid host address '{hostAddress}'! IP and port should be separated by a colon, for example: '127.0.0.1:7777'.";
if (!ParseHostAddress(hostAddress, out IPAddress ip, out ushort port))
{
connection = null;
return false;
}
IPEndPoint remoteEndPoint = new IPEndPoint(ip, port);
socket = new Socket(SocketType.Stream, ProtocolType.Tcp)
{
SendBufferSize = socketBufferSize,
ReceiveBufferSize = socketBufferSize,
NoDelay = true,
};
try
{
socket.Connect(remoteEndPoint); // TODO: do something about the fact that this is a blocking call
}
catch (SocketException)
{
// The connection failed, but invoking the transports ConnectionFailed event from
// inside this method will cause problems, so we're just goint to eat the exception,
// call OnConnected(), and let Riptide detect that no connection was established.
}
connection = tcpConnection = new TcpConnection(socket, remoteEndPoint, this);
OnConnected();
return true;
}
/// Parses into and , if possible.
/// The host address to parse.
/// The retrieved IP.
/// The retrieved port.
/// Whether or not was in a valid format.
private bool ParseHostAddress(string hostAddress, out IPAddress ip, out ushort port)
{
string[] ipAndPort = hostAddress.Split(':');
string ipString = "";
string portString = "";
if (ipAndPort.Length > 2)
{
// There was more than one ':' in the host address, might be IPv6
ipString = string.Join(":", ipAndPort.Take(ipAndPort.Length - 1));
portString = ipAndPort[ipAndPort.Length - 1];
}
else if (ipAndPort.Length == 2)
{
// IPv4
ipString = ipAndPort[0];
portString = ipAndPort[1];
}
port = 0; // Need to make sure a value is assigned in case IP parsing fails
return IPAddress.TryParse(ipString, out ip) && ushort.TryParse(portString, out port);
}
///
public void Poll()
{
if (tcpConnection != null)
tcpConnection.Receive();
}
///
public void Disconnect()
{
socket.Close();
tcpConnection = null;
}
/// Invokes the event.
protected virtual void OnConnected()
{
Connected?.Invoke(this, EventArgs.Empty);
}
/// Invokes the event.
protected virtual void OnConnectionFailed()
{
ConnectionFailed?.Invoke(this, EventArgs.Empty);
}
///
protected internal override void OnDataReceived(int amount, TcpConnection fromConnection)
{
DataReceived?.Invoke(this, new DataReceivedEventArgs(ReceiveBuffer, amount, fromConnection));
}
}
}