PDA

View Full Version : [C#]PROBLEMA CON SEMPLICE CHAT


SUPERALEX
26-05-2009, 17:26
Sto facendo una chat client-server facile ovvero server ascolta client si connette iniziano a parlare..è una cosa single threaded quindi in teoria una volta che il client si connette il server può anche smettere di ascoltare x altri..solo che ho 2 domande primo perchè quando avvio il server che si mette in ascolto dopo un pò si blocca e viene fuori che l'applicazione nn risponde anche se ho copiato la procedura standard da altri progetti che nn si bloccano..poi una volta che il client è connesso mi chiedevo come fare x far si che il server sia sempre pronto a ricevere i messaggi che gli arrivano..pensavo a un ciclo infinito che continua a invocare la funzione che guarda se ci sono dati disponibili ma nn mi sembra una bella cosa..cmq è fatto così:
private void StartServer(int porta)
{
try
{

this.eventi.AppendText("Avvio server\n");

ip = IPAddress.Parse("192.168.0.2");


listener = new TcpListener(ip, porta);
listener.Start();
while (true)
{
client = listener.AcceptTcpClient();
this.eventi.AppendText("Client connesso!!\n");
chat();
break;
}

private void chat()
{
this.stream = client.GetStream();
int n=0;
while (true)
{
if (stream.DataAvailable)
{
recMessage(n);
n = 1;
}
}
}

nn guardate come è scritto formalmente perchè ho fatto varie prove anche stupide x vedere se risolvevo..il bello è che quando clicco per avviarlo nn vengono fuori le scritte degli eventi avvio server ecc mentre alcune volte sono uscite:confused:

!k-0t1c!
26-05-2009, 18:17
Se posti il codice completo posso darti una mano, ad occhio e croce ti dico che sicuramente:
1. manca il catch per il try in StartServer
2. Non ha senso mettere while(true) se all'interno c'è un break eseguito incondizionatamente
3. Se è tutto su un solo thread allora è logico che si blocchi perché non processi gli eventi generati dall'utente, mentre se è su più threads devi controllare di effettuare il lock di tutto quel che è condiviso e può essere sia letto sia scritto altrimenti il tuo codice non è thread safe, devi verificare che non ci siano race conditions o deadlocks.

SUPERALEX
26-05-2009, 19:56
il catch c'è nn lo messo perchè era inutile la parte incriminata è quella so anche che il while è inutile ma avevo provato senza allora l'ho messo così avevo detto di nn guardare il codice in se x se..cmq è a singolo thread proprio perchè c'è un solo client che si connette e col quale parla il form2 è il client
public partial class Form1 : Form
{
private TcpListener listener;
private IPAddress ip;
NetworkStream stream;
String nick = null;
TcpClient client;

public Form1()
{
InitializeComponent();


}

private void Form1_Load(object sender, EventArgs e)
{
Form form2 = new Form2();
form2.Show();



}



private void StartServer(int porta)
{
try
{

this.eventi.AppendText("Avvio server\n");
/*ip = Dns.GetHostEntry("LocalHost").AddressList[0];*/
ip = IPAddress.Parse("192.168.0.2");


listener = new TcpListener(ip, porta);
listener.Start();
while (true)
{
client = listener.AcceptTcpClient();
this.eventi.AppendText("Client connesso!!\n");
chat();
break;
}

}
catch (Exception e)
{
MessageBox.Show("errore");
}


}

private void chat()
{
this.stream = client.GetStream();
int n=0;
while (true)
{
if (stream.DataAvailable)
{
recMessage(n);
n = 1;
}
}
}

private void reset()
{
this.eventi.AppendText("Server fermato\n");

}

private void stopServer()
{
stream.Close();
client.Close();
listener.Stop();

}

private void showInfo()
{
MessageBox.Show("Inserire un numero compreso tra 1024 e 65536");
}

private void recMessage(int n)
{
if (stream.CanRead)
{

byte[] readBuffer = new byte[1024];
StringBuilder completeMessage = new StringBuilder();
int num = 0;

// Incoming message may be larger than the buffer size.
do
{
num = stream.Read(readBuffer, 0, readBuffer.Length);
if (num == 0) return;
completeMessage.AppendFormat("{0}", Encoding.ASCII.GetString(readBuffer, 0, num));
} while (stream.DataAvailable);
if (String.Compare(completeMessage.ToString(), "close") == 0) stopServer();

if (n == 0)
{
nick = completeMessage.ToString();
lista.AppendText("Il client è " + nick);
}
else view.AppendText("" + nick + ": "+ completeMessage);

}




}

private void sendMessage()
{
byte[] writebuffer;
String mex;
writebuffer = Encoding.ASCII.GetBytes(testo.Text);
if (writebuffer.Length == 0) throw new Exception();

stream.Write(writebuffer, 0, writebuffer.Length);

view.AppendText("\nServer: " + testo.Text);
testo.Clear();

}

private void l(object sender, MouseEventArgs e)
{

}

private void start_Click(object sender, EventArgs e)
{

try
{

int port;
port = Int32.Parse(porta.Text);
if (port < 1024 || port > 65536) throw new Exception();
this.eventi.AppendText("Porta accettata\n");
StartServer(port);
}
catch (Exception ex)
{
MessageBox.Show("Numero di porta errato!!Inserire un numero intero compreso tra 1024 e 65536");
reset();

}
}

private void clear_Click(object sender, EventArgs e)
{
testo.Clear();
}

private void finish_Click(object sender, EventArgs e)
{
stopServer();
}





}

!k-0t1c!
26-05-2009, 23:21
Considerando che mantieni la connessione aperta per tutta la durata della chat e che si tratta di TCP, l'uso che fai dello stream è terribile.
Innanzitutto devi definire se è consentito mandare due messaggi di seguito; in secondo luogo definisci una lunghezza massima del messaggio. Se ciascuno manda un solo messaggio e aspetta la risposta basta fare stream.Read cercando di leggere la lunghezza massima del messaggio e controllare che non ritorni 0. Quando ritorna 0 la connessione è stata chiusa. A quel punto liberi le risorse ed esci. Quando ritorna un valore, è il numero di bytes letti, e te li gestisci come vuoi. Tutto sta comunque in un do/while dove la condizione è che l'ultima lettura non sia 0. Se vuoi lasciare la possibilità di mandare due messaggi di seguito allora la cosa più comoda e semplice è avere due threads (puoi condividere la connessione facilmente). Inoltre ricorda che stream.DataAvailable non ti garantisce affatto che tutti i dati inviati dal client siano già stati ricevuti, quindi per come stanno le cose su una rete reale potresti facilmente troncare la ricezione prematuramente.

SUPERALEX
27-05-2009, 11:59
uno può scrivere quanto vuole..ma senza multithread..quindi nn so come fare perchè sia sempre pronto a ricevere e contemporaneamente a mandare

!k-0t1c!
27-05-2009, 12:29
Senza multithreading lo scenario che hai in mente non esiste, non in maniera decente.

SUPERALEX
27-05-2009, 15:30
ok allora mettiamo che ognuno manda un messaggio alla volta..cmq deve stare sempre in ascolto perchè nn può sapere quando l'altro scriverà..e deve anche capire quando il messaggio è finito..

!k-0t1c!
27-05-2009, 15:37
Un messaggio alla volta, visto che si tratta di testo e non di dati, lo puoi gestire stabilendo una lunghezza massima del messaggio (arbitraria) e poi usando codice di questo genere:

var buf = new byte[LUNGHEZZA_MASSIMA_MESSAGGIO];
var letti = stream.Read(buf, 0, LUNGHEZZA_MASSIMA_MESSAGGIO);
if(letti != 0) //letti contiene la lunghezza del messaggio
{
//uso UTF8 arbitrariamente, puoi usare ASCII o Unicode o quel che preferisci
string messaggio = Encoding.UTF8.GetString(buf.Take(letti).ToArray()); //usiamo LINQ to Objects per evitare gli \0 finali nella string. si può fare anche diversamente ma così è più conciso.
//fai quel che vuoi con messaggio
}
else
{
//gestisci il caso in cui la connessione è stata chiusa
}

E dopo aver ricevuto il messaggio dovresti dar la possibilità all'utente di mandare il suo. Resta il fatto che senza multithreading, mentre il programma è in attesa di un messaggio se l'utente prova ad interagirci il sistema dirà all'utente che il programma non risponde etc etc.

SUPERALEX
27-05-2009, 19:17
ok ma questo deve essere in un ciclo infinito a ripetizione cioè continua a cercare di leggere finchè nn arrivano..ma se dopo che li ha ricevuti (e cmq continua a leggere il nulla)uno schiaccia il tasto x inviare li invia cmq no

!k-0t1c!
27-05-2009, 20:20
Il tuo ultimo post ha poco senso, cerca di esplicitare di più la sequenza temporale delle azioni e i soggetti. Ad ogni modo il codice di cui sopra è solo la parte di ricezione di un messaggio. Durante la sua esecuzione in un contesto a thread unico non è possibile interagire ulteriormente con il programma, ed in seguito all'esecuzione puoi integrare la parte che dovrebbe gestire l'invio e racchiudere tutto in un ciclo while con condizione che verifichi che la connessione è aperta. In questo modo hai ricezione-invio-ricezione-invio...

SUPERALEX
27-05-2009, 21:44
si ma l'invio dovrebbe avvenire ogni qualvolta uno schiaccia il pulsante invia nn quando lo decide il codice..è la ricezione che deve stare sempre all'erta

!k-0t1c!
27-05-2009, 23:23
Infatti, nessuno ti vieta di fare così. E la ricezione in un contesto a thread singolo non può "stare sempre all'erta" in maniera affidabile. Ad ogni modo ti consiglio di studiare qualcosa a livello di protocolli e di guardare qualche implementazione reale perché sembri avere le idee abbastanza confuse sulla materia e questa non mi pare la sede adatta per chiarirle.

SUPERALEX
30-05-2009, 11:24
ho provato con i thread che ne dici?l'unica cosa è che se avvio il form 1 nn mi visualizza il client nn so perchè mentre prima se lo facevo senza dargli un thread apposito appariva
namespace CHAT_FORM
{
public partial class Form1 : Form
{
private TcpListener listener;
private IPAddress ip;
NetworkStream stream;
String nick = null;
TcpClient client;
Thread recThread;
Thread sendThread;
Thread clientThread;

public Form1()
{
InitializeComponent();


}

private void Form1_Load(object sender, EventArgs e)
{
Form form2 = new Form2();
clientThread = new Thread(new ThreadStart(form2.Show));
clientThread.Start();





}



private void StartServer(int porta)
{
try
{

this.eventi.AppendText("Avvio server\n");

ip = IPAddress.Parse("127.0.0.1");


listener = new TcpListener(ip, porta);
listener.Start();
while (true)
{
client = listener.AcceptTcpClient();
this.eventi.AppendText("Client connesso!!\n");

recThread = new Thread(new ThreadStart(chat));
recThread.Start(client);
break;
}

}
catch (Exception e)
{
MessageBox.Show("errore");
}


}

private void chat()
{

this.stream = client.GetStream();
int n=0;
while (true)
{

recMessage(n,stream);
n = 1;

}
}

private void reset()
{
this.eventi.AppendText("Server fermato\n");

}

private void stopServer()
{
stream.Close();
client.Close();
listener.Stop();

}

private void showInfo()
{
MessageBox.Show("Inserire un numero compreso tra 1024 e 65536");
}

private void recMessage(int n,NetworkStream stream)
{
if (stream.CanRead)
{

byte[] readBuffer = new byte[1024];
StringBuilder completeMessage = new StringBuilder();
int num = 0;

// Incoming message may be larger than the buffer size.
do
{
num = stream.Read(readBuffer, 0, readBuffer.Length);

completeMessage.AppendFormat("{0}", Encoding.ASCII.GetString(readBuffer, 0, num));
} while (num>0);
if (String.Compare(completeMessage.ToString(), "close") == 0) stopServer();

if (n == 0)
{
nick = completeMessage.ToString();
lista.AppendText("Il client è " + nick);
}
else view.AppendText("" + nick + ": "+ completeMessage);

}




}

private void sendMessage()
{
byte[] writebuffer;
String mex;
writebuffer = Encoding.ASCII.GetBytes(testo.Text);
if (writebuffer.Length == 0) throw new Exception();

stream.Write(writebuffer, 0, writebuffer.Length);

view.AppendText("\nServer: " + testo.Text);
testo.Clear();

}

private void l(object sender, MouseEventArgs e)
{

}

private void start_Click(object sender, EventArgs e)
{

try
{

int port;
port = Int32.Parse(porta.Text);
if (port < 1024 || port > 65536) throw new Exception();
this.eventi.AppendText("Porta accettata\n");
StartServer(port);
}
catch (Exception ex)
{
MessageBox.Show("Numero di porta errato!!Inserire un numero intero compreso tra 1024 e 65536");
reset();

}
}

private void clear_Click(object sender, EventArgs e)
{
testo.Clear();
}

private void finish_Click(object sender, EventArgs e)
{
stopServer();
}

private void send_Click(object sender, EventArgs e)
{
sendThread = new Thread(new ThreadStart(sendMessage));
sendThread.Start();
do
{

if (!sendThread.IsAlive) sendThread.Abort();
} while (sendThread.IsAlive);
}





}
}

gugoXX
30-05-2009, 13:59
Per un problema del genere ti consiglierei di usare dei datagrammi UDP