Monday, August 4, 2008

Simple Socket Server


The idea of creating a good socket-server depends on several factors, which should surely be calculated in accordance to the requirements of the given task.
The most simple socket server exampl is the "One-Thread" server, which does all the job in its main thread. But this is not good enought for system, which should give a lot of time to each connected client to process.
Here come the threads. In this case, each client has its own (separete) thread, which in it works, and does its tasks as long as it need. Here is the main idea of most client-server applications.
The example project represents a simple Socket-Server, which keeps track of all connected clietns. Each client is represented by a ConnectionUser class instnace on the server. That class has a single "Run" method, which starts the processor of that client in a separete thread.
The project is a VS.Net 2005 Solution, but I have also included the source files in the post.


##########################################################################
############################SimpleServer.cs###############################

using System;
using System.Collections.Generic;
using System.Text;
using System.Net.Sockets;

namespace SimpleSocketServer
{
public class SimpleServer
{
///
/// The port, to be listened for connection requests
///

private int portToListen = -1;

///
/// The listener object, which through the clients should connect
///

private TcpListener listener;

///
/// Keeps the state of the server
///

private bool isStarted = false;

///
/// The list of connected users
///

private List users = new List();

///
/// Creates an instance of a SimpleServer class
///

public SimpleServer()
{

}

///
/// Creates an instance of a SimpleServer class configured for listening the given port
///

/// The port to be listened
public SimpleServer(int argPortToListen)
{
this.PortToListen = argPortToListen;
}

///
/// Starts the server on the pre-specified port.
/// If the port was not specified, throws an Exception
///

public void Start()
{
if (portToListen == -1)
{
throw new Exception("No port specified for the listener");
}

isStarted = true;
listener = new TcpListener(this.portToListen);
try
{
listener.Start();
while (isStarted)
{
try
{
//Get the TcpClient instance for the connection
TcpClient tmpNewClient = listener.AcceptTcpClient();

//Pass the retrieved object to the new processing unit
//that unit in a new threads
ConnectionUser tmpNewUser = new ConnectionUser(tmpNewClient);

tmpNewUser.ConnectionClosed += new EventHandler(ConnectionUser_ConnectionClosed);

lock (this.users)
{
//add the new user to the track list
this.users.Add(tmpNewUser);
}

//Start processing the client in a new thread
tmpNewUser.Run();
}
catch (Exception ex)
{

}
}
}
catch (Exception e)
{
//Handler code here
}
}

///
/// Handles the ConnectionClosed event of each connected user
///

/// The sender of the event
/// The event arguments
protected void ConnectionUser_ConnectionClosed(object sender, EventArgs e)
{
lock (this.users)
{
ConnectionUser tmpUser = sender as ConnectionUser;
tmpUser.ConnectionClosed -= new EventHandler(ConnectionUser_ConnectionClosed);
if (this.users.Remove(sender as ConnectionUser))
{
Console.WriteLine("Client disconnected");
}
}
}

///
/// Stops the server instance
///

public void Stop()
{
this.isStarted = false;
this.listener.Stop();

foreach (ConnectionUser tmpUser in users)
{
tmpUser.Stop();
}
}

///
/// Gets or sets the port
///

public int PortToListen
{
get
{
return this.portToListen;
}

set
{
if (value <= 0)
{
throw new ArgumentOutOfRangeException();
}

this.portToListen = value;
}
}
}
}


##########################################################################
###########################ConnectionUser.cs##############################
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading;
using System.Net.Sockets;
using System.IO;

namespace SimpleSocketServer
{
///
/// The class, which uses a single TcpClient
/// connection for communication with remote host
///

public class ConnectionUser
{
public event EventHandler ConnectionClosed;

protected TcpClient connection;
private bool isStarted = false;

public ConnectionUser(TcpClient argClient)
{
this.connection = argClient;
}

///
/// Starts a new Thread and runs current instance in that thread
///

public void Run()
{
Thread tmpNewThread = new Thread(new ThreadStart(StartProcessing));
tmpNewThread.Start();
}


///
/// The processor method, which keeps whole logic of the processor unit
/// Must be overriden in all descendant classes.
///

protected virtual void StartProcessing()
{
isStarted = true;
}

///
/// Gets the Network stream of the underlying connection
///

protected Stream NetworkStream
{
get
{
return connection.GetStream();
}
}


///
/// Stops the new-thread processor
///

public void Stop()
{
this.isStarted = false;
this.connection.Close();
}

///
/// Handles the connection close events
///

private void OnConnectionClose()
{
if (this.ConnectionClosed != null)
{
this.ConnectionClosed(this, null);
}
}
}
}

No comments: