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:
What can I do to improve to exception handling to better deal with the socket becoming disconnected (both unexpectedly and intentionally)?
As far as I can tell I have protected against multiple receive loops begin started. (
if (Receiving) return;
invoid 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();
}
}
1 Answer 1
Review
- Property
Connected
is not thread-safe. UseInterlocked.CompareExchange
to get the connection atomically. - Events
DataReceived
andDisconnected
are never cleared. This will cause a memory leak. Implement aClear
function and/or implementIDisposable
. - Methods
Send
,Disconnect
,EndReceive
are not thread-safe. Consider using a mutex/lock. - Checking
fullyReceived
onreceivedLength == 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.
-
\$\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– Tainin2019年06月26日 20:38:23 +00:00Commented Jun 26, 2019 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– dfhwze2019年06月27日 04:44:52 +00:00Commented Jun 27, 2019 at 4:44
-
1\$\begingroup\$ Thanks. I have posted another question. \$\endgroup\$Tainin– Tainin2019年06月27日 18:26:28 +00:00Commented Jun 27, 2019 at 18:26
Explore related questions
See similar questions with these tags.