I develop my first async TCP socket server and client program in c# and would like to review the first parts of it. I like to get some information’s about smelly code that I missed and what I could improve in the program. I know it's a little bit much code, but it would awesome if you help me to improve my skills!
I have to classes AsyncSocketListener
and AsyncClient
. Both use events to tell the main program if messages sent or received. Here are the classes:
AsyncSocketListener
static class AsyncSocketListener
{
private static ushort port = 8080;
private static ushort limit = 250;
private static ManualResetEvent mre = new ManualResetEvent(false);
private static Dictionary<int, StateObject> clients = new Dictionary<int, StateObject>();
#region Event handler
public delegate void MessageReceivedHandler(int id, String msg);
public static event MessageReceivedHandler MessageReceived;
public delegate void MessageSubmittedHandler(int id, bool close);
public static event MessageSubmittedHandler MessageSubmitted;
#endregion
/* Starts the AsyncSocketListener */
public static void StartListening()
{
IPHostEntry host = Dns.GetHostEntry(String.Empty);
IPAddress ip = host.AddressList[3];
IPEndPoint socket = new IPEndPoint(ip, port);
try
{
using (Socket listener = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp))
{
listener.Bind(socket);
listener.Listen(limit);
while (true)
{
mre.Reset();
listener.BeginAccept(new AsyncCallback(OnClientConnect), listener);
mre.WaitOne();
}
}
}
catch (SocketException)
{
// TODO:
}
}
/* Gets a socket from the clients dictionary by his Id. */
private static StateObject getClient(int id)
{
StateObject state = new StateObject();
return clients.TryGetValue(id, out state) ? state : null;
}
/* Checks if the socket is connected. */
public static bool IsConnected(int id)
{
StateObject state = getClient(id);
return !(state.listener.Poll(1000, SelectMode.SelectRead) && state.listener.Available == 0);
}
/* Add a socket to the clients dictionary. Lock clients temporary to handle multiple access.
* ReceiveCallback raise a event, after the message receive complete. */
#region Receive data
public static void OnClientConnect(IAsyncResult result)
{
mre.Set();
StateObject state = new StateObject();
try
{
lock (clients)
{
state.Id = !clients.Any() ? 1 : clients.Keys.Max() + 1;
clients.Add(state.Id, state);
Console.WriteLine("Client connected. Get Id " + state.Id);
}
state.listener = (Socket)result.AsyncState;
state.listener = state.listener.EndAccept(result);
state.listener.BeginReceive(state.buffer, 0, StateObject.BufferSize, SocketFlags.None, new AsyncCallback(ReceiveCallback), state);
}
catch (SocketException)
{
// TODO:
}
}
public static void ReceiveCallback(IAsyncResult result)
{
StateObject state = (StateObject)result.AsyncState;
try
{
int receive = state.listener.EndReceive(result);
if (receive > 0)
state.sb.Append(Encoding.UTF8.GetString(state.buffer, 0, receive));
if (receive == StateObject.BufferSize)
state.listener.BeginReceive(state.buffer, 0, StateObject.BufferSize, SocketFlags.None, new AsyncCallback(ReceiveCallback), state);
else
{
MessageReceived(state.Id, state.sb.ToString());
state.sb = new StringBuilder();
}
}
catch (SocketException)
{
// TODO:
}
}
#endregion
/* Send(int id, String msg, bool close) use bool to close the connection after the message sent. */
#region Send data
public static void Send(int id, String msg, bool close)
{
StateObject state = getClient(id);
if (state == null)
throw new Exception("Client does not exist.");
if (!IsConnected(state.Id))
throw new Exception("Destination socket is not connected.");
try
{
byte[] send = Encoding.UTF8.GetBytes(msg);
state.Close = close;
state.listener.BeginSend(send, 0, send.Length, SocketFlags.None, new AsyncCallback(SendCallback), state);
}
catch (SocketException)
{
// TODO:
}
catch (ArgumentException)
{
// TODO:
}
}
private static void SendCallback(IAsyncResult result)
{
StateObject state = (StateObject)result.AsyncState;
try
{
state.listener.EndSend(result);
}
catch (SocketException)
{
// TODO:
}
catch (ObjectDisposedException)
{
// TODO:
}
finally
{
MessageSubmitted(state.Id, state.Close);
}
}
#endregion
public static void Close(int id)
{
StateObject state = getClient(id);
if (state == null)
throw new Exception("Client does not exist.");
try
{
state.listener.Shutdown(SocketShutdown.Both);
state.listener.Close();
}
catch (SocketException)
{
// TODO:
}
finally
{
lock (clients)
{
clients.Remove(state.Id);
Console.WriteLine("Client disconnected with Id {0}", state.Id);
}
}
}
}
AsyncClient
class AsyncClient : IDisposable
{
private const ushort port = 8080;
private Socket listener = null;
private bool close = false;
public ManualResetEvent connected = new ManualResetEvent(false);
public ManualResetEvent sent = new ManualResetEvent(false);
public ManualResetEvent received = new ManualResetEvent(false);
#region Event handler
public delegate void ConnectedHandler(AsyncClient a);
public static event ConnectedHandler Connected;
public delegate void MessageReceivedHandler(AsyncClient a, String msg);
public static event MessageReceivedHandler MessageReceived;
public delegate void MessageSubmittedHandler(AsyncClient a, bool close);
public static event MessageSubmittedHandler MessageSubmitted;
#endregion
/* Starts the AsyncClient */
public AsyncClient()
{
}
public void StartClient()
{
IPHostEntry host = Dns.GetHostEntry(String.Empty);
IPAddress ip = host.AddressList[3];
IPEndPoint socket = new IPEndPoint(ip, port);
try
{
this.listener = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
this.listener.BeginConnect(socket, new AsyncCallback(OnConnectCallback), listener);
connected.WaitOne();
Connected(this);
}
catch (SocketException)
{
// TODO:
}
}
public bool IsConnected()
{
return !(this.listener.Poll(1000, SelectMode.SelectRead) && this.listener.Available == 0);
}
private void OnConnectCallback(IAsyncResult result)
{
Socket server = (Socket)result.AsyncState;
try
{
server.EndConnect(result);
connected.Set();
}
catch (SocketException)
{
}
}
#region Receive data
public void Receive()
{
StateObject state = new StateObject();
state.listener = this.listener;
state.listener.BeginReceive(state.buffer, 0, StateObject.BufferSize, SocketFlags.None, new AsyncCallback(ReceiveCallback), state);
}
private void ReceiveCallback(IAsyncResult result)
{
StateObject state = (StateObject)result.AsyncState;
int receive = state.listener.EndReceive(result);
if (receive > 0)
state.sb.Append(Encoding.UTF8.GetString(state.buffer, 0, receive));
if (receive == StateObject.BufferSize)
state.listener.BeginReceive(state.buffer, 0, StateObject.BufferSize, SocketFlags.None, new AsyncCallback(ReceiveCallback), state);
else
{
MessageReceived(this, state.sb.ToString());
state.sb = new StringBuilder();
received.Set();
}
}
#endregion
#region Send data
public void Send(String msg, bool close)
{
if (!IsConnected())
throw new Exception("Destination socket is not connected.");
byte[] response = Encoding.UTF8.GetBytes(msg);
this.close = close;
this.listener.BeginSend(response, 0, response.Length, SocketFlags.None, new AsyncCallback(SendCallback), this.listener);
}
private void SendCallback(IAsyncResult result)
{
try
{
Socket resceiver = (Socket)result.AsyncState;
resceiver.EndSend(result);
}
catch (SocketException)
{
// TODO:
}
catch (ObjectDisposedException)
{
// TODO;
}
MessageSubmitted(this, this.close);
sent.Set();
}
#endregion
private void Close()
{
try
{
if (IsConnected())
{
this.listener.Shutdown(SocketShutdown.Both);
this.listener.Close();
}
}
catch (SocketException)
{
// TODO:
}
}
public void Dispose()
{
connected.Close();
sent.Close();
received.Close();
Close();
}
}
I save the connection information in a third class called StateObject
.
StateObject
class StateObject
{
/* Contains the state information. */
private int id;
private bool close = false; // Used to close the socket after the message sent.
public Socket listener = null;
public const int BufferSize = 1024;
public byte[] buffer = new byte[BufferSize];
public StringBuilder sb = new StringBuilder();
public StateObject() { }
public int Id
{
get { return this.id; }
set { this.id = value; }
}
public bool Close
{
get { return this.close; }
set { this.close = value; }
}
}
With the following code I test the server and client:
public Server()
{
InitializeComponent();
/* I use this to test my code on one machine. */
new Thread(new ThreadStart(AsyncSocketListener.StartListening)).Start();
AsyncSocketListener.MessageReceived += new AsyncSocketListener.MessageReceivedHandler(ClientMessageReceived);
AsyncSocketListener.MessageSubmitted += new AsyncSocketListener.MessageSubmittedHandler(ServerMessageSubmitted);
AsyncClient.Connected += new AsyncClient.ConnectedHandler(ConnectedToServer);
AsyncClient.MessageReceived += new AsyncClient.MessageReceivedHandler(ServerMessageReceived);
AsyncClient.MessageSubmitted += new AsyncClient.MessageSubmittedHandler(ClientMessageSubmitted);
for (int i = 0; i < 10; i++)
{
AsyncClient client = new AsyncClient();
Thread thread = new Thread(new ThreadStart(client.StartClient));
thread.Name = "Client" + i;
thread.Start();
}
}
/* Code to handle the events from AsyncSocketListener and AsyncClient. */
#region Server Code
private static void ClientMessageReceived(int id, String msg)
{
AsyncSocketListener.Send(id, msg.Replace("client", "server"), true);
Console.WriteLine("Server get Message from client. {0} ", msg);
}
private static void ServerMessageSubmitted(int id, bool close)
{
if (close)
AsyncSocketListener.Close(id);
}
#endregion
#region Client code
private static void ConnectedToServer(AsyncClient a)
{
a.Send("Hello, I'm the client.", false);
a.sent.WaitOne();
a.Receive();
a.received.WaitOne();
}
private static void ServerMessageReceived(AsyncClient a, String msg)
{
Console.WriteLine("Client get Message from server. {0} ", msg);
}
private static void ClientMessageSubmitted(AsyncClient a, bool close)
{
if (close)
a.Dispose();
}
#endregion
If the code is fine, I will use polymorphism for the AsyncSocketListener
and AsyncClient
because some parts are very similar. What do you think?
Edit:
At the moment the client could connect and send a message to the server. The server could reply on it. But it is not possible to exchange messages after this. I'm trying to fix this, but I can't find a solution. Any idea?