Nessun risultato. Prova con un altro termine.
Guide
Notizie
Software
Tutorial
  • Lezione 59 di 59
  • livello principiante
Indice lezioni

Socket

Realizzare un semplice client per chat utilizzando i Socket di Silverlight
Realizzare un semplice client per chat utilizzando i Socket di Silverlight
Link copiato negli appunti

Per consentire un controllo più approfondito della rete, Silverlight fornisce un'implementazione managed dell'interfaccia di rete Socket, diversa a seconda dell'ambiente in cui gira l'applicazione, in Windows l'implementazione è basata sulle Windows Sockets (Winsock) mentre su Mac OS X è basata su Berkeley Software Distribution (BSD) UNIX.

L'implementazione è rappresentata dalla classe Socket contenuta nel namespace System.Net.Sockets dell'assembly System.Net, questo namespace contiene anche il resto delle classi necessarie per lo sviluppo di una applicazione basata su Socket.

Silverlight supporta sia IPv4 che IPv6 con TCP come canale di trasporto, a patto che sul computer locale sia abilitato il supporto per IPv4 e IPv6. Sempre a livello di trasporto UDP non è supportato.

Altra importante restrizione, cui fare attenzione con Silverlight, è quella sul numero di porta, gli unici numeri supportati sono quelli compresi nel range 4502 e 4534.

Un ruolo importante viene ricoperto anche dalla classe SocketAsyncEventArgs utilizzata per tutto il ciclo di vita della comunicazione via Socket, vediamo quali sono i passi da seguire per connettersi ad un ipotetico Chat Server raggiungibile sul solito dominio dell'applicazione alla porta 4530.

Il comportamento del Chat Server è molto semplice, accetta connessioni in ingresso sulla suddetta porta e notifica a tutti i client connessi il testo inviato da uno di essi. La nostra applicazione permetterà di inviare messaggi di testo al server e di visualizzare quelli ricevuti.

Tipicamente, ma non necessariamente, l'operazione di connessione con il server avviene all'avvio dell'applicazione, vediamo tutto il codice necessario, dopodiché lo commentiamo:

void Page_Loaded(object sender, RoutedEventArgs e)
{    
  endPoint = new DnsEndPoint(Application.Current.Host.Source.DnsSafeHost, 4530);
  
  socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
  
  SocketAsyncEventArgs args = new SocketAsyncEventArgs();
  args.UserToken = socket;
  args.RemoteEndPoint = endPoint;
  args.Completed += new EventHandler<SocketAsyncEventArgs>(OnSocketConnectCompleted);
  socket.ConnectAsync(args);
}

Anzitutto creiamo un'istanza di DnsEndPoint, che rappresenta gli estremi di collegamento al server: la proprietà Application.Current.Host.Source.DnsSafeHost restituisce il nome dell'host da cui è stata scaricata la pagina Web contenente l'applicazione Silverlight.

Il passo successivo è la creazione del Socket, dove possiamo scegliere il livello di internetworking (in questo IPv4).

Adesso tocca alla classe SocketAsyncEventArgs dove impostiamo l'EndPont ed il metodo di callback per l'evento Completed, scatenato al termine di ogni operazione eseguita sul Socket, che in questa fase rappresenta Connect Completed.

L'istanza della classe SocketAsyncEventArgs passerà attraverso tutte le fasi della trasmissione dati, perciò utilizziamo la proprietà UserToken come contenitore per l'istanza della classe Socket.

Per dare il via alla connessione non resta che invocare il metodo ConnectAsync della classe Socket passando l'istanza di SocketAsyncEventArgs. Se il metodo asincrono restituisce true, l'operazione di I/O è in sospeso, altrimenti sarà eseguita in maniera sincrona.

Al completamento dell'operazione verrà scatenato l'evento Completed sull'oggetto SocketAsyncEventArgs passato come parametro al metodo di callback, dove imposteremo le proprietà per le operazioni successive.

Ricezione dei dati

Vediamo le operazioni da eseguire nel metodo OnSocketConnectCompleted per ricevere dati server:

private void OnSocketConnectCompleted(object sender, SocketAsyncEventArgs e)
{
  Socket innerSocket = (Socket)e.UserToken;
  string data = string.Empty;
  if (!innerSocket.Connected)
  {
    data = "Connessione con il ChatServer fallita. n";
  }
  else
  {
    data = "Connessione effettuata con il ChatServer.";
    
    byte[] response = new byte[1024];
    e.SetBuffer(response, 0, response.Length);
    e.Completed -= new EventHandler<SocketAsyncEventArgs>(OnSocketConnectCompleted);
    e.Completed += new EventHandler<SocketAsyncEventArgs>(OnSocketReceive);                
    innerSocket.ReceiveAsync(e);
  }
  
  this.Dispatcher.BeginInvoke(new Action<string>(ShowReveicedText), new object[] { data });
}

In questo frammento di codice otteniamo l'istanza di Socket utilizzata per la connessione e verifichiamo che la connessione sia avvenuta, grazie alla proprietà Connected. In caso negativo informiamo l'utente con un apposito messaggio ed il ciclo conclude, altrimenti instaziamo un array di byte che fungerà da buffer e lo impostiamo nell'istanza della classe SocketAsyncEventArgs passata come parametro, rimuoviamo il metodo di callback per l'operazione di connessione completata ed impostiamo quello per l'operazione di ricezione completata, infine invochiamo il metodo ReceiveAsync utilizzando sempre la solita nell'istanza della classe SocketAsyncEventArgs.

A questo punto il meccanimo dovrebbe apparire più chiaro: ad ogni operazione asincrona invocata sul Socket sarà richiamato l'handler dell'evento Completed, spetta a noi sapere e/o capire in che fase siamo e reagire di conseguenza.

Un aiuto ci viene sicuramente fornito dalla proprietà LastOperation della classe SocketAsyncEventArgs, che è un enumeratore impostato sull'ultima operazione invocata sul Socket, i possibili valori sono:

  • None
  • Connect
  • Receive
  • Send

L'ultima riga di codice del frammento di codice non fa altro che visualizzare all'utente il messaggio di avvenuta o meno connessione sfruttando la classe Dispatcher dato che il metodo di callback gira in un Thread diverso da quello dell'interfaccia utente. Proseguiamo analizzando il metodo OnSocketReceive:

private void OnSocketReceive(object sender, SocketAsyncEventArgs e)
{
  string data = Encoding.UTF8.GetString(e.Buffer, e.Offset, e.BytesTransferred);
  this.Dispatcher.BeginInvoke(new Action<string>(ShowReveicedText), new object[] { data });
  Socket socket = (Socket)e.UserToken;
  socket.ReceiveAsync(e);
}

Tramite il metodo GetString della classe Encoding convertiamo l'array di byte in una strnga e con il solito meccanismo aggiorniamo l'interfaccia utente mostrando il messaggio ricevuto. Dopodiché ci rimettiamo in ricezione, quindi non dobbiamo far altro che ottenere la solita istanza di Socket ed invocare il metodo ReceiveAsync.

Invio dei dati

La parte di ricezione dati è conclusa, passiamo ad implementare la parte di invio dati al servser. L'invio avviene alla pressione di un Button da parte dell'utente, quindi la logica di invio sarà contenuta nell'handler dell'evento Click:

private void Button_Click(object sender, RoutedEventArgs e)
{
  if (socket.Connected)
  {
    byte[] data = Encoding.UTF8.GetBytes(NameTextBox.Text + ": " + InputTextBox.Text + ">");
    List> listData = new List>();
    listData.Add(new ArraySegment(data));
    
    InputTextBox.Text = string.Empty;
    
    sendEventArgs = new SocketAsyncEventArgs();
    sendEventArgs.RemoteEndPoint = endPoint;
    sendEventArgs.BufferList = listData;
    socket.SendAsync(sendEventArgs);
  }
  else
  {
    this.ChatConsole.Text += "Nessuna connessione con il ChatServer. n";
  }
} 

Come primo passo verifichiamo che il Socket sia connesso, se non lo è notifichiamo all'utente. Poi, sempre grazie alla classe Encoding, convertiamo il testo da inviare in un array di byte e lo incapsuliamo in una List di ArraySegment, come richiesto dalla proprietà BufferList della classe SocketAsyncEventArgs.

In questo caso dato che non dobbiamo effettuare nessuna operazione dopo l'invio generiamo e impostiamo una nuova istanza di SocketAsyncEventArgs senza callback per l'evento Completed ed adibita al solo invio dei dati. Infine invochiamo il metodo SendAsync della classe Socket.

Ti consigliamo anche