// 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)); } } }