Created
April 14, 2012 01:44
-
-
Save bschwind/2381448 to your computer and use it in GitHub Desktop.
Implements a simple TCP server with one thread per client
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using System.Collections.Generic; | |
using System.Linq; | |
using System.Text; | |
using System.Net; | |
using System.Net.Sockets; | |
using System.Threading; | |
using System.Collections.Concurrent; | |
namespace GraphicsToolkit.Networking | |
{ | |
public delegate void ServerHandlePacketData(byte[] data, int bytesRead, TcpClient client); | |
/// <summary> | |
/// Implements a simple TCP server which uses one thread per client | |
/// </summary> | |
public class Server | |
{ | |
public event ServerHandlePacketData OnDataReceived; | |
private TcpListener listener; | |
private ConcurrentDictionary<TcpClient, NetworkBuffer> clientBuffers; | |
private List<TcpClient> clients; | |
private int sendBufferSize = 1024; | |
private int readBufferSize = 1024; | |
private int port; | |
private bool started = false; | |
/// <summary> | |
/// The list of currently connected clients | |
/// </summary> | |
public List<TcpClient> Clients | |
{ | |
get | |
{ | |
return clients; | |
} | |
} | |
/// <summary> | |
/// The number of clients currently connected | |
/// </summary> | |
public int NumClients | |
{ | |
get | |
{ | |
return clients.Count; | |
} | |
} | |
/// <summary> | |
/// Constructs a new TCP server which will listen on a given port | |
/// </summary> | |
/// <param name="port"></param> | |
public Server(int port) | |
{ | |
this.port = port; | |
clientBuffers = new ConcurrentDictionary<TcpClient, NetworkBuffer>(); | |
clients = new List<TcpClient>(); | |
} | |
/// <summary> | |
/// Begins listening on the port provided to the constructor | |
/// </summary> | |
public void Start() | |
{ | |
listener = new TcpListener(IPAddress.Any, port); | |
Console.WriteLine("Started server on port " + port); | |
Thread thread = new Thread(new ThreadStart(ListenForClients)); | |
thread.Start(); | |
started = true; | |
} | |
/// <summary> | |
/// Runs in its own thread. Responsible for accepting new clients and kicking them off into their own thread | |
/// </summary> | |
private void ListenForClients() | |
{ | |
listener.Start(); | |
while (started) | |
{ | |
TcpClient client = listener.AcceptTcpClient(); | |
Thread clientThread = new Thread(new ParameterizedThreadStart(WorkWithClient)); | |
Console.WriteLine("New client connected"); | |
NetworkBuffer newBuff = new NetworkBuffer(); | |
newBuff.WriteBuffer = new byte[sendBufferSize]; | |
newBuff.ReadBuffer = new byte[readBufferSize]; | |
newBuff.CurrentWriteByteCount = 0; | |
clientBuffers.GetOrAdd(client, newBuff); | |
clients.Add(client); | |
clientThread.Start(client); | |
Thread.Sleep(15); | |
} | |
} | |
/// <summary> | |
/// Stops the server from accepting new clients | |
/// </summary> | |
public void Stop() | |
{ | |
if (!listener.Pending()) | |
{ | |
listener.Stop(); | |
started = false; | |
} | |
} | |
/// <summary> | |
/// This method lives on a thread, one per client. Responsible for reading data from the client | |
/// and pushing the data off to classes listening to the server. | |
/// </summary> | |
/// <param name="client"></param> | |
private void WorkWithClient(object client) | |
{ | |
TcpClient tcpClient = client as TcpClient; | |
if (tcpClient == null) | |
{ | |
Console.WriteLine("TCP client is null, stopping processing for this client"); | |
DisconnectClient(tcpClient); | |
return; | |
} | |
NetworkStream clientStream = tcpClient.GetStream(); | |
int bytesRead; | |
while (started) | |
{ | |
bytesRead = 0; | |
try | |
{ | |
//blocks until a client sends a message | |
bytesRead = clientStream.Read(clientBuffers[tcpClient].ReadBuffer, 0, readBufferSize); | |
} | |
catch | |
{ | |
//a socket error has occurred | |
Console.WriteLine("A socket error has occurred with client: " + tcpClient.ToString()); | |
break; | |
} | |
if (bytesRead == 0) | |
{ | |
//the client has disconnected from the server | |
break; | |
} | |
if (OnDataReceived != null) | |
{ | |
//Send off the data for other classes to handle | |
OnDataReceived(clientBuffers[tcpClient].ReadBuffer, bytesRead, tcpClient); | |
} | |
Thread.Sleep(15); | |
} | |
DisconnectClient(tcpClient); | |
} | |
/// <summary> | |
/// Removes a given client from our list of clients | |
/// </summary> | |
/// <param name="client"></param> | |
private void DisconnectClient(TcpClient client) | |
{ | |
if (client == null) | |
{ | |
return; | |
} | |
Console.WriteLine("Disconnected client: " + client.ToString()); | |
client.Close(); | |
clients.Remove(client); | |
NetworkBuffer buffer; | |
clientBuffers.TryRemove(client, out buffer); | |
} | |
/// <summary> | |
/// Adds data to the packet to be sent out, but does not send it across the network | |
/// </summary> | |
/// <param name="data">The data to be sent</param> | |
/// <param name="client">The client to send the data to</param> | |
public void AddToPacket(byte[] data, TcpClient client) | |
{ | |
if (clientBuffers[client].CurrentWriteByteCount + data.Length > clientBuffers[client].WriteBuffer.Length) | |
{ | |
FlushData(client); | |
} | |
Array.ConstrainedCopy(data, 0, clientBuffers[client].WriteBuffer, clientBuffers[client].CurrentWriteByteCount, data.Length); | |
clientBuffers[client].CurrentWriteByteCount += data.Length; | |
} | |
/// <summary> | |
/// Adds data to the packet to be sent out, but does not send it across the network. This | |
/// data gets sent to every connected client | |
/// </summary> | |
/// <param name="data">The data to be sent</param> | |
public void AddToPacketToAll(byte[] data) | |
{ | |
lock (clients) | |
{ | |
foreach (TcpClient client in clients) | |
{ | |
if (clientBuffers[client].CurrentWriteByteCount + data.Length > clientBuffers[client].WriteBuffer.Length) | |
{ | |
FlushData(client); | |
} | |
Array.ConstrainedCopy(data, 0, clientBuffers[client].WriteBuffer, clientBuffers[client].CurrentWriteByteCount, data.Length); | |
clientBuffers[client].CurrentWriteByteCount += data.Length; | |
} | |
} | |
} | |
/// <summary> | |
/// Flushes all outgoing data to the specified client | |
/// </summary> | |
/// <param name="client"></param> | |
private void FlushData(TcpClient client) | |
{ | |
client.GetStream().Write(clientBuffers[client].WriteBuffer, 0, clientBuffers[client].CurrentWriteByteCount); | |
client.GetStream().Flush(); | |
clientBuffers[client].CurrentWriteByteCount = 0; | |
} | |
/// <summary> | |
/// Flushes all outgoing data to every client | |
/// </summary> | |
private void FlushDataToAll() | |
{ | |
lock (clients) | |
{ | |
foreach (TcpClient client in clients) | |
{ | |
client.GetStream().Write(clientBuffers[client].WriteBuffer, 0, clientBuffers[client].CurrentWriteByteCount); | |
client.GetStream().Flush(); | |
clientBuffers[client].CurrentWriteByteCount = 0; | |
} | |
} | |
} | |
/// <summary> | |
/// Sends the byte array data immediately to the specified client | |
/// </summary> | |
/// <param name="data">The data to be sent</param> | |
/// <param name="client">The client to send the data to</param> | |
public void SendImmediate(byte[] data, TcpClient client) | |
{ | |
AddToPacket(data, client); | |
FlushData(client); | |
} | |
/// <summary> | |
/// Sends the byte array data immediately to all clients | |
/// </summary> | |
/// <param name="data">The data to be sent</param> | |
public void SendImmediateToAll(byte[] data) | |
{ | |
lock (clients) | |
{ | |
foreach (TcpClient client in clients) | |
{ | |
AddToPacket(data, client); | |
FlushData(client); | |
} | |
} | |
} | |
} | |
} |
NetworkBuffer
is here
Though to be honest, this code is super old and I can't say I'd recommend using it. Maybe good for just learning some network APIs, but I doubt any of this code is production-grade quality.
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
What is NetworkBuffer - is it a class outside this code file?