Here is my simple chat program. I'm wondering whether it has an acceptable design (both object oriented design and network design).
If you want to run it, I can provide the solution folder.
Server
public class Server
{
private TcpListener _tcpListener;
private readonly int _packetSize = 64;
private int _clientCount;
private readonly int _maxClientCount;
private Dictionary<IPAddress, Client> _clients;
private readonly object _token = new object();
public Action<byte[], Client> OnDataReceive;
public bool Running { get; set; }
public int Port
{
get { return ((IPEndPoint)_tcpListener.Server.LocalEndPoint).Port; }
}
public Dictionary<IPAddress, Client> Clients
{
get { return _clients; }
}
public Server(int port, int maxClientCount)
{
_clientCount = 0;
_maxClientCount = maxClientCount;
_clients = new Dictionary<IPAddress, Client>(_maxClientCount);
try
{
_tcpListener = new TcpListener(IPAddress.Any, port);
}
catch (Exception e)
{
CommandLine.Write(e.Message);
}
}
public void StartListen()
{
try
{
Running = true;
_tcpListener.Start();
CommandLine.Write("Started listening at port " + Port + ".");
}
catch (Exception e)
{
CommandLine.Write(e.Message);
}
while (Running)
{
lock (_token)
{
if (_clientCount >= _maxClientCount) { continue; }
}
var newClient = _tcpListener.AcceptTcpClient();
AddClient(newClient);
}
}
public void StopListen()
{
try
{
Running = false;
_tcpListener.Stop();
CommandLine.Write("Stopped listening at port " + Port + ".");
}
catch (Exception e)
{
CommandLine.Write(e.Message);
}
}
public void Send(Client client, string data)
{
if (client == null || !client.Connected) return;
var msg = new Message(data);
try
{
client.Stream.Write(msg.Data, 0, msg.Data.Length);
}
catch (Exception e)
{
CommandLine.Write(e.Message);
}
}
public void SendAll(string data)
{
foreach (var entry in _clients) { Send(entry.Value, data); }
}
private void AddClient(TcpClient newClient)
{
if (newClient == null) return;
var client = new Client(newClient);
_clients.Add(client.IP, client);
IncreaseClientCount();
var clientThread = new Thread(HandleClient) { IsBackground = true };
clientThread.Start(client);
CommandLine.Write("A new client connected. Client count is " + _clientCount + ".");
}
private void RemoveClient(Client client)
{
if (client == null) return;
_clients.Remove(client.IP);
DecreaseClientCount();
client.Close();
}
private void HandleClient(object newClient)
{
var client = (Client)newClient;
var currentMessage = new List<byte>();
while (true)
{
var readMessage = new byte[_packetSize];
int readMessageSize;
try
{
readMessageSize = client.Stream.Read(readMessage, 0, _packetSize);
}
catch (Exception e)
{
CommandLine.Write(e.Message);
break;
}
if (readMessageSize <= 0)
{
CommandLine.Write("The client [" + client.IP + "] has closed the connection.");
break;
}
foreach (var b in readMessage)
{
if (b == 0) break;
if (b == 4)
{
OnDataReceive(currentMessage.ToArray(), client);
currentMessage.Clear();
}
else
{
currentMessage.Add(b);
}
}
}
CommandLine.Write("Communication ended with client [" + client.IP + "].");
RemoveClient(client);
}
private void IncreaseClientCount()
{
lock (_token) { _clientCount++; }
}
private void DecreaseClientCount()
{
lock (_token) { _clientCount--; }
}
}
Client
public class Client
{
private TcpClient _client;
private readonly int _packetSize = 64;
public NetworkStream Stream
{
get { return _client.GetStream(); }
}
public IPAddress IP
{
get { return ((IPEndPoint)_client.Client.RemoteEndPoint).Address; }
}
public bool Connected
{
get { return _client.Connected; }
}
public Client(TcpClient client)
{
_client = client;
}
public void Send(string data)
{
var msg = new Message(data);
try
{
Stream.Write(msg.Data, 0, msg.Data.Length);
}
catch (Exception e)
{
CommandLine.Write(e.Message);
}
}
public void Receive()
{
var currentMessage = new List<byte>();
while (true)
{
var readMessage = new byte[_packetSize];
int readMessageSize;
try
{
readMessageSize = Stream.Read(readMessage, 0, _packetSize);
}
catch (Exception e)
{
CommandLine.Write(e.Message);
break;
}
if (readMessageSize <= 0) break;
foreach (var b in readMessage)
{
if (b == 0) break;
if (b == 4)
{
CommandLine.Write("[SRV] : " + new ASCIIEncoding().GetString(currentMessage.ToArray()));
currentMessage.Clear();
}
else
{
currentMessage.Add(b);
}
}
}
}
public void Close()
{
try
{
_client.Close();
_client = null;
}
catch (Exception e)
{
CommandLine.Write(e.Message);
}
}
}
Main
public class Program
{
private static Server _server;
private static Client _client;
public static void Main()
{
while (true)
{
string input = Console.ReadLine();
switch (input)
{
case "-srv":
{
if (_client != null) return;
Console.Title = "Server";
_server = new Server(15150, 3);
_server.OnDataReceive += OnReceive;
new Thread(_server.StartListen).Start();
}
break;
case "-clients":
{
if (_server == null) return;
int counter = 0;
foreach (var entry in _server.Clients)
{
if (entry.Value == null) return;
CommandLine.Write(++counter + "- " + entry.Key + "\n");
}
}
break;
case "-connect":
{
if (_server != null) return;
Console.Title = "Client";
var client = new TcpClient();
//ip adress below will be taken by user input after tests.
var serverEndPoint = new IPEndPoint(IPAddress.Parse("192.168.1.10"), 15150);
client.Connect(serverEndPoint);
_client = new Client(client);
new Thread(_client.Receive).Start();
}
break;
default:
{
if (_client != null) // if user is a client.
{
_client.Send(input);
}
else if (_server != null) // if user is the server.
{
_server.SendAll(input);
}
}
break;
}
}
}
private static void OnReceive(byte[] data, Client client)
{
CommandLine.Write("[" + client.IP + "] : " + new ASCIIEncoding().GetString(data));
}
}
Other classes
public class Message
{
public byte[] Data { get; private set; }
public Message(byte[] data)
{
var wrappedData = new LinkedList<byte>(data);
wrappedData.AddLast(4);
Data = wrappedData.ToArray();
}
public Message(string data)
{
data += (char)4;
Data = new ASCIIEncoding().GetBytes(data);
}
}
public static class CommandLine
{
private static readonly object _token = new object();
public static void Write(string text)
{
lock (_token)
{
Console.WriteLine(text);
}
}
}