I've only been coding C# a few weeks and was just hoping for a little constructive criticism of a socket server I've been working on:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net.Sockets;
using System.Net;
namespace NetworkCommunication
{
public class TCPSocketServer : IDisposable
{
private int portNumber;
private int connectionsLimit;
private Socket connectionSocket;
private List<StateObject> connectedClients = new List<StateObject>();
public event SocketConnectedHandler ClientConnected;
public delegate void SocketConnectedHandler(TCPSocketServer socketServer, SocketConnectArgs e);
public event SocketMessageReceivedHandler MessageReceived;
public delegate void SocketMessageReceivedHandler(TCPSocketServer socketServer, SocketMessageReceivedArgs e);
public event SocketClosedHandler ClientDisconnected;
public delegate void SocketClosedHandler(TCPSocketServer socketServer, SocketEventArgs e);
#region Constructors
public TCPSocketServer(int PortNumber) : this(PortNumber, 0) { }
public TCPSocketServer(int PortNumber, int ConnectionsLimit)
{
this.portNumber = PortNumber;
this.connectionsLimit = ConnectionsLimit;
startListening();
}
#endregion
#region Send Messages
public void SendMessage(string MessageToSend, int clientID)
{
try
{
byte[] byData = System.Text.Encoding.UTF8.GetBytes(MessageToSend + "\0");
foreach (StateObject client in connectedClients)
{
if (clientID == client.id)
{
// Send message on correct client
if (client.socket.Connected)
{
client.socket.Send(byData);
}
break;
}
}
}
catch (SocketException) { }
}
public void SendMessage(byte[] MessageToSend, int clientID)
{
try
{
foreach (StateObject client in connectedClients)
{
if (clientID == client.id)
{
// Send message on correct client
if (client.socket.Connected)
{
client.socket.Send(MessageToSend);
}
break;
}
}
}
catch (SocketException) { }
}
#endregion
#region Connection and Listening
private void startListening()
{
try
{
// Create listening socket
connectionSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
connectionSocket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true);
IPEndPoint ipLocal = new IPEndPoint(IPAddress.Any, this.portNumber);
// Bind to local IP Address
connectionSocket.Bind(ipLocal);
// Start Listening
connectionSocket.Listen(1000);
// Creat callback to handle client connections
connectionSocket.BeginAccept(new AsyncCallback(onClientConnect), null);
}
catch (SocketException) { }
}
private void onClientConnect(IAsyncResult asyn)
{
try
{
// Create a new StateObject to hold the connected client
StateObject connectedClient = new StateObject();
connectedClient.socket = connectionSocket.EndAccept(asyn);
if (connectedClients.Count == 0)
{
connectedClient.id = 1;
}
else
{
connectedClient.id = connectedClients[connectedClients.Count - 1].id + 1;
}
connectedClients.Add(connectedClient);
// Check against limit
if (connectedClients.Count > connectionsLimit)
{
// No connection event is sent so close socket silently
closeSocketSilent(connectedClient.id);
return;
}
// Dispatch Event
if (ClientConnected != null)
{
SocketConnectArgs args = new SocketConnectArgs();
args.ConnectedIP = IPAddress.Parse(((IPEndPoint)connectedClient.socket.RemoteEndPoint).Address.ToString());
args.clientID = connectedClient.id;
ClientConnected(this, args);
}
// Release connectionSocket to keep listening if limit is not reached
connectionSocket.BeginAccept(new AsyncCallback(onClientConnect), null);
// Allow connected client to receive data and designate a callback method
connectedClient.socket.BeginReceive(connectedClient.buffer, 0, StateObject.BufferSize, 0, new AsyncCallback(onReceivedClientData), connectedClient);
}
catch (SocketException) { }
catch (ObjectDisposedException) { }
}
private void onReceivedClientData(IAsyncResult asyn)
{
String content = String.Empty;
// Receive stateobject of the client that sent data
StateObject dataSender = (StateObject)asyn.AsyncState;
try
{
// Complete aysnc receive method and read data length
int bytesRead = dataSender.socket.EndReceive(asyn);
if (bytesRead > 0)
{
// More data could be sent so append data received so far
dataSender.sb.Append(Encoding.UTF8.GetString(dataSender.buffer, 0, bytesRead));
content = dataSender.sb.ToString();
if ((content.Length > 0) || (content.IndexOf("") > -1))
{
String formattedMessage = String.Empty;
formattedMessage += content.Replace("\0", "");
// Dispatch Event
if (MessageReceived != null)
{
SocketMessageReceivedArgs args = new SocketMessageReceivedArgs();
args.MessageContent = formattedMessage;
args.clientID = dataSender.id;
MessageReceived(this, args);
}
dataSender.sb.Length = 0;
}
try
{
dataSender.socket.BeginReceive(dataSender.buffer, 0, StateObject.BufferSize, 0, new AsyncCallback(this.onReceivedClientData), dataSender);
}
catch (SocketException) { }
}
else
{
closeSocket(dataSender.id);
}
}
catch (SocketException) { }
catch (ObjectDisposedException) { }
}
#endregion
#region Socket Closing
public void closeSocket(int SocketID)
{
foreach (StateObject client in connectedClients.ToList())
{
try
{
if (SocketID == client.id)
{
client.socket.Close();
client.socket.Dispose();
// Dispatch Event
if (ClientDisconnected != null)
{
SocketEventArgs args = new SocketEventArgs();
args.clientID = client.id;
ClientDisconnected(this, args);
}
connectedClients.Remove(client);
break;
}
}
catch (SocketException) { }
}
}
// This does not dispatch an event, this task is to be used when rejecting connections past the limit.
// No connection event is sent so no disconnection event should be sent.
private void closeSocketSilent(int SocketID)
{
foreach (StateObject client in connectedClients.ToList())
{
if (SocketID == client.id)
{
try
{
client.socket.Close();
client.socket.Dispose();
connectedClients.Remove(client);
break;
}
catch (SocketException) { }
}
}
}
public void closeAllSockets()
{
foreach (StateObject client in connectedClients.ToList())
{
closeSocket(client.id);
}
}
#endregion
public void Dispose()
{
this.ClientConnected = null;
this.ClientDisconnected = null;
this.MessageReceived = null;
connectionSocket.Close();
}
}
}
Here's a new updated version with the help I was given kindly below. I've added error handling and moved some parts around:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net.Sockets;
using System.Net;
namespace NetworkCommunication
{
public class TCPSocketServer : IDisposable
{
private const int MaxLengthOfPendingConnectionsQueue = 1000;
private int portNumber;
private int connectionsLimit;
private Socket connectionSocket;
private Dictionary<int, StateObject> connectedClients = new Dictionary<int, StateObject>();
public event SocketConnectedHandler ClientConnected;
public delegate void SocketConnectedHandler(TCPSocketServer socketServer, SocketConnectArgs e);
public event SocketMessageReceivedHandler MessageReceived;
public delegate void SocketMessageReceivedHandler(TCPSocketServer socketServer, SocketMessageReceivedArgs e);
public event SocketClosedHandler ClientDisconnected;
public delegate void SocketClosedHandler(TCPSocketServer socketServer, SocketEventArgs e);
#region Constructor
public TCPSocketServer(int PortNumber, int ConnectionsLimit = 0)
{
// Validate Port Number
if (PortNumber > 0 && PortNumber < 65536)
{
this.portNumber = PortNumber;
}
else
{
throw new InvalidPortNumberException("Ports number must be in the 1-65535 range. Note: 256 and bellow are normally reserved.");
}
this.connectionsLimit = ConnectionsLimit;
startListening();
}
#endregion
private StateObject GetClient(int clientId)
{
StateObject client;
if (!connectedClients.TryGetValue(clientId, out client))
{
return null;
}
return client;
}
#region Send Messages
public void SendMessage(string MessageToSend, int clientID)
{
byte[] data = System.Text.Encoding.UTF8.GetBytes(MessageToSend + "\0");
SendMessage(data, clientID);
}
public void SendMessage(byte[] MessageToSend, int clientID)
{
StateObject client = GetClient(clientID);
if (client != null)
{
try
{
if (client.socket.Connected)
{
client.socket.Send(MessageToSend);
}
}
catch (SocketException)
{
// Close socket
closeSocket(clientID);
}
}
}
#endregion
#region Connection and Listening
private void startListening()
{
try
{
// Create listening socket
connectionSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
connectionSocket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true);
IPEndPoint ipLocal = new IPEndPoint(IPAddress.Any, this.portNumber);
// Bind to local IP Address
connectionSocket.Bind(ipLocal);
// Start Listening
connectionSocket.Listen(MaxLengthOfPendingConnectionsQueue);
// Create callback to handle client connections
connectionSocket.BeginAccept(new AsyncCallback(onClientConnect), null);
}
catch (SocketException)
{
throw new SocketCannotListenException("Cannot listen on this socket. Fatal Error");
}
}
private void onClientConnect(IAsyncResult asyn)
{
// Create a new StateObject to hold the connected client
StateObject connectedClient = new StateObject();
try
{
connectedClient.socket = connectionSocket.EndAccept(asyn);
connectedClient.id = !connectedClients.Any() ? 1 : connectedClients.Keys.Max() + 1;
connectedClients.Add(connectedClient.id, connectedClient);
// Check against limit
if (connectedClients.Count > connectionsLimit && connectionsLimit != 0)
{
// No connection event is sent so close connection quietly
closeSocket(connectedClient.id, true);
return;
}
// Allow connected client to receive data and designate a callback method
connectedClient.socket.BeginReceive(connectedClient.buffer, 0, StateObject.BufferSize, 0, new AsyncCallback(onReceivedClientData), connectedClient);
}
catch (Exception)
{
closeSocket(connectedClient.id, true);
}
finally
{
// Perfomed here to not get any exceptions on the main socket caught up in client connection errors.
ReleaseConnectionSocket();
}
// Dispatch Event at the end as any errors in socket dispatch silent diconnections
if (ClientConnected != null)
{
SocketConnectArgs args = new SocketConnectArgs()
{
ConnectedIP = IPAddress.Parse(((IPEndPoint)connectedClient.socket.RemoteEndPoint).Address.ToString()),
clientID = connectedClient.id
};
ClientConnected(this, args);
}
}
private void ReleaseConnectionSocket()
{
try
{
// Release connectionSocket to keep listening
connectionSocket.BeginAccept(new AsyncCallback(onClientConnect), null);
}
catch (SocketException)
{
throw new SocketCannotListenException("Cannot listen on the main socket. Fatal Error");
}
}
private void onReceivedClientData(IAsyncResult asyn)
{
// Receive stateobject of the client that sent data
StateObject dataSender = (StateObject)asyn.AsyncState;
try
{
// Complete aysnc receive method and read data length
int bytesRead = dataSender.socket.EndReceive(asyn);
if (bytesRead > 0)
{
// More data could be sent so append data received so far
dataSender.sb.Append(Encoding.UTF8.GetString(dataSender.buffer, 0, bytesRead));
String content = dataSender.sb.ToString();
if (!string.IsNullOrEmpty(content))
{
String formattedMessage = content.Replace("\0", "");
// Dispatch Event
if (MessageReceived != null)
{
SocketMessageReceivedArgs args = new SocketMessageReceivedArgs() {
MessageContent = formattedMessage,
clientID = dataSender.id
};
MessageReceived(this, args);
}
dataSender.sb.Clear();
}
try
{
dataSender.socket.BeginReceive(dataSender.buffer, 0, StateObject.BufferSize, 0, new AsyncCallback(this.onReceivedClientData), dataSender);
}
catch (SocketException) {
closeSocket(dataSender.id);
}
}
else
{
closeSocket(dataSender.id);
}
}
catch (SocketException ex) {
// Socket closed at other end
if (ex.ErrorCode == 10054)
{
closeSocket(dataSender.id);
}
else
{
closeSocket(dataSender.id);
}
}
}
#endregion
#region Socket Closing
public void closeSocket(int SocketID)
{
closeSocket(SocketID, false);
}
// This does not dispatch an event, this task is to be used when rejecting connections past the limit.
// No connection event is sent so no disconnection event should be sent.
private void closeSocket(int SocketId, bool silent)
{
StateObject client = GetClient(SocketId);
if (client == null)
{
return;
}
try
{
client.socket.Close();
client.socket.Dispose();
if (!silent)
{
// Dispatch event
if (ClientDisconnected != null)
{
SocketEventArgs args = new SocketEventArgs()
{
clientID = client.id
};
ClientDisconnected(this, args);
}
}
}
catch (SocketException)
{
// Socket is being removed anyway.
}
finally
{
connectedClients.Remove(client.id);
}
}
public void closeAllSockets()
{
var keys = connectedClients.Keys;
foreach (int key in keys)
{
var client = connectedClients[key];
closeSocket(client.id);
}
}
#endregion
public void Dispose()
{
this.ClientConnected = null;
this.ClientDisconnected = null;
this.MessageReceived = null;
connectionSocket.Close();
}
}
}
EndReceive
returns 0 and you have no more data to send, perhaps you should executeShutdown
as part of the graceful termination of the socket connection (msdn.microsoft.com/en-us/library/windows/desktop/…). \$\endgroup\$