Entwickler-Ecke

WinForms - C# Server und Client


JoKaBo - Do 22.08.13 14:51
Titel: C# Server und Client
Hallo,

seit Kurzen beschäftige ich mich in C# mit der Server Programmierung. Jetzt wo ich gerne mal das ganze in eine schöne Windows Form packet möchte klappt es leider nicht mehr so, wie ich es mir wünsche.

Das Programm Startet ganz normal. Dann Drücke ich auf den Start Button, worauf cmdStart_Click aufgerufen wird.

Nach dem das Passiert ist, tut sich in dem Programm Garnichts mehr. Das Programm wird weder von Windows versucht zu Beenden noch kommt eine Exeption / Fehler Meldung.

Ich hoffe ihr könnt mir da helfen, da ich nicht mehr weiter weiß.

Liebe Grüße und Danke im voraus.

JoKaBo

Code:


C#-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
34:
35:
36:
37:
38:
39:
40:
41:
42:
43:
44:
45:
46:
47:
48:
49:
50:
51:
52:
53:
54:
55:
56:
57:
58:
59:
60:
61:
62:
63:
64:
65:
66:
67:
68:
69:
70:
71:
72:
73:
74:
75:
76:
77:
78:
79:
80:
81:
82:
83:
84:
85:
86:
87:
88:
89:
90:
91:
92:
93:
94:
95:
96:
97:
98:
99:
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.Net.Sockets;
using System.Net;
using System.Threading;
using Extensions;


namespace ServerAndClientWinForms
{
    public partial class Server : Form
    {
        static TcpListener tcpListener;        
        private static NetworkStream networkStream;
        private static Socket socketForClient;

        public Server()
        {
            InitializeComponent();
        }

        private void Server_Load(object sender, EventArgs e)
        {
        }

        private void WaitForClients()
        {
        }

        private void cmdStart_Click(object sender, EventArgs e)
        {
            var ipAdress = IPAddress.Parse("127.0.0.1");
            int port = 9050;

            tcpListener = new TcpListener(ipAdress, port);

            tcpListener.Start();

            socketForClient = tcpListener.AcceptSocket();
            networkStream = new NetworkStream(socketForClient);    

           
            WriteConsole("Server wurde gestartet");
            WriteConsole("Server IP: " + ipAdress);
            WriteConsole("Server Port: " + port.ToString());

            int numberOfClientsYouNeedToConnect = 10;
            for (int i = 0; i < numberOfClientsYouNeedToConnect; i++)
            {
                Thread newThread = new Thread(new ThreadStart(Listeners));
                newThread.Start();
            }
            
        }

        private void cmdStop_Click(object sender, EventArgs e)
        {
            tcpListener.Stop();
            ///socketForClient.Disconnect();
        }

        private void Listeners()
        {
            if (socketForClient.Connected)
            {
                WriteConsole("Client:" + socketForClient.RemoteEndPoint + " connected to server.");
                
                System.IO.StreamWriter streamWriter = new System.IO.StreamWriter(networkStream);
                System.IO.StreamReader streamReader = new System.IO.StreamReader(networkStream);

                while (true)
                {
                    string theString = streamReader.ReadLine();
                    WriteConsole("Client " + socketForClient.RemoteEndPoint + "send a message: " + theString + "\n");
                    if (theString == "exit")
                        break;
                }

                streamReader.Close();
                networkStream.Close();
                streamWriter.Close();
            }
            socketForClient.Close();
            WriteConsole("Press any key to exit from server program");
            Console.ReadKey();
        }

        private void WriteConsole (string text) 
        {
            rtbConsole.Text += text + "\n";
        }
    }
}


Th69 - Do 22.08.13 16:27

Hallo,

tcpListener.AcceptSocket() blockiert solange kein Client sich angemeldet hat.
Da WinForms ereignisorientiert arbeitet, darfst du also nicht einfach einen Blocker einbauen.
Ähnlich wie du auch für die Clients einen eigenen Thread startest, mußt du dies auch für den Listener machen.

Aber du hast m.E. noch einen groben Fehler in deinem Programm. Du startest bisher 10 Threads, um mit einen ClientSocket zu kommunizieren. Du solltest eher den Listener in einer Schleife laufen lassen und für jeden ClientSocket einen eigenen Thread erzeugen.


JoKaBo - Do 22.08.13 17:20

Danke für die schnelle Antwort. Ich teste das gleich mal!

Zitat:
Aber du hast m.E. noch einen groben Fehler in deinem Programm. Du startest bisher 10 Threads, um mit einen ClientSocket zu kommunizieren. Du solltest eher den Listener in einer Schleife laufen lassen und für jeden ClientSocket einen eigenen Thread erzeugen.


Nein, später möchte ich das erweitern, so das aus einer Konfiguratuions Datei der die Anzahl der "Slotz" angegeben werden kann.

LG
JoKaBo

EDIT:

tcpListener.AcceptSocket() Mit was muss ich das den ersetzten, also was muss ich dann dahin machen. ?


Th69 - Fr 23.08.13 11:57

Hallo,

habe dein "Edit" jetzt erst gelesem.

Wie ich schon schrieb;
Th69 hat folgendes geschrieben:
Ähnlich wie du auch für die Clients einen eigenen Thread startest, mußt du dies auch für den Listener machen.

Also die Methode in einem eigenen Thread ausführen lassen:

C#-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
private void cmdStart_Click(object sender, EventArgs e)
{
   Thread newThread = new Thread(new ThreadStart(WaitForClients));
   newThread.Start(); 
}

private void WaitForClients()
{
   var ipAdress = IPAddress.Parse("127.0.0.1");
   int port = 9050;

   tcpListener = new TcpListener(ipAdress, port);

   tcpListener.Start();

   while (true// <- oder andere Laufzeitbedingung, z.B. Server.Connected
   {
     // hier nun lokale Variablen benutzen, anstatt 'static' !!!
     Socket socketForClient = tcpListener.AcceptSocket();
     NetworkStream networkStream = new NetworkStream(socketForClient);

     // Start Thread ... (nun am besten mit ParameterizedThreadStart)
   }
}


JoKaBo - Mo 26.08.13 19:47

Hey,

sorry das ich mich jetzt erst melde, hatte viel mit Arbeit und Schule zu tun.

Erstmal vielen Dank für die schnelle Antwort.

Leider bekomme ich jetzt einen anderen Fehler, und ich habe auch mit Googlen nichts gefunden. Vielleicht liegt es daran, wie ich Google.
Na ja, vielleicht könnt ihr mir dabei ja helfen. :)

Title: "InvalidOpertaionExeption wurde nicht behandelt".
Message: "Ungültiger threadübergreifender Vorgang: Der Zugriff auf das Steuerelement rtbConsole erfolgte von einem anderen Thread als dem Thread, für den es erstellt wurde."

Hier nochmal der ganze Code:


C#-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
34:
35:
36:
37:
38:
39:
40:
41:
42:
43:
44:
45:
46:
47:
48:
49:
50:
51:
52:
53:
54:
55:
56:
57:
58:
59:
60:
61:
62:
63:
64:
65:
66:
67:
68:
69:
70:
71:
72:
73:
74:
75:
76:
77:
78:
79:
80:
81:
82:
83:
84:
85:
86:
87:
88:
89:
90:
91:
92:
93:
94:
95:
96:
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.Net.Sockets;
using System.Net;
using System.Threading;
using Extensions;


namespace ServerAndClientWinForms
{
    public partial class Server : Form
    {
        static TcpListener tcpListener;
        private static NetworkStream networkStream;
        private static Socket socketForClient;

        public Server()
        {
            InitializeComponent();
        }

        private void Server_Load(object sender, EventArgs e)
        {
        }

        private void cmdStart_Click(object sender, EventArgs e)
        {
            Thread newThread = new Thread(new ThreadStart(WaitForClients));
            newThread.Start();
        }

        private void WaitForClients()
        {
            var ipAdress = IPAddress.Parse("127.0.0.1");
            int port = 9050;

            tcpListener = new TcpListener(ipAdress, port);

            tcpListener.Start();
            WriteConsole("Server Startet");

            WriteConsole("Server wurde gestartet. Server wurde mit IP: " + ipAdress.ToString() + "und dem Port: " + port.ToString() + "Gestartet");
            while (true// <- oder andere Laufzeitbedingung, z.B. Server.Connected
            {
                // hier nun lokale Variablen benutzen, anstatt 'static' !!!
                Socket socketForClient = tcpListener.AcceptSocket();
                NetworkStream networkStream = new NetworkStream(socketForClient);

                // Start Thread ... (nun am besten mit ParameterizedThreadStart)
            }
        }

        private void cmdStop_Click(object sender, EventArgs e)
        {
            tcpListener.Stop();
            ///socketForClient.Disconnect();
        }

        private void Listeners()
        {
            if (socketForClient.Connected)
            {
                WriteConsole("Client:" + socketForClient.RemoteEndPoint + " connected to server.");

                System.IO.StreamWriter streamWriter = new System.IO.StreamWriter(networkStream);
                System.IO.StreamReader streamReader = new System.IO.StreamReader(networkStream);

                while (true)
                {
                    string theString = streamReader.ReadLine();
                    WriteConsole("Client " + socketForClient.RemoteEndPoint + "send a message: " + theString + "\n");
                    if (theString == "exit")
                        break;
                }

                streamReader.Close();
                networkStream.Close();
                streamWriter.Close();
            }
            socketForClient.Close();
            WriteConsole("Press any key to exit from server program");
            Console.ReadKey();
        }

        private void WriteConsole(string text)
        {
            rtbConsole.Text += text + "\n";
        }
    }
}


Ich hoffe, das ihr mir dabei nochmal helfen könnt.

Lieben Gruß
JoKaBo


Ralf Jansen - Mo 26.08.13 19:58

Zumindest deine WriteConsole Methode greift auf die UI zu. Auf die UI darf man nur aus dem Hauptthread aus zugreifen. Heißt in der WriteConsole Methode musst du den Aufruf in den Hauptthread zurück synchronisieren. Dabei hilft die Invoke [http://msdn.microsoft.com/de-de/library/zyzhdc6b(v=vs.90).aspx]Methode des betroffenen Controls (oder der Form)


C#-Quelltext
1:
2:
3:
4:
5:
6:
7:
private void WriteConsole (string text) 
{
    if (rtbConsole.InvokeRequired)
       rtbConsole.Invoke(new Action(() => this.WriteConsole(text)));
    else
       rtbConsole.Text += text + "\n";
}


JoKaBo - Mo 26.08.13 20:13

Hey,

danke für die Antwort. Hier kommt folgender Fehler:


Quelltext
1:
"lambda-Ausdruck" kann nicht in den Typ "System.Delegate" konvertiert werden, da dies kein Delegattyp ist.                    


Lieben Gruß
JoKaBo


Ralf Jansen - Mo 26.08.13 20:33

Sorry. Hab den Code korrigiert.


JoKaBo - Mo 26.08.13 20:47

Hey,

ich danke euch beiden, endlich Klappt das!

Eine sache, würde ich gerne noch wissen.

Ich will den Server auch Stoppen können (Siehe cmdStop).

tcpListener.Stop(); Wenn ich das mache, kommt Logischer weise eine "SocketExeption": "Ein Blockierungsvorgang wurde durch einen Aufruf von WSACancelBlockingCall unterbrochen"

Wie mache ich das richtig ?

LG
JoKaBo


Ralf Jansen - Mo 26.08.13 21:06

Du versucht den im nebenläufigen Thread erzeugten TcpListener aus dem Hauptthread zu stoppen. Das ist ebenfalls nicht erlaubt das muss aus dem gleichen Thread erfolgen.

Close den Listener auch in der WaitForClients Methode. Am besten vielleicht in dem du den Listener in einen using Block kapselst. Und in deiner Stop Methode Abort(e) dann den Thread anstatt nur den Listener. War eh Mist wie das gerade läuft. Du hättest den Listener geschlossen aber den Thread weiterrotieren lassen.