Vraag over Thread pool tcp server

Status
Niet open voor verdere reacties.

VoidLearn

Gebruiker
Lid geworden
29 sep 2011
Berichten
13
Hallo,

Ik ben bezig met een applicatie waar ik een TCP server in wil gebruiken.

Ik heb de code van een console applicatie omgezet naar een form applicatie.

De applicatie lijkt te werken (dit zie ik aan de trace) maar mijn form reageert niet meer.

Ik denk dat dit mogelijk kan komen omdat de server een endless while loop begint dat mogelijk op het main thread wordt uitgevoerd.

Server code:

Code:
class ServerThreadPool
  {
  //Declaration of the TcpListener
  private TcpListener client;

  //Constructor
  public ServerThreadPool()
  {
  IPEndPoint IpAdress = new IPEndPoint(IPAddress.Any, 8000);

  client = new TcpListener(IpAdress);
  client.Start();

  Trace.WriteLine("Waiting for clients...");

  while (true)
  {
  while (!client.Pending())
  {
  Thread.Sleep(1000);
  }

  ConnectionThread newconnection = new ConnectionThread();
  newconnection.threadListener = this.client;
  ThreadPool.QueueUserWorkItem(new WaitCallback(newconnection.HandleConnection));
  }
  }
  }


  class ConnectionThread
  {
  public TcpListener threadListener;
  private static int connections = 0;

  public void HandleConnection(object state)
  {
  int recv;
  byte[] data = new byte[1024];

  TcpClient client = threadListener.AcceptTcpClient();
  NetworkStream ns = client.GetStream();

  connections++;

  Trace.WriteLine(connections, "New client accepted: active connections");

  string welcome = "Lets get rocking :) ";

  data = Encoding.ASCII.GetBytes(welcome);
  ns.Write(data, 0, data.Length);

  while (true)
  {
  data = new byte[1024];
  recv = ns.Read(data, 0, data.Length);
  if (recv == 0)
  break;

  ns.Write(data, 0, recv);
  }

  ns.Close();
  client.Close();

  connections = connections - 1;

  Trace.WriteLine(connections, "Client disconnected: active connections");
  }
  }

In mijn form maar ik een server aan:

Code:
public partial class serverUI : Form
  {
  public serverUI()
  {
  InitializeComponent();
  }

  private void serverUI_Load(object sender, EventArgs e)
  {
  ServerThreadPool STP = new ServerThreadPool();

  }

  }

Ik kan aan de tracemeldingen zien dat de server clients accepteerd (2 in totaal) maar het form zelf reageert niet meer...

Is er een mogelijkheid dat ik de server op een eigen thread laat starten?

Graag jullie suggesties :)

Alvast bedankt.
 
Je gebruikt toch al een threaded server? :p

Je volgens mij de server gewoon in een thread zetten. Het is dan waarschijnlijk wel nodig een "start" methode toe te voegen aan de server, ipv alles te starten vanuit de constructor.

Thread oThread = new Thread(new ThreadStart(server.start));
oThread.Start();
 
Hoi Wampier,

Bedankt voor je reply :)

Ik heb de server enigszins aangepast en het idee van een threadpool laten vervallen.

Code:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net;
using System.Net.Sockets;
using System.Threading;
using System.Diagnostics;

namespace ExplorerTankProject
{
    class Server
    {
	
	// This server class is responsible for communication between clients.
	// Because only three clients connect to the server and for simplicity a few threads 
	// for every client is created instead of working with a threadpool.
	
        private TcpListener tcpListener;
        private Thread listenThread;
		
		int Connections;
		
		//Class Constructor
        public Server()
        {
            this.tcpListener = new TcpListener(IPAddress.Any, 8000);
            this.listenThread = new Thread(new ThreadStart(ListenForClients));
            
			this.listenThread.Start();

            Trace.WriteLine("Server is running :) ");
        }
		
		//Listens for new client connections. 
		//If client wants to connect, the client is started on a new thread and handled by the HandleClientConn.
        private void ListenForClients()
        {
            this.tcpListener.Start();

            while (true)
            {
                //blocks until a client has connected to the server
                TcpClient client = this.tcpListener.AcceptTcpClient();

                //create a thread to handle communication
                //with connected client
                Thread clientThread = new Thread(new ParameterizedThreadStart(HandleClientConn));
                clientThread.Start(client);
            }
        }

		//Handles a new client connection. For reading and writing purposes, two new threads are generated.
        private void HandleClientConn(object client)
        {
            TcpClient tcpClient = (TcpClient)client;
            NetworkStream clientStream = tcpClient.GetStream();

			//Number of connected Clients.
			connections++;
            Trace.WriteLine(connections, "New client accepted: active connections");
			
			//Client IP adress and port, used for client debugging.
            Trace.WriteLine(tcpClient.Client.RemoteEndPoint.ToString(), "HandleClientConn Endpoint:");

            byte[] message = new byte[256];
            int bytesRead;

			//Separate write thread
            Thread WriteThread = new Thread(new ParameterizedThreadStart(WriteData));
            WriteThread.Start(client);
			
			//Separate read thread
			Thread ReadThread = new Thread(new ParameterizedThreadStart(ReadData));
            ReadThread.Start(client);
			
			//Sends a message to the client to test the connection.
			byte[] buffer = ASCIIEncoding.ASCII.GetBytes("Hello Client!");
            clientStream.Write(buffer, 0, buffer.Length);
            clientStream.Flush();
        }

		//Handles the data that is send from the client to the server.
		private void ReadData(object client)
        {
            TcpClient tcpClient = (TcpClient)client;
            NetworkStream clientStream = tcpClient.GetStream();

            Trace.WriteLine("ReadThread created");

           int  bytesRead = 0;

		   while(true)
		   {
				//
                if (clientStream.DataAvailable == true || clientstream.canwrite == true)
                {
					//Client IP adress and port, used for client debugging.
					Trace.WriteLine(tcpClient.Client.RemoteEndPoint.ToString(), "ReadData Endpoint:");
					
                    try
                    {
                        //blocks until a client sends a message
                        bytesRead = clientStream.Read(message, 0, message.Length);
                        String EchoMsg;
                        EchoMsg = (ASCIIEncoding.ASCII.GetString(message));
                        Trace.WriteLine(EchoMsg);
                        EchoMsg = null;

                    }
                    catch (SocketException e)
                    {
                        //a socket error has occured
                        Trace.WriteLine(e.ToString());
						CheckClientConnectivity();
						break;
                    }
                }
				else
				{
				CheckClientConnectivity();
				break;
				}
            }
        }
		
		//Handles the data that is send from the server to the client.
        private void WriteData(object client)
        {

            TcpClient tcpClient = (TcpClient)client;
            NetworkStream clientStream = tcpClient.GetStream();

            Trace.WriteLine("WriteThread created");
  
			while(true)
			{
			
			//
			if (clientstream.canwrite == true || WriteBuffer.Length > 0)
			{
					//Client IP adress and port, used for client debugging.
					Trace.WriteLine(tcpClient.Client.RemoteEndPoint.ToString(), "WriteData Endpoint:");
					
                    try
                    {
                        clientStream.Write(WriteBuffer, 0, WriteBuffer.Length);
                        clientStream.Flush();
                    }
                    catch (SocketException e)
                    {
                        //a socket error has occured
                        Trace.WriteLine(e.ToString());
						CheckClientConnectivity();
						break;
                    }  
            }	
			else
			{
			CheckClientConnectivity();
			break;
			}
        }
		
		//Gets a byta array from outside the class to send to a client.
		public void Write(byte[] b)
		{
		 b = WriteBuffer;
		}	
		
		//Checks if a client is still connected prior to read or write action
		private void CheckClientConnectivity()
		{
			//Client IP adress and port, used for client debugging.
			Trace.WriteLine(tcpClient.Client.RemoteEndPoint.ToString(), "The following client is disconnected:" );
		
			clientStream.Close();
			tcpClient.Close();
			
			connection--;
			Trace.WriteLine(connections, "Current Active Client connections");
		}
    }
}

Het komt er op neer dat voor iedere cliënt die verbinding maakt een main, read and write thread wordt gemaakt.

Ik vraag me nu alleen af:

Bij iedere client die zich aanmeld wordt een thread gestart, hoe weet ik welke thread bij welke client hoort?

Als ik namelijk iets wil verzenden moet ik op een of andere manier een client ID meesturen ofzo? Is er iets van een thread ID wat ik mee moet sturen?

Alvast bedankt.
 
Je kunt dat op een aantal manieren doen. In principe is de basis dat je de "client" variabele die je aanmaakt kunt opslaan in een centrale opslag. Je kunt die eventueel combineren met een zelf aangemaakt ID. Je kunt bijvoorbeeld gebruik maken van een object list.

Indien je server dezelfde data uitzend naar alle clients kun je een callback gebruiken. Je kunt je client-objects ook de server laten pollen (komt eigenlijk op hetzelfde neer alleen de timing is anders).

Iets geavanceerder kun je ook de server->client thread bevriezen en activeren via het class object. Je kunt dan de data gesynchroniseerd uitsturen voor alle clients.

Gezien dat je zelf aangeeft dat het 3 threads zijn en je waarschijnlijk niet al te bang hoeft te zijn voor de server-load zou ik het niet al te moeilijk maken.
 
Hoi Wampier, bedankt voor je reply!

Ik ben vandaag bezig geweest met coderen en het een aantal object lists toegevoegd (ClientsConnected, ClientStreams en ClientNames).

Aangezien iedere client op een nieuwe thread wordt geopend wordt het object "client" naar de methode HandleClientConn meegestuurd,

Hier wordt vervolgens een TCP client aan het object toegekend:

Code:
TcpClient tcpClient = (TcpClient)client;

In de versie (zonder list) gaf dit geen problemen maar nu krijg ik een error:

Code:
Unable to cast object of type 'System.Collections.Generic.List`1[System.Net.Sockets.TcpClient]' to type 'System.Net.Sockets.TcpClient'.

Ik snap dat ik het doorgegeven object weer moet toekennen. Moet ik dit doen door deze weer op te slaan in een nieuwe list of zijn er ook andere mogelijkheden?

Groeten.

Volledige code:

Code:
class Server
    {

        // This server class is responsible for communication between clients.
        // Because only three clients connect to the server and for simplicity a few threads 
        // for every client is created instead of working with a threadpool.

        private TcpListener tcpListener;
        private Thread listenThread;

        List<TcpClient> ClientsConnected = new List<TcpClient>();
        List<NetworkStream> ClientStreams = new List<NetworkStream>();
        List<string> ClientNames = new List<string>();

        int Connections;

        //Class Constructor
        public Server()
        {
            this.tcpListener = new TcpListener(IPAddress.Any, 8000);
            this.listenThread = new Thread(new ThreadStart(ListenForClients));

            this.listenThread.Start();

            Trace.WriteLine("Server is running :) ");
        }

        //Listens for new client connections. 
        //If client wants to connect, the client is started on a new thread and handled by the HandleClientConn.
        private void ListenForClients()
        {
            this.tcpListener.Start();

            while (true)
            {
                //blocks until a client has connected to the server
                ClientsConnected.Add(this.tcpListener.AcceptTcpClient());

                //create a thread to handle communication
                //with connected client
                Thread clientThread = new Thread(new ParameterizedThreadStart(HandleClientConn));
                clientThread.Start(ClientsConnected);
            }
        }

        //Handles a new client connection. For reading and writing purposes, two new threads are generated.
        private void HandleClientConn(object client)
        {

            //TcpClient tcpClient = (TcpClient)client;

            ClientNames.Add(ClientsConnected[ClientsConnected.Count - 1].Client.RemoteEndPoint.ToString());
            ClientStreams.Add(ClientsConnected[ClientsConnected.Count - 1].GetStream());

            //Number of connected Clients.
            Connections++;
            Trace.WriteLine(Connections, "New client accepted: active connections");

            //Client IP adress and port, used for client debugging.
            Trace.WriteLine(ClientsConnected[ClientsConnected.Count - 1].Client.RemoteEndPoint.ToString(), "HandleClientConn Endpoint:");

            byte[] Message = new byte[256];

            //Separate write thread
            Thread WriteThread = new Thread(new ParameterizedThreadStart(WriteData));
            WriteThread.Start(client);

            //Separate read thread
            Thread ReadThread = new Thread(new ParameterizedThreadStart(ReadData));
            ReadThread.Start(client);

            //Sends a message to the client to test the connection.
            Trace.WriteLine(ClientsConnected[ClientsConnected.Count - 1].Client.RemoteEndPoint.ToString(), "Client Name");
            byte[] WriteBuffer = ASCIIEncoding.ASCII.GetBytes("Hello Client!");
            ClientStreams[ClientsConnected.Count - 1].Write(WriteBuffer, 0, WriteBuffer.Length);
            ClientStreams[ClientsConnected.Count - 1].Flush();
        }

        //Handles the data that is send from the client to the server.
        private void ReadData(object client)
        {
            //TcpClient tcpClient = (TcpClient)client;
            //NetworkStream clientStream = tcpClient.GetStream();

            Trace.WriteLine("ReadThread created");

            int bytesRead = 0;
            byte[] MessageRead = new byte[256]; 

            while (true)
            {
                //
                if (ClientStreams[ClientsConnected.Count - 1].DataAvailable == true && ClientStreams[ClientsConnected.Count - 1].CanWrite == true)
                {
                    //Client IP adress and port, used for client debugging.
                    Trace.WriteLine(ClientsConnected[ClientsConnected.Count - 1].Client.RemoteEndPoint.ToString(), "ReadData Endpoint:");

                    try
                    {
                        //blocks until a client sends a message
                        bytesRead = ClientStreams[ClientsConnected.Count - 1].Read(MessageRead, 0, MessageRead.Length);
                        String EchoMsg;
                        EchoMsg = (ASCIIEncoding.ASCII.GetString(MessageRead));
                        Trace.WriteLine(EchoMsg);
                        EchoMsg = null;

                    }
                    catch (SocketException e)
                    {
                        //a socket error has occured
                        Trace.WriteLine(e.ToString());
                        CheckClientConnectivity();
                        break;
                    }
                }
                else
                {
                    CheckClientConnectivity();
                    break;
                }
            }
        }

        //Handles the data that is send from the server to the client.
        private void WriteData(object client)
        {

            //TcpClient tcpClient = (TcpClient)client;
            //NetworkStream clientStream = tcpClient.GetStream();

            byte[] MessageWrite = new byte[0];

            Trace.WriteLine("WriteThread created");

            while (true)
            {

                //
                if (ClientStreams[ClientsConnected.Count - 1].CanWrite == true && MessageWrite.Length > 1)
                {
                    //Client IP adress and port, used for client debugging.
                    Trace.WriteLine(MessageWrite.Length.ToString(), "MessageWrite.Length");
                    Trace.WriteLine(ClientsConnected[ClientsConnected.Count - 1].Client.RemoteEndPoint.ToString(), "WriteData Endpoint");

                    try
                    {
                        ClientStreams[ClientsConnected.Count - 1].Write(MessageWrite, 0, MessageWrite.Length);
                        ClientStreams[ClientsConnected.Count - 1].Flush();
                    }
                    catch (SocketException e)
                    {
                        //a socket error has occured
                        Trace.WriteLine(e.ToString());
                        CheckClientConnectivity();
                        break;
                    }
                }
                else
                {
                    CheckClientConnectivity();
                    break;
                }
            }
        }

        //Gets a byta array from outside the class to send to a client.
        public void Write(byte[] b)
        {
            //b = MessageWrite;
        }

        //Checks if a client is still connected prior to read or write action
        private void CheckClientConnectivity()
        {
            //Client IP adress and port, used for client debugging.
            Trace.WriteLine(ClientsConnected[ClientsConnected.Count - 1].Client.RemoteEndPoint.ToString(), "The following client is disconnected:");

            ClientStreams[ClientsConnected.Count - 1].Close();
            ClientsConnected[ClientsConnected.Count - 1].Close();

            Connections--;
            Trace.WriteLine(Connections, "Current Active Client connections");
        }
    }
 
Ik krijg geen error. Er gaan wel een aantal andere dingen fout, maar geen compiler errors.

Je opzet is een beetje vreemd. De thread zelf heeft de informatie over zijn bestaan niet nodig. Intern kun je dus prima met de interne variabelen van je object werken. Het is dus niet nodig dat je threads intern met hun eigen ID gaan werken. Wat je nodig hebt is een manier om een client weer terug te vinden. Wat je nu doet is absoluut niet threadsafe. Indien er een client disconnect word keihard de laatste client uit de lijst verwijderd.

De bedoeling is nu juist dat de threads helemaal stand-alone werken. Indien een thread merkt dat zijn client disconnect moet er een update komen naar de centrale locatie, waar je master thread de client verwijderd uit de lijst en aan de hand daarvan de lijst bijwerkt.

Als je kijkt naar de connectionthread class uit je eerste server zie je dat de HandleConnection routine als thread meerdere keren zelfstandig kan draaien ten opzichte van de hoofdklasse.

Ik heb even je eerste server omgebouwd om te laten zien wat ik bedoel:

Code:
class ServerThreadPool
{
    //Declaration of the TcpListener
    private TcpListener client;

    //Constructor
    public ServerThreadPool()
    {
        IPEndPoint IpAdress = new IPEndPoint(IPAddress.Any, 8000);

        client = new TcpListener(IpAdress);
        client.Start();
        ConnectionThread newconnection = new ConnectionThread();
        newconnection.threadListener = this.client;

        Trace.WriteLine("Waiting for clients...");

        while (true)
        {
            while (!client.Pending())
            {
                Thread.Sleep(1000);
            }

            ThreadPool.QueueUserWorkItem(new WaitCallback(newconnection.HandleConnection));
        }
    }
}


class ConnectionThread
{
    public TcpListener threadListener;
    private static int connections = 0;
    List<object> tester = new List<object>();

    public void bcast(byte[] message)
    {
        foreach (TcpClient bcc in tester)
        {
            NetworkStream ns = bcc.GetStream();
            ns.Write(message, 0, message.Length);
        }
    }

    public void HandleConnection(object state)
    {
        int recv;
        byte[] data = new byte[1024];
        byte[] bbuffer = new byte[1024];

        TcpClient client = threadListener.AcceptTcpClient();
        NetworkStream ns = client.GetStream();

        connections++;

        tester.Add(client);

        Trace.WriteLine(connections, "New client accepted: active connections");

        string welcome = "Lets get rocking :) ";

        data = Encoding.ASCII.GetBytes(welcome);
        ns.Write(data, 0, data.Length);

        while (true)
        {
            data = new byte[1024];
            recv = ns.Read(data, 0, data.Length);
            if (recv == 0)
                break;

            //ns.Write(data, 0, recv);
            bcast(data);
        }

        ns.Close();
        client.Close();
        tester.Remove(client);
        connections = connections - 1;

        Trace.WriteLine(connections, "Client disconnected: active connections");
    }
}

je kunt nu 2 (of meer) telnet sessies starten naar de server. Iedereen krijgt te zien wat de ander intypt via de "bcast" functie die over de aangesloten clients loopt
 
Ok, ik snap je voorbeeld.

Het probleem is dat de server ook berichten naar een specifieke cliënt kan sturen.

Dus een textbox in een form en een listbox met beschikbare clients en een send button. Op deze manier kan ik de verschillende hardware specifieke taken laten uitvoeren. Een chatfunctie bcast is in mijn geval niet nodig

Ook dacht ik dat het beter was om een read en write thread te maken (ik wil namelijk niet dat het schrijven naar een cliënt niet mogelijk is omdat read de thread blocked.

Goed te zien dat je i.i.g. een class van de handleconnection heb gemaakt, dit was in namelijk ook van plan (leek mij efficiënter).

Zie hier een eerdere discussie op een ander forum over het project waar ik mee bezig ben:

http://www.c-sharpcorner.com/Forums/Thread/140803/difference-between-thread-pool-and-asynchronous-tcp-server.aspx

halverwege wordt het interessanter.

Ik ga vanavond weer verder met de server en zal gebruik maken van je input, bedankt.
 
Laatst bewerkt:
Het probleem is dat de server ook berichten naar een specifieke cliënt kan sturen.

Dat kan met dezelfde soort code, het enige wat je nodig hebt is het "object" van de andere client of een manier om dat object in de lijst te vinden.

In je eigen voorbeeld gebruik je 3 lists. Je kunt de relevante client vinden en dan het bijbehorende client object vinden in de object lijst.

Als je weet welk nr. in de lijst het object heeft, kun je zoiets doen:

Code:
public void scast(byte[] message, int clientnr)
    {
        TcpClient talkto = (TcpClient)tester[clientnr];   
        NetworkStream ns = talkto.GetStream();
        ns.Write(message, 0, message.Length);
        
    }

Je kunt ook een struct gebruiken en een enkele lijst maken met de gevulde struct. de struct bevat dan object-id, logische naam en andere informatie.

Overigens is mijn code inderdaad write-blocking, maar dat kun je voor grote servers dus verhelpen met een callback, writequeues of polling. Bijvoorbeeld: Je geeft in je object een queue mee, en beschrijft die queue apart voor elke thread. Vervolgens leest je client-thread zijn eigen queue. Overigens moet je onmenselijk snel zijn om met 3 clients een writeblock te veroorzaken, dus voor bijna alle practische gevallen is het voor een kleine server voldoende.
 
Ik krijg je voorbeeld niet werkend bij mij? Het formulier dat ik normaal gebruik wordt ook niet meer geladen?

Kan dit komen omdat dit in de constructor staat?

Code:
while (true)
        {
            while (!client.Pending())
            {
                Thread.Sleep(1000);
            }

            ThreadPool.QueueUserWorkItem(new WaitCallback(newconnection.HandleConnection));
        }
 
Laatst bewerkt:
start een nieuw c# console project. Drop mijn code onder de Main class en in de main class gebruik:

ServerThreadPool test = new ServerThreadPool();

Integreren met je form kan ik natuurlijk niet doen ;)
 
Het console programma werkt :)

Ik heb de hele avond zitten proberen ook maar iets in mijn form werkend te krijgen maar helaas..... Morgenavond ga ik weer verder.

Heb je nog nuttige tips voor de conversie van console naar form?

Cheers.
 
Ik heb nog een vraag:

Is het verstandig om in de constructor van een class een endless while loop te gebruiken? Op deze manier wordt het object toch nooit aangemaakt?

Ik zie namelijk wel in het voorbeeld staan?

Of gaat dit niet op voor een console program en wel voor een form? In het geval van een form applicatie wordt het form niet geladen (in mijn geval).

Cheers.
 
ik denk dat niet zo verstandig is.
Maar het hangt ook groten deels uit wat je in de endlessloop steekt.

in een contructor initializeer je enkel uw attributen.
Zo heb ik het altijd aangeleerd. ;)



Met vriendelijke groetjes, Jim
 
Laatst bewerkt:
Ik ben het even aan het proberen, maar tot nu toe lijkt het allemaal prima te werken in een form. Misschien kan ik later vanavond een compleet voorbeeld posten

Een loop in een constructor is niet netjes (heb ik ook niet in mijn versie), maar het is niets anders dan een andere subroutine.
 
Ik heb het ook werkend gekregen, heb me gisterenavond dood zitten staren op iets kleins.....

Ik denk dat het geheel aardig aan de praat heb gekregen:

Server code:
Code:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net;
using System.Net.Sockets;
using System.Threading;
using System.Diagnostics;

namespace ExplorerTankProject
{

    class ServerThreadPool
    {
        //Declaration of the TcpListener
        private TcpListener client;

        //Constructor
        public ServerThreadPool()
        {

        }

        public void Start()
        {
            IPEndPoint IpAdress = new IPEndPoint(IPAddress.Any, 8000);

            client = new TcpListener(IpAdress);
            client.Start();
            ConnectionThread newconnection = new ConnectionThread();
            newconnection.threadListener = this.client;

            Trace.WriteLine("Waiting for clients...");

            while (true)
            {
                while (!client.Pending())
                {
                    Thread.Sleep(1000);
                }

                ThreadPool.QueueUserWorkItem(new WaitCallback(newconnection.HandleConnection));
            }
        }
    }


    class ConnectionThread
    {
        public TcpListener threadListener;
        private static int connections = 0;

        public static List<TcpClient> ClientsConnected = new List<TcpClient>();
        public static List<NetworkStream> ClientStreams = new List<NetworkStream>();
        public static List<string> ClientNames = new List<string>();

        public void HandleConnection(object state)
        {
            int recv;
            byte[] data = new byte[1024];
            byte[] bbuffer = new byte[1024];
            int ClientID;

            String ReadData;

            ClientsConnected.Add(threadListener.AcceptTcpClient());
            ClientStreams.Add(ClientsConnected[ClientsConnected.Count - 1].GetStream());
            ClientNames.Add(ClientsConnected[ClientsConnected.Count - 1].Client.RemoteEndPoint.ToString());
            ClientID = ClientsConnected.Count - 1;

            connections++;

            Trace.WriteLine(connections, "New client accepted: active connections");
            Trace.WriteLine(ClientsConnected[ClientsConnected.Count - 1].Client.RemoteEndPoint.ToString(), "New client Endpoint");
            Trace.WriteLine(ClientID.ToString(), "New client ID");
            Trace.WriteLine(ClientsConnected.Count.ToString(), "ListIndex");
            GetClients(ClientNames);
            Trace.WriteLine("");

            while (true)
            {
                data = new byte[1024];

                try
                {
                    recv = ClientStreams[ClientID].Read(data, 0, data.Length);

                    ReadData = Encoding.ASCII.GetString(data);

                    Trace.WriteLine(ReadData.ToString());

                    if (recv == 0)
                        break;
                }
                catch (SocketException e)
                {
                    Trace.WriteLine(e.ToString(), "Socket error");
                }
                catch (Exception e)
                {
                    Trace.WriteLine(e.ToString(), "Error");
                }
            }

            try
            {
                Trace.WriteLine(ClientsConnected[ClientID].Client.RemoteEndPoint.ToString(), "Disconnected client Endpoint");
                ClientStreams[ClientID].Close();
                ClientsConnected[ClientID].Close();
                
                //ClientNames.RemoveAt(ClientID);
                //ClientStreams.RemoveAt(ClientID);
                //ClientsConnected.RemoveAt(ClientID);

                connections--;

                Trace.WriteLine(connections, "Client disconnected: active connections");
                GetClients(ClientNames);
                Trace.WriteLine("");
            }
            catch (SocketException e)
            {
                Trace.WriteLine(e.ToString(), "Socket error");
            }
            catch (Exception e)
            {
                Trace.WriteLine(e.ToString(), "Error");
            }
        }

        public void GetClients(List<string> t)
        {
            t.ForEach(PrintList);
        }

        private static void PrintList(object o)
        {
            Trace.WriteLine(o.ToString());
        }
    }

    class SendToClient
    {

        public void Send(byte[] b, string EP)
        {

            //string StringEP = EP.ToString();

           int ClientID = ConnectionThread.ClientNames.IndexOf(EP);

           TcpClient talkto = ConnectionThread.ClientsConnected[ClientID];
           NetworkStream ns = talkto.GetStream();
           ns.Write(b, 0, b.Length);

        }    
    }  
}

Trace to Txt (print de trace lines naar een textbox)

Code:
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Text;
using System.Windows.Forms;

namespace ExplorerTankProject
{
    public class TextBoxTraceListener : TraceListener
    {
        private TextBox _target;
        private StringSendDelegate _invokeWrite;

        public TextBoxTraceListener(TextBox target)
        {
            _target = target;
            _invokeWrite = new StringSendDelegate(SendString);
        }

        public override void Write(string message)
        {
            _target.Invoke(_invokeWrite, new object[] { message });
        }

        public override void WriteLine(string message)
        {
            _target.Invoke(_invokeWrite, new object[] { message + Environment.NewLine });
        }

        private delegate void StringSendDelegate(string message);
        private void SendString(string message)
        {
            // No need to lock text box as this function will only 

            // ever be executed from the UI thread

            _target.Text += message;
        }
    }
}

Form code:

Code:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Threading;
using System.Net;
using System.Net.Sockets;
using System.Diagnostics;

namespace ExplorerTankProject
{
    public partial class serverUI : Form
    {
        TextBoxTraceListener _textBoxListener;
        
        SendToClient GamePadSend = new SendToClient();

        public serverUI()
        {
            InitializeComponent();
        }

        private void serverUI_Load(object sender, EventArgs e)
        {

            TxtDiag.Multiline = true;
            TxtDiag.ScrollBars = ScrollBars.Both;
            TxtDiag.ReadOnly = true;

            _textBoxListener = new TextBoxTraceListener(TxtDiag);
            Trace.Listeners.Add(_textBoxListener);

            ServerThreadPool Server = new ServerThreadPool();

            Thread oThread = new Thread(new ThreadStart(Server.Start));
            oThread.Start();
        }

        private void btnSend_Click(object sender, EventArgs e)
        {

            string test = "Hallo, hier de server!!";

            byte[] send = Encoding.ASCII.GetBytes(test);

            string endPoint = txtSend.Text.ToString();

            GamePadSend.Send(send, endPoint);
        }
    }
}

:)

Graag jullie mening!
 
Het is een iets andere aanpak die je misschien ook wel interessant gaat vinden. Het is wat algemener in gebruik.

grote lijnen waren al uren af, maar ik heb me nu ingelezen in multithreaded calls en event handlers :p

Clients worden opgeslagen in een client-container. In principe kan deze klasse alles bevatten wat specifiek voor een bepaalde client van toepassing is. De server houdt een lijst bij van aangesloten client objecten en doet de centrale aansturing. Ook genereert de server events, zodat de rest van de applicatie weet dat er, bijvoorbeeld, een verandering is van het aantal aangesloten clients. De huidige software doet niets met data die de clients opsturen, maar dan kan naar smaak aangepast worden. indien er een disconnect is van een client wordt de server op de hoogte gebracht en die ruimt het oude object op.

server klasse
Code:
class SimpleServer //mijn server klasse
{
    int TCPPort; //welke poort de server op moet draaien
    TcpListener server; //de ontvangende poort.
    bool serverup = false; //server op laten komen als er geen failure is
    List<ClientContainer> ClientList = new List<ClientContainer>(); //lijst waarin alle clients objecten worden opgeslagen
    int connections = 0;

    public event EventHandler OnConnection;
    public event EventHandler OnDisconnect;
    
    public SimpleServer(int port) //poort waarop de server moet draaien opslaan
    {
        this.TCPPort = port;
    }

    public void start() //de "worker" thread van de server
    {
        try
        {
            IPEndPoint IpAdress = new IPEndPoint(IPAddress.Any, TCPPort);
            server = new TcpListener(IpAdress);
            server.Start();
            serverup = true;
            
        }
        catch
        {
            MessageBox.Show("Server may already be up or the selected port is not available", "failed to create server");
        }

        while (serverup) //overslaan indien aanmaken van een listener faalt, ook gebruikt om de server van buiten te stoppen.
        {
            
            while (!server.Pending())
            {
                Thread.Sleep(1000);
                if (!serverup)
                {
                    break;
                }
            }
            if (!serverup)
            {
                break;
            }
            HandleConnection(this.server); //handeld de nieuwe client verder af
        }
    }


    void HandleConnection(TcpListener input) //wijst een client object toe aan de clients.
    {
        connections++; //teller ophogen zodat elke client een unieke naam krijgt.

        ClientList.Add(new ClientContainer(input, "client"+connections.ToString(),this)); //nieuwe clientcontainer toevoegen en opslaan in de clientlist
        if (this.OnConnection != null)  this.OnConnection(this, new EventArgs()); //eventueel geïnteresseerden laten weten dat er een nieuwe client is
    }

    public void stop() //server uitzetten
    {

        for (int i = 0; i < ClientList.Count; i++) //door alle clients lopen
        {
            ClientList[i].Close(); //client opdracht geven te sluiten
            ClientList[i] = null; //client object weggooien
        }
        GC.Collect(); //rotzooi opruimen
        ClientList.Clear(); //lijst van aangesloten clients leegmaken
        serverup = false; //server thread stoppen
        server.Stop(); //TCPserver stoppen

    }

    public void disconnected(ClientContainer CC) //een client heeft zich afgemeld
    {
        int a = ClientList.IndexOf(CC); //client zoeken in de lijst
        ClientList[a] = null; //object weggooien
        ClientList.RemoveAt(a); //en uit de lijst verwijderen
        GC.Collect(); //rotzooi opruimen
        if (this.OnDisconnect != null) this.OnDisconnect(this, new EventArgs()); //eventueel geïnteresseerden laten weten dat er een client weg is
    }

    public string[] namelist() //lijst van namen van aangesloten clients teruggeven, voornamelijk voor het makkelijk vullen van de listbox
    {
        List<string> templist = new List<string>();
        foreach (ClientContainer CC in ClientList)
        {
            templist.Add(CC.Name);
        }
        return templist.ToArray();
    }

    public void TextToClient(string clientname, string text) //text naar een bij naam gespecificeerde client sturen
    {
        foreach (ClientContainer CC in ClientList)
        {
            if (CC.Name == clientname)
            {
                CC.Write(text);
            }
        }
    }

}

Clientscontainer klasse:

Code:
class ClientContainer //het object dat alle client gerelateerde zaken afhandeld, 1 per client
{
    TcpClient client;
    public string Name;
    Thread Reader;
    SimpleServer parent; //voor callbacks naar de server.

    public ClientContainer(TcpListener input, string FriendlyName, SimpleServer parent) //constructor
    {
        this.parent = parent;
        this.Name = FriendlyName;
        
        client = input.AcceptTcpClient();
        Reader = new Thread(new ThreadStart(ReadWorker));
        Reader.Start();
        
    }

    ~ClientContainer() //destructor
    {
        try
        {
            client.Close();
            Reader.Abort();
        }
        catch
        {
        }
    }

    public void ReadWorker() //de leesthread van de client. momenteel wordt er niets gedaan met de inkomende input
    {
        NetworkStream ns = client.GetStream();
        int recv;
        byte[] data = new byte[1024];
        data = Encoding.ASCII.GetBytes(Name); //deze twee regels zijn vooral om wat feedback te geven bij debuggen
        ns.Write(data, 0, data.Length); //eigenlijk is het netter om dat af te handelen via de write routine

        while (true)
        {
            data = new byte[1024];
            recv = ns.Read(data, 0, data.Length);
            if (recv == 0)
            {
                break;
            }
        }
        parent.disconnected(this); //server object vertellen dat de client gedisconnect is
        
    }

    public void Write(string message) //schrijven naar de client
    {
        NetworkStream ns = client.GetStream();
        byte[] data = new byte[1024];
        data = Encoding.ASCII.GetBytes(message);
        ns.Write(data, 0, data.Length);
    }


    public void Close() //connectie afsluiten
    {
        try
        {
            client.Close();
            Reader.Abort();
        }
        catch
        {
        }

    }
}

en mijn form:

3 buttons: 1 voor start server; 1 voor stop server en 1 voor send text
een listbox waarin je aangesloten clients kunt selecteren
een textbox waarin de tekst komt die je naar een client wil sturen.

Code:
namespace WindowsFormsApplication1
{
    public partial class Form1 : Form
    {
        Thread serverthread; //thread waarin de server gaat draaien
        SimpleServer MyServer; //referentie naar de server voor calls e.d.
        delegate void ListUpdate(); //delegate voor het Asynchroon updaten van de listbox
        
        public Form1()
        {
            InitializeComponent();
        }

        

        private void button1_Click(object sender, EventArgs e) //server start button
        {
            try
            {

                MyServer = new SimpleServer(8000); //nieuwe server aanmaken op poort 8000
                Thread TheServer = new Thread(new ThreadStart(MyServer.start)); //thread voor de server
                serverthread = TheServer; //opslaan, ***mogelijk kan dit weg*** REVIEW
                TheServer.Start(); //server starten
                MyServer.OnConnection += new EventHandler(MyServer_OnConnection); //event handler voor nieuwe connecties
                MyServer.OnDisconnect += new EventHandler(MyServer_OnDisconnect); //event handler voor disconnects
            }
            catch
            {
                MessageBox.Show("Server may already be up or the selected port is not available", "failed to create server");
            }

        }

        private void update_listbox() //listbox updaten met actieve clients
        {
            listBox1.Items.Clear();
            listBox1.Items.AddRange(MyServer.namelist());
        }

        void MyServer_OnDisconnect(object sender, EventArgs e)
        {
            listBox1.Invoke(new ListUpdate(this.update_listbox)); //listbox updaten als er een disconnect is
        }

        void MyServer_OnConnection(object sender, EventArgs e)
        {
            listBox1.Invoke(new ListUpdate(this.update_listbox)); //listbox updaten als en een connect is
        }

        private void button2_Click(object sender, EventArgs e) //server stoppen
        {
            if (MyServer != null)
            {
                MyServer.stop();
            }
            listBox1.Items.Clear();
        }

        private void Form1_FormClosing(object sender, FormClosingEventArgs e)
        {
            if (serverthread!=null) //server stoppen
            {
                MyServer.stop();
                serverthread.Abort();
            }
        }

        private void button3_Click(object sender, EventArgs e) //text naar een client sturen via textbox
        {
            if (listBox1.SelectedIndex > -1)
            {
                MyServer.TextToClient((string)listBox1.Items[listBox1.SelectedIndex], textBox1.Text);
                textBox1.Text = "";
            }
            else
            {
                MessageBox.Show("select client first");
            }

        }

    }
}
 
Laatst bewerkt:
Ik heb jammer genoeg nog geen tijd gehad om uitgebreid door de code te lopen, maar ik zie bij je client creatie:

ClientID = ClientsConnected.Count - 1;

Mogelijk zie ik iets over het hoofd, maar als je twee clients hebt, en client 1 disconnect en reconnect heb je volgens mij twee clients met ID 2

Voor de rest gebruiken we in grote lijnen dezelfde setup. Echter mijn OO insteek vind het gebruik van drie lijsten niet netjes als je ook 1 class kunt gebruiken :p

Voor mezelf heb ik de functie dus wat uitgebreid zodat er ook makkelijke interactie is met het form en de server class toch onafhankelijk draait van de andere functies. Hoewel ik redelijk wat met threads heb gewerkt was het toch weer een redelijk leermoment, vooral omdat form componenten dus niet thread-safe geschreven zijn (zucht :p ). Bij een console applicatie heb je die beperkingen een stuk minder. Nagenoeg alle "grotere" servers zijn geschreven als console met een eventuele besturing via een aparte client-aansluiting.
 
Ik heb zelf nog eens kritisch door mijn code heengelopen (in vergelijking met jouw code) en kom to de conclusie dat ik ongeveer hetzelfde idee in gedachten had maar mijn gebrek aan c# kennis het probleem is.

Ik heb mijn code deels herschreven en het begint nu aardig te werken. Het leuke is dat ik me meteen verder aan het verdiepen ben in delegates en events (mede door jouw voorbeeld ervan).

Ik wil je bij dezen direct bedanken voor alle hulp! Ik stel dit zeer op prijs :)
 
Het wordt nog veel leuker als je met asynchrone streams gaat werken ipv discrete input en output ;) . Hoewel ik niet verwacht dat dat nodig is voor je server zoals jij dat nu wilt.

Het moeilijkste met OO programmeren is echt alles schrijven waar het lokaal en zelfstandig kan draaien. Vervolgens moet je dan weer veel procedures schrijven om de mogelijkheden naar buiten weer open te zetten, e.d. Het voelt altijd extreem omslachtig aan, vooral als je nog aan het leren bent. Ook komen daar dan hulpmiddelen zoals delegates en events bij kijken, die eigenlijk weer een probleem oplossen dat OO programmeren zichzelf aan doet :) . Puristen zullen zeggen dat mijn voorbeeld ook nog niet OO genoeg is, maar het is niet alsof deze code in productie gaat :)

Overigens vind ik je code erg netjes. Ik denk dat het meer gewenning en je hoofd in de C# denkwijze persen is, dan beperkte taalkennis. Het is in ieder geval duidelijk dat je vaker geprogrammeerd hebt :) . Ik had hetzelfde probleem. Ik ben 20 jaar geleden met C gestart als hobby. Daarna in '95 langzaam met C++ begonnen (er waren weinig goede compilers voor windows onder de 400 gulden toen, dus die heb ik op internet "gevonden"). Met de introductie van C# nog jaren naar de taal gekeken als een slap aftreksel van de "hardcore" C taal en ook was de development environment dus nog gesloten en redelijk bewerkelijk.

Met de introductie van de gratis express versies toch maar eens gaan proberen. Daar programmeren voor mij nog steeds pure hobby is, ben ik toch langzaam overtuigd geraakt, omdat een hoop van de windows-specifieke dingen die ik vaak wil doen gewoon al ergens in een collectie zit en dat spaart mij veel tijd en denkwerk :p
 
Ik heb in het verleden een aantal applicatie's VB .NET gemaakt. Nadeel was dat ik geen gebruik maakte van classes ect... Hierdoor ontstonden er lange lappen tekst in de form class.

Aangezien ik ben begonnen met .NET Micro (Netduino Plus) leek het mij verstandig om me eens wat in C# te verdiepen. Ik heb een 21 daagse cursus gedaan (tijdens het woon - werk verkeer) en vandaar uit ben ik met deze applicatie begonnen.

Waar ik voorheen de neiging had om voorbeelden van internet over te nemen (om snel resultaat te hebben) denk ik het een en ander nu beter uit. Uit de praktijk is namelijk gebleken dat dit efficiënter werkt.

Nu loop ik bijvoorbeeld tegen cross threading aan (er worden namelijk form controls van andere threads aangeroepen), ik ben eerst begonnen met een korte literatuurstudie om het probleem structureel te tackelen i.p.v. bij ieder form control dat op een ander thread wordt aangeroepen een object.InvokeRequired ect... te gebruiken.

Ik merk dat ik hierdoor mijn code overzichtelijker en efficiënter op weet te bouwen.
 
Status
Niet open voor verdere reacties.
Terug
Bovenaan Onderaan