Autor Beitrag
PetziBaer
Hält's aus hier
Beiträge: 4



BeitragVerfasst: Fr 18.11.16 14:00 
Hallo, ich habe folgendes Anliegen:

Ich programmiere zur Zeit einen Messenger zum senden von Text bzw Daten.
Der Austausch zwischen Server und Client funktioniert zum Teil. Texte können bereits gesendet werden. Sowohl privat als auch öffentlich.
Für das senden habe ich mir ein kleines Protokoll angelegt, damit der Server die Daten verwalten kann.

Senden von Text:

Serverseite
ausblenden C#-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
private void SendTelegram(TcpClient receiverName, string telegram)
        {
            try
            {
                NetworkStream stream = receiverName.GetStream();
                StreamWriter writer = new StreamWriter(stream);
                stream.Flush();
                writer.WriteLine(telegram);
                writer.Flush();
            }
            catch (Exception)
            {
                if (allClients.ContainsKey(receiverName))
                {
                    allClients.Remove(receiverName);
                }
            }
        }


Das telegram sieht dann beispielsweise so aus:
SND_PRV|User1|Nachricht

Als erstes wird das Comand angeführt, gefolgt vom Empfänger und im anschluss die eigentliche Nachricht.

Empfangen der Nachricht
Client:

ausblenden 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:
private void ParseMessage()
        {
            while (isReading)
            {
                try
                {
                    NetworkStream stream = client.GetStream();
                    StreamReader reader = new StreamReader(stream);
                    string telegram = reader.ReadLine();

                    if (!String.IsNullOrEmpty(telegram))
                    {
                        string[] message = telegram.Split('|');
                        string command = message[0];

                        switch(command)
                        {
                            case "MSG_PRV":
                                {
                                    writeConsole("--> " + message[1] + ": " + message [2]);

                                    break;
                                }
                            case "MSG_PUB":
                                {

                                ...


So funtioniert das senden und Empfangen von Nachrichten in beide Richtungen. Nun soll das Programm erweitert werden, um Dateien zu versenden.
Jetzt stehe ich aber vor folgendem Problem.

Zum senden einer Datei würde ich einen Filestream nutzen. Die Datei wird in den Stream geladen und verschickt. Nun betshet das Problem das ich kein Comand verschicken würde. Daher dachte ich mir, es könnte funktionieren, wenn ich die Datei in ein Byte-Array aufteile. Das Array soll dann erweitert werden, damit ich ein Comand vor die Datei-Daten hängen kann.
Wird dieses Byte-Array nun so verschickt, werden beim Senden mehrere Pakete geschickt aber nur im ersten steht das Comand. Also haben alle weiteren Pakete kein Comand und der Client kann die Pakete nicht zuordnen.
Nun kam mir die Idee ein Flag zu setzen, welches aussagt, dass solange Packete von der Datei kommen immer wieder in ein Case für Dateien springt. Sobald alle Pakete empfangen wurden soll das Flag zurück gesetzt werden und der Client kann wieder Text bzw. Dateien empfangen.
Ansatzweise funktionierte dies auch schon, aber die Daten, welche empfangen werden sind entweder unbrauchbar oder werden falsch interpretiert.

Senden von Dateien, Serverseite:
ausblenden 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:
private void SendFile(string data, TcpClient destination)
        {
            string command = "SND_FILE|";

            NetworkStream nwStream = destination.GetStream();
            FileStream fStream = File.OpenRead(data);

            byte[] bytes = new byte[fStream.Length];

            fStream.Read(bytes, 0, bytes.Length);

            byte[] cmdSize = new byte[command.Length];
            cmdSize = Encoding.ASCII.GetBytes(command);

            Array.Resize(ref bytes, (bytes.Length + cmdSize.Length));

            Buffer.BlockCopy(bytes, 0, bytes, command.Length, (int)fStream.Length);
            Buffer.BlockCopy(cmdSize, 0, bytes, 0, cmdSize.Length);

            nwStream.Write(bytes, 0, bytes.Length);

            writeConsole("Data send complete!");

            nwStream.Close();
            fStream.Close();
        }


Empfangen von Dateien, Client:

ausblenden volle Höhe 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:
private void ParseMessage()
        {
            while (isReading)
            {
                try
                {
                    NetworkStream stream = client.GetStream();
                    StreamReader reader = new StreamReader(stream);
                    string telegram = reader.ReadLine();

                    if (!String.IsNullOrEmpty(telegram))
                    {
                        string[] message = telegram.Split('|');
                        string command = message[0];

                        if (getFile)
                        {
                            command = "SND_FILE";
                        }

                        switch(command)
                        {

                            .....

                            case "SND_FILE":
                                {
                                    if (!getFile)
                                    {
                                        //Sobald das erste Datenpacket einer Datei kommt wird das Flag gesetzt
                                        writeConsole("Getting file!");
                                        getFile = true;
                                        
                                        fs = new FileStream(pathToSave, FileMode.Create);
                                        fileData = new byte[1000];
                                    }
                                    fileData = Encoding.ASCII.GetBytes(telegram);

                                    fs.Write(fileData, 0, fileData.Length);
                                    break;
                               }
                           default:
                               {
                                    writeConsole("Unknown comand!");
                               } 
                        }
                else
                    {
                        //writeConsole("Filestream closed!");
                        getFile = false;
                        fs.Close();
                    }


Es wäre nett wenn hier Vorschläge gegeben werden, wie ich das Problem lösen kann. Ich weis nicht ob der Ansatz überhaupt richtig ist oder totaler Käse. Da ich dabei bin mich in dieses Thema eigenständig ein zu lesen gibt es durchaus viele Dinge mit denen ich mir vieles erleichtern könnte oder welche sinnvoller sind. Aber ich kann es mir auch nicht aus den Fingern saugen.

Vielen Dank
Gruß
Frühlingsrolle
Ehemaliges Mitglied
Erhaltene Danke: 1



BeitragVerfasst: Fr 18.11.16 14:28 
- Nachträglich durch die Entwickler-Ecke gelöscht -
Th69
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starontopic star
Moderator
Beiträge: 4764
Erhaltene Danke: 1052

Win10
C#, C++ (VS 2017/19/22)
BeitragVerfasst: Fr 18.11.16 14:54 
Hallo und :welcome:,

dein Fehler liegt bei
ausblenden C#-Quelltext
1:
Encoding.ASCII.GetBytes(telegram);					

ASCII ist nur im Bereich von 0-127 definiert (der Rest liefert '?').
Benutze besser Encoding.Default oder Encoding.Utf8 (fürs Senden und Empfangen)!

Soll es bei den Dateien denn nur um Textdateien gehen oder aber um beliebige (also auch Binärdateien)?
Dateien solltest du generell immer 1:1 als byte[] übertragen und interpretieren, niemals als String! Dafür kannst du dann die BinaryReader-Klasse benutzen.

Ansonsten wäre noch Base64 eine Möglichkeit, um die Binärdaten (wie bei einer E-Mail auch) zu übertragen: Convert.ToBase64String.
PetziBaer Threadstarter
Hält's aus hier
Beiträge: 4



BeitragVerfasst: Fr 18.11.16 15:10 
user profile iconFrühlingsrolle hat folgendes geschrieben Zum zitierten Posting springen:

schau mal, ob dich dieses Beispiel weiter bringt: Sending-Files-using-TCP.


Über diese Seite bin ich bereits gestolpert. Konnte leider nicht viel damit anfangen, weil ich noch das Problem habe, dass meine Texte anders versendet werden als die Files und zur Zeit da der Knackpunkt ist. Würde ich einfach das ankommende Datenpaket empfangen und mit einem Filestream auseinander nehmen würde es ja funktionieren, aber das senden von Texten ginge mir damit wieder zerschossen.

Aber trotzdem danke.


user profile iconTh69 hat folgendes geschrieben Zum zitierten Posting springen:


Soll es bei den Dateien denn nur um Textdateien gehen oder aber um beliebige (also auch Binärdateien)?


Nein, es sollen für den Anfang beliebige Dateien gesendet werden können. Also auch Bilder, Musik, etc. Später soll dies evtl. noch eingeschränkt werden.

user profile iconTh69 hat folgendes geschrieben Zum zitierten Posting springen:

Dateien solltest du generell immer 1:1 als byte[] übertragen und interpretieren, niemals als String!


Soll das jetzt heißen, dass mein Comand darin stört oder was ist hier genau gemeint?

Wichtig ist halt, dass der Client bzw. später auch der Server weiß, was gerate kommt, damit die Case-Anweisung richtig abgearbeitet werden kann.
Th69
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starontopic star
Moderator
Beiträge: 4764
Erhaltene Danke: 1052

Win10
C#, C++ (VS 2017/19/22)
BeitragVerfasst: Fr 18.11.16 15:50 
Du sollst nur nicht die ganzen Daten per String einlesen sondern nur den Header - und den Rest dann binär.
Nimm dazu einfach
ausblenden Quelltext
1:
2:
SND_PRV|User1|
Nachricht

(also einen Zeilenumbruch nach dem Header).
Bisher liest du ja mittels reader.ReadLine() schon einen Teil der Binärdaten ein.
PetziBaer Threadstarter
Hält's aus hier
Beiträge: 4



BeitragVerfasst: Fr 18.11.16 17:10 
user profile iconTh69 hat folgendes geschrieben Zum zitierten Posting springen:
Du sollst nur nicht die ganzen Daten per String einlesen sondern nur den Header - und den Rest dann binär.
(also einen Zeilenumbruch nach dem Header).
Bisher liest du ja mittels reader.ReadLine() schon einen Teil der Binärdaten ein.


Das ist ein sehr guter Hinweis. Das werde ich später gleich mal ausprobieren. Vielen Dank erstmal, ich melde mich sobal ich nicht mehr weiter komme ;)
PetziBaer Threadstarter
Hält's aus hier
Beiträge: 4



BeitragVerfasst: Mo 21.11.16 15:09 
Ich bräuchte noch etwas Hilfe bezüglich der Datei die versendet werden soll.
Als Header schicke ich nun einfach meinen Comand. Der Client empfängt dies und setzt sein Flag. So weiß der Client, dass als nächstes eine Datei kommt.

Nun mache ich aber noch einen Fehler beim Senden oder aber Empfangen der Datei.
Die Datei die ich sende, ein Bild, ist ursprünglich 700kB groß. Die "empfangene" Datei welche erzeugt wird aber nur 200kB.

Hier nochmal der Code zum senden:

ausblenden C#-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
private void SendFile(string data, TcpClient destination)
        {
            string command = "SND_FILE|";

            NetworkStream nwStream = destination.GetStream();
            FileStream fStream = File.OpenRead(data);

            byte[] bytes = new byte[fStream.Length];

            fStream.Read(bytes, 0, bytes.Length);

            byte[] cmd = new byte[command.Length];
            cmd = Encoding.Default.GetBytes(command);

            nwStream.Write(cmd, 0, cmd.Length);
            nwStream.Write(bytes, 0, bytes.Length);

            writeConsole("Data send complete!");

            nwStream.Close();
            fStream.Close();
        }


Und so empfange ich die Daten:

ausblenden 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:
case "SND_FILE":
{
    if (!getFile)
        {
            //Sobald das erste Datenpacket einer Datei kommt wird das Flag gesetzt
            writeConsole("Getting file!");

            //Flag setzen
            getFile = true;
                                        
            fs = new FileStream(pathToSave, FileMode.Create);
            fileData = new byte[1000];

            //case schließen, da nur der "SND_FILE"-Comand gesendet wurde
            break;
        }
    
    //Gesendete daten encoden und mit dem Filestream speichern
    //Im telegram stehen nurnoch die reinen Daten der Datei, der Headder wurde bereits ausgelesen.
    fileData = Encoding.Default.GetBytes(telegram);

    fs.Write(fileData, 0, fileData.Length);
    break;
}


Wäre super wenn mir da nochmal jemand weiterhelfen könnte.
Th69
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starontopic star
Moderator
Beiträge: 4764
Erhaltene Danke: 1052

Win10
C#, C++ (VS 2017/19/22)
BeitragVerfasst: Mo 21.11.16 16:02 
Hallo,

ich hatte dir doch schon geschrieben, daß du die Binärdaten wieder direkt als byte[] auslesen sollst, ohne den Umweg über String, also lösche diese Zeile:
ausblenden C#-Quelltext
1:
fileData = Encoding.Default.GetBytes(telegram);					

Nach dem Auslesen des Headers (als String) erzeuge einen BinaryReader auf dem Stream:
ausblenden C#-Quelltext
1:
using (BinaryReader reader = new BinaryReader(stream))					

und lese daraus dann die Daten (per ReadBytes - s. z.B. An elegant way to consume (all bytes of a) BinaryReader?).

Du wechselst also einfach den Auslesemodus auf dem NetworkStream (der StreamReader kann nur auf String mit einem definierten Encoding arbeiten, nicht mit Binärdaten).

PS: Du kannst auch direkt auf dem NetworkStream mittels der Read-Methode die Daten auslesen (der BinaryReader hat jedoch noch ein paar andere nette Methoden für Binärdaten).