196 lines
7.8 KiB
C#
196 lines
7.8 KiB
C#
// 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 Riptide.Utils;
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Net;
|
|
using System.Net.Sockets;
|
|
|
|
namespace Riptide.Transports.Tcp
|
|
{
|
|
/// <summary>Represents a connection to a <see cref="TcpServer"/> or <see cref="TcpClient"/>.</summary>
|
|
public class TcpConnection : Connection, IEquatable<TcpConnection>
|
|
{
|
|
/// <summary>The endpoint representing the other end of the connection.</summary>
|
|
public readonly IPEndPoint RemoteEndPoint;
|
|
|
|
/// <summary>Whether or not the server has received a connection attempt from this connection.</summary>
|
|
internal bool DidReceiveConnect;
|
|
|
|
/// <summary>The socket to use for sending and receiving.</summary>
|
|
private readonly Socket socket;
|
|
/// <summary>The local peer this connection is associated with.</summary>
|
|
private readonly TcpPeer peer;
|
|
/// <summary>An array to receive message size values into.</summary>
|
|
private readonly byte[] sizeBytes = new byte[sizeof(int)];
|
|
/// <summary>The size of the next message to be received.</summary>
|
|
private int nextMessageSize;
|
|
|
|
/// <summary>Initializes the connection.</summary>
|
|
/// <param name="socket">The socket to use for sending and receiving.</param>
|
|
/// <param name="remoteEndPoint">The endpoint representing the other end of the connection.</param>
|
|
/// <param name="peer">The local peer this connection is associated with.</param>
|
|
internal TcpConnection(Socket socket, IPEndPoint remoteEndPoint, TcpPeer peer)
|
|
{
|
|
RemoteEndPoint = remoteEndPoint;
|
|
this.socket = socket;
|
|
this.peer = peer;
|
|
}
|
|
|
|
/// <inheritdoc/>
|
|
protected internal override void Send(byte[] dataBuffer, int amount)
|
|
{
|
|
if (amount == 0)
|
|
throw new ArgumentOutOfRangeException(nameof(amount), "Sending 0 bytes is not allowed!");
|
|
|
|
try
|
|
{
|
|
if (socket.Connected)
|
|
{
|
|
Converter.FromInt(amount, peer.SendBuffer, 0);
|
|
Array.Copy(dataBuffer, 0, peer.SendBuffer, sizeof(int), amount); // TODO: consider sending length separately with an extra socket.Send call instead of copying the data an extra time
|
|
socket.Send(peer.SendBuffer, amount + sizeof(int), SocketFlags.None);
|
|
}
|
|
}
|
|
catch (SocketException)
|
|
{
|
|
// May want to consider triggering a disconnect here (perhaps depending on the type
|
|
// of SocketException)? Timeout should catch disconnections, but disconnecting
|
|
// explicitly might be better...
|
|
}
|
|
}
|
|
|
|
/// <summary>Polls the socket and checks if any data was received.</summary>
|
|
internal void Receive()
|
|
{
|
|
bool tryReceiveMore = true;
|
|
while (tryReceiveMore)
|
|
{
|
|
int byteCount = 0;
|
|
try
|
|
{
|
|
if (nextMessageSize > 0)
|
|
{
|
|
// We already have a size value
|
|
tryReceiveMore = TryReceiveMessage(out byteCount);
|
|
}
|
|
else if (socket.Available >= sizeof(int))
|
|
{
|
|
// We have enough bytes for a complete size value
|
|
socket.Receive(sizeBytes, sizeof(int), SocketFlags.None);
|
|
nextMessageSize = Converter.ToInt(sizeBytes, 0);
|
|
|
|
if (nextMessageSize > 0)
|
|
tryReceiveMore = TryReceiveMessage(out byteCount);
|
|
}
|
|
else
|
|
tryReceiveMore = false;
|
|
}
|
|
catch (SocketException ex)
|
|
{
|
|
tryReceiveMore = false;
|
|
switch (ex.SocketErrorCode)
|
|
{
|
|
case SocketError.Interrupted:
|
|
case SocketError.NotSocket:
|
|
peer.OnDisconnected(this, DisconnectReason.TransportError);
|
|
break;
|
|
case SocketError.ConnectionReset:
|
|
peer.OnDisconnected(this, DisconnectReason.Disconnected);
|
|
break;
|
|
case SocketError.TimedOut:
|
|
peer.OnDisconnected(this, DisconnectReason.TimedOut);
|
|
break;
|
|
case SocketError.MessageSize:
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
catch (ObjectDisposedException)
|
|
{
|
|
tryReceiveMore = false;
|
|
peer.OnDisconnected(this, DisconnectReason.TransportError);
|
|
}
|
|
catch (NullReferenceException)
|
|
{
|
|
tryReceiveMore = false;
|
|
peer.OnDisconnected(this, DisconnectReason.TransportError);
|
|
}
|
|
|
|
if (byteCount > 0)
|
|
peer.OnDataReceived(byteCount, this);
|
|
}
|
|
}
|
|
|
|
/// <summary>Receives a message, if all of its data is ready to be received.</summary>
|
|
/// <param name="receivedByteCount">How many bytes were received.</param>
|
|
/// <returns>Whether or not all of the message's data was ready to be received.</returns>
|
|
private bool TryReceiveMessage(out int receivedByteCount)
|
|
{
|
|
if (socket.Available >= nextMessageSize)
|
|
{
|
|
// We have enough bytes to read the complete message
|
|
receivedByteCount = socket.Receive(peer.ReceiveBuffer, nextMessageSize, SocketFlags.None);
|
|
nextMessageSize = 0;
|
|
return true;
|
|
}
|
|
|
|
receivedByteCount = 0;
|
|
return false;
|
|
}
|
|
|
|
/// <summary>Closes the connection.</summary>
|
|
internal void Close()
|
|
{
|
|
socket.Close();
|
|
}
|
|
|
|
/// <inheritdoc/>
|
|
public override string ToString() => RemoteEndPoint.ToStringBasedOnIPFormat();
|
|
|
|
/// <inheritdoc/>
|
|
public override bool Equals(object obj) => Equals(obj as TcpConnection);
|
|
/// <inheritdoc/>
|
|
public bool Equals(TcpConnection other)
|
|
{
|
|
if (other is null)
|
|
return false;
|
|
|
|
if (ReferenceEquals(this, other))
|
|
return true;
|
|
|
|
return RemoteEndPoint.Equals(other.RemoteEndPoint);
|
|
}
|
|
|
|
/// <inheritdoc/>
|
|
public override int GetHashCode()
|
|
{
|
|
return -288961498 + EqualityComparer<IPEndPoint>.Default.GetHashCode(RemoteEndPoint);
|
|
}
|
|
|
|
#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member
|
|
public static bool operator ==(TcpConnection left, TcpConnection right)
|
|
#pragma warning restore CS1591 // Missing XML comment for publicly visible type or member
|
|
{
|
|
if (left is null)
|
|
{
|
|
if (right is null)
|
|
return true;
|
|
|
|
return false; // Only the left side is null
|
|
}
|
|
|
|
// Equals handles case of null on right side
|
|
return left.Equals(right);
|
|
}
|
|
|
|
#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member
|
|
public static bool operator !=(TcpConnection left, TcpConnection right) => !(left == right);
|
|
#pragma warning restore CS1591 // Missing XML comment for publicly visible type or member
|
|
}
|
|
}
|