6
\$\begingroup\$

The following NetworkEndpoint class serves as a simple send / receive interface for either side of a network connection (client or server) when provided with a socket to operate through.

I have two main concerns about the code in it's current state:

  1. What can I do to improve to exception handling to better deal with the socket becoming disconnected (both unexpectedly and intentionally)?

  2. As far as I can tell I have protected against multiple receive loops begin started. (if (Receiving) return; in void InitializeReceiveLoop()) but are there any other major thread safety issues I have overlooked??

My knowledge of C# is primarily self taught so I apologize if my code does't reflect best practices or standard style guides.

Any advice on either of these issues or advice in general on improving my code would be greatly appreciated.

public class NetworkEndpoint {

    private Socket connection;
    public bool Connected { get => connection != null ? connection.Connected : false; }
    public bool Receiving { get; private set; }

    public event EventHandler<NetworkReceiveEventArgs> DataReceived;
    private void OnDataReceived(NetworkHeader header, byte[] data) =>
    DataReceived?.Invoke(this, new NetworkReceiveEventArgs(header, data));

    public event EventHandler Disconnected;
    private void OnDisconnected() =>
    Disconnected?.Invoke(this, new EventArgs());

    public NetworkEndpoint(Socket socket) {
        connection = socket ?? throw new ArgumentNullException("socket");
        Receiving = false;
    }

    public void Send(NetworkHeader header, byte[] data) {
        if (!Connected) throw new InvalidOperationException("NetworkEndpoint must be connected before sending.");
        try {
            connection.Send(ByteUtils.Combine(header.Serialize(), data));
            } catch (SocketException ex) {
            Disconnect();
        }

    }

    public void Disconnect() {
        connection?.Shutdown(SocketShutdown.Both);
        connection?.Close();
        connection = null;
        OnDisconnected();
    }

    public void InitializeReceiveLoop() {
        if (Receiving) return;

        Receiving = true;
        BeginReceive();
    }

    private byte[] headBuffer = new byte[NetworkHeader.ByteSize];
    private void BeginReceive() {
        connection.BeginReceive(headBuffer, 0, NetworkHeader.ByteSize, SocketFlags.None, EndReceive, null);
    }

    private void EndReceive(IAsyncResult result) {
        bool fullyReceived = false;
        byte[] dataBuffer = null;
        NetworkHeader header = null;

        try {
            if (connection.EndReceive(result) > 0) {
                header = NetworkHeader.Deserialize(headBuffer);
                dataBuffer = new byte[header.DataSize];

                int receivedLength = connection.Receive(dataBuffer, 0, dataBuffer.Length, SocketFlags.None);
                fullyReceived = (receivedLength == header.DataSize);
            }
        } catch (SocketException) { }

        if (fullyReceived) {
            OnDataReceived(header, dataBuffer);
            BeginReceive();
        } else if (connection.Connected) Disconnect();
    }
}
\$\endgroup\$
5
\$\begingroup\$

Review

  • Property Connected is not thread-safe. Use Interlocked.CompareExchange to get the connection atomically.
  • Events DataReceived and Disconnected are never cleared. This will cause a memory leak. Implement a Clear function and/or implement IDisposable.
  • Methods Send, Disconnect, EndReceive are not thread-safe. Consider using a mutex/lock.
  • Checking fullyReceived on receivedLength == header.DataSize is naive and can be broken. TCP messages can be sent in several packets. Use an internal buffer instead. Also, receivedLength = 0 is a clean way of determining no more data is available.
  • Catching SocketException blindly is a code-smell.
\$\endgroup\$
  • \$\begingroup\$ Thank you for your answer. This is exactly the kind of tips I was looking for. I have posted the changes I made along with comments detailing what I did and questions I still have here: link If you wouldn't mind taking a second look and letting me know if it is any better now, that would be great. \$\endgroup\$ – Tainin Jun 26 at 20:38
  • 1
    \$\begingroup\$ I took a look at your code, but I cannot review it in an answer of another question. I suggest you post a new question, referring to this one and with your updated code. I am sure several people are eager to review the updated code :) \$\endgroup\$ – dfhwze Jun 27 at 4:44
  • 1
    \$\begingroup\$ Thanks. I have posted another question. \$\endgroup\$ – Tainin Jun 27 at 18:26

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.