modulo 1 protocolli di trasporto

Transcript

modulo 1 protocolli di trasporto
MODULO 1
PROTOCOLLI DI TRASPORTO
Servizi di rete
TCP 7
UDP 21
HTTP
25
WebSocket
Client/Server
Modulo 2
TPSIT 5
2
46
50
79
um 1 di 434
SERVIZI DI RETE
INTRODUZIONE
La comunicazione tra PC avviene in base alle regole stabilite nei protocolli di trasmissione
che possono essere suddivisi nei seguenti tipi.
1. Protocolli applicativi: dipendono dallo specifico servizio disponibile in rete, si servono
congiuntamente delle funzioni svolte dai protocolli di trasporto TCP (Transmission
Control Protocol) e UDP (User Datagram Protocol) e dal protocollo di rete per trasferirsi
i loro messaggi e dati; per esempio HTTP (HyperText Transfer Protocol) definisce il
formato delle richieste e risposte sul web.
2. Protocolli di trasporto: si preoccupano di stabilire le regole per la trasmissione dei
singoli byte o bit; per esempio TCP prende in carico i messaggi HTTP e quelli degli altri
protocolli applicativi e gestisce le singole conversazioni tra i client e i server.
3. Protocollo di rete: il più comune è IP (Internet Protocol) che instrada all’host
destinatario i segmenti TCP, imbustati in pacchetti con il suo indirizzo logico.
4. Protocolli di accesso alla rete: è gestito dalle NIC (Network Interface Card) che
svolgono due funzioni.
4.1. Gestione del collegamento dati: prende i pacchetti e li incapsula in trame adatte
alla trasmissione sulla LAN (Local Area Network), per esempio Ethernet,
assegnando loro se serve (le linee seriali punto-punto non lo necessitano) un
indirizzo fisico dell’host destinatario.
4.2. Trasmissione fisica sulla rete: governa il modo in cui i bit sono rappresentati e
spediti sui mezzi fisici e interpretati dall’host ricevente.
PROTOCOLLI DI TRASPORTO
Quando le applicazioni, come FTP (File Transfer Protocol) o HTTP, hanno bisogno di
conferma della ricezione dei loro messaggi, usano TCP che funziona come le
raccomandate con ricevuta di ritorno.
TCP suddivide i dati in segmenti, li numera, li affida al protocollo IP e prende nota di quelli
trasmessi.
Se non riceve conferma entro un certo tempo, assume che si siano persi e rimanda solo
quelli non confermati.
Presso il ricevente, TCP riassembla i dati per la consegna.
Se invece non occorre conferma di ricevuta e l’importante è andare veloci, è meglio usare
UDP che è come una lettera normale.
UDP è preferibile nelle applicazioni di “streaming” video e audio e nei sistemi VoIP (Voice
over IP), dove le ritrasmissioni sono indesiderate in quanto peggiorerebbero le cose:
invece di offrire all’utente una breve interruzione dell’audio o del video, fermerebbero la
riproduzione in attesa dei segmenti ritrasmessi e l’interruzione sarebbe più lunga e
fastidiosa.
NUMERI DI PORTA
Ogni server ha un unico indirizzo ma gestisce molti servizi, per esempio server web, FTP
e posta SMTP (Simple Mail Transfer Protocol).
Di conseguenza, dovrebbe avere altrettanti server su altrettanti indirizzi diversi.
Per evitare un proliferare d’indirizzi inutili esistono le porte, in pratica numeri cui sono
associati i vari servizi.
Inoltre, per ricevere contemporaneamente dati eterogenei tra la stessa coppia di host, è
stato sviluppato il concetto di multiplazione di porte, grazie al quale ogni protocollo
applicativo utilizza una specifica porta, un numero di 16 bit (0-65535), per distinguere, a
TPSIT 5
um 2 di 434
livello di trasporto, i propri pacchetti in transito.
Sono concettualmente simili agli interni di un palazzo: una volta arrivato ad un certo
numero civico, indirizzo IP, il pacchetto è consegnato all’interno, la porta che il mittente ha
indicato nell’intestazione del pacchetto.
Sono divisi in tre gruppi e gestiti da ICANN (Internet Corporation for Assigned Names and
Numbers).
1. Well Known Ports: letteralmente porte ben note, da 0 a 1023 sono i servizi di rete più
diffusi, per esempio HTTP e SMTP, alcune eccezioni sono rappresentate da servizi
quali ICQ (I Seek You), Real Audio e alcuni servizi di proxying.
2. Registered Ports: da 1024 a 49152 (3/4 di 65536) possono essere usate sia come
porte sorgente scelte dai client sia per applicazioni registrate, per esempio IM (Instant
Messaging).
3. Private Ports: da 49152 a 65535, sono usate liberamente solo come porte sorgente.
Ogni tipo di comunicazione prevede sempre almeno due interlocutori che comunichino
sulla stessa porta contemporaneamente, altrimenti chi manda il messaggio troverebbe la
“porta chiusa” dell’altro e viceversa.
La comunicazione tra client/server deve quindi comprendere quattro valori numerici.
1. L’indirizzo IP del destinatario.
2. La porta di destinazione per consentire una risposta.
3. L’indirizzo IP del mittente.
4. La porta dell’applicazione che ha avviato la comunicazione.
Il numero di porta è contenuto in ogni segmento, in modo che i segmenti di una
conversazione tra due host siano sempre destinati agli stessi servizi che la stanno
generando.
In pratica, le porte sono due.
1. Porta destinazione: è assegnata dai client, per indicare quale servizio di un server è
richiesto; per esempio un server potrebbe offrire servizi web (80) e FTP (21).
2. Porta sorgente: è generata casualmente dal client, sopra 1024 e identifica la
conversazione lato client, tra le varie che esso può aprire verso diversi server.
Entrambi i numeri di porta si trovano nel segmento che è poi incapsulato in un pacchetto,
con gli indirizzi IP del mittente e del destinatario.
L’insieme di un indirizzo IP, della corrispondente porta e del protocollo TCP o UDP usato
prende il nome di socket.
I milioni di conversazioni contemporanee sul web sono mantenuti distinti da coppie di
socket.
Esempio.
Nella coppia indirizzo IP/porta TCP i due valori sono separati con i due punti.
Indirizzo IP = 132.125.18.35.
Porta TCP = 80.
Socket = 132.125.18.35:80.
TPSIT 5
um 3 di 434
Le porte TCP attive si controllano con il seguente comando.
C:\>netstat -a
Connessioni attive
Proto
Indirizzo locale
TCP
ubertini:smtp
TCP
ubertini:http
TCP
ubertini:epmap
TCP
ubertini:https
TCP
ubertini:microsoft-ds
TCP
ubertini:1025
TCP
ubertini:1027
TCP
ubertini:1029
Indirizzo esterno
0.0.0.0:0
0.0.0.0:0
0.0.0.0:0
0.0.0.0:0
0.0.0.0:0
0.0.0.0:0
0.0.0.0:0
0.0.0.0:0
Servizio
FTP Data
FTP Control
SSH (Secure SHell)
TELNET (TELetype on NETwork)
SMTP
DNS (Domain Name System)
TFTP (Trivial File Transfer Protocol)
finger
HTTP
POP3
RPC (Remote Procedure Call)
NNTP (Network News Transport Protocol)
NTP (Network Time Protocol)
IMAP (Internet Message Access Protocol)
News
SNMP (Simple Management Network Protocol)
SNMP (traps)
BGP (Border Gateway Protocol)
HTTPS (HTTP over Secure Socket Layer)
rlogin
rexec
talk
ntalk
RIP (Routing Information Protocol)
Socks
Open VPN (Virtual Private Network)
IPSec (IP Security)
L2TP (Layer Two Tunneling Protocol)
Open Windows
NFS (Network File System)
X11
TPSIT 5
Stato
LISTENING
LISTENING
LISTENING
LISTENING
LISTENING
LISTENING
LISTENING
LISTENING
Numero di Porta/Protocollo
20/TCP
21/TCP
22/TCP AND 22/UDP
23/TCP
25/TCP
53/TCP AND 53/UDP
69/UDP
79/TCP
80/TCP
110/TCP
111/UDP
119/TCP
123/TCP AND 123/UDP
143/TCP
144/TCP
161/UDP
162/UDP
179/TCP
443/TCP
513/TCP
514/TCP
517/TCP AND 517/UDP
518/TCP AND 518/UDP
520/UDP
1080/TCP
1194/TCP AND 1194/UDP
1293/TCP AND 1293/UDP
1701/UDP
2000/TCP AND 2000/UDP
2049/TCP
6000/TCP AND 6000/UDP
um 4 di 434
SOCKET
Letteralmente “presa”, “attacco”, è il modello più utilizzato nella realizzazione di una
comunicazione via rete tra dispositivi o tra più applicazioni all’interno dello stesso
dispositivo.
La prima volta questo meccanismo fu impiegato nella BSD (Berkeley Software
Distribution) dell’università della California a Berkeley.
Un socket è una porta di comunicazione, concettualmente simile ad una presa elettrica.
Qualsiasi cosa progettata per comunicare tramite il protocollo standard TCP/IP, può
collegarsi ad un socket e comunicare tramite questa porta di comunicazione, così come un
qualsiasi apparecchio elettrico alimentato con la corrente 220 V può collegarsi ad una
presa elettrica e sfruttare la tensione che la rete di distribuzione elettrica mette a
disposizione.
Nel mondo dei socket la rete di distribuzione è locale, geografica o Internet e invece
dell’elettricità, nella rete viaggiano pacchetti TCP/IP.
In questo modo i socket forniscono un’altra astrazione oltre a quella fornita da TCP/IP che
permette di far comunicare dispositivi diversi che utilizzano lo stesso protocollo.
In altre parole, i socket possono rappresentare il livello 6 del modello ISO/OSI
(International Standard Organization/Open Systems Interconnection) che nel TCP/IP è
assente.
Il TCP ha bisogno di sapere a quale porta sia destinato il pacchetto e da quale porta arrivi,
in modo tale che possa rispondere con un ACK (ACKnowledge) a pacchetto ricevuto.
In maniera analoga, un pacchetto del livello IP porta con sé l’indirizzo IP d’origine e quello
di destinazione.
Un socket è costituito dal concatenamento di un indirizzo IP (identifica il PC sulla
rete) con il numero di porta (identifica un’applicazione che risiede su quel PC).
Un socket identifica una coppia univoca composta da PC e applicazione, i cui
numeri sono separati da (:).
Un socket è un oggetto attraverso il quale un’applicazione che ne fa uso in modo specifico
invia o riceve dati attraverso la rete con altri socket che fanno uso dell’IPS (Internet
Protocol Suite).
I socket sono bidirezionali ossia consentono nello stesso momento un flusso di dati sia in
entrata sia in uscita.
Esistono due tipi di socket.
1. Stream sockets che consentono un flusso di dati continuo e illimitato con la garanzia
di evitare duplicazione di dati inviati.
TPSIT 5
um 5 di 434
2. Datagram sockets non garantiscono che l’ordine di ricezione dei dati sia lo stesso di
quello d’invio con il rischio di non poter evitare duplicazione di dati, in altre parole gli
stessi pacchetti di dati possono arrivare a destinazione più volte.
Un server utilizzerà in prevalenza passive socket (passivi), impiegati sulle macchine che
devono rimanere in attesa di richieste di connessione.
Quando un client richiede una connessione ad un server crea invece un active socket.
L’unica differenza fra i due è la maniera con cui sono gestiti dal S/W e dal SO (Sistema
Operativo).
Stabilita la connessione tra i due socket, lo scambio d’informazioni avviene in modo full
duplex utilizzando i metodi Send e Receive.
Winsock (Windows Sockets)
È una DLL (Dynamic Link Library), progettata nel 1991, usata da Windows.
Descrive le comunicazioni da e per lo stack TCP/IP, in pratica l’applicazione dice a
Winsock che cosa fare, queste istruzioni passano poi allo stack TCP/IP che, a sua volta, le
passa a Internet.
Il controllo Winsock consente di utilizzare i protocolli di trasmissione TCP e UDP.
INET (INtErnet Transfer)
Permette d’inviare e di recuperare file usando FTP, HTTP e comandi specifici per eseguire
operazioni per entrambi i protocolli.
Per esempio, cancellare e rinominare i file con i comandi DELETE e RENAME.
Supporta il trasferimento dei file sincrono e asincrono.
È invisibile in un form, come il controllo Timer; tutte le sue funzionalità sono implementate
attraverso i suoi metodi e non richiede UI (User Interface).
Nella programmazione managed, il programmatore pone la propria attenzione sul livello
più alto della comunicazione, trascurando le modalità di scambio dei pacchetti e
concentrandosi sui dati e sulla businnes logic.
Le operazioni di basso livello sono gestite dal SO e sono del tutto trasparenti sia per il
programmatore sia per l’applicazione stessa.
Localhost
Ogni interfaccia di rete connessa a Internet ha un indirizzo IP.
Lo standard prevede per ogni PC anche un’interfaccia interna, chiamata di loopback, il cui
indirizzo è 127.0.0.1, maschera 255.0.0.0 e indirizzo di rete 127.0.0.0.
Usata dalle applicazioni client/server per parlarsi direttamente, anche in assenza di
connessioni esterne.
TPSIT 5
um 6 di 434
TCP
INTRODUZIONE
Il namespace Sytem.Net è associato a operazioni di alto livello, come l’invio e la ricezione
di file e richieste HTTP.
Il namespace System.Net.Sockets è coinvolto in operazioni di basso livello giacché
fornisce un’implementazione gestita dell’interfaccia Winsock che permette uno stretto
controllo sull’accesso alla rete.
Per esempio, socket permette un controllo capillare di tutte le impostazioni di connessione.
Oltre a questo, c’è una serie di strumenti specializzati nella comunicazione con i protocolli
applicativi, come HTTP o FTP che sollevano il programmatore dall’onere della gestione
diretta dei byte.
TPSIT 5
um 7 di 434
TCP è un protocollo che garantisce la corretta trasmissione dei dati inviati e ricevuti,
conservandone la corretta sequenza di trasmissione e ricezione.
Questo controllo è oneroso in termini di tempo di trasmissione, per contro, offre una
notevole garanzia in materia di qualità della comunicazione.
È un protocollo orientato alla connessione concepito per la trasmissione da punto a
punto tra un client e un server, per questo in un’applicazione che lo utilizza, la cosa
fondamentale da stabilire è se si tratta di un’applicazione client o server.
In altre parole, si può dire che il funzionamento del TCP è analogo al funzionamento del
telefono tradizionale, prima di dialogare bisogna stabilire una connessione.
Il protocollo TCP è indicato per applicazioni client/server, in altre parole per le applicazioni
che permettono lo scambio di dati tra una macchina denominata server e una o più
macchine denominate client.
Il protocollo TCP è ideale per sviluppare applicazioni come giochi che interagiscono con
utenti in rete o in chat, oppure nel trasferimento file e dati perché l’attesa è giustificata da
un dato completamente valido e utilizzabile dall’applicazione del richiedente, per esempio
il protocollo HTTP si appoggia a TCP perché un file HTML (HyperText Markup Language)
deve arrivare al browser integro e interpretabile.
In pratica, il protocollo TCP instaura un canale di comunicazione bidirezionale tra un client
e un server: il client può inviare dati al server e il server può inviare dati al client, in pratica
supera la limitazione del protocollo HTTP.
Differenze tra protocollo HTTP e TCP
 TCP non è basato su un pattern richiesta/risposta ma su un flusso bidirezionale.
 TCP non è basato su messaggi.
Questo significa.
 Quando il cIient o il server inviano un messaggio, questi non rimangono in attesa di
una risposta.
 I dati che sono scambiati non hanno il formato di un messaggio ma sono array di byte.
Le classi per la gestione dei protocolli di trasporto si trovano già implementate nella BCL
(Base Class Library) del .NET Framework e sono TcpClient e TcpListener che
semplificano il lavoro, lasciando in disparte molti dei dettagli implementativi di basso livello
TPSIT 5
um 8 di 434
che implica l’uso dei socket.
La classe TcpClient consente uno scambio di dati in modalità socket, quindi di basso
livello, in pratica incapsula una connessione TCP e fornisce un certo numero di proprietà
per la gestione del buffering e dei timeout e metodi per la connessione, l’invio e la
ricezione in modalità di blocco sincrono.
La classe TcpListener fornisce i metodi che attendono e accettano richieste di
connessione in ingresso in modalità di blocco sincrono; alla richiesta di connessione di un
client è possibile utilizzare un TcpClient per stabilire una connessione con un TcpListener.
Un TcpListener si crea con un IPEndPoint che è costituito da un indirizzo IP e da un
numero di porta.
Per consentire all’oggetto TcpClient di stabilire la connessione e scambiare informazioni, è
necessario che un TcpListener, il server, sia in attesa delle richieste di connessione.
È possibile connettersi al listener creando un oggetto TcpClient passando al costruttore
l’IP del server e il numero di porta su cui è in ascolto.
Il metodo Start permette di avviare la fase di attesa per le richieste di connessione in
ingresso.
Il metodo AcceptTcpClient accetta la connessione dei client, mentre la gestione della
comunicazione è fatta da un thread separato.
Esempio, inviare e ricevere immagini con un client e un server TCP.
TcpClient
File MAINWINDOW.XAML.CS
Si recupera lo stream di comunicazione di tipo NetworkStream grazie al metodo
GetStream, con il quale s’inviano i dati al server.
Nell’esempio, si scrivono i byte dell’immagine selezionata.
using System.Windows;
using System.Windows.Media.Imaging;
using System.IO;
using Microsoft.Win32;
using System.Net.Sockets;
TPSIT 5
um 9 di 434
namespace TcpClient
{ public partial class MainWindow : Window
{ public MainWindow()
{ InitializeComponent(); }
private void btnInvia_Click(object sender, RoutedEventArgs e)
{ using (Stream fileStream = File.OpenRead(this.txbNome.Tag.ToString()))
{ System.Net.Sockets.TcpClient client = new System.Net.Sockets.TcpClient();
client.Connect("localhost", 8080);
NetworkStream netStream = client.GetStream();
byte[] sendBuffer = new byte[1024];
int bytesRead = 0;
do {
bytesRead = fileStream.Read(sendBuffer, 0, 1024);
if (bytesRead > 0)
netStream.Write(sendBuffer, 0, bytesRead);
}
while (bytesRead > 0);
netStream.Close();
}
}
private void btnSfoglia_Click(object sender, RoutedEventArgs e)
{ OpenFileDialog fileDialog = new OpenFileDialog
{ Filter = "immagini (*.jpg)|*.jpg" };
bool? show = fileDialog.ShowDialog();
if (show.HasValue & show.Value) {
using (Stream fileStream = fileDialog.OpenFile())
{ this.txbNome.Text = fileDialog.SafeFileName;
this.txbNome.Tag = fileDialog.FileName;
MemoryStream dataStream = new MemoryStream();
byte[] dataByte = new byte[1024];
int i = 0;
do {
i = fileStream.Read(dataByte, 0, 1024);
if (i > 0)
dataStream.Write(dataByte, 0, i);
}
while (i > 0);
dataStream.Seek(0L, SeekOrigin.Begin);
BitmapImage bmpImage = new BitmapImage();
bmpImage.BeginInit();
bmpImage.StreamSource = dataStream;
bmpImage.EndInit();
this.imgImmagine.Source = bmpImage;
}
}
}
}
}
File MAINWINDOW.XAML
<Window x:Class="TcpClient.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
TPSIT 5
um 10 di 434
Title="TCPClient" Height="350" Width="425"
WindowStartupLocation="CenterScreen">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<StackPanel Orientation="Horizontal"
FlowDirection="LeftToRight" HorizontalAlignment="Center">
<TextBlock Height="23"
x:Name="txbNome" Width="180" />
<Button Content="Sfoglia"
Height="23" x:Name="btnSfoglia"
Click="btnSfoglia_Click"
Width="55" />
<Button Content="Invia"
Height="23" x:Name="btnInvia"
Click="btnInvia_Click"
Width="55" />
</StackPanel>
<Image Grid.Row="1"
x:Name="imgImmagine" Stretch="Uniform" />
</Grid>
</Window>
TcpServer
File MAINWINDOW.XAML.CS
L’oggetto IPEndPoint specifica gli indirizzi e le porte degli host su cui l’oggetto TcpListener
rimane in ascolto in attesa di possibili connessioni, mentre IPAddress.Any apre a tutti i
potenziali client.
All’interno del while, con il metodo Pending si verifica la presenza di una connessione in
ingresso: in caso positivo, si attiva la comunicazione con AcceptTcpClientAsync che
restituisce l’oggetto TcpClient, mediante il quale è possibile inviare e ricevere dati.
Si procede alla lettura dei dati, salvandoli in un MemoryStream per utilizzarli come
sorgente dell’oggetto BitmapImage e di mostrarli a video con un controllo di tipo Image.
Per selezionare l’immagine si usa l’oggetto OpenFileDialog.
Dopo aver aperto lo stream dell’immagine presente sul file system, si deve inizializzare
l’oggetto TcpClient e aprire la connessione all’host locale, sulla porta 8080 con il metodo
Connect; proprio per la natura del protocollo, è importante che, al momento della chiamata
del metodo Connect, il server sia già attivo e in ascolto.
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.IO;
using System.Net.Sockets;
using System.Net;
using Microsoft.Win32;
namespace TcpServer {
public partial class MainWindow : Window {
TPSIT 5
um 11 di 434
public MainWindow()
{ InitializeComponent();
this.Loaded += async (o, e) => await ReiceveData();
}
private async void btnInvia_Click(object sender, RoutedEventArgs e)
{ using (Stream fileStream = File.OpenRead(this.txbNome.Tag.ToString()))
{ var client = new TcpClient();
client.Connect("localhost", 8080);
var netStream = client.GetStream();
var sendBuffer = new byte[1024];
int bytesRead = 0;
do {
bytesRead = await fileStream.ReadAsync(sendBuffer, 0, 1024);
if (bytesRead > 0)
netStream.Write(sendBuffer, 0, bytesRead);
}
while (bytesRead > 0);
netStream.Close();
}
}
private void btnSfoglia_Click(object sender, RoutedEventArgs e)
{ var fileDialog = new OpenFileDialog
{ Filter = "immagini (*.jpg)|*.jpg" };
bool? show = fileDialog.ShowDialog();
if (show.HasValue & show.Value) {
using (fileDialog.OpenFile())
{ this.txbNome.Tag = fileDialog.FileName;
this.txbNome.Text = fileDialog.SafeFileName;
}
}
}
private async Task ReiceveData()
{ var server = new TcpListener(new IPEndPoint(IPAddress.Any, 8080));
server.Start();
while (true) {
var localClient = await server.AcceptTcpClientAsync();
var netStream = localClient.GetStream();
if (netStream.CanRead) {
var dataStream = new MemoryStream();
var dataByte = new byte[1024];
int i = 0;
do {
i = await netStream.ReadAsync(dataByte, 0, 1024);
if (i > 0)
dataStream.Write(dataByte, 0, i);
}
while (i > 0);
dataStream.Seek(0, SeekOrigin.Begin);
var bmpImage = new BitmapImage();
bmpImage.BeginInit();
bmpImage.StreamSource = dataStream;
bmpImage.EndInit();
var img = new Image
TPSIT 5
um 12 di 434
{ Stretch = Stretch.Uniform,
Source = bmpImage
};
stpPannello.Children.Add(img);
}
localClient.Close();
netStream.Close();
}
}
}
}
File MAINWINDOW.XAML
<Window x:Class="TcpServer.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="TCPServer" Height="350" Width="425"
WindowStartupLocation="CenterScreen">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<StackPanel Orientation="Horizontal"
FlowDirection="LeftToRight" HorizontalAlignment="Center">
<TextBlock Height="23" x:Name="txbNome" Width="180" />
<Button Content="Sfoglia" Height="23" x:Name="btnSfoglia" Click="btnSfoglia_Click"
Width="55" />
<Button Content="Invia" Height="23" Click="btnInvia_Click" x:Name="btnInvia"
Width="55" />
</StackPanel>
<StackPanel Grid.Row="1"
Orientation="Vertical" x:Name="stpPannello"></StackPanel>
</Grid>
</Window>
TPSIT 5
um 13 di 434
TCP E WINDOWS 8.X
Le classi per comunicare con il protocollo TCP non appartengono al .NET Framework ma
sono classi native di Windows 8.X, namespace Windows.Networking.Sockets.
Il protocollo TCP è utile per app che richiedono un colloquio bidirezionale ma presenta un
problema: la porta 80 è utilizzata per il traffico HTTP, i server socket comunicano usando
altre porte che, però, spesso sono bloccate dal firewall e quindi rendono impossibile il
collegamento tra client e server.
Aprire la connessione con il server
La classe StreamSocket permette di gestire il ciclo di vita della comunicazione TCP.
Per aprire la connessione si usa il metodo ConnectAsync che accetta in input un oggetto
HostName che rappresenta il nome del server verso cui aprire la connessione e una
stringa contenente la porta.
private StreamSocket _socket = new StreamSocket();
await _socket.ConnectAsync(new HostName("127.0.0.1"), "8888");
Inviare dati al server
La classe StreamSocket espone la proprietà OutputStream di tipo IOutputStream che
rappresenta lo stream tramite il quale è possibile inviare dati al server.
Per inviare dati al server non si deve utilizzare la proprietà OutputStream ma si deve
creare
un’istanza
della
classe
DataWriter
contenuta
nel
namespace
Windows.Storage.Streams alla quale si passa in input lo stream, espone diversi metodi
tipizzati: WriteInt32, WriteString e WriteBoolean che scrivono in un buffer di memoria.
Terminato di scrivere nel buffer, s’invia il contenuto con il metodo StoreAsync e si scollega
il writer dallo stream con il metodo DetachStream.
Esempio, creare un messaggio dove i primi 4 byte contengono la lunghezza del
messaggio e i successivi contengono il corpo del messaggio.
Mantenendo questo formato, il server è in grado d’interpretare il messaggio ricevuto.
TPSIT 5
um 14 di 434
async private void SendMessage(StreamSocket socket, string message)
{ var writer = new DataWriter(socket.OutputStream);
var len = writer.MeasureString(message);
writer.WriteInt32((int)len);
writer.WriteString(message);
var ret = await writer.StoreAsync();
writer.DetachStream();
LogMessage(string.Format("Spedito (a {0}) {1}",
socket.Information.RemoteHostName.DisplayName, message));
}
Ricevere dati dal server
La classe StreamSocket espone la proprietà InputStream che rappresenta lo stream sul
quale sono ricevuti i dati dal server.
Non si devono leggere i dati direttamente dallo stream ma si deve creare un’istanza della
classe DataReader alla quale si passa in input lo stream per poi leggere i dati tramite
questa classe.
Per recuperare i dati dallo stream s’invoca il metodo LoadAsync della classe DataReader
che accetta in input il numero di byte da leggere.
Una volta recuperati i byte, si possono leggere con i metodi tipizzati che la classe
DataReader mette a disposizione: ReadInt32, ReadString e ReadBoolean.
Tramite il metodo LoadAsync i primi 4 byte del messaggio che rappresentano la
lunghezza, sono recuperati dallo stream e allocati nel buffer del reader.
Il metodo ReadInt32 trasforma i 4 byte nella lunghezza del messaggio.
Ottenuta la lunghezza, si usa di nuovo il metodo LoadAsync per caricare nel buffer del
reader il resto del messaggio e dopo si usa il metodo ReadString per trasformare l’array di
byte in una stringa.
Il metodo è ricorsivo perché deve rimanere in ascolto di nuovi messaggi dal server.
Subito dopo aver stabilito la connessione, s’invoca il metodo WaitForData per fare in modo
che il client cominci ad intercettare i messaggi inviati dal server.
Esempio, leggere i dati.
async private void WaitForData(StreamSocket socket)
{ var dr = new DataReader(socket.InputStream);
var stringHeader = await dr.LoadAsync(4);
if (stringHeader == 0) {
LogMessage(string.Format("Disconnesso (da {0})",
socket.Information.RemoteHostName.DisplayName));
return;
}
int strLength = dr.ReadInt32();
uint numStrBytes = await dr.LoadAsync((uint)strLength);
string msg = dr.ReadString(numStrBytes);
LogMessage(string.Format("Ricevuto (da {0}): {1}",
socket.Information.RemoteHostName.DisplayName, msg));
WaitForData(socket);
}
TPSIT 5
um 15 di 434
JAVA
Le classi Socket e ServerSocket implementano socket su TCP.
1. Il server aprirà una porta sul proprio indirizzo IP e resterà in ascolto su essa.
2. Il client punterà a quell’indirizzo IP e invierà dei messaggi sulla porta aperta dal server.
3. Il server non farà altro che rimandare il messaggio ricevuto al client, “EchoServer”.
4. Il server chiuderà il socket e quindi la porta, quando riceverà un messaggio vuoto.
La classe che Java mette a disposizione per assolvere alle funzioni previste per il server è
la classe ServerSocket, infatti, è possibile accettare connessioni dai client attraverso la
rete su una ben determinata porta.
Quindi quando si vuole realizzare un server si possono seguire i seguenti tre passi.
1. Creare un oggetto di classe ServerSocket impostando al costruttore il numero di porta
locale, in altre parole la porta in cui il server rimarrà in ascolto di richieste di
connessioni.
2. Invocare il metodo accept della classe ServerSocket, in modo tale da attendere le
connessioni da parte dei client.
3. Usare il socket ottenuto ad ogni connessione, per comunicare con il client.
package es;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.UnknownHostException;
public class EchoServer implements Runnable {
public static final int PORT = 4422;
private boolean loop = true;
private ServerSocket sSocket = null;
private boolean running = false;
Si è specificato su un campo costante e pubblico su quale porta il server rimanga in
ascolto, in modo tale che i client possano saperlo facilmente.
Un altro membro è proprio sSocket che implementa le funzionalità del server.
Metodo run.
public void run()
TPSIT 5
um 16 di 434
In primo luogo è necessario istanziare un ServerSocket sulla porta scelta.
Se questa operazione non andasse a buon fine sarebbe inutile proseguire e, infatti, sul
catch di IOException, dopo aver stampato l’errore, si esce direttamente dal metodo.
{ try
{ sSocket = new ServerSocket(PORT); }
catch (IOException e) {
e.printStackTrace();
return;
}
Ci si deve preparare per ricevere un connessione da parte di un client.
Socket clientSocket = null;
PrintWriter out = null;
BufferedReader in = null;
running = true;
Il metodo accept invocato immediatamente dopo della classe ServerSocket crea un
oggetto Socket per ogni connessione.
Il server non può accettare una nuova connessione finché non incontra nuovamente il
metodo accept che è bloccante, in altre parole il server si blocca finché non c’è una
connessione del client.
Se mentre sta servendo un client, arriva una seconda richiesta da parte di un altro client,
la connessione non è accettata ed è sospesa finché il server non si libera e fa un’altra
accept.
Il server potrà poi comunicare con il client attraverso lo stream d’input e quello di output
grazie al BufferedReader e al PrintWriter.
try {
while (loop) {
clientSocket = sSocket.accept();
out = new PrintWriter(clientSocket.getOutputStream(),true);
in = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));
String line;
while ((line = in.readLine()) != null)
out.println("Server response: "+ line);
}
}
catch (IOException e)
{ e.printStackTrace(); }
finally {
Una volta accettata la connessione, s’incapsula lo stream d’input nel reader e quello di
output nel writer.
Fatto ciò, si legge quanto il client ha inviato e lo si rispedisce indietro con il suffisso Server
response:.
Questa parte di codice è racchiusa all’interno di un ciclo while che ha come condizione di
terminazione la variabile loop.
Infatti, ogni server deve continuare a rimanere in ascolto finché non è “messo giù”.
Il metodo accept rimane in attesa finché non riceve un messaggio.
Una volta uscito da questo loop è necessario chiudere gli stream e i socket.
TPSIT 5
um 17 di 434
if (out != null)
out.close();
try {
if (in != null)
in.close();
if(clientSocket != null)
clientSocket.close();
if(sSocket != null)
sSocket.close();
}
catch (IOException e)
{ e.printStackTrace(); }
running = false;
}
System.err.println("Server is down");
}
Bisogna evitare di chiudere i socket prima che siano chiusi i loro relativi stream.
Questa classe deve avere un comportamento da server, il loop fa sì che il server rimanga
in attesa della prossima richiesta da parte del client.
Come prima caratteristica è necessario che, così come si può avviare il servizio tramite il
metodo run, allo stesso modo sia possibile fermarlo, basta settare la variabile loop del
ciclo while a false.
In realtà non funzionerebbe, per due motivi.
1. La modifica del valore della variabile loop dev’essere effettuata da un thread esterno a
quello che ha lanciato run perché quest’ultimo è “bloccato” sul ciclo while.
2. Anche mettendo a false la variabile loop questo non garantirebbe l’uscita dal ciclo,
infatti, la riga di codice clientSocket = sSocket.accept(); rimane in attesa della prossima
richiesta; risulta quindi necessario, inoltre, inviare anche un messaggio che serva
esclusivamente a “sbloccare” il ciclo da quel punto.
Si ricorre al metodo stop del server.
public void stop()
{
loop = false;
Socket dummySocket = null;
PrintWriter writer = null;
try {
dummySocket = new Socket("127.0.0.1", PORT);
writer = new PrintWriter(dummySocket.getOutputStream(),true);
writer.println("");
}
catch (UnknownHostException e)
{ e.printStackTrace(); }
catch (IOException e)
{ e.printStackTrace(); }
finally {
if (writer != null) writer.close();
if (dummySocket != null)
try
{ dummySocket.close(); }
catch (IOException e)
{ e.printStackTrace(); }
}
TPSIT 5
um 18 di 434
}
La prima cosa che si fa è settare la variabile globale loop a false, dopo di che si deve
sbloccare il server dal suo ciclo, si deve mandare un messaggio vuoto al server.
Naturalmente è scontato che il server cui si deve inviare tale messaggio risieda sullo
stesso host, per cui l’indirizzo cui punterà il dummySocket sarà 127.0.0.1.
Una volta stabilita la connessione non resterà da fare altro che scrivere sul proprio stream
di output in modo tale che “dall’altra parte” il server legga tale messaggio.
Vogliamo che il server possa servire un client e contemporaneamente forzare a fare un
accept per ascoltare altri client.
Un server multithreaded consente di servire più client contemporaneamente, nonché di
accettare nuove connessioni, tipicamente è impiegato un thread per client.
Per utilizzare il server in un ambiente multithread sarà quindi anche indispensabile sapere
quando il server è effettivamente attivo, in pratica quando si è veramente collegato alla
porta specificata e si è messo in ascolto.
Questo è il significato della variabile running che è impostata a true appena prima
dell’ingresso al ciclo while e a false appena usciti da quest’ultimo.
Si è implementata alla classe EchoServer l’interfaccia Runnable; infatti sarà possibile
lanciare tale classe come thread parallelo.
Sapere quando il server è effettivamente attivo è di fondamentale importanza, infatti tutti i
client dovranno attendere l’attivazione di quest’ultimo prima d’iniziare qualsiasi
comunicazione con esso, a tale scopo è stato implementato il metodo waitForRunnig.
public boolean isRunning()
{ return running; }
public void waitForRunnig()
{ while(!isRunning())
Thread.yield();
}
Questo metodo non fa altro che controllare ciclicamente lo stato della variabile running,
terminando quando essa assume il valore true.
È importante notare che non si tratta di un semplice ciclo while di “attesa” bensì è invocato
il metodo statico Thread.yield che dice alla VM (Virtual Machine): “io per adesso posso
attendere, esegui pure gli altri thread”.
Instaurare la prima comunicazione client/server.
public static void main(String[] args) throws IOException
{
EchoServer serverSocket = new EchoServer();
Thread server = new Thread(serverSocket);
server.start();
serverSocket.waitForRunnig();
È necessario istanziare un server e incapsularlo all’interno di un oggetto thread, una volta
lanciato quest’ultimo si attende che sia attivo grazie al metodo waitForRunnig.
Socket echoSocket = null;
PrintWriter out = null;
BufferedReader in = null;
try {
echoSocket = new Socket("127.0.0.1", PORT);
out = new PrintWriter(echoSocket.getOutputStream(), true);
in = new BufferedReader(new
TPSIT 5
um 19 di 434
InputStreamReader(echoSocket.getInputStream()));
}
catch (UnknownHostException e) {
System.err.println("Don’t know about host: taranis.");
System.exit(1);
Una volta che il server risulta attivo si può collegare il socket client e comunicare
attraverso i suoi stream.
BufferedReader stdIn = new BufferedReader(new InputStreamReader(System.in));
String userInput;
while ((userInput = stdIn.readLine()) != null) {
if(userInput.equals("")) {
serverSocket.stop();
break;
}
out.println(userInput);
System.out.println(in.readLine());
}
out.close();
in.close();
stdIn.close();
echoSocket.close();
}
In particolare l’applicazione userà lo standard input per comunicare con il server, per
esempio digitare Ciao e il server risponde.
Inoltre, si è stabilito che un semplice INVIO senza alcun messaggio fermi il server.
TPSIT 5
um 20 di 434
UDP
INTRODUZIONE
È un protocollo utilizzato soprattutto per lo scambio di messaggi.
Non si occupa del controllo dei dati in transito perché sono inviati sotto forma di
datagrammi che il ricevente può o non ricevere.
È impiegato nelle comunicazioni, dove ha più importanza la velocità di trasmissione
rispetto alla qualità come, per esempio, nelle trasmissioni broadcast (in pratica
contemporaneamente su più nodi della rete) dove è trascurabile perdere alcuni byte, nel
complesso della comunicazione.
L’UDP a differenza del TCP non richiede una connessione esplicita tra i PC, in altre parole
è di tipo connectionless.
La mancanza di una connessione ne fa un protocollo poco affidabile ma veloce e semplice
da utilizzare.
Con il protocollo UDP i PC possono avere un comportamento equivalente, possono
trasmettere e ricevere allo stesso modo, non è definita la figura del client e del server, per
questo le applicazioni che utilizzano l’UDP si possono considerare di tipo P2P (Peer To
Peer).
La classe UdpClient consente uno scambio di dati in modalità socket, quindi di basso
livello, con il protocollo UDP, senza stato e senza la necessità di attendere la connessione
con un host specifico.
Esempio, inviare e ricevere messaggi con un client e un server UDP.
UdpClient
File MAINWINDOW.XAML.CS
Per iniziare la connessione, si utilizza il metodo Connect e si specifica come parametri sia
l’host cui si desidera inviare i messaggi sia la porta di comunicazione che si utilizzerà,
nell’esempio s’inviano i messaggi sullo stesso PC, "localhost", attraverso la porta 8080.
TPSIT 5
um 21 di 434
Questo valore dev’essere superiore a 1024 per non entrare in conflitto con le Well Known
Port Number e la porta non dev’essere utilizzata da altre applicazioni.
L’effettivo invio dei messaggi avviene con il metodo SendAsync cui si passa l’array di byte
corrispondente al messaggio che si vuole trasmettere, oltre alla lunghezza dell’array
stesso.
using System.Text;
using System.Windows;
using System.Net.Sockets;
namespace Client {
public partial class MainWindow : Window {
public MainWindow()
{ InitializeComponent(); }
private void btnInvia_Click(object sender, RoutedEventArgs e)
{ var client = new UdpClient();
client.Connect("localhost", 8080);
byte[] sendByte = Encoding.ASCII.GetBytes(txtInvia.Text);
client.SendAsync(sendByte, sendByte.Length);
}
}
}
File MAINWINDOW.XAML
L’interfaccia è una semplice finestra con una casella di testo in cui è possibile immettere il
messaggio e un pulsante.
<Window x:Class="Client.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Client"
Height="80"
Width="300">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<StackPanel Orientation="Horizontal">
<TextBox Height="23"
x:Name="txtInvia"
Width="180" />
<Button Content="Invia"
Height="23"
x:Name="btnInvia"
Width="55"
Click="btnInvia_Click" />
</StackPanel>
</Grid>
</Window>
UdpServer
File MAINWINDOW.XAML.CS
TPSIT 5
um 22 di 434
Per ricevere i messaggi si utilizza il metodo Receive, non prima di essersi messi in ascolto
su una combinazione specifica d’indirizzo/porta.
Il protocollo UDP non prevede che sia stabilita una connessione, quindi la ricezione è del
tutto svincolata dall’invio e necessita, pertanto che sia eseguita in continuo.
Nell’esempio, grazie al metodo ReceiveAsync, il ciclo while può continuare senza che
l’interfaccia rimanga bloccata, a ricevere tutti i messaggi provenienti da tutti gli host in
comunicazione sulla porta 8080.
Infine, bisogna convertire in stringa i byte ricevuti e visualizzarli nella casella di testo.
using System;
using System.Text;
using System.Windows;
using System.Net.Sockets;
namespace Server {
public partial class MainWindow : Window {
public MainWindow()
{ InitializeComponent();
ReiceveData();
}
private void btnInvia_Click(object sender, RoutedEventArgs e)
{ var client = new UdpClient();
client.Connect("localhost", 8080);
byte[] sendByte = Encoding.ASCII.GetBytes(this.txtInvia.Text);
client.SendAsync(sendByte, sendByte.Length);
}
private async void ReiceveData()
{ var server = new UdpClient(8080);
while (true) {
var result = await server.ReceiveAsync();
var reiceveByte = result.Buffer;
var reiceveString = Encoding.ASCII.GetString(reiceveByte);
txtRicevi.Text += String.Format(" {0}", reiceveString);
}
}
}
}
File MAINWINDOW.XAML
<Window x:Class="Server.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Server" Height="350" Width="425" WindowStartupLocation="CenterScreen">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<StackPanel Orientation="Horizontal">
<TextBox Height="23"
x:Name="txtInvia"
Width="180" />
<Button Content="Invia"
Height="23"
TPSIT 5
um 23 di 434
x:Name="btnInvia"
Width="55"
Click="btnInvia_Click" />
</StackPanel>
<TextBox Margin="0,30,0,0"
Grid.Row="1"
HorizontalAlignment="Stretch"
x:Name="txtRicevi"
VerticalAlignment="Stretch" />
</Grid>
</Window>
JAVA
La classe DatagramSocket implementa socket su UDP.
Fornisce solo la possibilità d’inviare e ricevere un pacchetto: ritrasmissione e ordinamento
sono a carico del programmatore.
La classe MulticastSocket implementa socket multicast su UDP, estende DatagramSocket
con la possibilità d’inviare lo stesso pacchetto ad un gruppo di riceventi anziché ad uno
solo.
TPSIT 5
um 24 di 434
HTTP
INTRODUZIONE
Per spedire o scaricare dati sulla rete, il client apre un collegamento TCP con un server.
Il TCP collega gli indirizzi IP e le porte alle due estremità e si occupa di spedire i dati
senza errori tra i due.
Poiché le informazioni non devono soltanto essere trasportate ma anche comprese, sopra
il livello del TCP vi è un protocollo applicativo attraverso il quale comunicano il client e il
server: nel caso dei siti web si tratta dell’HTTP.
Anche se il TCP, perché livello di trasporto, offre una spedizione di dati bidirezionale,
l’HTTP non usa questa possibilità.
L’HTTP funziona secondo un criterio semplice: il client chiede, il server risponde.
La comunicazione è realizzata con sequenze richiesta/risposta, tipico delle architetture
client/server, per esempio il client è il browser che invia un messaggio di “richiesta” al
server web che restituisce i dati con un messaggio di “risposta”.
A differenza degli altri protocolli dello stesso livello lavora in modalità disconnessa:
questo vuol dire che non s’instaura un canale di comunicazione permanente tra client e
server ma, a ogni richiesta, è aperto e poi richiuso il canale di comunicazione.
In pratica, il protocollo HTTP è stateless, senza stato: ogni scambio richiesta/risposta è
indipendente dagli altri e a livello di protocollo non esiste uno stato condiviso.
Questo ha spinto i programmatori a trovare soluzioni alternative, tramite i linguaggi di
programmazione, per la gestione dello stato.
La stato dev’essere mantenuto e gestito a livello applicativo in tre modi.
1. URL (Uniform Resource Locator) rewriting passare le informazioni di stato sull’URL
nella parte di query.
2. ViewState utilizzare dei campi nascosti per passare i dati con un POST.
3. Cookie: sono informazioni scambiate tra client e server a ogni richiesta/risposta, sono
memorizzati sul client come file, quindi mantengono uno stato che rimane tra una
richiesta e l’altra.
Una comunicazione HTTP è composta da un messaggio di richiesta che viaggia dal client
verso il server e da un messaggio di risposta che viaggia dal server verso il client.
Il client richiede una risorsa al server e il server risponde o con la risorsa o con un
messaggio di errore.
Il protocollo HTTP permette di scambiare messaggi ma ha le limitazioni seguenti.
 È sempre il client che deve attivare la comunicazione.
TPSIT 5
um 25 di 434
 Il server non può inviare in autonomia messaggi al client.
Ogni messaggio è composto da due parti.
1. Le intestazioni HTTP: contengono una serie di coppie chiave/valore che rappresentano
i metadati, in pratica specificano il formato della richiesta e della risposta, per esempio
le lingue supportate dal client e la lunghezza del messaggio.
2. Il corpo: è la parte del messaggio che contiene i dati.
Con la creazione dei servizi REST (REpresentational State Transfer) sono diventate molto
importanti le intestazioni HTTP che specificano il formato della richiesta e della risposta.
È possibile monitorare le transazioni HTTP con uno strumento tracer HTTP.
HTTP 1.1
Consente una richiesta soltanto per ciascun collegamento TCP, di conseguenza tutti gli
elementi di una pagina web sono inviati singolarmente in sequenza.
I browser per evitare questo, generano fino a 6 collegamenti TCP paralleli.
Ciò non è efficiente perché un server può gestire ogni collegamento aggiuntivo con un
intervallo di 500 ms soltanto e a ogni collegamento invia inutilmente un nuovo header
HTTP.
Inoltre, HTTP consente soltanto al client d’inviare richieste.
HTTP 2 (originariamente HTTP 2.0)
Google SPDY: consente l’invio in parallelo dei pacchetti HTTP, comprime i dati e li cifra
SSL (Secure Sockets Layer), questa tecnologia è nata come miglioramento di HTTP 1.1 e
poi divenuta la base su cui l’organizzazione ha implementato HTTP 2.
TPSIT 5
um 26 di 434
Microsoft Speed+Mobility: usata per le app e i dispositivi mobili; i WebSocket generano un
continuo collegamento bidirezionale tra client e server.
HTTP 2 è stato sviluppato dal Working Group Hypertext Transfer Protocol dell’Internet
Engineering Task Force, è un protocollo binario invece che testuale, supporta l’invio di dati
in multiplexing e usa la compressione dell’header per ridurre lo spreco di banda secondo
la specifica HPACK.
Permette ai server di “spingere” proattivamente le proprie risposte alle richieste dei client.
Tiene un canale aperto tra il client e il server.
Richiesta HTTP
Il formato è il seguente.
<request-line>
<headers>
<blank line>
[<request-body>]
La prima riga indica il tipo di richiesta, la risorsa cui accedere e la versione di HTTP in uso.
La sezione header indica informazioni che possono essere utili al server.
Poi c’è una riga vuota, alla quale possono eventualmente seguire altri dati, corpo.
L’ultima riga, il body è il corpo del messaggio.
Richiesta GET
GET / HTTP/1.1
Host: www.miosito.com
Accept-Language: en-us
User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.7.6)
Host: localhost:8080
Connection: Keep-Alive
La prima riga specifica la risorsa richiesta, in questo caso si vuole accedere, GET, alla
risorsa identificata dall’URL.
HTTP ha diversi metodi che specificano quali azioni si possono eseguire su una risorsa.
GET (formato stringa): richiede una risorsa al server; le chiamate effettuate dal browser
sulla barra degli indirizzi sono di tipo GET.
POST (formato binario): invia dati al server specificando che servono per aggiornare dati
già esistenti sul server; quando si completa un form e si fa clic sul pulsante INVIO il
browser invia una richiesta HTTP che inizia con POST; nel body della richiesta sono
elencati i parametri.
HEAD: come GET ma il server restituisce solo l’intestazione della risposta e non il file.
PUT: invia dati al server specificando che servono per aggiornare una risorsa.
DELETE: invia dati al server specificando che servono per cancellare dati già esistenti sul
server; non è possibile usare il browser per inviare un comando DELETE in HTTP, anche
se si può fare con JavaScript, creando un link con questo codice.
TRACE.
OPTIONS.
La (/)indica che è richiesta la radice, root, del dominio.
HTTP/1.1 indica la versione di protocollo HTTP utilizzata per formulare la richiesta.
Host: indica il nome del dominio al quale la richiesta è inviata.
Accept-Language: indica quali sono i linguaggi accettati dal client.
Accept-text/html: indica quali sono i formati accettati dal client.
User-Agent: comunica al server web il tipo di browser che ha fatto la richiesta.
TPSIT 5
um 27 di 434
Connection: Keep-Alive: serve per le operazioni del browser.
Il client può passare una serie di parametri, inserendoli in una richiesta HTTP.
L’invio di parametri per una richiesta GET richiede che le informazioni extra siano aggiunte
in coda all’URL dopo il (?), il formato è del tipo seguente.
URL ? name1=value1&name2=value2&..&nameN=valueN
Quest’informazione, chiamata querystring, è duplicata nella riga di richiesta della
richiesta HTTP come segue.
GET /books/?name=Professional%20Program HTTP/1.1
….
Il testo Professional Program dev’essere codificato, sostituendo lo spazio con %20, in
modo da inviarlo all’URL come parametro: questa è chiamata codifica URL.
Richiesta POST
Fornisce al server informazioni aggiuntive nel corpo della richiesta.
POST / HTTP/1.1
Host: www.miosito.com
User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.7.6)
Content-Type: application/x-www-form-urlencoded
Content-Length: 40
Connection: Keep-Alive
name=Professional%20Program&publisher=Wiley
La prima riga specifica la risorsa richiesta, in questo caso si vuole accedere, POST, alla
risorsa identificata dall’URL.
HTTP ha diversi metodi che specificano quali azioni si possono eseguire su una risorsa.
POST (formato binario): invia dati al server specificando che servono per aggiornare dati
già esistenti sul server; quando si completa un form e si fa clic sul pulsante INVIO il
browser invia una richiesta HTTP che inizia con POST; nel body della richiesta sono
elencati i parametri.
Gli header Host e User-Agent sono sempre presenti.
Content-Type mostra come è codificato il corpo della richiesta.
Content-Length indica la lunghezza in byte del corpo della richiesta.
Dopo l’header Connection e la riga vuota c’è il corpo della richiesta.
I metodi GET e HEAD sono sicuri perché sono utilizzati solo per il recupero
d’informazioni e quindi non cambiano lo stato del server.
I metodi POST, PUT e DELETE non sono sicuri perché modificano lo stato del server.
I metodi PUT, GET, HEAD e DELETE sono idempotenti perché la stessa richiesta
produrrà sempre gli stessi risultati.
Il metodo POST non è né sicuro né idempotente perché la stessa richiesta ripetuta più
volte può produrre risultati differenti.
Risposta HTTP
Il formato è il seguente.
<status-line>
TPSIT 5
um 28 di 434
<headers>
<blank line>
[<response-body>]
La prima riga contiene informazioni di stato, sono indicazioni sulla risorsa richiesta tramite
un codice di stato.
La sezione header indica informazioni che possono essere utili al server.
Poi c’è una riga vuota, alla quale possono eventualmente seguire altri dati, corpo.
L’ultima riga, il body è il contenuto della risposta.
Risposta HTTP
HTTP/1.1 200 OK
Date:
Server:
Connection: Close
Content-Length: 777
Content-Type: text/plain
This is the content of file.txt.
HTTP/1.1: indica la versione del protocollo usata dal server web per formattare la risposta.
200: è il codice di stato.
1xx: richiesta ricevuta, processata e completata correttamente.
2xx: la richiesta è stata recepita correttamente.
3xx: il browser deve effettuare una redirezione verso un altro indirizzo.
304 (NOT MODIFIED): la risorsa non è stata modificata dall’ultima richiesta, questo è
utilizzato più di frequente per i meccanismi di cache dei browser.
4xx: errore del client; la richiesta non è formulata correttamente.
401 (UNAUTHORIZED): il client non è autorizzato ad accedere alla risorsa, in questi casi il
browser richiederà un nome utente e una password per effettuare il login al server.
403 (FORBIDDEN): il client non ha ottenuto autorizzazione, questo accade solitamente se
fallisce il login mediante nome utente e password dopo un codice 401.
404 (NOT FOUND): la risorsa non esiste all’indirizzo fornito.
5xx: errore del server; il server non è riuscito a rispondere ad una richiesta valida.
OK: è la “reason” dello stato.
Date: data e ora in cui la risposta è stata inviata.
Server: nome del server che ha inviato la risposta.
Content-Length: lunghezza del corpo della risposta in byte.
Content-Type: indica il tipo MIME (Multipurpose Internet Mail Extension) dei dati restituiti
nel body che è composto da due parti, sono questi i dati che il browser mostra all’utente.
Non vi è indicazione del tipo di richiesta che ha provocato tale risposta ma questo non ha
conseguenza alcuna per il server.
È compito del client sapere quali tipi di dati debbano essere rinviati per ogni tipo di
richiesta e decidere come utilizzarli.
1. Tipo: text.
2. Sottotipo: plain.




text/plain = testo ASCII (American Standard Code for Information Interchange).
text/html = documento HTML.
text/xml = documento XML.
image/gif = immagine in formato GIF (Graphics Interchange Format).
TPSIT 5
um 29 di 434
L’elenco dei tipi MIME nel PC si trova nel registro di sistema alla chiave seguente.
HKEY_CLASSES_ROOT\MIME\DATABASE\CONTENT TYPE.
I programmatori hanno previsto dei metodi per la comunicazione live tramite HTTP.
Il metodo più semplice è il polling, nel quale, a intervalli stabiliti, uno script nel browser
chiede informazioni al server riguardo nuovi eventi.
A ogni richiesta il polling crea una nuova connessione che il server termina dopo la
risposta, anche se non deve riferire alcuna modifica.
Tutto questo richiede molto tempo, aumenta il traffico a causa dell’overhead e l’utilizzo
della rete a causa della frequente creazione di nuovi collegamenti TCP.
Il long polling mantiene il collegamento fino al momento in cui il server deve annunciare
gli eventi, questo accorcia la latenza tra evento e risposta ma provoca comunque traffico e
utilizzo della rete.
Il metodo migliore è lo streaming HTTP: il collegamento rimane aperto per lungo tempo,
mentre il server può inviare i dati in sequenze irregolari.
Lo svantaggio risiede nella dispendiosa implementazione dell’oggetto JavaScript
XMLHttpRequest che non è lo stesso in ogni browser.
Inoltre, per la comunicazione in tempo reale c’è sempre la necessità di due collegamenti
HTTP.
Esempio, selezionare un’immagine, visualizzarne l’anteprima e inviarla al server web.
Con le classi astratte WebRequest e WebResponse, si entra nel modello richiesta/risposta
tipica dei protocolli TCP, una particolarità di queste due classi è che non sono istanziate
direttamente ma utilizzano un design pattern creazionale chiamato Factory.
Per esempio la classe HttpWebRequest, per istanziare un oggetto di questo tipo, si deve
passare attraverso la sua classe “Creator”, ovvero la WebRequest, la quale, tramite il
metodo Create, può restituire un oggetto di tipo HttpWebRequest.
Grazie alla classe HttpWebRequest si ha la possibilità d’interagire con le risorse esposte
da un server web con il protocollo HTTP.
Questi oggetti dispongono di molte proprietà.
GetResponse
Permette di recuperare il messaggio di risposta.
TPSIT 5
um 30 di 434
ContentType
Permette di conoscere le informazioni relative al tipo.
Last Modified
Data dell’ultima modifica, da usare insieme a ISFromCache per stabilire la validità della
risorsa stessa.
StatusCode
Permette di conoscere il codice di stato HttpStatusCode.
TPSIT 5
um 31 di 434
Headers
Permette di controllare tutte le header del messaggio di risposta, collezione di tipo
WebHeaderCollection.
Cookies
Permette di accedere alla collezione di oggetti Cookie relativi alla risposta, è possibile
anche la gestione in scrittura con la proprietà CookieContainer dell’oggetto
HttpWebRequest.
GetResponseStream
Permette di recuperare lo stream di risposta e di elaborarlo in funzione del tipo di risposta.
GetRequestStreamAsync
Permette d’inviare dati aprendo uno stream.
È importante conoscere il modello esecutivo delle estensioni del server web relativamente
al modo in cui queste gestiscono le richieste web.
Nel caso dei CGI (Common Gateway Interface), ad ogni Request corrisponde un processo
nuovo che termina con l’invio della Response.
Nel caso di plugin, ogni Request è soddisfatta da un thread di un unico processo che
termina anch’esso con la Response.
File MAINWINDOW.XAML.CS
Il metodo Create della classe WebRequest crea l’oggetto HttpWebRequest con l’URI
(Uniform Resource Identifier) della pagina ASP.NET (Active Server Page), si apre, quindi,
la richiesta dello stream in modo asincrono, con GetRequestStreamAsync.
In quest’ultimo, si scrivono i byte del file selezionato e si trasmettono con il metodo
GetResponseAsync che resta in ascolto del messaggio di risposta, sempre in modo
asincrono, infine, si visualizza a video il risultato trasmesso dal server web.
using System.Windows;
using System.Windows.Media.Imaging;
using System.IO;
using Microsoft.Win32;
using System.Net;
using System;
namespace HTTP {
public partial class MainWindow : Window {
Uri uploadUri;
Stream fileStream;
public delegate void ManageDataDelegate(string result);
public MainWindow()
{ InitializeComponent(); }
private void btnSfoglia_Click(object sender, RoutedEventArgs e)
{ OpenFileDialog fileDialog = new OpenFileDialog
{ Filter = "immagini (*.jpg)|*.jpg" };
bool? show = fileDialog.ShowDialog();
if (show.HasValue & show.Value) {
using (Stream fileStream = fileDialog.OpenFile())
{ this.txbNome.Text = fileDialog.SafeFileName;
this.txbNome.Tag = fileDialog.FileName;
MemoryStream dataStream = new MemoryStream();
byte[] dataByte = new byte[1024];
TPSIT 5
um 32 di 434
int i = 0;
do {
i = fileStream.Read(dataByte, 0, 1024);
if (i > 0)
dataStream.Write(dataByte, 0, i);
} while (i > 0);
dataStream.Seek(0, SeekOrigin.Begin);
BitmapImage bmpImage = new BitmapImage();
bmpImage.BeginInit();
bmpImage.StreamSource = dataStream;
bmpImage.EndInit();
this.imgImmagine.Source = bmpImage;
this.uploadUri = new
Uri(string.Format("http://localhost:1503/Upload.aspx?path={0}", fileDialog.SafeFileName));
}
}
}
private async void btnInvia_Click(object sender, RoutedEventArgs e)
{ fileStream = File.OpenRead(txbNome.Tag.ToString());
var request = WebRequest.Create(uploadUri) as HttpWebRequest;
request.Method = "POST";
var postStream = await request.GetRequestStreamAsync();
var sendBuffer = new byte[1024];
int bytesRead = 0;
do {
bytesRead = await this.fileStream.ReadAsync(sendBuffer, 0, 1024);
if (bytesRead > 0)
postStream.Write(sendBuffer, 0, bytesRead);
}
while (bytesRead > 0);
var response = await request.GetResponseAsync();
var streamResponse = response.GetResponseStream();
var streamRead = new StreamReader(streamResponse);
var responseString = streamRead.ReadToEnd();
txbNome.Text = responseString;
streamResponse.Close();
streamRead.Close();
response.Close();
fileStream.Close();
}
}
}
File MAINWINDOW.XAML
<Window x:Class="HTTP.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="HTTP" Height="350" Width="425" WindowStartupLocation="CenterScreen">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
TPSIT 5
um 33 di 434
<StackPanel Orientation="Horizontal"
FlowDirection="LeftToRight" HorizontalAlignment="Center">
<TextBlock Height="23"
x:Name="txbNome" Width="180" />
<Button Content="Sfoglia"
Height="23" x:Name="btnSfoglia"
Click="btnSfoglia_Click" Width="55" />
<Button Content="Invia"
Height="23" x:Name="btnInvia"
Click="btnInvia_Click"
Width="55" />
</StackPanel>
<Image Grid.Row="1"
x:Name="imgImmagine"
Stretch="Uniform" />
</Grid>
</Window>
TPSIT 5
um 34 di 434
Progettare un downloader di file che prende una lista d’indirizzi web e, tramite richieste
HTTP, ottiene in risposta degli stream che utilizza per copiare i file in una cartella locale.
Uno stream è un flusso di dati in I/O come una sequenza di byte, la classe Stream del
.NET Framework è una classe astratta alla base di tutti i tipi di stream.
Questi possono essere ad esempio FileStream per la lettura e scrittura di file o
NetworkStream per l’invio e la ricezione di dati mediante socket.
Schema di funzionamento.
File HTTPDOWLOADMANAGER.CS
Contiene i metodi per salvare lo stream su file, il metodo statico, Shared, Download della
classe HttpDownloadManager come prima cosa istanzia un oggetto di tipo
HttpRequestManager che invia la richiesta HTTP e riceve una risposta.
Una volta ricevuta la risposta, occorre effettuare il download del file, a tale scopo, si
utilizza un FileStream Destination.
È, quindi, letto lo Stream ottenuto tramite il metodo GetResponseStream della classe
HttpWebResponse e salvato sul file di destinazione creato precedentemente.
L’applicazione non fa altro che leggere una lista d’indirizzi HTTP ed utilizzare il metodo
Download per ciascuno di essi.
File HTTPREQUESTMANAGER.CS
Contiene i metodi per effettuare le richieste HTTP al server e ricevere uno stream in
risposta, un costruttore per generare la richiesta e i metodi per leggere lo stream di
risposta.
Il costruttore della classe invia una richiesta HTTP tramite il metodo Create della classe
WebRequest che restituisce un oggetto “castabile” in HttpWebRequest.
TPSIT 5
um 35 di 434
Una volta creata la richiesta, è utilizzato il metodo GetResponse della classe
HttpWebRequest per ottenere un oggetto che è “castabile” in HttpWebResponse.
Progettare un server web capace di gestire richieste a pagine HTML statiche, senza
supporto per lo scripting.
File SERVERSETTINGS.SETTINGS.CS
namespace HTTPServer {
/* la classe consente la gestione di eventi specifici sulla classe delle impostazioni:
* l'evento SettingChanging è generato prima della modifica di un valore dell'impostazione;
* l'evento PropertyChanged è generato dopo la modifica di un valore dell'impostazione;
* l'evento SettingsLoaded è generato dopo il caricamento dei valori dell'impostazione;
* l'evento SettingsSaving è generato prima del salvataggio dei valori dell'impostazione */
sealed partial class ServerSettings {
File SERVERSETTINGS.SETTINGS
Il file di configurazione contiene le impostazioni del server.
DefaultFiles: è la lista dei possibili file di default.
default.htm
default.html
Directories: le coppie cartella virtuale/cartella fisica separate da uno spazio.
/ C:\www/
/mysite/ C:\www\mysite\
MimeTypes: i tipi MIME conosciuti, coppie estensione /tipo MIME.
TPSIT 5
um 36 di 434
.html text/html
.htm text/html
.bmp image/bmp
.gif image/gif
.jpg image/jpg
Un tipo MIME identifica il tipo di dato che è inviato attraverso i protocolli HTTP o SMTP.
È uno standard per il formato dei documenti scambiati su Internet.
All’inizio era utilizzato per la posta elettronica e si limitava a messaggi in formato ASCII.
Quando due applicazioni si scambiano informazioni attraverso la rete, il mittente deve
specificare che tipo di messaggio sta inviando secondo lo standard MIME, così facendo,
l’applicazione che lo riceve, sa come trattarlo e può decodificarlo in maniera opportuna.
File HTTPCONFIGREADER.CS
Contiene la classe per la lettura del file di configurazione, usa tre metodi statici che
restituiscono il file di default, i MIME configurati e la cartella.
Tutte e tre ciclano gli elementi della proprietà desiderata e trovano eventuali
corrispondenze.
Il metodo ReadConfiguredMimes esegue un ciclo sulla Collection _MIMETYPES e
controlla se tra i tipi MIME supportati dal server c’è quello richiesto.
File HTTPSERVERMANAGER.CS
Contiene la classe che rappresenta il kernel del server web.
Quando è creata una nuova istanza della classe, sono valorizzate le proprietà _IP di tipo
IPAddress e _Port di tipo int.
La classe IPAddress rappresenta un indirizzo IP che identifica univocamente un
dispositivo all’interno di una rete TCP/IP.
La ListBox Messages è utilizzata per visualizzare i messaggi del server.
L’avvio del server avviene tramite il metodo ServerStart.
È istanziato un oggetto di tipo TcpListener sulla porta specificata e quindi un thread che fa
partire il metodo Listen.
Come prima cosa, è creato un socket per consentire il flusso dei dati, tramite il metodo
AcceptSocket dell’oggetto _Listener.
Socket Sock = _Listener.AcceptSocket();
A questo punto, quando avviene una connessione, il server segue il flusso seguente.
 Riceve la richiesta: ReceiveRequest.
 Processa la richiesta: ParseRequest.
 Legge la cartella: GetDirectory.
TPSIT 5
um 37 di 434
 Legge il nome del file: GetFile.
 Legge il tipo MIME: HTTPConfigReader.Read ConfiguredMimes.
 Se tutto va a buon fine, invia il file al browser: SendFileToBrowser.
Il metodo ReceiveRequest riceve in ingresso il socket e restituisce in output una stringa
contenente la richiesta per la lettura della quale è utilizzato il metodo Receive della classe
Socket.
Il metodo SendFileToBrowser legge il file dal percorso fisico tramite un BinaryReader e lo
invia al browser, per farlo, utilizza due metodi.
1. SendHeader che invia l’header HTTP.
2. SendToBrowser che invia il contenuto al browser.
Il metodo SendToBrowser riceve in ingresso un array di byte che invierà tramite il metodo
Send della classe Socket.
File MAINFORM.CS
Contiene le funzioni di avvio/interruzione del servizio e una ListBox per i messaggi.
TPSIT 5
um 38 di 434
http E WINDOWS 8.X
Il .NET Framework usa la classe HttpClient che supporta il paradigma async/await.
La classe HttpClient contiene un metodo per ognuno dei metodi elencati e funzionano tutti
allo stesso modo: accettano in input un URL e un oggetto HttpContent che rappresenta i
dati da inviare al server e restituiscono un oggetto HttpResponseMessage contenente la
risposta del server.
 Get*Async: sfrutta il metodo GET.
 PostAsync: sfrutta il metodo POST.
 PutAsync: sfrutta il metodo PUT.
 DeleteAsync: sfrutta il metodo DELETE.
I browser supportano nativamente solo chiamate GET (usate solo per recuperare una
risorsa) e POST (usate per inviare dati al server che decide l’operazione da effettuare in
base ai dati inviati e non in base al metodo).
PUT e DELETE non sono supportati dai browser.
Recuperare dati dal server
Si deve istanziare la classe HttpClient e dopo usare il metodo GetStringAsync per
recuperare la stringa dal server remoto, il formato della stringa è trasparente al metodo,
sarà compito dell’app interpretare la stringa per processarne il contenuto.
Per recuperare altri tipi di dati come immagini, video e file si usano i metodi
GetByteArrayAsync quando i file sono di piccole dimensioni e GetStreamAsync per
scaricare grandi quantità di dati.
È possibile usare il metodo GetAsync per cancellare una richiesta ancora in corso e di
stabilire il momento in cui una richiesta è considerata conclusa, al ricevimento delle
intestazioni HTTP o al ricevimento del messaggio.
// recupera l’HTML di una pagina web da un server remoto tramite HTTP
private async void GetHTMLAsync_Click(object sender, RoutedEventArgs e)
{ try {
HttpClient client = new HttpClient();
string body = await client.GetStringAsync("http://127.0.0.1");
GetHTMLAsyncResult.Text = (body);
}
catch (HttpRequestException ex)
{ GetHTMLAsyncResult.Text = ex.Message; }
}
// scarica un’immagine
private async void GetHTMLImageAsync_Click(object sender, RoutedEventArgs e)
{ try {
HttpClient client = new HttpClient();
var imgAsByteArray=await client.GetByteArrayAsync("http://127.0.0.1/esempio.png");
}
catch (HttpRequestException ex)
{ GetHTMLAsyncResult.Text = ex.Message; }
}
Inviare dati al server
Affinché sia in grado di processarli e salvarli sul proprio DB (DataBase).
Il secondo parametro del metodo PostAsync accetta in input un oggetto di tipo
StringContent che è una classe che eredita da HttpContent.
StringContent permette d’inviare stringhe al server ma ci sono altre classi che ereditano da
HttpContent e che permettono d’inviare dati in altri formati.
Per esempio, c’è la classe FormUrlEncodedContent che permette d’inviare coppie chiave
TPSIT 5
um 39 di 434
valore al server, come fa il browser quando s’invia un form.
La classe ByteArrayContent che permette d’inviare i dati come array di byte.
La classe MultipartContent che serializza i dati nel formato multipart.
Risposta del server: il metodo PostAsync restituisce un oggetto HttpResponseMessage
che non contiene solo i dati ma anche le intestazioni HTTP e il codice dello stato della
risposta.
La classe HttpResponseMessage espone dei metodi per recuperare i dati restituiti dal
server, per esempio il metodo ReadAsStringAsync, ReadAsStreamAsync e
ReadAsByteArrayAsync.
// inviare una stringa al server e recuperare la risposta sempre come stringa
private async void Post(object sender, RoutedEventArgs e)
{ try {
var content = new StringContent("Ciao");
HttpClient client = new HttpClient();
var response = await client.PostAsync("http://127.0.0.1:1892/home/post", content);
var textResponse = await response.Content.ReadAsStringAsync();
StringBuilder sb = new StringBuilder();
foreach (var item in response.Headers)
sb.AppendLine(item.Key + ": " + item.Value.First());
PostResult.Text = textResponse;
}
catch (HttpRequestException ex)
{ PostResult.Text = ex.Message; }
}
Manipolare le intestazioni HTTP
Con MVC (Model View Controller) WebAPI, il client può inviare al server l’intestazione
HTTP Accept impostando come valore application/xml per specificare che intende ricevere
i dati in formato XML.
Il metodo Add della proprietà DefaultRequestHeaders permette di aggiungere
un’intestazione HTTP da inviare al server.
La proprietà Headers, dell’oggetto contenente la risposta del server, recupera tutte le
intestazioni contenute nella risposta del server.
// aggiungere l’intestazione HTTP e leggere le intestazioni della risposta
private async void Post(object sender, RoutedEventArgs e)
{ try {
var content = new StringContent("Ciao");
HttpClient client = new HttpClient();
client.DefaultRequestHeaders.Add("Accept", "application/xml");
var response = await client.PostAsync("http://127.0.0.1:1892/home/post", content);
var textResponse = await response.Content.ReadAsStringAsync();
StringBuilder sb = new StringBuilder();
foreach (var item in response.Headers)
sb.AppendLine(item.Key + ": " + item.Value.First());
PostResult.Text = textResponse;
}
catch (HttpRequestException ex)
{ PostResult.Text = ex.Message; }
}
WebAPI
TPSIT 5
um 40 di 434
Permettono di accedere ad un determinato WS (Web Service).
Progettare un’Applicazione universale per l’upload e il download di file.
Usare in modo asincrono le richieste GET e POST.
TPSIT 5
um 41 di 434
TPSIT 5
um 42 di 434
JAVA
Esempio, leggere un URL da linea di comando, se non è valido o non ci sono parametri
aprire per default l’URL http://www.miosito.it/index.asp, prelevare la pagina letta e salvarla
su disco.
package web;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.MalformedURLException;
import java.net.URL;
public class Web {
public static void main(String[] args)
{ String un;
try
{un=args[0];}
catch (ArrayIndexOutOfBoundsException e)
{ un="http://www.miosito.it/index.asp";
System.out.println("Nessun URL definito, prendo "+un);
}
System.out.println("URL:"+un);
URL url;
boolean pippo=false;
try
{ url= new URL(un); }
catch (MalformedURLException e)
{ System.out.println("URL errato! Uso: http://www.miosito.it/index.asp ");
url = null;
pippo=true;
}
if (pippo)
try
{ url = new URL ("http://www.miosito.it/index.asp"); }
catch (MalformedURLException e){};
BufferedReader stream;
try
{ stream = new BufferedReader (new InputStreamReader (url.openStream())); }
catch (IOException e) {
System.out.println("Errore di apertura del file!");
stream=null;
System.exit(0);
}
File out=new File(".\\"+url.getFile());
FileWriter Output;
TPSIT 5
um 43 di 434
try
{ Output=new FileWriter(out); }
catch (IOException e)
{ Output=null; }
String l;
try {
while ((l=stream.readLine())!=null)
Output.write(l);
Output.flush();
Output.close();
}
catch (IOException e)
{ System.out.println("Errore di lettura!"); }
}
}
Esempio, visualizzare il codice HTML di una pagina di un server web.
File BROWSER.JAVA
package web;
import java.io.*;
import java.net.URL;
public class Browser {
public Browser (URL url)
{ System.out.println ("Inserisci URL: " +url);
try {
BufferedReader inStream = new BufferedReader (new
InputStreamReader(url.openStream()));
String line;
while ((line = inStream.readLine()) != null)
System.out.println (line);
inStream.close();
}
catch (IOException ex)
{ System.out.println ("Non esiste URL!"); }
}
}
File WEB.JAVA
TPSIT 5
um 44 di 434
package web;
import java.net.URL;
import java.net.MalformedURLException;
public class Web {
public static void main(String[] args)
{ try {
URL url = new URL ("http://localhost/fauser/");
Browser app = new Browser(url);
}
catch (MalformedURLException ex )
{ System.out.println ("Errore URL!"); }
}
}
TPSIT 5
um 45 di 434
WEBSOCKET
INTRODUZIONE
Il protocollo WebSocket, offre il meglio dei protocolli HTTP e TCP.
 Permette di avere un canale bidirezionale tra client e server (vantaggio del protocollo
TCP) sfruttando la porta 80 (vantaggio del protocollo HTTP): si riducono i tempi di
latenza e di utilizzo della rete.
 Possibilità d’inviare dati sia come stream di byte il cui formato è deciso dal
programmatore (tipico del protocollo TCP) sia come messaggi dal formato standard
(tipico del protocollo HTTP).
Frame WebSocket
Il frame dati è semplice: contiene informazioni sullo stato di un particolare frame, la
lunghezza del contenuto, la masking-key e i dati veri e propri.
Partendo dall’alto e da sinistra verso destra si ha.
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-------+-+-------------+-----------------------------+
|F|R|R|R|opcode|M| Payload len | Extended payload length
|
|I|S|S|S| (4) |A| (7)
| (16/64)
|
|N|V|V|V|
|S|
| (if payload len==126/127)
|
| |1|2|3|
|K|
| |
+-+-+-+-+-------+-+-------------+ - - - - - - - - - - - - - - +
| Extended payload length continued, if payload len == 127
|
+ - - - - - - - - - - - - - - - +------------------------------+
|
|Masking-key, if MASK set to 1 |
+-------------------------------+------------------------------+
| Masking-key (continued)
| Payload Data
|
+-------------------------------- - - - - - - - - - - - - - - -+
|
Payload Data continued ...
|
Bit descrizione
Fin (bit 0) serve ad indicare l’ultimo frame del messaggio, sarà quindi impostato a 1
quando il frame è l’ultimo di una serie, oppure se il messaggio è composto da un singolo
frame (in quanto primo e ultimo della serie).
RSV1, RSV2,RSV3 (bit 1-3)
Sono riservati alle estensioni dei WebSoket e dovrebbero essere 0 a meno che ne sia
richiesto l’uso da parte di una specifica estensione.
Opcode (bit 4-7)
Determinano la tipologia di frame, i frame di controllo comunicano lo stato del WebSoket,
mentre gli altri trasportano dati.
Le diverse tipologie di codici sono le seguenti.
TPSIT 5
um 46 di 434
 0x0: continuation frame, contiene dati che devono essere messi in coda al frame
precedente.
 0x1: text frame, questo frame ed eventuali successivi, contiene del testo.
 0x2: binary frame, questo frame ed eventuali successivi, contiene dati in formato
binario.
 0x3 - x7: riservati a frame che non siano di controllo, per essere sfruttati da estensioni
di WebSoket.
 0x8: close frame, questo frame dovrebbe chiudere la connessione.
 0x9: ping frame.
 0xA: pong frame.
 0xB - 0xF: riservati a frame di controllo.
Mask (bit 8)
Determina se un certo frame usi una maschera o no.
Lunghezza del Payload (bit 9-15 o 16-31 o 16-79)
Questi sette bit indicano la lunghezza del contenuto: se il valore in questo set è 126, la
lunghezza è rappresentata dai seguenti 2 byte (bit 16-31), se invece è127, la
rappresentazione è estesa fino al bit 79 (4byte in più).
Masking Key (i seguenti 4 byte)
Rappresenta la maschera, quando il bit MASK è impostato a 1.
Payload (tutti i dati successivi)
È possibile trasferire il payload con più frame: si conosce la lunghezza effettiva del
messaggio grazie all’informazione sulla lunghezza codificata nell’header del frame ed è
possibile aggiungere dati finché non si riceve un frame con il flag FIN.
Tutti i frame consecutivi saranno contrassegnati dall’opcode 0X0 (continuation frame).
Il frame dei dati permette di capire quanto sia grande un messaggi, il tipo di codifica e
l’eventuale maschera.
È possibile inviare messaggi di qualunque lunghezza, purché questa sia rappresentabile
con un numero base-64, in pratica 9.223.372.036.854.775.808 cifre.
Il WebSocket, oltre all’indirizzo IP e alla porta, mantiene un canale duraturo con il server,
in questo modo il client e il server possono inviare contemporaneamente dati tramite lo
stesso collegamento senza necessità che vi sia prima una richiesta.
Per la creazione del collegamento, sfrutta una funzione dell’handshake HTTP: il cambio di
protocollo tramite l’upgrade.
Quest’opzione serviva originariamente a criptare collegamenti tramite la porta 80 non
codificati attraverso un upgrade TLS.
Handshake HTTP: client.
Upgrade WebSocket
Richiesta HTTP di upgrade al
protocollo WebSocket.
Richiesta di sicurezza
Sicurezza del client tramite richiesta
dell’origine e chiave.
Handshake HTTP: server.
Conferma upgrade
Conferma della richiesta di upgrade
tramite risposta HTTP.
TPSIT 5
Chiave di sicurezza
Conferma del protocollo del server
tramite una chiave di sicurezza.
um 47 di 434
Affinché comunichino fra loro solo punti finali WebSocket ammessi, gli sviluppatori hanno
inserito nell’header HTTP alcuni meccanismi di sicurezza: il client genera nella sua
richiesta una chiave di sicurezza in Base64 che il server integra con una stringa standard,
producendo un valore di hash SHA-1 (Secure Hash Algorithm) che è a sua volta codificato
e rispedito al client.
In questo modo client e server si assicurano di utilizzare il protocollo WebSocket.
L’indicazione di origine nella richiesta protegge, inoltre, un server WebSocket da accessi
indesiderati da fonti esterne, facendo sì che solo client a lui noti e da lui supportati
possano costituire una connessione.
L’ultimo meccanismo di difesa ha luogo dopo l’handshake HTTP: il client WebSocket deve
codificare ogni pacchetto di dati con una maschera EX-OR, affinché server proxy
intermedi non considerino erroneamente il traffico WebSocket come richieste HTTP.
PROGRAMMAZIONE
Le classi per comunicare con il protocollo WebSocket sono incluse nel namespace
Windows.Networking.Sockets e sono: MessageWebSocket e StreamWebSocket.
Aprire la connessione con il server
La classe MessageWebSocket permette di gestire il ciclo di vita della comunicazione TCP
tramite messaggi; per aprire la connessione si usa il metodo ConnectAsync che accetta in
input un oggetto Uri che rappresenta l’URI del server verso cui aprire la connessione.
webSocket = new MessageWebSocket();
await webSocket.ConnectAsync(server);
Inviare messaggi al server
La classe MessageWebSocket espone la proprietà OutputStream di tipo IOutputStream
che rappresenta lo stream tramite il quale è possibile inviare dati al server.
Per inviare dati al server non si deve utilizzare la proprietà OutputStream ma si deve
creare
un’istanza
della
classe
DataWriter
contenuta
nel
namespace
Windows.Storage.Streams alla quale si passa in input lo stream, espone diversi metodi
tipizzati: WriteInt32, WriteString e WriteBoolean che scrivono in un buffer di memoria.
Terminato di scrivere nel buffer, s’invia il contenuto con il metodo StoreAsync e si scollega
il writer dallo stream con il metodo DetachStream.
La sola differenza tra il protocollo WebSocket e il protocollo TCP è che con il protocollo
WebSocket non ci si deve preoccupare del formato del messaggio (con il protocollo TCP
si metteva prima la lunghezza del messaggio e poi il messaggio stesso) perché
l’impacchettamento del messaggio è gestito dalla classe MessageWebSocket.
Ricevere messaggi dal server
La classe MessageWebSocket espone l’evento MessageReceived che si scatena ogni
volta che si riceve un messaggio dal server.
In questo modo non si deve fare altro che sottoscriversi all’evento e processare il
messaggio nel delegate.
Il delegate MessageReceived riceve l’istanza della classe MessageWebSocket che ha
generato l’evento e un parametro che rappresenta il messaggio ricevuto.
Di quest’ultimo oggetto s’invoca il metodo GetDataReader che restituisce un oggetto
DataReader contenente il messaggio.
Infine, s’invoca il metodo ReadString per ottenere il messaggio come stringa.
webSocket.MessageReceived += MessageReceived;
private void MessageReceived(MessageWebSocket sender,
MessageWebSocketMessageReceivedEventArgs args)
TPSIT 5
um 48 di 434
{ try {
Log("Messaggio Ricevuto; Tipo: " + args.MessageType);
using (DataReader reader = args.GetDataReader()) {
reader.UnicodeEncoding = Windows.Storage.Streams.UnicodeEncoding.Utf8;
Log(reader.ReadString(reader.UnconsumedBufferLength)); }
}
catch (Exception ex) {
WebErrorStatus status=WebSocketError.GetStatus(ex.GetBaseException().HResult);
Log("Errore: " + status + " - " + ex.Message);
}
}
WINDOWS 8.X
Progettare un’applicazione Windows Store che usa WebSocket.
TPSIT 5
um 49 di 434
CLIENT/SERVER
INTRODUZIONE
Un’applicazione client/server è divisa in due parti.
1. Applicazione server: fornitore di servizi.
2. Applicazione client: utilizzatore di servizi del server.
Client e server risiedono su PC diversi connessi da una rete di comunicazione.
 Aderisce ad un comune protocollo.
 Indipendente dal tipo di piattaforma, di SO e meccanismi base di comunicazione.
 Modello non simmetrico: richiesta/risposta.
 Ogni macchina può gestire molteplici client, oppure molteplici server o entrambi.
Esempio, comunicazione asimmetrica, molti client e un server.
Un server può essere implementato in due modi.
1. Server iterativo
È in grado di servire un client alla volta, questa limitazione può essere non influente nel
caso di client che restano connessi solo per il tempo necessario a ottenere una risposta
dal server.
while (server_attivo) {
// affinché il server è in attività accetta nuova richiesta dal client
if (richiesta_accettabile)
While (client_connesso) // affinché il client non decide di sconnettersi
<Leggi richiesta client>
<Rispondi al client>
}
// sconnettiti dal client
}
2. Server concorrente
Nel momento in cui ricevono una richiesta di connessione da parte di un client, generano
un nuovo processo che gestisce lo specifico client in modo da poter accettare e servire
altre richieste di connessione contemporanee.
Deve rispondere simultaneamente a quanti più client possibile.
La sua implementazione può essere eseguita utilizzando tre possibili meccanismi.
2.1 Server multithreading
Il server crea un thread per ogni client, quindi ad un certo istante ci sono tanti thread in
esecuzione quanti sono i client connessi.
Il server dealloca il thread quando il client si sconnette.
Vantaggi: molto semplice da implementare.
TPSIT 5
um 50 di 434
Svantaggi: richiede risorse in termini di CPU e memoria.
2.2 Single threading
Il server crea un solo thread che ha il compito di gestire la comunicazione con tutti i client.
La tecnica di comunicazione è l’overlapped I/O basata sul seguente meccanismo.
Ogni operazione d’I/O è eseguita in background.
Il thread è libero di eseguire nel frattempo altre operazioni, quando il SO segnala il
completamento di un’operazione d’I/O, il thread procede alla R/W dei dati.
Vantaggi: richiede meno risorse di memoria e CPU.
Svantaggi: è di più complessa implementazione.
2.3 Thread pool
Un numero prefissato di thread è creato e attivato, alla chiusura della connessione, il
thread relativo non è terminato ma è sospeso e “riciclato” per una nuova connessione.
Il client/server è il modello di riferimento per il networking a qualunque livello, si
contrappone come filosofia e come struttura al mainframe.
Il client manda un messaggio al server chiedendo un servizio.
Il server esegue il servizio richiesto e fornisce i risultati al client.
LIVELLI
Come evitare l’overhead dei protocolli a stack con molti livelli, per esempio ISO/OSI.
1. Modello connection oriented: tutto il traffico usa la stessa connessione, usato nelle
WAN (Wide Area Network), protocollo standard TCP.
2. Modello connectionless: usato nelle LAN, protocollo IP o UDP, lo stack ha meno
livelli, ne bastano tre.
Livello fisico: si occupa della trasmissione.
Livello collegamento dati: si occupa di gestire il traffico.
Livello rete: non è necessario, se è una LAN.
Livello trasporto: non è necessario.
Livello sessione: definisce l’insieme di richieste/risposte consentite.
Esempio, il client vuole fare la copia di un file utilizzando i servizi forniti da un server.
Client e server devono condividere qualche definizione, quindi si può includere lo stesso
file di definizioni nel codice delle due applicazioni.
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#define TRUE 1
#define MAX_PATH 255
#define BUF_SIZE 1024
TPSIT 5
/* definizioni necessarie per client e server */
/* lunghezza massima del nome del file */
/* quantità di dati da trasferire in una volta */
um 51 di 434
#define FILE_SERVER 243
/* indirizzo di rete del file del server */
/* definizioni delle operazioni consentite*/
#define CREATE 1
/* crea un nuovo file */
#define READ 2
/* legge i dati da un file e lo restituisce */
#define WRITE 3
/* scrive i dati sul file */
#define DELETE 4
/* cancella un file esistente */
/* codici di errore */
#define OK 0
/* operazione eseguita correttamente */
#define E_BAD_OPER -1
/* operazione richiesta sconosciuta */
#define E_BAD_PARAM -2
/* errore in un parametro */
#define E_IO -3
/* errore del disco o altro errore d'I/O */
/* definizione del formato del messaggio*/
struct message {
long source;
/* identità del mittente */
long dest;
/* identità del ricevente */
long opcode;
/* operazione richiesta */
long count;
/* numero di byte da trasferire */
long offset;
/* posizione nel file per avviare I/O */
long result;
/* risultato dell'operazione */
char name[MAX_PATH];
/* nome del file in corso di gestione */
char data[BUF_SIZE];
/* dati che devono essere letti o scritti */
};
int copy(char *src, char *dst);
void initialize (void);
void receive (int ,message m);
void send (long m, message n);
int do_create (message m,message n);
int do_read (message m,message n);
int do_write (message m,message n);
int do_delete (message m,message n);
int main(void)
{
message ml, m2;
/* messaggi in entrata/uscita */
int r;
/* codice di risultato */
system("cls");
/* Il server gira senza fine */
while(TRUE) {
/* protocollo richiesta/risposta*/
receive(FILE_SERVER, ml); /* blocco in attesa di un messaggio */
/* messaggio concernente la tipologia di richiesta */
switch(ml.opcode) {
case CREATE: r = do_create(ml, m2);
break;
case READ: r = do_read(ml, m2);
break;
case WRITE: r = do_write(ml, m2);
break;
case DELETE: r = do_delete(ml, m2);
break;
default: r = E_BAD_OPER;
}
m2.result = r;
/* ritorna il risultato al client */
/* protocollo richiesta/risposta */
send(ml.source, m2);
/* invio risposta */
TPSIT 5
um 52 di 434
}
system("pause");return(0);
}
void initialize (void) { }
void receive (int ,message m) { }
void send (long m, message n) { }
int do_create (message m,message n)
{ return (0); }
int do_read (message m,message n)
{ return (0);}
int do_write (message m,message n)
{ return (0); }
int do_delete (message m,message n)
{ return (0);}
/* copia i file usando il server */
int copy(char *src, char *dst)
{
struct message ml;
/* buffer messaggio*/
long position;
/* posizione corrente del file */
long client = 110;
/* indirizzo client */
initialize();
/* preparare esecuzione */
position = 0;
do {
ml.opcode = READ;
/* operazione è in lettura */
ml.offset = position;
/* posizione corrente nel file */
ml.count = BUF_SIZE;
/* numero di byte da leggere */
strcpy(ml.name, src);
/* copia del nome del file da leggere */
/* protocollo richiesta/risposta */
send(FILE_SERVER, ml);/* invia un messaggio al file server */
/* attesa per la risposta */
receive (client, ml);
/* scrivere i dati appena ricevuti per il file di destinazione */
ml.opcode = WRITE;
/* operazione è una scrittura */
ml.offset = position;
/* posizione corrente nel file */
ml.count = ml.result;
/* numero di byte da scrivere */
strcpy(ml.name, dst);
/* copia del nome del file da scrivere */
/* protocollo richiesta/risposta */
send(FILE_SERVER, ml);/* invia un messaggio al file server */
/* blocco di attesa per la risposta */
receive(client, ml);
position += ml.result;
} while( ml.result > 0 );
return(ml.result >= 0 ? OK : ml.result);
}
INDIRIZZAMENTO
macchina.processo
TPSIT 5
um 53 di 434
Bisogna individuare il server attraverso un identificatore univoco: un indirizzo.
Per sapere l’indirizzo del server, esistono diverse modalità.
Se il server gestisce più processi, come sapere qual è il processo giusto?
Il kernel del server non può saperlo.
Occorre che il client identifichi sia la macchina sia il processo.
Il messaggio è indirizzato alla macchina o ad un singolo processo?
Come numerare i processi?
Ogni macchina può numerarli partendo da 0.
Non è necessario uno schema globale perché la coppia macchina.processo identifica
univocamente un processo, senza ambiguità.
Problema: il sistema macchina.processo non è trasparente, per esempio, se il file server
inizialmente è attivo sulla macchina 23 e poi è spostato sulla macchina 17, allora qualsiasi
riferimento alla 23 non va più bene.
Altra soluzione.
Assegnare a ogni processo un indirizzo unico che non contiene il numero della macchina.
È necessario un processo centralizzato che fornisca tali numeri, può avere problemi di
scalabilità.
Altra soluzione.
Ogni processo sceglie a caso un numero da un insieme molto grande, per esempio 64 bit.
La probabilità che due processi scelgano lo stesso numero è bassa.
Name server
Inserire una macchina che realizzi il mapping: nome macchina in indirizzo.
Problema: è centralizzato ma si possono realizzare dei name server distribuiti, come per
esempio DNS.
Broadcast
TPSIT 5
um 54 di 434
Per individuare la macchina, può essere utilizzato anche dal server nel momento in cui
sceglie il suo numero identificativo per verificare che non sia già assegnato ad altri.
Il sender manda uno speciale pacchetto locate packet contenente il numero del processo
destinazione.
Tutte le macchine ricevono tale messaggio.
Tutti i kernel controllano se il processo destinazione è loro.
La macchina giusta rispedisce nella rete il proprio indirizzo.
Il sender prende questo indirizzo e lo usa per eventuali comunicazioni future.
Questa tecnica può essere applicata solo se esiste la possibilità di fare broadcast, per
esempio in Ethernet è semplice fare un broadcast.
Problema: la rete rischia di essere “inondata” di locate packet.
Si può mitigare il problema grazie al caching.
PRIMITIVE DI COMUNICAZIONE
Bloccante/non bloccante?
Bufferizzata/non bufferizzata?
Sender: se c’è il buffer può bloccarsi solo in S1 (se il buffer è pieno), altrimenti può
bloccarsi in S2 (quando il messaggio è stato inviato, in altre parole ha lasciato il nodo di
partenza), oppure in S3 (quando il messaggio è arrivato al nodo destinazione) oppure in
S4 (quando il messaggio è stato prelevato dal destinatario).
Receiver: si blocca sempre in S3.
La send bloccante con scambio di messaggi è quella che blocca finché il messaggio non è
stato prelevato dal destinatario, quella non bloccante è quella che prosegue non appena il
messaggio è stato preso in carico dal sottosistema di comunicazione, scaricata nel buffer
locale, se c’è o immessa nella rete.
La gestione del buffer presso il ricevitore può essere difficile per il SO.
Cosa fare se arriva un messaggio e nessuno ha dichiarato di voler ricevere qualcosa?
La cosa si semplifica un po’ se a livello utente (chi programma) si rende esplicita la
possibilità di bufferizzazione attraverso la definizione di mailbox che sono porte di
comunicazione con possibilità di bufferizzazione entro un limite definito.
TPSIT 5
um 55 di 434
Come essere sicuri che un messaggio spedito arrivi a destinazione?
Esempio, considerare primitive bloccanti.
Il client esegue una send e si blocca.
Quando il messaggio è stato spedito, allora il client si sblocca.
Se il sottosistema di comunicazione non è affidabile, il client non sa se il messaggio è
davvero arrivato a destinazione.
Possibili approcci.
Ridefinire la semantica della send, in modo che il sistema non garantisca la consegna dei
messaggi.
È l’utente che deve preoccuparsi d’implementare meccanismi di affidabilità, per esempio il
sistema postale.
Si richiede al kernel ricevente di mandare un messaggio di ACK al mittente.
Il client si sblocca solo dopo aver ricevuto l’ACK.
Se si sfrutta il fatto che nel paradigma client/server si hanno i seguenti punti.
1. Richiesta dal client al server.
2. Risposta dal server al client.
Il server non manda l’ACK ma è la risposta che funge da ACK.
Quando è opportuno inviare l’ACK?
A ogni singolo pacchetto?
Problematiche di comunicazione.
I messaggi sono divisi in pacchetti che sono trasmessi.
Vantaggio: se un pacchetto va perso, basta ritrasmetterlo.
Svantaggio: molto traffico nella rete.
A ogni messaggio?
Vantaggio: meno traffico.
Svantaggio: se un pacchetto va perso, il client non ricevendo l’ACK invia nuovamente
l’intero messaggio.
REQ
REP
ACK
AYA
IAA
TA
AU
TPSIT 5
Client – Server
Server – Client
Client – Server
Server – Client
Server – Client
Server – Client
Si usa per fare la richiesta.
È la risposta del server.
Conferma arriva di un pacchetto.
Are You Alive?
I Am Alive.
Try Again (mailbox piena).
Address Unknown.
um 56 di 434
ALTRI METODI PER LA PROGETTAZIONE DI APPLICAZIONI CLIENT/SERVER
IPC (Inter Process Communication)
Se client e server si trovano sulla stessa macchina, si hanno a disposizione gli strumenti
IPC del SO.
 Linguaggi concorrenti, in altre parole dotati d’istruzioni apposite per attivare e far
sincronizzare/comunicare più processi/thread.
 System calls messe a disposizione dal SO.
 Librerie per lo sviluppo di applicazioni concorrenti.
RPC
Fu progettata da Sun Microsystems nei primi anni ’80.
La gestione dell’interazione tra client e server avveniva tramite primitive d’I/O del SO: il
livello di astrazione era troppo basso.
Allora si cercò di fornire al programmatore un meccanismo per accedere a risorse remote
attraverso la chiamata a sottoprogramma, nascondendo tutti i dettagli relativi al setup della
connessione, alla serializzazione dei parametri e all’eterogeneità delle piattaforme.
Un’applicazione sulla macchina A chiama un sottoprogramma sulla macchina B.
A si sospende, inizia l’esecuzione del sottoprogramma sulla macchina B.
Il sottoprogramma chiamante, lato client, richiama una versione diversa del
sottoprogramma, chiamata client stub.
Il client stub costruisce il messaggio e tramite send lo invia.
Il server passa il messaggio al server stub tramite una receive.
Il server stub legge il messaggio, chiama il sottoprogramma che ritorna il risultato allo stub
e costruisce il messaggio da inviare al client.
Dal punto di vista concettuale è come se l’invocazione avvenisse in locale.
L’azione di mettere i parametri in un messaggio è chiamata marshaling.
L’azione di estrarre il risultato dal messaggio è chiamata unmarshaling.
TPSIT 5
um 57 di 434
TPSIT 5
um 58 di 434
DCOM (Distributed Component Object Model)
Metodo OO (Object Oriented), permette agli oggetti COM in esecuzione su una macchina
di creare oggetti COM su altre macchine, è un prodotto Microsoft ma è uno standard
aperto.
CORBA (Common Object Request Broker Architecture)
È la specifica di un’architettura standard per ORB (Object Request Brokers), in altre parole
intermediari di richieste a oggetti.
Permette agli oggetti su una macchina di richiamare i metodi di oggetti su altre macchine,
è uno standard aperto.
Java RMI (Remote Method Invocation)
È una trasposizione in ambito OO di RPC, è fornita la possibilità d’invocare un metodo su
un oggetto remoto come se fosse locale.
PROGRAMMAZIONE
È sempre un client a contattare in modalità sincrona il server e mai viceversa.
Il server è sempre in ascolto per una richiesta del client.
Al ricevimento di una richiesta del client, il server risponde prima alla richiesta iniziale.
Quando sono connessi, esiste una relazione paritetica il cui il client o il server possono
richiedere informazioni uno all’altro.
Quest’approccio è sempre corretto, però in qualche caso può non essere sufficiente, per
esempio è il server a segnalare o a richiedere qualcosa ai client e non viceversa.
Queste situazioni possono essere gestite con il modello unidirezionale: i client eseguono
un polling al server per verificare che questi non abbia da comunicargli qualcosa.
L’architettura del .NET Framework Remoting offre la possibilità di una comunicazione
vera tra server e client, per far questo è necessario che il client sia disposto ad accettare
richieste provenienti dal server.
Come questo è implementato dipende dal canale utilizzato.
HTTP: non consente questo modello di bidirezionalità.
TCP: lo consente, a patto, però, di aprire una nuova porta TCP sul client per ricevere le
richieste dal server, in pratica due canali aperti su due porte differenti.
Sulla prima il client contatta il server e sulla seconda il server contatta il client.
Thick client (spesso, grosso)
È un’applicazione che prevede una logica elaborata per la manipolazione dei dati che gli
sono trasmessi dal server.
Thin client (sottile, leggero)
È un’applicazione che non fornisce alcun supporto elaborativo e si limita a visualizzare i
dati che gli sono trasmessi dal server.
Un client deve impacchettare la sua richiesta in modo tale che questa possa essere
compresa dal server.
Il server riceve la richiesta, esegue il servizio e reinvia la risposta in modo comprensibile
dal client.
I passi sono gli stessi di un’applicazione che effettui una chiamata ad un sottoprogramma
locale definito in una libreria.
L’interazione client/server è qualcosa di simile ad una chiamata a sottoprogramma locale.
1. Impone al server un’interfaccia standard per il servizio.
2. Fa sì che il programmatore possa invocare un servizio remoto in modo semplice.
Come nei sottoprogrammi locali, un servizio può essere reso più efficiente in modo
trasparente al client, a patto che l’interfaccia rimanga inalterata.
TPSIT 5
um 59 di 434
L’architettura a livelli è un modo per designare sistemi composti da più sotto sistemi
distinti, ciascuno dei quali svolge un insieme coerente di funzionalità e concorre con gli
altri al raggiungimento dell’obiettivo complessivo del sistema.
In ogni livello gli elementi devono svolgere una determinata funzione e scambiare, se
necessario, dati e risultati con gli altri elementi dello stesso livello e con quelli del livello
inferiore o superiore.
Ogni livello può essere costituito da uno o più componenti residenti sullo stesso PC o
distribuiti su PC diversi.
Le architetture possono differire a seconda di cosa si mette sul client e cosa sul server.
Si può scomporre le funzionalità in tre insiemi.
1. UI.
2. Elaborazione.
3. Gestione persistente dei dati.
A seconda di come sono allocate queste funzionalità si distinguono.
1. Architetture a due livelli (2-tier): two-tiered architectures
Sviluppata negli anni ‘80, partendo dall’esperienza dei file server.
È formata da tre componenti distribuiti su due livelli.
1. GUI (Graphics User Interface): interfaccia di comunicazione con l’utente, è il livello di
presentazione Client Tier; si hanno tutte le tecnologie dedicate all’interfaccia utente
come Silverlight ma anche le tecnologie per la gestione di dati locali e di connessione a
servizi di rete RIA (Rich Internet Application) Services.
2. PM (Processing Management): contiene gli elementi che implementano il cuore
dell’applicazione, in altre parole le regole con le quali devono essere elaborate le
informazioni presenti sul DB prima di essere mostrate attraverso lo strato di
presentazione, Middle o Business Tier; si hanno le tecnologie condivise con il livello
client, come Data Workspace e RIA Services ma anche la parte logica
dell’applicazione.
3. DBM (DB Management): è il livello di accesso ai dati, responsabile dell’accesso e del
mantenimento dei dati persistenti dell’applicazione, esso si estende fino al DBMS.
L’architettura assegna la GUI al client, il DBM al server e divide il PM tra client e server.
Il client comunica direttamente con il server, senza intermediari, scarsa scalabilità, in altre
parole la capacità di adattarsi alla crescita del sistema, infatti, se il server ricevesse un
carico di lavoro troppo elevato da parte dei client, l’unica soluzione consisterebbe
nell’aggiornare il server.
In pratica, l’applicazione gira sul desktop, componenti server e DB sono su un PC di rete.
Presentazione distribuita
Tutta la logica è nel server e il client si occupa solo d’interagire con il server che gli affida
compiti specifici di presentazione.
Presentazione remota
La logica applicativa e i dati sono nel server, il client fa da pannello di controllo remoto.
Logica distribuita
Il client contiene anche una parte della logica distribuita, si cerca di alleggerire il server dal
punto di vista computazionale, il client si appesantisce ma si rende più scalabile il server.
Accesso remoto ai dati
Il server fornisce solo l’accesso ai dati grezzi, il client è un front end per la loro gestione.
Base di dati distribuita
Il client contiene anche una parte dei dati.
TPSIT 5
um 60 di 434
Questa suddivisione dà buoni risultati fino a quando non si verifica la situazione in cui un
server restituisce più dati di quelli che il client è in grado di elaborare.
All’aumentare del numero degli utenti attivi, il carico sul DB aumenta.
All’aumentare del numero dei DB con cui un’applicazione deve interagire, aumenta il
numero di connessioni che l’applicazione deve mantenere aperte.
2. Architetture a tre livelli (3-tier): three-tiered architectures
Sviluppata negli anni ’90, è adottatata in Internet, un livello client (browser), un livello di
presentazione (server web), un livello di gestione dati (DB server) e un livello applicazione.
È formata da tre componenti distribuiti su tre livelli.
1. GUI: client.
2. DBM: server.
3. MiddleWare: si trova tra la GUI e il DBM e fornisce la business logic dell’applicazione.
Lo spostamento della business logic e l’accesso ai dati nel livello intermedio significa una
quantità minore di codice per i client, semplifica, inoltre, la gestione dei componenti di
processo aziendali e la logica di accesso ai dati.
MiddleWare
Letteralmente S/W di mezzo posto a metà fra il SO e le applicazioni, consiste in un
insieme di servizi e/o di ambienti di sviluppo di applicazioni distribuite che permettono a
più entità (processi e oggetti), residenti su uno o più PC, d’interagire attraverso una rete
d’interconnessione a dispetto di differenze nei protocolli di comunicazione, architetture dei
sistemi locali e SO.
Alcuni tipi di middleware sono TM (Transaction Monitor) e ORB, per esempio IBM
(International Business Machines) WebSphere e Microsoft Transaction Server.
L’utilizzo del middleware consente di ottenere un elevato livello di servizio per gli utenti e
un elevato livello di astrazione per i programmatori.
La flessibilità e la natura distribuita delle applicazioni multilivello rendono quest’architettura
ideale sia per progetti Internet sia per progetti di reti aziendali.
In pratica, l’applicazione gira sul desktop, componenti server e DB girano sul server web.
Server diversi ma un solo DB.
Vantaggi.
 Scalabilità: l’architettura può essere estesa.
 Gestibilità: isolamento dei problemi, distribuzioni e configurazioni.
TPSIT 5
um 61 di 434
 Trasparenza: commutazione da un PC ad un altro.
Modalità connessa
Nelle applicazioni client/server, si stabilisce una connessione ad un DB all’avvio
dell’applicazione e si mantiene attiva durante tutto il ciclo di vita dell'applicazione.
Quest’approccio diminuisce il tempo di attesa delle operazioni sul DB poiché elimina il
tempo necessario alla connessione ma in alcuni casi non è possibile utilizzare per risorse
di sistema limitate.
Modalità disconnessa
Si apre una connessione solo quando serve, si estrae un blocco di dati, si memorizza sul
client e quindi si chiude la connessione per rilasciare le risorse lato server ad essa
associate; dopo aver caricato i dati sul client, è possibile utilizzarli per eseguire qualsiasi
tipo di elaborazione, come per esempio aggiungere nuovi record oppure modificare o
eliminare quelli esistenti.
Una volta che sono terminate le opportune elaborazioni sui dati, si riapre la connessione,
si scaricano i dati sul server e si richiude di nuovo.
Le applicazioni sono connesse al DB solo il tempo necessario per recuperare o aggiornare
i dati, di contro, aprire e chiudere la connessione significa maggiore tempo di attesa.
Architetture browser a tre livelli
Il browser è un thick client perché elabora codice lato client che gli è trasmesso ogni volta
che accede all’applicazione, il server web è il server.
La differenza sostanziale è che, a differenza di una connessione su una LAN, non si
hanno garanzie che la connessione ad un server di dati su Internet rimanga attiva.
Le tecnologie di accesso ai dati utilizzate in Internet spesso partono dal presupposto che
una connessione sia continua fino al completamento di una transazione.
Le implicazioni sono minime per applicazioni che eseguono solo richieste ma sono
rilevanti per quelle che richiedono funzionalità come la modifica e l’eliminazione dei dati.
Il risultato di una query dev’essere conservato anche se la connessione è interrotta.
In fase di aggiornamento, è necessario che sia eseguita un’operazione automatica di
riconnessione e sincronizzazione del DB, questa situazione è definita accesso ai dati
senza connessione, modalità di lavoro offline: le tecnologie di accesso ai dati includono sia
funzionalità con connessione sia senza connessione.
La creazione di script server con tecnologie di accesso ai dati incorporate consente di
accedere in modalità browser a dati aziendali su Internet e di fornire le informazioni ai
client come dati in formato HTML.
In questo modo un utente può visualizzare i dati aziendali nello stesso modo in cui
visualizza altre informazioni su Internet.
ARCHITETTURA DI UN’APPLICAZIONE CLIENT/SERVER
TPSIT 5
um 62 di 434
Server
Contiene la logica dell’applicazione, monitora una porta TCP e attende le richieste di
connessione da parte dei client.
Client
È un’applicazione desktop che permette all’utente di collegarsi al server, non deve far altro
che adeguarsi al protocollo e lavorare come un tramite tra l’utente e il server: è un
intermediario.
Il server deve svolgere i seguenti compiti.
1. Autenticare: l’identità e l’autenticità del client; per esempio l’utente deve digitare
username e password, il server provvede all’autorizzazione, validando la richiesta di
accesso, associando il client ad un’utenza del sistema.
2. Autorizzare: il client a compiere determinate operazioni.
Il collegamento è temporaneo, dura finché il client è connesso e si chiama sessione.
Networking
Il server deve monitorare una porta del PC, in attesa delle connessioni in ingresso, per
esempio il server web ascolta la porta 63301.
I client, per connettersi al server, devono conoscere il suo indirizzo e la porta da esso
ascoltata.
Stabilita la connessione, il protocollo TCP permette di scambiare dati in ambo le direzioni.
I dati scambiati, a livello del protocollo di trasporto, sono sequenze di byte.
Client e server sono collegati in una LAN o via Internet: una rete di questo tipo non può
essere definita “ad alta affidabilità”, se qualcuno intercetta la comunicazione, infatti, può
leggervi in chiaro i dati dell’utente connesso.
Il server deve offrire un meccanismo di connessione criptato, per esempio basato su
SSL.
La GUI gira al server qualsiasi richiesta svolta dall’utente e attende dal server una risposta
o una conseguenza.
Per evitare che un rallentamento della rete influenzi negativamente il grado di reattività del
client, tutte le comunicazioni con il server devono essere svolte in thread secondari, diversi
da quello usato dalla GUI per dialogare con l’utente.
La caduta della connessione, sia lato server sia lato client, manda in crash l’applicazione.
Bisogna prevedere un meccanismo di recovery, da implementare sia lato server sia lato
client che consenta all’utente di riprendere l’applicazione dal punto che si era interrotta
dopo essersi riconnesso.
Si potrebbe, per esempio stabilire un timeout: se la connessione cade, il server aspetta
per un minuto che il client si riconnetta e solo allo scadere di questo intervallo lo esclude
automaticamente e fa procedere gli altri client.
TPSIT 5
um 63 di 434
Protocollo applicativo
Il protocollo usato per le applicazioni è un protocollo over TCP/IP, perché si basa sui
socket TCP per lo scambio bidirezionale di dati.
Il server, dialoga attraverso delle richieste HTTP, all’interno delle quali è possibile inserire
comandi, sotto forma di parametri.
Il vantaggio delle applicazioni over HTTP è che possono essere utilizzate anche da dietro
un firewall o in una LAN, attraverso un proxy HTTP.
Se puoi navigare sul web, allora puoi anche usare l’applicazione, perché il canale di
comunicazione è lo stesso.
Il modo più semplice per progettare un protocollo di livello applicativo per un server di tipo
testuale è quello di prevedere parole chiave corrispondenti alle operazioni che il server
deve rendere disponibili e che costituiscono i veri e propri comandi.
I dati scambiati, a livello del protocollo applicativo, danno significato alle sequenze di byte
inviate e ricevute.
Tipo di dato scambiato
I byte scambiati attraverso la connessione sono interpretati come caratteri ASCII.
Non appena la connessione è stabilita, il server resta in attesa di una richiesta da parte del
client.
Tutto il protocollo applicativo si articola sul paradigma richiesta/risposta: il client fa una
richiesta e il server gli risponde.
Richieste
Un messaggio è costituito da parti, ciascuna delle quali è una stringa.
La prima parte è il comando, mentre le successive sono i parametri.
Sono comprese in una sola riga che ha il seguente formato.
COMANDO PARAMETRO1 PARAMETRO2 …
Il comando esprime la natura della richiesta.
La quantità e la qualità dei parametri dipendono dallo specifico comando espresso.
Comandi e parametri sono separati da un blank.
Per esempio, il comando per chattare è il seguente.
MESSAGE MESSAGGIO
MESSAGE Ciao
I comandi e parametri sono codificati con la codifica URL encoding, la stessa che si
utilizza per la composizione degli indirizzi URL.
Per esempio, il messaggio di chat “In che città vivi?” è inviato con la richiesta seguente.
MESSAGE In+che+citt%C3%A0+vivi%3F
Risposte
Una volta inviata la richiesta, il client attende risposta dal server.
Le risposte possono essere articolate e contenere molti dati.
Per questo saranno organizzate nella seguente maniera.
BEGIN
MESSAGGIO1
MESSAGGIO2
TPSIT 5
um 64 di 434
MESSAGGIO3
…
END
Ciascuno dei messaggi contenuti nella risposta ha la stessa forma delle richieste.
COMANDO PARAMETRO1 PARAMETRO2 …
Comando e parametri devono essere sottoposti alla codifica URL.
Esecuzione
Per provare il server, occorre un client.
Se il server usa un protocollo applicativo puramente testuale, la verifica del suo corretto
funzionamento può essere eseguita utilizzando un client generico TELNET.
TELNET esegue una connessione al server individuato dall’indirizzo IP e dal numero di
porta TCP indicati, dopo di che ciò che è digitato dal client è trasmesso mediante il socket
e ciò che è ricevuto è visualizzato.
Eseguendo contemporaneamente più client TELNET, è possibile verificare la modalità di
funzionamento del server.
Avviare il server, quindi aprire la CLI (Command Line Interface) e digitare il comando
seguente.
Premere INVIO.
Stabilita la connessione, è possibile digitare i comandi.
Al limite, il server può fare solo l’eco di tutti i dati che riceve ritrasmettendoli al client da cui
li ha ricevuti.
TPSIT 5
um 65 di 434
TPSIT 5
um 66 di 434
Microsoft Visual C#
Progettare una chat su TCP/IP.
TPSIT 5
um 67 di 434
Progettare una chat sicura su TCP/IP.
Client
Di tipo WPF (Windows Presentation Foundation), implementa le classi che permetteranno
la comunicazione con il server in modalità criptata con il flusso NetCryptoStream,
l’infrastruttura di comunicazione sicura è del tipo end-to-end.
Server
Di tipo console, implementa le classi necessarie alla coordinazione dei messaggi criptati in
ricezione e spedizione tra i vari client.
Libreria SECURENET.DLL
Entrambe le applicazioni fanno uso della libreria di classi, serve a mantenere le classi
comuni tra client e server che implementa il flusso criptografato NetCryptoStream.
Flusso criptografico
È in grado di trasformare le informazioni inviate in chiaro dai client in informazioni criptate
e di ritrasformarle in chiaro in ricezione.
Il server decifra le informazioni ricevute e le rispedisce criptate a tutti i client connessi che,
a loro volta, le decifreranno e le visualizzeranno a video.
Questo poiché ogni client utilizza una propria chiave segreta condivisa solo con il server.
La lettura e la scrittura sul canale creato è gestita tramite un oggetto NetworkStream,
passato al costruttore della classe che implementa il flusso criptografato, inizializzato dal
metodo GetStream dell’oggetto TcpClient.
Il thread di ascolto implementato nel metodo ClientConnected, una volta avviato e stabilita
la comunicazione criptata, rimane in attesa dei messaggi dei client da trasmettere a tutti gli
altri utenti connessi.
TPSIT 5
um 68 di 434
TPSIT 5
um 69 di 434
Progettare una chat.
TPSIT 5
um 70 di 434
Microsoft Visual C++
Progettare un’applicazione per un gioco di carte.
Il server è in ascolto e quando il client stabilisce una connessione, il server avvia la partita.
Il client, dopo aver impostato l’IP del PC su cui è in esecuzione il server, si connette e
resta in attesa che il server gli comunichi tutte le carte del mazzo.
Dopo aver “distribuito” le carte, si gioca a turno.
L’applicazione invierà la carta giocata all’avversario e resterà in attesa di una risposta.
Se si è collegati a Internet, è possibile inviare dal server un’email, indirizzata al client,
contenente l’indirizzo IP.
Il protocollo di comunicazione lavora nelle due direzioni e contiene come primo carattere
un carattere di controllo che corrisponde al tipo di messaggio che un PC invia all’altro.
Il server si occupa della gestione della partita: inizia, mescola il mazzo, invia al client tutte
le informazioni (4 per ogni carta, messaggi B, C, D, E) relative al mazzo, invia il turno
(messaggio F) e il messaggio A che indica al client che la partita può iniziare.
I messaggi G e H indicano che l’avversario ha giocato la sua carta e la comunica all’altro
giocatore.
Carattere di controllo
A
B
C
D
E
F
G
H
N
M
Tipo di Messaggio
Direzione
Fine trasmissione carte del mazzo, più il
S->C
turno.
Carta mazzo numero.
S->C
Carta mazzo seme.
S->C
Carta mazzo punto.
S->C
Carta mazzo immagine.
S->C
Turno iniziale.
S->C
Posizione carta giocata dal server (IG1).
S->C
Posizione carta giocata dal client (IG2).
C->S
Il server si è disconnesso oppure ha chiuso
S->C
l’applicazione.
Il Client si è disconnesso oppure ha chiuso il
C->S
programma.
Passare allo scanner le 40 carte più una carta rovesciata che rappresenta il mazzo e
salvarle in un file BMP (BitMaP) con risoluzione 256 colori.
Creare un file di risorse e inserire tante nuove bitmap quante sono le carte e ricopiare in
ognuna le immagini salvate precedentemente.
Creare una DLL di risorse.
Supporre di voler distribuire l’applicazione in un Paese in cui per giocare a carte si utilizzi
un mazzo con immagini diverse.
Basta inserire le immagini delle nuove carte in un’altra DLL di risorse, mantenendo
invariati i nomi delle immagini nella risorsa e chiedere al giocatore, all’avvio
dell’applicazione, di scegliere il mazzo di carte che vuole utilizzare.
Si può fare la stessa cosa per le didascalie dei componenti visuali dell’applicazione,
inserendo in una risorsa le stringhe delle didascalie tradotte nella lingua locale.
TPSIT 5
um 71 di 434
TPSIT 5
um 72 di 434
Microsoft Visual Basic
TPSIT 5
um 73 di 434
SIGNALR
Permette la comunicazione bidirezionale in real-time tra client e server, in altre parole i dati
visualizzati devono essere aggiornati costantemente, per esempio le quotazioni di borsa.
In questi casi è il server che deve inviare gli aggiornamenti al client in modalità push,
quindi evitando che sia quest’ultimo a controllare periodicamente la presenza di nuovi dati
in modalità polling.
Il protocollo HTTP non permette chiamate push, per questo è stato progettato SignalR.
L’architettura è basata su due API (Application Programming Interface).
1. PersistentConnection: basso livello.
2. Hub: alto livello.
Per eseguire le comunicazioni, SignalR usa le modalità di trasporto seguenti.
WebSocket
Dev’essere supportata sia dal server sia dal client.
Server Sent Events
Un browser riceve aggiornamenti automatici dal server.
Forever Frame
È creato un iFrame (inline Frame) nascosto che esegue una richiesta verso un endpoint
del server che non è mai completata.
Il server invia script al client che sono eseguiti immediatamente, realizzando, quindi, una
comunicazione monodirezionale tra server e client.
La comunicazione da client a server usa una connessione separata e, come nelle richieste
HTTP, una nuova connessione è creata ogni volta che devono essere inviati dati.
AJAX Long Polling
Non crea una connessione persistente ma fa polling verso il server con una richiesta che
rimane aperta fino a che quest’ultimo non invia una risposta.
A questo punto, la connessione è chiusa e subito ne è fatta una nuova.
All’avvio della connessione, è eseguita una negoziazione per stabilire quale modalità di
trasporto utilizzare: questa procedura è chiamata TransportNegotiation.
SignalR è costruito sopra OWIN (Open Web Interface for .NET), un’astrazione che
TPSIT 5
um 74 di 434
disaccoppia le applicazioni web dal server e, quindi, può essere usato per il self-hosting di
un’applicazione web in un proprio processo, al di fuori di IIS (Internet Information
Services).
Il client non dev’essere necessariamente una pagina HTML, in pratica le piattaforme
supportate sono molteplici, per esempio in Xamarin il client è disponibile come libreria di
classi portabile, quindi si può usare nelle app per iOS e Android.
Esempio, progettare un canale di comunicazione per monitorare in real-time la posizione
dei dispositivi mobili e inviare a essi comandi per eseguire determinate operazioni.
Server
Fare clic su File/Nuovo/Progetto… (CTRL+MAIUSC+N).
Selezionare Modelli/Visual C#/Web/Applicazione Web ASP.NET vuota.
Fare clic con il pulsante destro sul nome del progetto in Esplora soluzioni, dal menu
contestuale selezionare Aggiungi/Nuovo elemento… e nella finestra che si apre
selezionare Visual C#/Web/SignalR/Classe SignalR Hub (v2).
Il template crea l’Hub seguente.
public class MyHub1 : Hub {
public void Hello()
{ Clients.All.hello(); }
}
Il metodo Hello potrà essere richiamato da ogni client connesso, non è definito nel codice
perché è un oggetto di tipo dinamico e quindi è risolto a compile-time.
L’istruzione Clients.All.hello fa sì che tutti i client ricevano un evento, generato dal server,
con questo nome tramite una comunicazione push, in pratica è inviato un payload in
formato JSON (JavaScript Object Notation) che indica che dev’essere invocato il gestore
corrispondente.
L’applicazione è composta da due attori.
1. Admins: sono gli amministratori che tracciano la posizione dei dispositivi e mandano
loro messaggi.
TPSIT 5
um 75 di 434
2. Clients: sono gli utenti che comunicano agli amministratori i loro spostamenti e
possono inviare richieste di aiuto.
Terminata la progettazione dell’Hub SignalR, bisogna registrare il servizio all’avvio
dell’applicazione.
Fare clic con il pulsante destro sul nome del progetto in Esplora soluzioni, dal menu
contestuale selezionare Aggiungi/Nuovo elemento… e nella finestra che si apre
selezionare Visual C#/Web/Generale/Classe di avvio di OWIN.
Il template crea il metodo seguente, bisogna aggiungere l’istruzione per configurare l’Hub
in modo che sia accessibile attraverso il web.
public void Configuration(IAppBuilder app)
{ app.MapSignalR(); }
Client Windows Phone 8.1
File PACKAGES.CONFIG
Aggiungere al progetto i seguenti pacchetti.
<?xml version="1.0" encoding="utf-8"?>
TPSIT 5
um 76 di 434
<packages>
<package id="Coding4Fun.Toolkit.Controls" version="2.0.7" targetFramework="wp80" />
<package id="Microsoft.AspNet.SignalR.Client"version="2.0.1"targetFramework="wp80"/>
<package id="Microsoft.Bcl" version="1.0.19" targetFramework="wp80" />
<package id="Microsoft.Bcl.Build" version="1.0.13" targetFramework="wp80" />
<package id="Microsoft.Net.Http" version="2.1.10" targetFramework="wp80" />
<package id="Newtonsoft.Json" version="5.0.8" targetFramework="wp80" />
<package id="WPtoolkit" version="4.2013.08.16" targetFramework="wp80" />
</packages>
File MAINPAGE.XAML
L’interfaccia è composta da una sola pagina che visualizza una mappa con la posizione
corrente dell’utente.
Affinché l’app sia in grado di utilizzare il servizio di localizzazione dello smartphone,
bisogna aggiungere le seguenti funzionalità.
<Capabilities>
<Capability Name="ID_CAP_NETWORKING" />
<Capability Name="ID_CAP_MEDIALIB_AUDIO" />
<Capability Name="ID_CAP_MEDIALIB_PLAYBACK" />
<Capability Name="ID_CAP_SENSORS" />
<Capability Name="ID_CAP_WEBBROWSERCOMPONENT" />
<Capability Name="ID_CAP_LOCATION" />
TPSIT 5
um 77 di 434
<Capability Name="ID_CAP_MAP" />
</Capabilities>
Client Windows 8.1
Fornisce le funzionalità di amministrazione del sistema, in pratica visualizza su una mappa
la posizione di tutti i client connessi.
Aggiungere al progetto gli stessi pacchetti e, inoltre, il Bing Maps SDK (Software
Development Kit).
TPSIT 5
um 78 di 434
MODULO 2
APPLICAZIONI DISTRIBUITE
Introduzione
80
Comunicazione cross process 83
Messaggi su code 88
WCF 106
WS 124
Grid service
192
REST/POX
194
Applicazioni orientate ai servizi
TPSIT 5
206
um 79 di 434
INTRODUZIONE
GENERALITà
URI
È una stringa che identifica univocamente una risorsa generica che può essere un
indirizzo web, un documento, un’immagine, un servizio o un indirizzo di posta elettronica.
Serve a “identificare”, mentre un URL serve a “localizzare”.
Il fraintendimento nasce dal fatto che localizzare, equivale anche a identificare, dunque
ogni URL è anche un URI, al contrario, esistono degli URI che non sono URL.
Esempio, Andrea Sperelli può essere un URI perché identifica una persona, dal suo nome,
però, non è possibile ricavare alcuna informazione su dove risieda, dunque non è un URL.
Al contrario, Via Roma 148, Novara, è un URL, perché identifica un luogo, se a ogni
numero civico corrisponde una sola residenza, ecco in questo caso allora definire un luogo
equivale a definire anche un’identità, dunque un URI.
La struttura generale è la seguente.
potocollo:// host.dominio.toplevel:port/path/filename[?query][#fragment]
Dove.
protocollo: può essere HTTP, HTTPS, FTP, SMTP, …
host: il nome del PC, in genere WWW (World Wide Web).
dominio: è una stringa che identifica il dominio di secondo livello.
toplevel: è una stringa che identifica il dominio di primo livello, .com, .net, .it.
port: indica la porta TCP su cui il protocollo HTTP è in ascolto, per default 80.
path: opzionale, indica il percorso della cartella per raggiungere la risorsa.
filename: è il nome della risorsa.
query: indica un elenco di argomenti di cui la pagina può disporre.
www.miosito.it/search.jsp?nome=Andrea&cognome=Sperelli
fragment: il browser identifica una sottoparte della risorsa, invece è ignorato dal server
web.
La sintassi URL permette d’indicare anche risorse non HTTP, per esempio FTP.
ftp://[user[:password]@]host[:port]/path
Dove.
user: se omesso, qualifica una connessione anonima, anonymous.
port: se omesso, è di default 21.
La sintassi URL permette d’indicare anche risorse non HTTP, per esempio SMTP,
naturalmente comporta l’apertura del client di posta di default.
mailto:user@host[;user@host[...]][?Subject=...&Body=...]
Dove.
TPSIT 5
um 80 di 434
Subject: è l’oggetto dell’email.
Body : è il testo dell’email.
Un URL può anche indicare una risorsa locale.
file://C:/Documenti/Immagini/2.GIF
WWW
È un’applicazione client/server.
1. Un server in grado di restituire, a richiesta, secondo un determinato protocollo,
documenti ipertestuali memorizzati in locale, sul disco del server.
2. Un visualizzatore client in grado di richiedere documenti al server e visualizzarli in
forma grafica.
URL
È un URI che, oltre a identificare una risorsa, fornisce mezzi per agire su o per ottenere
una rappresentazione della risorsa descrivendo il suo meccanismo di accesso primario o
la sua ubicazione in una rete.
Esempio, l’URL http://www.miosito.it è un URI che identifica una risorsa l’home page e
lascia intendere che una rappresentazione di tale risorsa, il codice HTML della versione
corrente di tale home page, è ottenibile via HTTP da un host di rete chiamato
www.miosito.it.
Non tutti i caratteri sono ammessi in un URL che classifica i caratteri in tre categorie.
1. I caratteri alfanumerici, maiuscoli o minuscoli, numerici e alcuni di punteggiatura, _!~*,
non sono riservati.
2. Alcuni caratteri sono riservati e sono invece utilizzati con significato particolare
:;,/?&@$=+
3. Caratteri che non hanno significato particolare ma che non possono essere utilizzati in
un URL: lo spazio, in generale i caratteri non stampabili, #%<>.
URN (Uniform Resource Name)
È un URI che identifica una risorsa mediante un “nome” in un particolare dominio di nomi,
namespace, può essere usato per parlare di una risorsa senza lasciar intendere la sua
ubicazione o come ottenerne una rappresentazione.
Esempio, l’URN: ISBN:0-395-36341-1 è un URI che consente d’individuare univocamente
un libro mediante il suo nome 0-395-36341-1 nel namespace dei codici ISBN
(International Standard Book Number) ma non suggerisce dove e come si può ottenere
una copia di tale libro.
Un’applicazione distribuita è un progetto costituito da singole parti tali che ciascuna
parte potrebbe girare su PC diversi da quello locale, in altre parole comunica con
diversi protocolli.
Esempio, sul PC A potrebbe girare l’applicazione che recupera informazioni da un DB sul
PC B potrebbe girare l’applicazione che utilizza questi dati, sul PC C potrebbe girare il DB:
in pratica le singole applicazioni scambiano i dati fra loro.
TPSIT 5
um 81 di 434
SOA (SERVICE ORIENTED ARCHITECTURE)
C’è una ragione architetturale che ha spinto alla creazione di varie tecniche per far
comunicare sistemi eterogenei.
Per esempio, è sempre più rara la presenza del DB in locale sulla macchina client, si
preferisce farlo risiedere su un server ed esporlo tramite servizi.
Quest’architettura si chiama SOA e ha i seguenti vantaggi.
 Si basa sui servizi.
 Disaccoppiamento.
 Versioning.
 Centralizzazione della business logic.
 Scalabilità.
Con quest’architettura si può cambiare la tecnica di accesso al DB in modo trasparente,
eseguire del caching al posto dell’accesso diretto ai dati, oppure bilanciare i carichi di
lavoro su diverse macchine.
Le tecnologie più comuni sono quattro.
1. Comunicazione cross process.
2. Messaggi su code.
3. WS.
4. REST/POX (Plain Old Xml).
SOA definisce quattro concetti.
1. Definizione di confini espliciti: i limiti entro i quali un servizio deve muoversi e
funzionare devono essere ben definiti; è importante distinguere l’accesso a oggetti
locali dall’accesso ad un servizio.
2. I servizi sono autonomi: ogni servizio dev’essere capace di funzionare senza
dipendere da altri servizi.
3. Un servizio condivide schemi e contratti e non classi e interfacce: ogni servizio
accetta uno schema di dati, costituendo così architetture tra loro disaccoppiate che
rimangono stabili nel tempo.
4. Compatibilità dei servizi basata su policy: ogni servizio deve stabilire esplicite
regole per il suo utilizzo in forma leggibile, in modo da consentire la separazione tra il
servizio e le modalità di accesso al servizio.
La comunicazione fra elementi che utilizzano protocolli e modalità non omogenee
rappresenta, in un mondo dove il networking assume una rilevanza importante e dove la
programmazione diventa sempre maggiormente distribuita, un problema di dimensioni
colossali.
TPSIT 5
um 82 di 434
COMUNICAZIONE CROSS PROCESS
INTRODUZIONE
Permette la comunicazione di due unità che si possono trovare all’interno dello stesso
processo e condividono quindi la stessa area di memoria o vivono all’interno della stessa
macchina.
Il .NET Framework Remoting è la soluzione Microsoft per la comunicazione cross process,
consente sia di richiamare funzioni esposte da “processi” che girano su macchine remote
ma in modo del tutto trasparente, sia di gestire chiamate a processi differenti che girano
sulla stessa macchina.
È una soluzione proprietaria e non aderisce ad alcuno degli standard d’interoperabilità di
protocolli remoti.
È una tecnologia legacy mantenuta per una questione di compatibilità con le applicazioni
esistenti di versioni precedenti e non è consigliato per il nuovo sviluppo.
Le applicazioni distribuite devono essere sviluppate utilizzando WCF.
Rappresenta una validissima soluzione per chi voglia realizzare un’architettura multi-tier,
anche in senso fisico e non solo con diversi strati di classi che girano nello stesso
eseguibile ma non voglia investire risorse per attrezzare server con IIS per far girare i WS.
I client eseguono chiamate ai metodi sugli oggetti remoti inviando messaggi al dominio
dell’applicazione remota.
Ciò è portato a termine da un set di oggetti canale.
Il dominio dell’applicazione client contiene un canale client e il dominio dell’applicazione
remota contiene un canale remoto.
Ogni canale è composto di una serie di sink di canale collegati insieme in una catena.
Prima dell’invio o dopo la ricezione di un messaggio, i canali inviano ciascun messaggio
lungo una catena di oggetti sink di canale.
La catena di sink contiene sink necessari per le funzionalità di base del canale, quali sink
del formattatore, di trasporto o del generatore di stack ma è possibile personalizzare la
TPSIT 5
um 83 di 434
catena di sink di canale per l’esecuzione di attività speciali con un messaggio o un flusso.
La catena di sink di canale elabora qualsiasi messaggio inviato a o da un dominio di
applicazione.
Un sink di canale ha accesso al messaggio elaborato e l’elaborazione successiva utilizza il
messaggio restituito al sistema dopo averlo elaborato.
Si tratta del momento più adatto per implementare un servizio di registrazione o un
qualsiasi tipo di filtro.
Ogni sink di canale elabora il flusso e quindi passa il flusso al prossimo sink di canale.
Ciò vuol dire che i sink precedenti o successivi ad uno specifico sink devono sapere come
gestire il flusso ricevuto.
Prima che i messaggi tra le applicazioni remote siano inviati sui canali sono codificati nei
seguenti modi.
 In binario per le applicazioni critiche.
 In XML dove è essenziale interoperabilità, i messaggi XML utilizzano sempre SOAP
(Simple Object Access Protocol).
I servizi di remoting offerti dal .NET Framework nascondono al programmatore la
complessità dei metodi di chiamata remoti.
Quando un client attiva un oggetto remoto ne riceve un proxy con il quale interagisce.
Prima si registra un canale di comunicazione HTTP o TCP, poi diventa possibile registrare
gli oggetti remoti.
Per la registrazione di un oggetto remoto servono i seguenti dati.
1. Il nome del tipo di oggetto remoto.
2. L’URI che i client utilizzeranno per individuare l’oggetto.
3. La modalità oggetto richiesta per l’attivazione da server sono due.
3.1. SigleCall: crea un’istanza di classe per ogni chiamata del client, anche se le
chiamate provengono dallo stesso client; l’invocazione successiva, sarà sempre
servita da una differente istanza del server, anche se la precedente non è stata
ancora riciclata dal sistema.
3.2. Singleton: non presenta mai più di un’istanza contemporaneamente, se un’istanza
è già esistente la richiesta è soddisfatta da quell’istanza, se l’istanza non esiste,
alla prima richiesta è creata dal server e tutte le successive richieste sono
soddisfatte da quell’istanza.
La differenza è sostanziale e dev’essere tenuta in conto quando si decide d’implementare
un’architettura distribuita nelle proprie applicazioni usando .NET Remoting.
Per esempio, si supponga di voler istanziare l’oggetto Server seguente.
Server remoteObject = new Server();
Dopo questa istruzione l’oggetto Server non è ancora stato contattato per cui nessuna
istanza sul server è creata né tanto meno è controllato che il server sia in grado di
rispondere, soltanto all’invocazione del metodo la richiesta giunge effettivamente al server.
DataSet ds = remoteObject.GetData();
Se l’oggetto Server ha una proprietà Name di tipo R/W, si osservi il codice in tabella.
Istruzione
Versione
Locale
Versione
remota
SingleCall
Versione
remota
Singleton
restituisce
"Andrea"
restituisce null
restituisce
"Andrea"
Server firstObject = new Server();
firstObject.Name = "Andrea";
MessageBox.Show(firstObject.Name);
TPSIT 5
um 84 di 434
Server secondObject = new Server();
MessageBox.Show(secondObject.Name);
restituisce
null
restituisce null
restituisce
"Andrea"
secondObject.Name = "Sperelli";
MessageBox.Show(secondObject.Name);
MessageBox.Show(firstObject.Name);
restituisce
"Sperelli"
restituisce
"Andrea"
restituisce
"Sperelli"
restituisce
restituisce null
"Sperelli"
restituisce null
Il comportamento di tale codice, con l’oggetto Server che gira in remoting, è
completamente diverso rispetto al suo omologo in locale e anche nella versione SingleCall
e singleton ci sono differenze consistenti.
I vantaggi del Singleton sono evidenti in termini di semplificazione della programmazione.
Esso, infatti, consente di mantenere gli stati interni da una chiamata all’altra (stateful) e,
inoltre, fa da memoria centrale per tutte le richieste.
Con un singleton, infatti, si possono conservare tutte le informazioni di tutti i client e avere
sempre un’idea precisa di quanto sta accadendo nel S/W perché ogni richiesta passa dal
singleton e questo è in grado di tenerne traccia e di ricordarsene.
Lo svantaggio è evidente: se il singleton va giù tutti i client vanno giù.
Il SingleCall, d’altro canto, impone una programmazione senza stati (stateless) in cui tutto
ciò che serve a rispondere ad una richiesta di un client dev’essere passato come
parametri del client stesso o dev’essere reperito dal server in altre forme, per esempio da
un DB e non si può programmare sperando che sia sempre la stessa istanza a rispondere
e a conservare l’impostazione della chiamata precedente.
Questo modello in compenso fornisce una scalabilità e una robustezza ben maggiori.
PROGRAMMAZIONE
File CLIENT.CS
using System;
using System.Runtime.Remoting;
using System.Runtime.Remoting.Channels;
using System.Runtime.Remoting.Channels.Http;
TPSIT 5
um 85 di 434
using Remote;
namespace Client {
public class ClientProcess {
[MTAThread]
public static void Main(string[] args)
{ HttpChannel channel = new HttpChannel();
ChannelServices.RegisterChannel(channel, false);
// registra la classe remota
RemotingConfiguration.RegisterWellKnownClientType(
Type.GetType("Remote.ServiceClass,
remote"),"http://localhost:8080/object1uri");
ServiceClass object1 = new ServiceClass();
try
{ Console.WriteLine("ServerTime: " + object1.GetServerTime()); }
catch (Exception ex) {
Console.WriteLine("Exception of type: " + ex.ToString() + " occurred.");
Console.WriteLine("Details: " + ex.Message);
}
}
}
}
File REMOTE.CS
using System;
namespace Remote {
public class ServiceClass : MarshalByRefObject {
private DateTime m_startTime;
public ServiceClass()
{ Console.WriteLine("ServiceClass creata senza il costruttore. Istanza hash: " +
GetHashCode().ToString());
m_startTime = DateTime.Now;
}
~ServiceClass()
{ Console.WriteLine("Collezione dopo: " + (new TimeSpan(DateTime.Now.Ticks m_startTime.Ticks)).ToString() + " secondi."); }
public DateTime GetServerTime()
{ Console.WriteLine("Tempo richiesto dal client.");
return DateTime.Now;
}
public int InstanceHash
{ get { return GetHashCode(); } }
}
}
File SERVER.CS
using System;
using System.Runtime.Remoting;
using System.Runtime.Remoting.Channels;
using System.Runtime.Remoting.Channels.Http;
using Remote;
namespace Server {
class Server {
static void Main(string[] args)
TPSIT 5
um 86 di 434
{ HttpChannel channel = new HttpChannel(8080);
ChannelServices.RegisterChannel(channel, false);
ServiceClass object1 = new ServiceClass();
// crea una singola istanza di ServiceClass
// tutti i client la usano
ObjRef ref1 = RemotingServices.Marshal(object1, "object1uri");
Console.WriteLine("ObjRef.URI: " + ref1.URI);
Console.WriteLine("Running. Premi <INVIO> per terminare la pubblicazione.");
Console.ReadLine();
RemotingServices.Disconnect(object1);
Console.WriteLine();
Console.WriteLine("Disconnessione dell'oggetto. Client adesso riceve una
RemotingException.");
Console.WriteLine("Premi <INVIO> per non registrare il canale.");
Console.ReadLine();
// in questo punto l'oggetto ServerClass esiste
ChannelServices.UnregisterChannel(channel);
Console.WriteLine("Canale non registrato. Client adesso riceve una
WebException.");
Console.WriteLine("L'applicazione host e' ancora in running. Premi <INVIO> per
terminare il processo.");
Console.ReadLine();
GC.Collect();
GC.WaitForPendingFinalizers();
}
}
}
TPSIT 5
um 87 di 434
MESSAGGI SU CODE
INTRODUZIONE
L’idea che ne è alla base consiste di due attori.
1. Il mittente mette in una coda i messaggi da processare.
2. Il destinatario recupera i messaggi, smaltendo la coda.
Aumentando il numero di attori che processano la coda, si può aumentare la velocità di
smaltimento dei messaggi e mantenere costanti le prestazioni anche all’aumentare del
carico di lavoro: scalabilità.
A differenza delle comunicazioni basate su socket o su altri protocolli di rete, il mittente e il
destinatario possono essere attivi in momenti differenti.
Infatti, nel momento in cui i messaggi sono spediti, il destinatario può essere offline.
In un secondo tempo, quando il destinatario ritorna online, riceverà i messaggi senza
ulteriori interventi da parte del mittente.
I messaggi su code sono usati sia in ambienti “alwayson” sia in ambienti scollegati.
1° Caso
Un agente si reca presso i clienti e carica i dati sul client che invia un messaggio per ogni
lettura alla coda di messaggi del sistema locale; quando l’agente ritorna in azienda e si
collega col dispositivo alla rete, tutti i messaggi in coda sono automaticamente trasferiti dal
sistema della coda client alla coda di destinazione per la successiva elaborazione.
2° Caso
In un’azienda si vuole ridurre il sovraccarico di richieste per un server, allora ogni richiesta
è accodata e le può essere associata una priorità, in modo da essere elaborata di notte.
3° Caso
Sito web per l’acquisto online, se la comunicazione con l’istituto di credito è interrotta, non
è possibile dire all’utente di riprovare più tardi, allora si accoda la richiesta e s’informa
l’utente che il suo acquisto è in fase di “processing” e che riceverà una comunicazione
sull’esito della transazione.
La soluzione è progettare una tabella in un DB locale dove memorizzare le informazioni
che saranno spedite ad un’applicazione remota al momento in cui c’è connettività.
Il programmatore deve gestire la frequenza d’invio, l’eventuale priorità con cui spedire i
record, le politiche transazionali con cui inviare il messaggio, la cancellazione del record
dalla tabella locale quando è arrivato a destinazione.
TPSIT 5
um 88 di 434
I vantaggi derivanti dall’utilizzo dei messaggi su code sono i seguenti.
Applicazioni a basso accoppiamento
L’applicazione client può eseguire gli invii senza necessariamente sapere se l’applicazione
destinataria è disponibile a processare i messaggi; questo riduce notevolmente la
dipendenza tra le due applicazioni.
Failure isolation
Le applicazioni che inviano o ricevono i messaggi verso una coda possono generare
eccezioni senza influenzare l’altra applicazione; se, ad esempio, il destinatario fallisce, il
mittente può continuare a inviare i messaggi senza bloccarsi; una volta riattivato, il
destinatario può tranquillamente processare i restanti messaggi.
Bilanciamento del livello di carico
L’applicazione mittente può essere più veloce e performante dell’applicazione destinatario.
Le code consentono di livellare questa differenza.
Operazioni disconnesse
L’invio, la ricezione e l’elaborazione dei messaggi sono operazioni che possono funzionare
correttamente anche nelle fasi di lavoro disconnesso, consentendo ad esempio di
continuare a inviare messaggi anche se l’endpoint di riferimento non è al momento
disponibile.
MSMQ (MICROSOFT MESSAGE QUEUE)
È stato progettato come servizio per lo scambio di messaggi tra applicazioni.
 Gestire il recapito.
 Gestire il routing (instradamento).
 Gestire la sicurezza durante il trasporto.
 Gestire la comunicazione tra processi: IPC sia per applicazioni installate sullo stesso
PC sia per applicazioni sparse per i nodi della rete.
 Fornire le interfacce di programmazione per accedere in R/W alle code.
 Far comunicare macchine remote, anche su Internet, con il protocollo HTTP/HTTPS.
Installazione e configurazione
Fare clic su Pannello di controllo/Programmi/Attivazione o disattivazione delle
funzionalità Windows/Microsoft Message Queue (MSMQ) Server.
Una volta installati i componenti necessari, dev’essere avviato il relativo servizio
TPSIT 5
um 89 di 434
Accodamento messaggi.
Code
Secondo l’utilizzo, è possibile specificare un tipo diverso di coda.
 Pubbliche: sono accessibili da tutti i siti connessi in rete senza conoscere il PC su cui
si trovano perché sfruttano Active Directory, MSMQ dev’essere configurato con questa
modalità.
 Private: sono presenti solo sul PC su cui sono configurate, è possibile accedere
conoscendo il percorso completo che punta alla coda o l’etichetta.
 Journal: sono utilizzate per tenere traccia dei messaggi inviati e ricevuti.
 Di amministrazione: hanno la funzione di notificare l’avvenuta ricezione del
messaggio al mittente.
 Di sistema: hanno diversi scopi tra cui la gestione dei messaggi transazionali.
 Deadletter: mantengono i messaggi scaduti che non sono stati consegnati nel tempo
prestabilito o che non possono essere trattati correttamente dal ricevente.
 Di report: possono essere usate per il tracciamento dei percorsi effettuati dai
messaggi o per eventuali messaggi di prova.
Active directory
È un servizio di directory che fornisce un’infrastruttura centralizzata e gerarchica per la
memorizzazione d’informazioni sugli utenti, sulle risorse di rete e sui servizi.
Per il raggruppamento d’informazioni elementari, si basa sui concetti di dominio, alberi e
foreste, ogni informazione elementare è vista come un oggetto.
Per l’accesso in lettura e scrittura di oggetti direttamente nella base dati del servizio .NET
mette a disposizione il namespace System.DirecoryServices.
La prima cosa da fare è la creazione della coda, due possibilità.
1. Codice.
MessageQueue queue = null;
string nomecoda = @".\private$\codaprivata";
if (MessageQueue.Exists(nomecoda))
queue = new MessageQueue(nomecoda);
else
queue = MessageQueue.Create(nomecoda, false);
2. SO.
Per creare una coda servono i privilegi di amministratore.
Fare clic su Start/Tutti i programmi/Strumenti di amministrazione/Gestione computer.
Selezionare Servizi e Applicazioni/Accodamento messaggi/Code private, nella finestra
che si apre sarà possibile creare nuove code, eliminare quelle esistenti e controllare
l’elenco dei messaggi.
Con il pulsante destro del mouse, scegliere Nuova e digitare il nome per creare la coda.
TPSIT 5
um 90 di 434
Le code possono essere aperte in due modi.
1. Nome di percorso: è dato dal nome del server e dal nome della coda.
2. Nome di formato: è associato automaticamente dal sistema alla creazione della coda
ed è rappresentato da un GUID (Global Unique Identifier).
Esempi.
Coda pubblica: nome_PC\nomecoda.
Coda privata: nome_PC\private$\ nomecoda.
Coda diario: nome_PC\Journals$.
Coda deadletter: nome_PC\DeadLetters$.
Programmazione
MSMQ si colloca in una realtà distribuita in cui le comunicazioni fra i vari componenti S/W
che costituiscono l’applicazione girano su macchine diverse che possono essere
connesse da linee non sempre attive, oppure quando è necessario far comunicare
applicazioni che girano su SO differenti e/o non si vuole che problemi di rete possano
fermare o rallentare l’esecuzione.
Le comunicazioni asincrone risolvono il problema delle performance, infatti, il tempo
necessario per scrivere un messaggio in una coda può essere considerato quasi uguale a
zero, mentre attendere una comunicazione sincrona con un’applicazione più lenta, su
una connessione di rete lenta, significa rallentare anche l’applicazione chiamante.
In una comunicazione sincrona le richieste sono processate nell’ordine in cui sono inviate
dal chiamante, mentre con un sistema di messaggistica è possibile assegnare una priorità
a ogni messaggio.
Un crash nella comunicazione sincrona causa un rollback, un annullamento di tutte le
operazioni effettuate, mentre MSMQ può eseguire un numero di retry impostabile
dall’applicazione.
È possibile definire il tempo massimo in cui il messaggio può raggiungere la coda di
destinazione e un tempo massimo in cui il messaggio dev’essere letto.
In caso di timeout è il servizio MSMQ che s’incarica d’inserire un messaggio
amministrativo di mancato recapito o mancata lettura che è possibile recuperare e
interpretare.
Nel caso in cui un certo numero di client eseguono richieste sincrone di aggiornamento
TPSIT 5
um 91 di 434
dati occorre definire una politica di lock sulle risorse rallentando l’operazione di
aggiornamento nel caso in cui la risorsa sia bloccata.
Una coda non ha alcun problema di concorrenza perché i messaggi sono inseriti e
accodati dalle varie applicazioni del mittente.
L’applicazione del ricevente processerà i messaggi uno ad uno e quindi eseguirà degli
aggiornamenti “sequenziali” sulle risorse.
Se l’applicazione del ricevente gira in multithread facendo riemergere il problema dei lock
sulla risorsa finale, però il problema è spostato dall’applicazione che l’utente usa
all’applicazione del ricevente, liberando così il front-end dalle problematiche descritte.
Un’applicazione che invia un messaggio in coda deve referenziare la libreria seguente.
using System.Messaging;
Deve creare un oggetto MessageQueue che rappresenta la coda, è il wrapper che
gestisce l’interazione con MSMQ e permette l’invio, la ricezione e l’anteprima dei
messaggi.
Deve creare un oggetto Message che rappresenta il messaggio, contenuto e formato, da
spedire nella coda.
Un messaggio è composto da un body contenente i dati da trasmettere e da un header
che lo descrive.
Il messaggio può includere altre informazioni riguardanti il mittente, il valore di priorità, l’ID
(IDdentificatore) della transazione cui è associato e l’eventuale configurazione per la
scadenza di validità.
Il corpo può contenere qualsiasi tipo d’informazione e prevede appositi formattatori che
serializzano il contenuto del messaggio, implementano l’interfaccia ImessageFormatter e
sono previste tre classi.
1. XMLMessageFormatter: formatta il messaggio in binario in XML, default.
2. BinaryMessageFormatter: formatta il messaggio in binario.
3. ActiveMessageFormatter: formatta il messaggio in binario e dà la possibilità ai
messaggi di essere letti da oggetti COM.
Esistono due versioni di consegna dei messaggi.
1. Recoverable: garantisce la consegna del messaggio anche nel momento in cui ci sono
problemi di rete o nel PC, in questo caso i messaggi sono memorizzati come file nella
cartella <WINDIR>\SYSTEM32\MSMQ\STORAGE.
2. Express: variante più veloce ma insicura poiché usa solo la memoria per
immagazzinare i messaggi prima del recapito.
Per accedere ad una coda privata occorre definire un oggetto di tipo MessageQueue e alla
sua istanza bisogna passare il nome del server, oppure anteporre un punto indicante il PC
locale e il nome della coda privata.
MessageQueue queue = new MessageQueue(@".\private$\codaprivata ");
Il nome di formato è obbligatorio in un ambiente disconnesso ed è utilizzato per ottenere il
nome del server MSMQ tramite Active Directory.
Se il nome della coda è conosciuto, non è necessario compiere ricerche e si può accedere
direttamente.
Viceversa, la ricerca può essere fatta cercando l’etichetta, la categoria o il nome di formato
tramite i seguenti metodi.
 GetPublicQueuesByLabel.
 GetPublicQueuesByCategory.
 GetPublicQueuesByMachine.
 GetPublicQueues: restituisce tutte le code pubbliche del dominio in una struttura ad
array.
TPSIT 5
um 92 di 434
Per spedire un messaggio, si usa il metodo seguente.
queue.Send("Corpo del messaggio","Ttitolo del messaggio");
Per leggere da una coda esistono varie tecniche.
Se si desidera leggere e rimuovere solo il primo messaggio dalla coda, si usa il metodo
seguente.
Message message = queue.Receive();
Se non si desidera rimuovere il messaggio dalla coda, si usa il metodo seguente.
Message message = queue.Peek();
Esistono delle varianti che permettono di leggere/rimuovere uno specifico messaggio in
base al suo ID.
Message message = queue.PeekById(msgID);
Message message = queue.ReceiveById(msgID);
Il metodo seguente legge tutti i messaggi della coda in una struttura array.
Message[] messages = queue.GetAllMessages();
foreach (Message message in messages) {
//elaborazione del messaggio
}
Un’alternativa è tramite l’uso dell’enumeratore seguente.
MessageEnumerator enumerator = queue.GetMessageEnumerator2();
while (enumerator.MoveNext())
enumerator.RemoveCurrent();
Si può avviare la lettura o la ricezione in modalità asincrona tramite i metodi seguenti.
queue.BeginPeek();
queue.BeginReceive();
E completare l’operazione specificata con i rispettivi.
queue.EndPeek();
queue.EndReceive();
In modalità asincrona è necessario definire un gestore per l’evento scatenato al termine
dell’operazione associato alla proprietà PeekCompleted.
queue.PeekCompleted += new PeekCompleted
EventHandler(MyPeekCompletedEventHandler);
…
void MyPeekCompletedEventHandler (Object source,PeekCompletedEventArgs
asyncResult)
{ // connessione alla coda
MessageQueue queue = (MessageQueue)source;
TPSIT 5
um 93 di 434
// fine operazione asincrona
Message message = queue.EndPeek(asyncResult.AsyncResult);
// eventuale elaborazione del messaggio
Console.WriteLine("Message: " + (string) message.Body);
// riavvio operazione asincrona
queue.BeginPeek();
return;
}
In molti scenari ha senso suddividere le tipologie di messaggio da spedire/ricevere
secondo diversi criteri.
Per esempio, per i messaggi relativi a Ordini da processare e per i messaggi relativi a
Spedizioni da effettuare bisogna creare due code diverse in modo da rendere indipendenti
i messaggi.
L’applicazione che deve analizzare il contenuto del messaggio aprirà la coda Ordini se
deve processare Ordini o la coda Spedizioni per analizzare le spedizioni da effettuare.
Per esempio, differenziare fra ordini urgenti e meno urgenti.
La posizione che il messaggio assume nella coda è di tipo FIFO (First In First Out).
La priorità è una delle proprietà impostabili sul singolo messaggio da spedire: è
l’applicazione del mittente che imposta questa proprietà.
È possibile dare una priorità ai messaggi con l’enumeratore seguente.
enum System.Messaging.MessagePriority
Per impostare la priorità al messaggio; le opzioni disponibili sono le seguenti.
Highest, VeryHigh, High, AboveNormal, Normal (default), Low,VeryLow, Lowest.
I messaggi nella coda di arrivo saranno tolti in base a tale priorità.
Un messaggio non Recoverable, il default, è tenuto in RAM dalla macchina che lo deve
inviare verso la coda di destinazione, se la macchina va in crash, oppure il servizio MSMQ
è riavviato, i messaggi “normali” sono persi.
Utile per esempio in campo industriale: le macchine di produzione inviano il loro stato ad
applicazioni di monitoraggio che tramite indicatori di livello, grafici, mostrano queste
informazioni ad un operatore a video.
Se la connettività fra le macchine è interrotta o è riavviato il PC della macchina industriale
è inutile tenere i messaggi informativi perché non avrebbero più significato.
In tutti gli altri casi in cui è importante che tutti i messaggi spediti siano recapitati a
destinazione, bisogna impostare la proprietà Recoverable a true per fare in modo che le
macchine coinvolte nella spedizione del messaggio verso la coda di destinazione
appoggino i messaggi stessi su disco.
Un’applicazione che monitorizza le quotazioni di borsa invia al DB che poi alimenta il sito
pubblico consultabile dai clienti aggiornando le quotazioni ogni 10 minuti.
La proprietà TimeToReachQueue indica il tempo, in secondi,’ che il messaggio può
impiegare per raggiungere la coda di destinazione: in pratica il tempo valido entro cui il
messaggio ha senso che sia spedito nella coda di destinazione.
Utile per non appesantire il sistema con la trasmissione di messaggi che non hanno
senso.
Per ottenere una risposta da parte del destinatario sull’esito dell’operazione in base al
contenuto del messaggio, occorre un controllo sui messaggi spediti, i messaggi arrivati e/o
letti e i messaggi non spediti per timeout o non letti entro il timeout.
Tutte queste opzioni sono a carico di MSMQ, però non forniscono informazioni sull’esito
dell’operazione fatta dal destinatario sul messaggio stesso.
TPSIT 5
um 94 di 434
La prima opzione per tracciare tutti i messaggi spediti è abilitare il journal dov’è tenuta una
copia di tutti i messaggi recapitati tramite la proprietà UseJournalQueue, oppure definire il
journaling a livello di coda dalla maschera delle proprietà, inoltre è possibile limitare la
dimensione in KB.
Inoltre, MSMQ ha una coda di sistema denominata MESSAGGI JOURNAL dove tenere
una copia di tutti i messaggi transitati dalla macchina.
La seconda opzione per tracciare tutti i messaggi spediti è abilitare, con la proprietà
UseDeadLetterQueue, l’utilizzo della coda di sistema deadletter dove recapitare tutti i
messaggi impossibili da spedire.
Dall’interfaccia di amministrazione o da codice è possibile aprire tale coda per
ispezionarne il contenuto e quindi sapere quali e/o quanti messaggi non sono stati
recapitati.
Volendo avere più controllo sull’esito dell’invio dei messaggi, si può impostare sul singolo
messaggio le proprietà seguenti.
AcknoledgeType.
AdministrationQueue: indica una coda, definita dall’applicazione, dove recapitare l’esito
d’invio di ogni messaggio, la coda deve esistere.
L’enum System.Messaging.AcknowledgeTypes consente d’indicare.
1. FullReachQueue.
2. FullReceive indica se il messaggio è arrivato a destinazione ed è stato letto
dall’applicazione del destinatario.
3. NegativeReceive: indica se il messaggio non è stato ricevuto.
4. None.
5. NotAcknowledgeReachQueue.
6. NotAcknowledgeReceive.
7. PositiveArrival.
8. PositveReceive.
Il messaggio recuperato con Receive espone la proprietà Acknowledgement con la quale
è possibile capire cosa è successo al messaggio.
 ReachQueue: indica che il messaggio ha raggiunto la coda di destinazione.
 ReachQueueTimeout: indica che è scaduto il timeout indicato per raggiungere la coda
e quindi il messaggio non è stato recapitato.
 Receive: indica che il messaggio ha avuto una ricezione positiva da parte
dell’applicazione del destinatario.
L’applicazione del mittente indica con la proprietà ResponseQueue, dove vorrebbe che
fossero recapitate le risposte che ha compiuto il destinatario in base al contenuto del
messaggio, sull’esito dell’operazione.
Di norma, la coda di risposta si trova sulla stessa macchina dell’applicazione del mittente.
L’applicazione del destinatario invierà un messaggio di risposta sulla coda indicata dal
client.
TPSIT 5
um 95 di 434
File DESTINATARIO.CS
using System;
using System.Messaging;
namespace Destinatario {
class Destinatario {
static void Main(string[] args)
{ try {
using (MessageQueue mq = new MessageQueue(@".\private$\codaprivata"))
// il messaggio, per default, è serializzato in XML all’interno della coda
{ mq.Formatter = new XmlMessageFormatter(new Type[] { typeof(String) });
// ricezione del messaggio ed eliminazione dalla coda
Message messageReceived = mq.Receive();
// tipo di contenuto del messaggio
String contenuto = (String) messageReceived.Body;
// processo il contenuto del messaggio
try {
using (MessageQueue risposta = new
MessageQueue(@".\private$\rispostedestinatario"))
{ Message messaggio = new Message();
messaggio.Body = "Risposta negativa al messaggio " +
messageReceived.Id;
risposta.Send(messaggio);
}
Console.WriteLine("Inviato!");
}
catch (MessageQueueException ex)
{ Console.WriteLine("Errore invio risposta!" + ex.ToString()); }
Console.WriteLine("Messaggio ricevuto: " +contenuto);
}
}
catch (MessageQueueException ex)
{ Console.WriteLine("Errore ricezione messaggio!" + ex.ToString()); }
Console.ReadKey();
}
}
}
TPSIT 5
um 96 di 434
File MITTENTE.CS
using System;
using System.Messaging;
namespace esempio {
class Mittente {
static void Main(string[] args)
{ try {
// Dispose dell'oggetto che rappresenta la coda
using (MessageQueue mq = new MessageQueue(@".\private$\codaprivata"))
{ Message messaggio = new Message();
// contenuto del messaggio
messaggio.Body = "Ciao dal mittente.";
// imposta la priorità
messaggio.Priority = MessagePriority.High;
// messaggi salvati su disco
messaggio.Recoverable = true;
// tempo in secondi entro cui spedire il messaggio
messaggio.TimeToReachQueue = TimeSpan.FromMinutes(10);
// recapita un messaggio di acknoledgement
messaggio.AdministrationQueue = new
MessageQueue(@".\private$\esitomessaggi");
// si vuole sapere tutto
messaggio.AcknowledgeType = AcknowledgeTypes.FullReceive;
// risposte del destinatario
messaggio.ResponseQueue = new
MessageQueue(@".\private$\rispostedestinatario");
mq.Send(messaggio);
}
Console.WriteLine("Messaggio inviato!");
}
// intercetta gli errori durante l’invio del messaggio
catch (MessageQueueException ex)
{ Console.WriteLine("Errore invio messaggio!" + ex.ToString()); }
Console.ReadKey();
}
}
}
TPSIT 5
um 97 di 434
TPSIT 5
um 98 di 434
Progettare un’applicazione per lo scambio di messaggi.
Esplora server, la console di gestione del server per Visual Studio, consente di esplorare
le code esistenti sui PC della rete cui si ha accesso.
TPSIT 5
um 99 di 434
MQTT (MESSAGE QUEUE TELEMETRY TRANSPORT)
Il protocollo ha le seguenti caratteristiche.
 È molto snello, in altre parole i pacchetti possono avere una dimensione di pochi byte.
 È orientato agli eventi e ai messaggi.
 Permette ai dispositivi di comunicare in maniera asincrona fra loro, anche all’interno di
una rete non performante e poco affidabile.
 È payload agnostic, in altre parole non entra nel merito del contenuto dei pacchetti,
quindi può essere usato come trasporto per qualsiasi tipo di dato, sarà compito del
livello applicazione interpretarne il contenuto.
Il suo principale utilizzo è nell’ambito della comunicazione M2M (Machine-To-Machine) e
dell’Internet of Things, in pratica in quei sistemi in cui ci sono esclusivamente macchine
che si scambiano dei dati senza l’intervento umano, per poi giungere ad un server per
un’elaborazione massiva degli stessi.
Il protocollo è di tipo mittente/destinatario perché ciascun client esprime l’interesse a
ricevere determinate tipologie di messaggi, così com’è in grado di pubblicarne altri con
determinati tipi di contenuti.
Ogni messaggio è formato da un topic (argomento) che è alla base del meccanismo di
sottoscrizione così come a quello di pubblicazione; ha una struttura simile ad un URI e,
quindi, è caratterizzato da una gerarchia i cui livelli sono separati da (/).
Un client può essere interessato ad uno specifico argomento, per cui sottoscrive la
ricezione di tutti i messaggi per uno specifico topic, così come può pubblicare messaggi
appartenenti ad uno specifico topic, non necessariamente lo stesso.
Le sottoscrizioni possono essere anche gestite in modo tale che, se il client si disconnette,
il broker continua ad immagazzinare i messaggi per quel client, retain message, per poi
trasferirli nel momento in cui esso ritornerà online.
Al momento della connessione, il client può fornire al broker un messaggio di testamento,
associato ad un certo topic che trasmetterà agli interessati nel caso in cui la connessione
con il client dovesse essere interrotta.
La persistenza della connessione tra client e broker è garantita da un messaggio di keepalive che il client invia periodicamente, l’intervallo di tempo è indicato al broker al momento
della connessione.
La gestione della coda dei messaggi e dello smistamento da parte del broker può essere
specificato per ogni singolo messaggio un livello di QoS (Quality of Service) tra i seguenti.
 At most once: letteralmente al massimo uno, il recapito di un messaggio è lasciato al
protocollo sottostante TCP/IP, per cui ci possono essere messaggi persi o duplicati.
 At least once: letteralmente almeno uno, è garantita la sicurezza di recapito del
messaggio ma possono comunque verificarsi casi di duplicazione dello stesso.
 Exactly once: letteralmente esattamente uno, è garantito che un messaggio arrivi una
e una sola volta ai client che sono sottoscritti al topic di appartenenza.
Architettura
TPSIT 5
434
um 100 di
Broker
I client e il server non comunicano mai in maniera diretta ma passano attraverso una
figura centrale, il broker, il cui compito è di accettare le connessioni dei client e garantire lo
smistamento dei messaggi.
Il broker può essere scelto sul sito http://mqtt.org, per esempio IBM WebSphere MQ
Telemetry, Mosquito e RSMB (Really Small Message Broker).
Avviare il broker, da CLI, con il comando seguente.
Client
Gli utenti possono registrarsi, loggarsi, chiedere le amicizie e scambiarsi messaggi.
Comunicano con un broker, per esempio usando la libreria M2MQTT che funziona con il
.NET Framework, il .NET Compact Framework e .il NET Micro Framework.
Server
È un client per il broker ma è un vero e proprio server per i client.
Gestisce la registrazione e l’autenticazione degli utenti.
Il server è implementato nella classe MqttMessengerServer che fa uso di un’istanza della
classe MqttClient per comunicare con i client attraverso il broker.
Il metodo Connect stabilisce la connessione ed esegue la sottoscrizione ai topic definiti
nella classe MqttMessengerTopic.
Avviare il server, da CLI, con il comando seguente.
TPSIT 5
434
um 101 di
L’operazione di richiesta di una nuova amicizia prevede i seguenti passi.
1. Il client invia al server la richiesta con il metodo SendFriendshipRequest che pubblica il
messaggio sul topic MqttMessengerTopic.FRIENDSHIP_REQ.
2. Il server, ricevuta la richiesta, verifica l’esistenza dell’utente e gira la richiesta
pubblicandola sul topic MqttMessengerTopic.FRIENDSHIP_REQ_USER.
3. Il client destinazione, ricevuto il messaggio di richiesta di una nuova amicizia, solleva
l’evento FriendshipRequested al livello superiore; è compito dell’utente, tramite l’UI,
dare conferma o meno; in caso di amicizia accettata, il client invia la risposta con il
metodo SendFriendshipResponse che pubblica il messaggio sul topic
MqttMessengerTopic.FRIENDSHIP_RESP.
4. Il server riceve l’esito sul topic suddetto e la rigira al client mittente sul topic
MqttMessengerTopic.FRIENDSHIP_RESP_USER, inoltre salva la nuova amicizia con
il datasource e rinvia la lista degli amici aggiornata ai due client coinvolti.
5. Il client mittente, una volta ricevuto l’esito, solleva l’evento FriendshipResponse in
modo che il livello di UI possa essere avvisato della nuova amicizia.
Lo scambio dei messaggi non coinvolge il server ma è gestito da un canale diretto tra i
due client.
Ciascun client è registrato al topic MqttMessengerTopic.MESSAGE_CHANNE che riceve i
messaggi trasmessi da un qualsiasi altro client.
L’invio è effettuato con il metodo SendMessage che esegue la pubblicazione su tale topic,
mentre la ricezione è garantita dalla sottoscrizione al medesimo topic.
L’informazione del client mittente è trasportata all’interno del messaggio rappresentato
dalla classe MessageMsg.
La ricezione di un messaggio scatena l’evento MessageReceived per l’UI che visualizza il
contenuto.
Avviare due client.
TPSIT 5
434
um 102 di
WEB MESSAGE API - HTML 5.0
Con la progettazione di applicazioni web moderne che richiedono sempre più la possibilità
di utilizzare servizi esterni forniti da altri provider, per esempio le mappe, è nata la
necessità di superare la limitazione imposta dal rigido sistema same origin policy.
Le Web Message implementano un sistema di messaggistica che consente lo scambio di
dati, tra documenti appartenenti a domini differenti, in modo sicuro e affidabile.
Il procedimento per inviare e ricevere i messaggi consente di far comunicare in modo
bidirezionale due documenti caricati in pagine web provenienti da differenti origini, dove
per origine s’intende l’indirizzo da cui proviene il messaggio.
Tale origine è composta da uno schema, il nome di un host e una porta e non tiene in
considerazione gli eventuali percorsi.
Per esempio, una pagina servita dall’indirizzo https://127.0.0.1:99 ha un’origine differente
da una pagina servita dall’indirizzo http://127.0.0.1:99 perché differisce nella componente
schema
HTTPS
e
HTTP,
mentre
una
pagina
servita
dall’indirizzo
TPSIT 5
434
um 103 di
http://127.0.0.1:99/check.html non ha un’origine differente da una servita dall’indirizzo
http://127.0.0.1:99/widget/chat.html perché i percorsi non sono considerati nella
valutazione dell’equivalenza delle origini.
La funzione seguente, invia un messaggio alla window da cui è invocata e l’evento
message consente di riceverlo e processarlo.
window.postMessage(message, targetOrigin [, ports ])
Dove.
message rappresenta la stringa di messaggio da inviare.
targetOrigin rappresenta una stringa contenente l’URL del documento cui inviare il
messaggio.
ports opzionale, rappresenta un array di porte.
L’evento message, di tipo MessageEvent, è assegnabile ad un oggetto e ha le seguenti
proprietà.
data
Di tipo any, ritorna il dato del messaggio.
origin
Di tipo DOMString, ritorna l’URL originatore del messaggio.
lastEventId
Di tipo DOMString, ritorna il valore dell’ultimo ID, assegnato per un oggetto di tipo
EventSource che è utilizzato per inoltrare messaggi, in modalità push, da un server remoto
ad una pagina web; questa modalità è definita Server Sent Events.
source
Di tipo WindowProxy, ritorna la window originatrice del messaggio.
ports
Di tipo MessagePortArray, ritorna un array di oggetti di tipo MessagePort.
La sicurezza contro attacchi di tipo cross-site scripting è garantita in primo luogo dal
browser, poiché quando si utilizza la funzione postMessage esso fa un primo controllo che
la window da cui s’invia il messaggio abbia la stessa origine di quella specificata dal
parametro dove s’inserisce il target destinatario del messaggio e, infatti, in caso di
discordanza il messaggio non è inviato.
Inoltre, all’atto della ricezione del messaggio, l’identificazione dell’originatore è inclusa
come parte del messaggio dal browser stesso, in modo che non possa essere falsificata:
spoofing.
In secondo luogo la sicurezza può essere ulteriormente garantita dal programmatore che
può verificare se l’URL originatore è tra quelle inserite in un’eventuale white-list,
controllando la proprietà origin dell’oggetto evento passato al gestore dell’evento message
e facendo attenzione a come valutare la stringa del messaggio.
Infatti, in quest’ultimo caso, bisogna fare attenzione a non usare proprietà o funzioni che
parserizzano in modo non sicuro il dato del messaggio come, ad esempio, fanno la
proprietà innerHTML o la funzione eval.
Channel messaging
La Web Message API supporta, oltre al meccanismo di comunicazione appena descritto,
TPSIT 5
434
um 104 di
anche channel messaging, dove i messaggi sono inoltrati e ricevuti avvalendosi di canali,
creati come oggetti di tipo MessageChannel e di porte, di tipo MessagePort.
Esempio, inviare un messaggio, da un documento, INDEX.HTML, caricato dall’URL
http://127.0.0.1:99, al documento caricato nella window dell’iframe all’URL
http://www.altrosito.it/chatwidget.html e scrivere anche un gestore per l’evento message
che consente di ricevere la risposta dell’elaborazione della pagina CHATWIDGET.HTML.
File INDEX.HTML
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
<title>CDM</title>
<script>
window.addEventListener("load", function () {
window.setTimeout(function ()
{ var frame_w = document.getElementsByTagName("iframe")[0].contentWindow;
frame_w.postMessage(JSON.stringify({ left: 10, top: 10 }), "http://altrosito.com:81");
}, 1000);
}, false);
window.addEventListener("message", function (ev) {
if (ev.origin == "http://altrosito.com:81") {
// processa il messaggio ricevuto...
var d = ev.data;
}
}, false);
</script>
</head>
<body>
<iframe src="http://user1.com:81/chatwidget.html" width="800" height="200"></iframe>
</body>
</html>
File CHATWIDGET.HTML
Gestore per l’evento message, è in grado di processare i dati inviati e d’inviarli
nuovamente alla window originatrice.
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
<title></title>
<script>
window.addEventListener("message", function (ev) {
if (ev.origin == "http://127.0.0.1:99") {
// processa in qualche modo il messaggio...
var d = JSON.parse(ev.data);
var res = d.left * 2 + d.top * 5;
// ...e lo invia all’originatore
ev.source.postMessage("Ok processing terminato ... ecco il risultato: " + res,
"http://127.0.0.1:99");
TPSIT 5
434
um 105 di
}
}, false);
</script>
</head>
<body>
</body>
</html>
JMS (JAVA MESSAGING SERVICE)
È un insieme d’interfacce tale che un client basato su Java possa fare uso di un sistema di
messaggistica, definisce due tipi di messaggi.
1. P2P: si basa sul concetto di coda.
2. Pub/Sub (Publish and Subscribe): un client s’iscrive (subscribe) ad un topic per
ricevere messaggi riguardanti uno specifico argomento, gli altri client pubblicano
(publish) i messaggi verso questo topic e i messaggi sono spediti a tutti i client che
sono iscritti al topic in questione.
MantaRay
È un sistema di messaggistica open source, usato in sistemi server-less, in altre parole
privi di un server centrale, perciò basati su un’architettura puramente distribuita.
WCF (WINDOWS COMMUNICATION FOUNDATION)
INTRODUZIONE
È il framework per lo sviluppo di applicazioni SOA di Microsoft, permette di uniformare lo
scambio d’informazioni e di creare e consumare servizi.
Il suo punto forte è l’indipendenza dal mezzo di trasporto.
Si presenta come l’evoluzione delle tecnologie seguenti.
 ASP.NET WS.
 WSE (WS Enhancements).
 .NET Remoting.
 .NET Enterprise Services.
Coniuga le potenzialità delle diverse tecnologie in un unico modello di programmazione,
rendendo semplice il passaggio da una modalità di utilizzo all’altra data e la versatilità con
TPSIT 5
434
um 106 di
cui è possibile utilizzare un protocollo piuttosto che un altro senza, in alcun modo,
modificare il codice del servizio sviluppato.
È un’infrastruttura di comunicazione utilizzata per creare applicazioni distribuite, permette
d’inviare dati come messaggi asincroni da un endpoint del servizio ad un altro.
ARCHITETTURA
Il messaggio è la componente fondamentale su cui è basato il complesso processo di
scambio d’informazioni tra due entità: mittente e destinatario.
L’architettura prevede una suddivisione in livelli.
Contratti
Definiscono i vari aspetti del sistema del messaggio, il contratto dati descrive ogni
parametro che costituisce ogni messaggio che un servizio può creare o utilizzare.
I parametri del messaggio sono definiti da documenti XSD (XML [eXtensible Markup
Language] Schema Definition language) e consentono a qualsiasi sistema compatibile con
XML di elaborare i documenti.
Il contratto del messaggio definisce parti specifiche del messaggio utilizzando protocolli
SOAP e consente un controllo più preciso delle parti del messaggio laddove tale
precisione è richiesta dall’interoperabilità.
Il contratto di servizio specifica le firme effettive del metodo del servizio ed è distribuito
come interfaccia in uno dei linguaggi di programmazione supportati.
I criteri e le associazioni stipulano le condizioni necessarie per la comunicazione con un
servizio, l’associazione, ad esempio, deve specificare almeno il trasporto utilizzato HTTP o
TCP e una codifica.
I criteri comprendono requisiti di sicurezza e altre condizioni che devono essere
soddisfatte per la comunicazione con un servizio.
L’architettura prevede una suddivisione in livelli che consentono d’isolare e raggruppare le
varie componenti che la formano.
TPSIT 5
434
um 107 di
Fase di esecuzione del servizio
Contiene i comportamenti che si verificano solo durante l’operazione effettiva del servizio.
La limitazione delle richieste controlla la quantità dei messaggi elaborati che può essere
variata se la richiesta per il servizio raggiunge un limite preimpostato.
Un comportamento dell’errore specifica la situazione che ha luogo quando si verifica un
errore interno nel servizio, ad esempio controllando le informazioni comunicate al client.
Il comportamento dei metadati stabilisce il modo e l’opportunità di renderli disponibili al
mondo esterno.
Il comportamento dell’istanza specifica il numero delle istanze del servizio che possono
essere eseguite, ad esempio, un singleton specifica una sola istanza per l’elaborazione di
tutti i messaggi.
Il comportamento della transazione consente il rollback delle operazioni transazionali in
caso di errore.
Il comportamento della distribuzione è il controllo del modo in cui un messaggio è
elaborato nell’infrastruttura WCF.
L’estendibilità consente la personalizzazione di processi della fase di run-time.
Il controllo messaggi, ad esempio, è la funzionalità che consente di controllare parti di un
messaggio e il filtro parametri consente il verificarsi di azioni preimpostate in base a filtri
che agiscono sulle intestazioni del messaggio.
Messaggistica
Questo livello è composto da canali: sono componenti che elaborano in vario modo un
messaggio, ad esempio autenticandolo.
I canali operano su messaggi e intestazioni del messaggio, inoltre illustrano i possibili
formati e modelli di scambio dei dati.
Un set di canali, inoltre, è noto come stack di canali.
È un livello diverso dalla fase di run-time del servizio che verte essenzialmente
TPSIT 5
434
um 108 di
sull’elaborazione del contenuto del corpo del messaggio.
Esistono tre tipi di canali.
1. Canali del trasporto: leggono e scrivono messaggi dalla rete o da altri punti di
comunicazione con il mondo esterno; esempi di trasporti sono HTTP, NamedPipes,
TCP e MSMQ, esempi di codifiche sono XML e sistema binario ottimizzato.
2. Canali di codifica: convertono i messaggi nella/dalla rappresentazione del flusso di
byte utilizzata dalla rete.
3. Canali del protocollo: implementano protocolli di elaborazione dei messaggi, spesso
leggendo o scrivendo intestazioni aggiuntive nel messaggio; esempi di tali protocolli
comprendono WS-Security (consente la protezione a livello di messaggio) e WSReliability Messaging (consente la garanzia di recapito dei messaggi).
La figura evidenzia i passaggi che il messaggio percorre prima di giungere a destinazione.
Attivazione e hosting
È l’ambiente di esecuzione dei servizi sviluppati in WCF, infatti, nella forma finale, un
servizio è un’applicazione.
Gli hosting più usati sono i seguenti.
1. IIS.
2. WAS (Windows Activation Services): una tecnologia studiata per WCF e che consente
di utilizzare su IIS anche protocolli differenti dall’HTTP.
Per abilitare WAS su IIS si usa il comando seguente.
C:\Programmi\iiS express>appcmd.exe
Strumento di amministrazione generico di IIS per la riga dei comandi.
APPCMD (comando) (tipo-oggetto) <identificatore> </parametro1:valore1 ...>
Tipi di oggetto supportati:
SITE
Amministrazione di siti virtuali
APP
Amministrazione di applicazioni
VDIR
Amministrazione di directory virtuali
APPPOOL Amministrazione di pool di applicazioni
CONFIG Amministrazione di sezioni di configurazione generale
MODULE Amministrazione di moduli del server
TRACE Utilizzo di registri di traccia di richieste non riuscite
(Per elencare i comandi supportati da ogni oggetto utilizzare /?, ad esempio 'ap
pcmd.exe site /?')
Parametri generali:
/?
Visualizza un messaggio della Guida sensibile al contesto.
/text<:valore> Genera l'output in formato testo (predefinito).
/text:* mostra in dettaglio tutte le proprietà degli oggetti.
/text:<attributo> mostra il valore dell'attributo
specificato per ogni oggetto.
/xml
Genera l'output in formato XML.
Usare per produrre output che può essere inviato a un altro
comando in esecuzione nella modalità /in.
/in o Legge e utilizza l'input XML da un input standard.
Usare per un input prodotto da un altro
TPSIT 5
434
um 109 di
comando in esecuzione nella modalità /xml.
/config<:*>
Mostra la configurazione per gli oggetti visualizzati.
/config:* include anche la configurazione ereditata.
/metadata
Mostra i metadati quando si visualizza la configurazione.
/commit
Imposta il percorso per salvare le modifiche di configurazione.
Può indicare un percorso di configurazione specifico, "site",
"app", "parent" o "url" per salvare la porzione corretta del
percorso modificato dal comando, "apphost", "webroot"
o "machine" per il livello di configurazione corrispondente.
/apphostconfig Specificare un altro file applicationHost.config da modificare.
/debug
Mostra le informazioni di debug per l'esecuzione del comando.
quali ad esempio "/!debug:value" utilizzato per l'impostazione della proprietà d
i configurazione "debug".
I servizi possono, inoltre, essere eseguiti manualmente come file eseguibili.
Un servizio può essere eseguito automaticamente come servizio Windows.
Scenari di esempio.
 Applicazione dashboard che esegue il polling di uno o più servizi per i dati e che li
visualizza in una presentazione logica.
 Esposizione di un flusso di lavoro implementato utilizzando WWF (Windows Workflow
Foundation) come servizio WCF.
 Applicazione Silverlight per eseguire il polling di un servizio per i feed di dati più
recenti.
ENDPOINT
Per inviare un messaggio, un mittente ha la necessità di conoscere alcune informazioni,
specifiche del servizio, per costruire correttamente la chiamata.
Il messaggio può essere semplice come una parola o come un singolo carattere inviato in
formato XML o complesso come un flusso di dati binari.
1. Deve sapere l’indirizzo verso cui indirizzare il suo messaggio, infatti, un destinatario in
attesa di ricevere richieste, è in attesa su di un determinato indirizzo.
2. Il mittente deve conoscere il modo in cui può comunicare con il destinatario del
messaggio; il destinatario di un messaggio, ad esempio, può richiedere l’invio di
particolari intestazioni oppure può richiedere che i messaggi scambiati devono
rispettare determinate regole di sicurezza, come confidenzialità e integrità.
3. Il mittente deve conoscere le operazioni che il servizio mette a disposizione e la
strutturazione che le richieste e le risposte devono avere.
In pratica, l’endpoint è uno specifico punto di accesso di un servizio che utilizza un tipo di
dati e un determinato protocollo, può appartenere ad un servizio disponibile in modo
continuo ospitato da IIS oppure essere un servizio ospitato in un’applicazione.
Per esempio, un endpoint può essere un client di un servizio che richiede dati da un
endpoint del servizio.
Questi tre elementi insieme compongono l’endpoint del servizio.
1. A (Address): è l’URL sul quale il servizio è in ascolto in attesa di ricevere una
richiesta, sintassi <protocollo>://<servername>:<porta>.
2. B (Binding): stabilisce la modalità d’interfacciamento con il servizio destinatario e
definisce i protocolli da utilizzare per la comunicazione.
3. C (Contract): è la rappresentazione dei servizi e delle operazioni richiamabili e
utilizzabili da un fruitore, esistono diverse tipologie di contratto.
3.1. Service Contract: contiene i metodi, chiamati operation e i parametri che un client
deve rispettare per comunicare con il servizio.
3.2. Data Contract: definisce gli schemi che rappresentano i tipi di dati scambiati tra le
TPSIT 5
434
um 110 di
entità.
3.3. Message Contract: consente di lavorare direttamente sul messaggio SOAP,
impostando le proprietà che appartengono all’header e al body.
3.4. Fault Contract: la gestione delle eccezioni è soggetta, anche se non in maniera
obbligatoria, all’osservazione di un particolare contratto.
3.5. Callback Contract.
TRACKBACK
SOAP è nato da un protocollo preesistente XML-RPC e vi ha aggiunto una serie di
funzionalità che ha sporcato il protocollo originario, rendendolo più complesso e meno
efficace del necessario.
XML-RPC non ha la complessità di SOAP pur assolvendo le stesse funzioni, inoltre è
usato da una grande varietà di blog per implementare il meccanismo di TrackBack per la
comunicazione e la notifica tra due risorse.
La risorsa A manda un Trackback ping, chiamato Pingback, alla risorsa B, la quale
risponde con un messaggio di avvenuta notifica o con un eventuale errore.
La risorsa A dovrebbe mandare un ping alla risorsa B, nel caso in cui sia presente, nella
risorsa A, un approfondimento o una citazione della risorsa B.
Il Trackback si è molto diffuso nei blog.
In questo caso, un blog riceve una serie di ping da altri blog e, solitamente, mostra, sotto a
ogni post, l’elenco dei ping ricevuti e riferiti a quello specifico post.
Il Trackback ping può contenere informazioni riguardanti il titolo della risorsa notificata, un
estratto della stessa e il titolo del sito web che ha inviato il ping.
L’unica informazione obbligatoria che un ping deve contenere è il permalink della risorsa
notificata.
Il termine Trackback è nato nel 2002, così battezzato da Six Apart che introdusse questo
sistema nella sua piattaforma di blogging Movable Type, sebbene il Trackback utilizzi
funzionalità presenti nelle specifiche HTTP 1.0 e quindi già dal 1992.
Un fenomeno diffuso è l’utilizzo dei Trackback a scopo di SPAM, in pratica l’invio
indiscriminato di Trackback ping allo scopo di far apparire i propri link nell’elenco dei ping
di qualche blog.
Esistono, però, filtri per individuare i Trackback di SPAM, implementati in molte delle
piattaforme di blogging che supportano il Trackback, riducendo, almeno parzialmente, il
fastidio causato dallo SPAM.
Chiamata
Il seguente codice rappresenta una chiamata alla procedura remota getStateName alla
quale è passato come parametro il valore 41.
POST /RPC2 HTTP/1.0
User-Agent: Frontier/5.1.2 (WinNT)
Host: betty.userland.com
Content-Type: text/xml
Content-length: 181
<?xml version="1.0"?>
<methodCall>
<methodName>getStateName</methodName>
<params>
<param>
<value>
<i4>41</i4>
</value>
TPSIT 5
434
um 111 di
</param>
</params>
</methodCall>
La chiamata si compone di due parti.
1. Header: si trova l’User-Agent e l’host che stanno effettuando la chiamata, il content
type text/xml e il content-lenght che segna la lunghezza del messaggio.
2. Payload: è formato da una struttura il cui nodo radice è <methodCall> che indica che si
sta effettuando una richiesta; all’interno di questo nodo si trova <methodName> che
contiene il metodo da richiamare; ancora più internamente nella gerarchia si trova
<params> al cui interno possono essere contenuti uno o più valori <param> ciascuno
valorizzato con un <value>.
È possibile indicare un tipo che specifica il senso del valore del parametro.
<i4> o <int>
<boolean>
<string>
<double>
<dateTime.iso8601>
<base64>
Un intero di 4 byte
0 (false) or 1 (true)
string
numeri float
date/time
base64-encoded
-12
1
hello world
-12.214
19980717T14:08:55
binaryeW91IGNhbid0IHJlYW QgdGhpcyE=
Il tipo <struct>, è un record al cui interno sono contenute delle celle che hanno un nome e
un valore che a sua volta può anche essere di tipo <struct>.
<struct>
<member>
<name>lowerBound</name>
<value>
<i4>18</i4>
</value>
</member>
<member>
<name>upperBound</name>
<value>
<i4>139</i4>
</value>
</member>
</struct>
L’ultimo tipo strutturato che può essere passato come parametro ad una procedura di
remote call è il tipo <array>.
<array>
<data>
<value>
<i4>12</i4>
</value>
<value>
<string>Egypt</string>
</value>
<value>
<boolean>0</boolean>
TPSIT 5
434
um 112 di
</value>
<value>
<i4>-31</i4>
</value>
</data>
</array>
Risposta
Il server remoto restituisce l’esito al chiamante, utilizzando il formato XML.
La struttura di una risposta si compone di un header e di un payload.
HTTP/1.1 200 OK
Connection: close
Content-Length: 426
Content-Type: text/xml
Date: Fri, 17 Jul 1998 19:55:02 GMT
Server: UserLand Frontier/5.1.2-WinNT
<?xml version="1.0"?>
<methodResponse>
<fault>
<value>
<struct>
<member>
<name>faultCode</name>
<value>
<int>4</int>
</value>
</member>
<member>
<name>faultString</name>
<value>
<string>
Too many parameters.
</string>
</value>
</member>
</struct>
</value>
</fault>
</methodResponse>
La root <methodResponse> contiene un <params> che a sua volta contiene un <param>>
che a sua volta contiene ancora un singolo <value> che definisce il valore della risposta.
Viceversa il <methodResponse> potrebbe anche contenere un <fault> che a sua volta
contiene dei dati che identificano un eventuale problema.
In XML non possono convivere segmenti <fault> e segmenti <params>.
Se una richiesta non ha dato esito corretto, ovviamente non può esserci un params che
identifica il risultato.
APPLICATION DOMAIN
Ogni servizio WCF non può esistere da solo ma dev’essere obbligatoriamente ospitato
all’interno di un processo Windows chiamato processo host.
TPSIT 5
434
um 113 di
In ogni processo, possono esistere più di un Application Domain e all’interno di ognuno di
essi può essere ospitato anche più di un servizio WCF.
Di conseguenza, un servizio WCF può essere ospitato in una qualsiasi tipologia di
applicazione.
 Console Application.
 Windows Forms Application.
 ASP.NET/IIS/WAS.
 Enterprise Services.
 Windows Services.
Self-hosting: in pratica, un servizio WCF è ospitato all’interno di un’applicazione .NET.
class ServiceHost
Ogni servizio WCF è eseguito all’interno del suo host.
Il servizio è gestito da una classe chiamata ServiceHost.
WCF fornisce sia una classe base astratta, il ServiceHostBase sia un’implementazione di
default direttamente utilizzabile, il ServiceHost.
Esiste un rapporto uno a uno tra il servizo WCF e il ServiceHost presso il quale è ospitato.
Un ServiceHost può ospitare un solo tipo di servizio.
Quando si lancia un’applicazione segue la creazione di un processo che altro non è che
l’immagine dell’applicazione in memoria.
Lo spazio di memoria riservato ai processi e ai dati che gestiscono è isolato, quindi il
processo A non può accedere all’area di memoria riservata al processo B.
Per scambiare dati fra processi è necessario stabilire un meccanismo di comunicazione
fra processi, il SO usa l’RPC che gestisce la comunicazione fra processi.
Il .NET Framework supera questo concetto, introducendo il concetto di application domain.
I processi di questo tipo sono svincolati dall’uso di RPC, la memoria è interamente gestita
dal CLR (Common Language Runtime).
In .NET è possibile creare più application domain e persino application domain annidati
all’interno di un’unica applicazione.
Ciascun application domain ha una gestione della memoria separata.
Ovviamente .NET Remoting deve poi individuare dei metodi affinché gli oggetti
appartenenti ai vari application domain possano comunicare.
Quando s’invoca un oggetto presente nell’application domain corrente, è sufficiente una
chiamata locale.
Se invece l’oggetto è presente in un diverso application domain si rende necessaria una
chiamata remota.
In tal caso sul client è creato un proxy, in pratica un’istanza della classe TransparentProxy,
mentre sul server è creato uno stub.
La potenza e la caratteristica di .NET Remoting, rispetto ad altre architetture di
remotizzazione concorrenti, è che tutto questo meccanismo è completamente automatico
e non è quindi richiesto al programmatore l’onere di codificare tali strati proxy/stub.
MARSHALING
Risolve due problemi.
1. Conversione dei parametri in stream di byte
I parametri dei metodi devono essere impacchettati secondo un formato che sia compreso
da client e server e che sia gestibile direttamente dallo strato di trasporto.
Le strutture dati complesse, per esempio struct devono essere ridotte a sequenze di byte,
è la serializzazione.
TPSIT 5
434
um 114 di
2. Conversione fra dati appartenenti a piattaforme eterogenee
Macchine diverse hanno rappresentazioni diverse dei dati, per esempio rappresentazione
little endian e big endian.
Il codice che esegue la serializzazione e la deserializzazione è generato automaticamente
dalla specifica delle interfacce dei metodi, descritta in un IDL (Interface Definition
Language) che fa parte degli stub.
Tramite questo meccanismo la locazione dell’oggetto è completamente trasparente
all’applicazione che l’utilizza, nel senso che è la tecnologia che risolve la chiamata al
metodo remoto sia che l’oggetto si trovi sullo stesso PC, all’interno di una LAN oppure su
di un server remoto cui si accede tramite Internet.
Ci sono due diversi metodi per rendere disponibile ad un client un oggetto remoto.
1. Un oggetto MBV (Marshal By Value) è creato sul server, serializzato in uno stream,
trasmesso al client su cui è ricostruita un’esatta replica dell’oggetto stesso che non ha
più alcuna relazione con l’istanza originaria.
2. Un oggetto MBR (Marshal By Reference) passa il riferimento ad un oggetto creato ed
eseguito sul server al client che ne trattiene, dunque, solo un riferimento, proxy.
Di fatto gli oggetti MBR si possono considerare i veri oggetti di remotizzazione proprio
perché essi effettivamente sono eseguiti su un server e non trasferiti.
Inoltre, gli MBV possono andare incontro ad incongruenze quali il pericolo di portare al loro
interno riferimenti a risorse che hanno senso e visibilità solo sul server e che invece li
perdono a seguito dei trasferimenti.
Si pensi, ad esempio, alla connessione ad un server DB o l’accesso ad un file fisicamente
raggiungibile solo dal server o ad una periferica hardware del server.
Ad un oggetto MBV è richiesta, come unico requisito, la presenza dell’attributo Serializable
a livello di classe oppure l’implementazione dell’interfaccia Iserializable.
[Serializable]
public class MyMBVObject {
int Code;
string Name;
double Execute(int quantity);
}
Oppure.
public class MyMBVObject : ISerializable {
int Code;
string Name;
double Execute(int quantity);
// unico metodo da implementare dell’interfaccia ISerializable
void GetObjectData(SerializationInfo info, StreamingContext context);
}
Un oggetto MBR, invece, deve necessariamente discendere direttamente o indirettamente
dalla classe System.MarshalByRefObject.
public class MyMBRObject :MarshalByRefObject {
int Code;
string Name;
double Execute(int quantity);
}
TPSIT 5
434
um 115 di
Alla base del principio di remotizzazione delle chiamate c’è il meccanismo di
serializzazione delle richieste al server e delle risposte e di come sono salvate le
informazioni in un pacchetto informativo dalla struttura nota ai due capi della
comunicazione: tale compito spetta ai Formatter.
Il .NET Framework è flessibile: prevede due modalità di formattazione delle richieste.
1. SoapFormatter: usa il formato XML SOAP per il formato delle chiamate e delle risposte
remote.
2. BinaryFormatter: usa un formato completamente binario e quindi più ottimizzato.
Esse non sono che l’implementazione di una specifica più generica e astratta, basata sul
polimorfismo d’implementazione dell’interface IFormatter che prevede la libertà di
estendere e aggiungere nuovi formati di formattazione semplicemente fornendo nuove
implementazioni di tale interfaccia.
WS-RELIABLE-MESSAGING
Un fruitore, client di un servizio, invia una richiesta che può potenzialmente attraversare
diversi sistemi, S/W e H/W, come ad esempio una LAN.
Cosa succede se la rete non è in grado di servire le richieste?
Cosa succede se il server verso il quale si spediscono i messaggi non è disponibile?
WS-Reliability-Messaging è un’evoluzione della specifica WS-Reliability ed è stata studiata
appositamente per garantire la consegna affidabile dei messaggi tra sistemi eterogenei.
Il sender si predispone per l’invio di una sequenza di tre messaggi (1).
Dopo aver inizializzato la comunicazione (2), il sender incomincia l’invio della sequenza di
messaggi (3, 4, 5) allegando nell’intestazione un identificatore univoco (UID) che il
receiver utilizzerà per collegare tra loro i diversi messaggi ricevuti.
L’ultimo messaggio conterrà anche un lastMessage token il quale ha lo scopo d’indicare al
receiver che l’invio dei messaggi è terminato (5).
A questo punto il receiver invia un report per elencare quali sono i messaggi che sono stati
correttamente ricevuti (6).
Il sender ritenterà l’invio dei messaggi che mancano (7) e resta in attesa di un nuovo
resoconto da parte del receiver.
TPSIT 5
434
um 116 di
Questo ciclo non termina fino a quando il receiver non avrà ricevuto tutti i messaggi (8).
In alcuni casi, dipendentemente dalle impostazioni del servizio, il receiver può riordinare i
messaggi nella giusta sequenza e solo a questo punto inoltrarli al servizio per il suo
utilizzo (9).
Utilizzando questo meccanismo, ogni messaggio è inviato e ricevuto solo una volta.
Questo significa che è possibile evitare l’invio e la conseguente elaborazione di messaggi
duplicati.
L’ordine dei messaggi è, invece, una caratteristica opzionale.
Se l’applicazione richiede che i messaggi devono essere elaborati rispettando la sequenza
in cui sono stati inviati, allora il receiver si occuperà di riordinarli appena li avrà ricevuti
tutti.
MESSAGGI SU CODE
Che cosa accadrebbe nel caso in cui una delle due entità è offline?
La certezza di recapito dei messaggi rimane inalterata ma come si può garantirli?
In WCF si usa MSMQ che fornisce affidabilità, anche se una delle entità, per svariati
motivi, non è disponibile.
Ci sono due binding che consentono di registrare i messaggi o di recuperarli da MSMQ.
1. <msmqIntegrationBinding>: è il binding che garantisce la totale interoperabilità con le
attuali tecnologie esistenti.
2. <netMsmqBinding>: ottimizzato se sia il client sia il server sono entrambi sviluppati
utilizzando WCF.
TRANSAZIONI
Che cosa accade se durante le fasi d’invio, ricezione o elaborazione di un messaggio o di
una richiesta, è generata un’eccezione?
L’esecuzione di una serie di passaggi può essere considerata conclusa con successo se e
solo se, tutti i passaggi sono andati a buon fine.
Le transazioni nascono proprio per garantire il corretto funzionamento dell’applicazione
anche alla presenza di eccezioni.
Questo tipo di operazioni sono definite di tipo ACID (Atomicity, Consistency, Isolation,
Durability).
In WCF le transazioni sono gestite utilizzando la specifica WS-AT (Atomic Transaction),
un protocollo sviluppato per garantire l’interoperabilità tra sistemi eterogenei.
Usa un protocollo a due fasi chiamato Two-Phase Commit.
I binding che supportano le transazioni sono i seguenti.
<netTcpBinding>
<netNamedPipeBinding>
<WSHttpBinding>
<WSDualHttpBinding>
<WSFederationHttpBinding>
SESSIONE
Come si controllano le sessioni dei servizi in esecuzione all’interno dei ServiceHost.
È bene notare che quando si parla di sessione non c’è alcun riferimento alla gestione della
sessione di ASP.NET sebbene, per certi versi concettualmente simili.
La sessione in WCF è il mantenimento dei valori di un’istanza attraverso più chiamate
provenienti dallo stesso client.
Il meccanismo che regola le sessioni è basato su un identificativo, generato a seguito della
prima richiesta e che, come in ASP.NET, identifica univocamente il client.
In WCF la sessione è supportata con specifici canali che implementano l’interface
TPSIT 5
434
um 117 di
ISessionChannel:IInputSessionChannel,IOutputSessionChannel, IDuplexSessionChannel.
Questi canali si occupano di generare e gestire il session ID.
La gestione della sessione è un’esigenza del servizio e perciò definita lato server.
Il client ne dev’essere assolutamente a conoscenza.
Per questo motivo è sul contratto di servizio che si deve scegliere se quello specifico
contratto può funzionare solo in presenza di una sessione.
Purtroppo, anche se il contratto lo prevede, non tutti i binding possono gestire la sessione.
A run-time è, infatti, effettuato un controllo per verificare se il binding scelto è compatibile
con l’impostazione data al controllo di sessione.
Se la sessione non è supportata allora sarà generata un’eccezione.
I binding che consentono di utilizzare la sessione sono i seguenti.
<WSHttpBinding>
<WSDualHttpBinding>
<netTcpBinding>
<netNamedPipeBinding>
<CustomBinding>
CONCORRENZA
Gestire la sessione è un’operazione delicata considerando che contemporaneamente
possono esserci diverse richieste da diversi client.
L’istanza del servizio può essere considerata o non thread safe.
L’utilizzo dell’impostazione PerCall per l’InstanceContextMode rende l’istanza del servizio
automaticamente thread safe proprio perché ogni richiesta verso il servizio avrà una
particolare istanza dedicata all’esecuzione di quella specifica chiamata.
In questo caso non c’è bisogno di proteggere le informazioni così come non c’è alcuna
necessità di sincronizzarle.
Al contrario, l’utilizzo della modalità PerSession necessità di gestire la concorrenza e la
sincronizzazione.
Uno stesso proxy, infatti, potrebbe gestire diversi thread lato client che inviano
contemporaneamente richieste verso il servizio.
Infine, la modalità Single è quella che maggiormente deve prevedere meccanismi di
sincronizzazione e di gestione della concorrenza per garantire un corretto funzionamento.
Più client diversi tra loro hanno, infatti, accesso alla stessa istanza.
In WCF, l’enumeratore ConcurrencyMode consente di stabilire la modalità di gestione
della concorrenza che dev’essere messa in atto.
SICUREZZA
Sicurezza applicativa
WCF permette di utilizzare diversi meccanismi di autenticazione.
 Windows: il client correda la richiesta con le sue credenziali e il servizio autentica
utilizzando, per esempio Kerberos.
 Username: il client invia username e password e il servizio provvede alla verifica
dell’esistenza di queste credenziali, per esempio un DB.
 Certificati: la richiesta è inoltrata con un certificato assegnato al client e che consente
d’identificarlo univocamente, il servizio valida il certificato e autenticare la richiesta.
 Custom: è possibile creare una tipologia di credenziale personalizzata.
 Issued Token: un client ottiene da un Security Token Service, un token che poi
presenta al servizio richiesto, quest’ultimo, fidandosi di chi ha rilasciato il token,
autentica il client e di conseguenza la sua richiesta.
WS-Security.
TPSIT 5
434
um 118 di
 XML Encryption, per la criptazione delle informazioni e quindi la loro confidenzialità.
 XML Digital Signature, per la firma digitale e l’integrità dei dati.
PROGETTO
Si basa sulla creazione di tre componenti.
1. Un oggetto che può essere invocato in modo remoto.
2. Un application domain che aspetta le richieste e le gestisce, applicazione host.
3. Un application domain che esegue le richieste, applicazione client.
Lo scopo è di gestire la comunicazione fra questi tre componenti utilizzando .NET
Remoting.
Occorre realizzare un oggetto che esponga metodi che possono essere invocati in modo
remoto.
using System;
using System.Data;
using System.Data.SqlClient;
namespace progetto.RemotingServices {
public class Server : MarshalByRefObject {
// apertura e connessione al DB
}
public Server() {
InitializeComponent();
}
public string ScalarMethod(string parametro1, ref int parametro2) {
parametro2 = parametro2 + 2;
return "prova: " + parametro1;
}
public DataSet GetData() {
DataSet ds = new DataSet("Ds Prova");
sqlDataAdapter1.Fill(ds);
sqlDataAdapter3.Fill(ds);
sqlDataAdapter2.Fill(ds);
return ds;
}
A parte il dover ereditare dalla classe MarshalByRefObject, requisito che comunque non
ne pregiudica l’esecuzione in locale, non vi è alcun elemento che lascerebbe supporre la
natura “remota” di questa classe.
C’è un costruttore di default, il metodo ScalarMethod che accetta parametri scalari, una
stringa per valore e un intero per referenza e restituisce un altro scalare di tipo stringa.
Il metodo GetData restituisce un oggetto strutturato, in altre parole DataSet ed è un
trasferimento di oggetti complessi via remoting.
Tale metodo si occupa autonomamente di aprire una connessione ad un SQL (Structured
Query Language) Server locale puntando ad un DB.
La connectionstring è letta da una costante presente nella classe.
Host
È un’applicazione che si pone in attesa di richieste per un oggetto e le gestisce.
A differenza di altri sistemi di remotizzazione più complessi quali COM+ o CORBA che
sono tenuti in piedi da un application server che funge da Object Broker, .NET Remoting
fornisce solo una tecnologia di remotizzazione delle chiamate e non una vera e propria
infrastruttura che funga da incubatrice delle chiamate remote.
TPSIT 5
434
um 119 di
Non esiste un’applicazione standard che funge da host, si deve scriverne una da zero.
Un host non è altro che una qualsiasi applicazione .NET eseguibile: WinForms, Console o
un Windows Service.
L’unica alternativa alla scrittura di un proprio host è di utilizzare IIS come host accettando,
però, alcune limitazioni come quella di poter utilizzare solo il canale HTTP.
Scrivere un proprio host, corrisponde a scrivere una linea di codice, tutto, infatti, si basa
sul file di configurazione che, opzionalmente, può accompagnare le applicazioni .NET di
tipo WinForms, Console o Windows Service, in altre parole il file APP.CONFIG, oppure, se
si usa IIS come host, il file WEB.CONFIG.
L’unico requisito è che l’applicazione e quindi il processo Windows che l’ha generato, resti
in vita per tutto il tempo.
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<system.runtime.remoting>
<customErrors mode="off" />
<application name="remotingData">
<lifetime
leaseTime="100D"
sponsorshipTimeOut="3M"
renewOnCallTime="5M"
leaseManagerPollTime="1M"
/>
<channels>
<channel ref="tcp" port="8737">
<serverProviders>
<formatter ref="binary" typeFilterLevel="Full" />
</serverProviders>
</channel>
</channels>
<service>
<wellknown mode="SingleCall"
type="RemotingServices.InterfaceServer, Server"
objectUri="myFirstService" />
</service>
</application>
</system.runtime.remoting>
</configuration>
È un file XML con un nodo principale <configuration>.
Sotto di esso trovano posto diversi altri nodi che servono a configurare aspetti e tecnologie
standard del Framework: dai file di log, alla versione degli assembly.
Si può usare per salvare le proprie proprietà applicative in luogo dei file INI o di file XML.
C’è anche la specifica per remoting, il nodo <system.runtime.remoting>, composto dal
nodo <customErrors>, di default impostato a false che indica se dev’essere permesso che
le eccezioni del componente di remoting risalgano fino al client che l’ha invocato, valore
false oppure che tutti gli errori siano filtrati e sostituiti con messaggi di errore applicativi.
A questo punto c’è un nodo <application> che rappresenta il servizio di remoting da
pubblicare, ce ne può essere uno solo per host.
Nell’esempio l’applicazione si chiama remotingData, in essa sono definiti tutti gli aspetti del
servizio esposto: dal <channels> usato per le comunicazioni, al <formatter> usato per lo
streaming dei dati, al nome della classe vera e proprio da pubblicare.
TPSIT 5
434
um 120 di
Il nodo <channels> enumera i canali che s’intendono usare nell’host; nell’esempio è usato
il canale tcp fornito con il .NET.
Il servizio è pubblicato sulla porta 8737 e farà uso del formatter di tipo binary.
Il nodo <service> rappresenta il servizio esposto, è definito nel sotto nodo <wellknown>
che riporta, nell’attributo type, proprio il full qualified name del tipo da pubblicare, in altre
parole il nome della classe comprensivo di namespace e l’assembly in cui è contenuto
separati da una virgola, l’alias simbolico del tipo sarà myFirstService.
Codifica UTF-8 (Unicode Transformation Format 8 bit)
Simboli e caratteri che non rientrano nell’intervallo standard ASCII vanno trasformati in
entità HTML.
Client
Anche in questo caso serve un file di configurazione simile a quanto visto per l’host.
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<system.runtime.remoting>
<application name="myClient">
<channels>
<channel ref="tcp" port="0">
<clientProviders>
<formatter ref="binary" />
</clientProviders>
</channel>
</channels>
<lifetime
leaseTime="5000M"
sponsorshipTimeout="5000M"
renewOnCallTime="5000M"
leaseManagerPollTime="5000M"
/>
<client>
<wellknown
type="RemotingServices.InterfaceServer, Server"
url="tcp://localhost:8737/remotingData/myFirstService"
/>
</client>
</application>
</system.runtime.remoting>
</configuration>
All’apparenza le due versioni sono quasi indistinguibili: esiste un nodo <client> al posto del
nodo <service>, in cui è definito il tipo di cui effettuare l’invocazione remota e il suo URL.
L’altra differenza sta nel nodo <channels> che, oltre all’alias del canale da usare, definisce
anche un attributo port che non è altro che la parte del client su cui il server potrà aprire
una comunicazione in senso contrario, in pratica il server che contatta il client, in modo da
realizzare una comunicazione bidirezionale.
A differenza dell’host, nel caso del client è richiesta anche l’aggiunta dell’assembly del
componente remoto tra i riferimenti del progetto.
Il client effettuerà una chiamata e un’esecuzione remota della classe seguente.
TPSIT 5
434
um 121 di
System.Runtime.Remoting.RemotingConfiguration.Configure(Application.ExecutablePath
+ ".config");
Server srv = new Server();
DataSet ds = srv.GetData();
Basta la sola chiamata del caricamento della configurazione di remoting, fatta una sola
volta per tutta la durata dell’applicazione e, da quel momento in poi, ogni volta che il client
cercherà d’invocare la classe Server, il sotto sistema di remoting di .NET si accorgerà che
il tipo RemotingServices.Server è un tipo remoto raggiungibile ad un indirizzo.
Invocherà la chiamata remota, creerà una classe proxy, in altre parole una classe
isomorfica con proprietà e metodi identici a quelli della classe remota, usando il formatter
specificato dalla comunicazione, preparerà dei pacchetti di dati contenenti i parametri da
passare al metodo remoto chiamato, trasferirà l’intera chiamata insieme a questi pacchetti
all’oggetto remoto usando il canale previsto per la comunicazione, attenderà il risultato
dell’esecuzione e poi eseguirà l’operazione contraria con i valori di ritorno della chiamata
indietro verso il client.
Tutto in modo trasparente all’utente e anche al programmatore.
MICROSOFT AXUM
Ha un grande supporto per la scrittura di applicazioni distribuite, in effetti, uno dei motivi
per l’adozione di una tale linea d’isolamento è che i domini possono interagire a livello
locale o in remoto senza alcun cambiamento nel modello.
Per esempio, il modo in cui il web è programmato con domini, servizi, domande di
applicazioni SOA, agenti di gestione dei protocolli.
Per raggiungere un agente all’interno di un dominio, si deve avere un indirizzo, questo è
vero in locale e in remoto, all’interno di un processo, è un po’ più facile, perché il nome del
tipo di agente si comporta come un indirizzo di default.
Il run-time di Axum attraverso un’interface denominata IHost, permette di dare un agente
indirizzo all’interno di un dominio, per essere precisi, quello che si associa con un indirizzo
è una fabbrica per gli agenti di tipo hosted che è utilizzato per creare istanze agente
quando si crea una nuova connessione.
Ogni comunicazione di base o servizio di hosting deve avere la propria implementazione
di IHost, Axum è dotato di uno per WCF e uno per inprocess communication.
L’indirizzo può essere associato ad un’istanza di dominio esistente, nel qual caso ha
creato un’istanza agente associata a tale dominio.
Per esempio, è possibile ospitare domini come servizi con il codice seguente.
channel Simple
{ input string Msg1;
output string Msg2;
}
domain ServiceDomain
{ agent ServiceAgent : channel Simple
{ public ServiceAgent ()
{ // … }
}
}
agent Server : channel Microsoft.Axum.Application
{ public Server ()
{ var hst = new WcfServiceHost(new NetTcpBinding(SecurityMode.None, false));
hst.Host<ServiceDomain.ServiceAgent>("net.tcp://localhost/Service1"); }
}
TPSIT 5
434
um 122 di
Ogni volta che un client si connette all’indirizzo net.tcp://localhost/Service una nuova
istanza di ServiceAgent è creata, associata con una nuova istanza ServiceDomain.
Se, invece, si vuole creare agenti da associare ad una singola istanza di dominio, si deve
passare uno per un Host.
hst.Host<ServiceDomain.ServiceAgent("net.tcp:...", new ServiceDomain());
Vi è un’interface corrispondente per il lato client, chiamata ICommunicationProvider che è
utilizzata per creare una nuova connessione ad un servizio di Axum o qualsiasi altro
servizio che è scritto in Axum: è un accoppiamento lasco.
La connessione al servizio è simile a questa.
var prov=new WcfCommunicationProvider(new NetTcpBinding(SecurityMode.None, alse));
var chan = prov.Connect<Simple>("net.tcp://localhost/Service1");
Non è necessario creare un nuovo provider di comunicazione per ogni connessione o un
nuovo host per ogni chiamata Host.
TPSIT 5
434
um 123 di
WS
INTRODUZIONE
Un servizio è una potenzialità messa a disposizione da un fornitore e utilizzata da uno o
più consumatori.
Chi sviluppa S/W, indipendentemente dalle architetture utilizzate, sa quanto siano
importanti i servizi messi a disposizione dalle librerie, siano esse dell’ambiente che si sta
utilizzando oppure di terze parti che permettono di utilizzare servizi computazionali
senza conoscere la loro implementazione, trattandoli come black box che dichiarano di
ricevere certi parametri e di restituire un risultato computazionale certo e corretto.
Importante diventa il canale attraverso il quale un servizio è erogato e distribuito.
La distribuzione dei servizi, in altre parole la possibilità di avere fornitori di servizio che
non siano soltanto accanto ai loro consumatori ma che siano geograficamente distribuiti,
ha fatto nascere l’esigenza di avere la disponibilità di servizi dotati della massima forma di
distribuzione, in pratica Internet e all’interno di essa il sistema di distribuzione delle
informazioni che più si addice a diventare canale di erogazione di un servizio è il canale
web, ecco nascere quindi il concetto di WS, in italiano servizio web, in altre parole un
servizio computazionale erogato attraverso il web.
Importante è il concetto d’interoperabilità di un servizio, in altre parole completa
indipendenza dal linguaggio di programmazione, dal SO, dalla piattaforma, dal modello di
oggetti e dalla particolare periferica utilizzata: grazie all’utilizzo di protocolli standard basati
su XML, un WS può essere interrogato da qualsiasi applicazione, scritta in qualsiasi
linguaggio, ospitato in un SO qualunque.
L’unica cosa da conoscere per poterlo utilizzare è l’interfaccia dei metadati che espone,
esattamente quello che serve per utilizzare un servizio tradizionale.
Nelle organizzazioni industriali non si utilizza un’unica piattaforma informatica, in cui
possono essere condivise applicazioni e dati; si trovano, invece, piattaforme diverse, con
standard diversi che rendono difficile condividere le applicazioni e soprattutto i dati.
La soluzione adottata è stata quella di duplicare i DB sulle diverse piattaforme: problemi.
 Allineamento dei dati tra le varie piattaforme.
 Utilizzo di risorse per il trasporto di dati da una parte all’altra.
 Verifiche che devono essere fatte dal personale.
Lo standard XML ha reso possibile l’interoperabilità tra piattaforme diverse: vantaggi.
 È più semplice scambiare dati tra una piattaforma e l’altra.
 Non occorre duplicare i dati.
 Nessuna necessità di spazio di archiviazione aggiuntivo.
 Nessuna necessità di verifiche e allineamento di dati.
 Nessun utilizzo di risorse macchina per la replicazione di dati.
I WS non sono il primo tentativo di creare un ponte di comunicazione fra elementi installati
in luoghi geograficamente distanti, tuttavia hanno apportato un cambiamento proprio per la
loro caratteristica di aggirare i limiti imposti dai linguaggi per diventare “intercomunicanti” in
modo del tutto indipendente dalla piattaforma di sviluppo e dal SO.
Il programmatore A crea un’applicazione, un WS e lo rende disponibile su un sito web.
Il programmatore B interroga quel sito web e senza avere conoscenze di com’è
implementato il WS, deve sapere solo quali metodi del WS invocare, ne usa le
informazioni.
Questo consentirebbe a chi scrive l’applicazione di evitare di dover perdere tempo con la
creazione di funzionalità base e invece di potersi concentrare sulla personalizzazione
richiesta dai clienti, realizzando applicazioni più funzionali in meno tempo.
TPSIT 5
434
um 124 di
Sono piccole unità di codice, sviluppate per eseguire un limitato insieme di compiti, gli
esempi tipici possono essere: servizi di validazione per i numeri di carte di credito,
aggiornamenti in tempo reale delle quotazioni di borsa, servizi di traduzione online.
I WS consentono un veloce riutilizzo del codice: grazie alla facile reperibilità e alla
semplicità con cui è possibile interrogarli, sarà sempre più frequente l’utilizzo di WS al
posto di implementazione di codice ex novo.
L’utilizzo dei WS è molto importante perché permette un più facile scambio d’informazioni
tra un’applicazione e un client, anche quando il client è un’app installata su uno
smartphone: l’uso dei WS può portare a nuovi tipi di app per utenti sempre in movimento.
WS
È un servizio disponibile in rete che utilizza il linguaggio XML per lo scambio dei
messaggi tra il sistema richiedente e il sistema che eroga il servizio.
Un WS presenta le seguenti caratteristiche.
 È ricercabile e recuperabile dinamicamente sulla rete.
 È definito dalla sua interfaccia, indipendente dall’implementazione del servizio stesso.
Un WS è quindi un’entità debolmente accoppiata, da un punto di vista tecnologico poiché
può essere implementato e invocato da sistemi realizzati con diverse tecnologie.
Gli attori coinvolti in un’interazione con un WS sono i seguenti.
1. Consumer: è un sistema S/W che richiede un servizio.
2. Provider: è un sistema che fornisce il servizio.
3. Registry: è un repository in rete di tutti i servizi consultabili e fruibili.
I WS danno “il meglio di se stessi” in un’architettura distribuita e in questo caso s’intende
in particolar modo un’architettura geograficamente distribuita, in altre parole in una
situazione peculiare di chi ha, ad esempio, più filiali, dislocate su un ampio territorio che in
qualche modo devono comunicare tra loro.
Il concetto principale in OOP (Object Oriented Programming) è che un oggetto può essere
visto come una black box di cui è necessario conoscere solamente l’interfaccia senza
doversi preoccupare della sua implementazione interna.
Questo permette di sfruttare le librerie esterne, API, senza avere alcun tipo d’informazione
su come esse siano state implementate.
È ovvio che non è possibile utilizzare API scritte in un linguaggio su un altro; ad esempio
una libreria scritta in .NET non è utilizzabile su una piattaforma Java.
Sviluppare un’applicazione che interagisca con un DB centralizzato dislocato in un
qualsiasi punto, inoltre, l’applicazione dovrà girare su piattaforme diverse Windows, Linux,
Mc OS, Solaris e che implicitamente o esplicitamente sia imposto anche l’ambiente di
sviluppo .NET, Java, PHP, PERL (Practical Extraction and Report Language).
Una soluzione tra le più immediate ma meno efficiente dal punto di vista progettuale è
quella di scrivere ogni volta l’applicazione ex novo, facendosi carico quindi sia della
business logic sia dell’interazione con il DBMS (DataBase Management System).
Se da una parte la business logic può essere progettata astraendo dallo specifico
linguaggio di programmazione, per l’interazione con il DB occorrono i driver per ogni
specifico linguaggio e si deve lasciare aperta un porta dedicata sia sul server di DB sia
sulla macchina che ospiterà l’applicazione con tutti i problemi di sicurezza che ne
conseguono.
Inoltre, alcuni DBMS commerciali richiedono che le interrogazioni passino attraverso un
loro client proprietario, quindi soldi per le licenze.
È meglio progettare un’applicazione che si occupi di esporre un’unica interfaccia che
TPSIT 5
434
um 125 di
permetta l’interazione con il DB e che allo stesso tempo sia fruibile da qualsiasi linguaggio
di programmazione.
I WS sono la soluzione: si può progettare un’istanza di un qualsiasi WS come se
s’istanziasse una qualsiasi classe da una libreria esterna e utilizzarla nel WS.
Il WS funge da layer intermezzo tra DB e applicazioni client.
Due filosofie progettuali diverse.
1. Progettare un WS “su misura” del DB
Se si prende in considerazione un DB e si vogliono rendere disponibili le informazioni sui
clienti presenti nella tabella Clienti, si deve ricevere in ingresso al metodo il nome,
implementare all’interno la logica d’interrogazioni e restituire i risultati.
Se da un lato questa soluzione è molto sicura perché il client non si occuperà mai del DB,
dall’altra presuppone che ogni DB abbia il suo WS specifico, perché cambiando il DB
dovrà cambiare tutta la logica che sta dietro il WS stesso.
2. Progettare un WS generico
Si deve spostare la logica sul client per tenere il WS il più generale possibile, in pratica i
suoi metodi si occupano di ricevere in ingresso un generico comando SQL.
WS SOAP
Immaginare di utilizzare nell’applicazione Windows i servizi di un componente S/W
realizzato su Linux e in esecuzione in un server di cui s’ignora l’ubicazione geografica e
che si può raggiungere solo tramite un canale di comunicazione.
TPSIT 5
434
um 126 di
Immaginare di chiamare metodi di questo componente, ricevere i risultati e restituirli a
un’applicazione chiamante, di cui s’ignora tutto fuorché il fatto che ha chiamato tramite
Internet l’applicazione.
In altre parole, varie applicazioni, diverse per SO, linguaggio di programmazione, tipo e
località del server in cui sono eseguite, possano collaborare automaticamente e
scambiarsi dati, fare query su DB e completare calcoli, grazie all’architettura SOA.
Occorrono quattro protocolli, oltre a quello di trasmissione sulla rete che può essere
HTTP su TCP/IP che permettono il dialogo tra diverse piattaforme e rendono possibile
l'esportazione di componenti già esistenti verso client diversi grazie a quattro protocolli.
UDDI (Universal Description Discovery and Integration)
È un protocollo, nato da un consorzio formato da Microsoft, IBM e Ariba che permette di
localizzare i WS di proprio interesse, non è altro che un protocollo comune per la ricerca di
WS che può aiutare a trovare soluzioni in breve tempo e facendolo da un’unica fonte.
Si suddivide in tre elenchi (http://www.uddi.org).
1. Pagine Bianche: contengono le informazioni anagrafiche delle imprese registrate.
2. Pagine Gialle: descrivono i prodotti e i servizi offerti.
3. Pagine Verdi: mostrano i termini tecnici attraverso i quali basare lo scambio informativo
diretto tra i partner.
Alcune aziende hanno il compito di far si che i repositori UDDI siano sempre disponibili
che dei dati presenti in tali repositori sia fatto il backup e che sia, inoltre, garantita un’opera
di replica, di sincronizzazione delle informazioni tra i diversi repositori in modo tale da non
obbligare una determinata azienda a fare advertising, a registrare i propri servizi su
ognuno di questi siti.
Un metodo per trovare i siti che espongono servizi: DISCO (Discovery).
WSDL
Come può il client sapere a priori quali sono le operazioni messe a disposizione da un
TPSIT 5
434
um 127 di
server e in che modo invocarle?
Un file WSDL è un documento XML che risolve questo problema: contiene la definizione di
tutti i metodi, i parametri e i tipi restituiti per il dato WS.
Le operazioni e i messaggi sono definiti in maniera astratta e solo in seguito sono legati
(binding) ad un protocollo e ad un formato per definire un servizio concreto.
Un file WSDL rappresenta quindi un contratto pubblicato dall’entità che espone il servizio e
che è sottoscritto da tutti i client che hanno intenzione d’interagire con esso.
Ogni WS deve esporre il proprio file WSDL.
Per la difficoltà d’implementazione del formato, il programmatore non scrive a mano il file
WSDL ma si affida a framework, come ASP.NET che consente la generazione dinamica
del file WSDL.
 Definisce i metodi, i tipi di dati e i parametri.
 Definisce i messaggi.
 Definisce le operazioni come combinazione di messaggi: one-way, richiesta/risposta.
 Definisce i rapporti tra protocolli e formato dei messaggi: SOAP, HTTP GET/POST e
MIME.
Interrogando il WS con il parametro ?WSDL, si ottiene la sua descrizione formale,
generata automaticamente dal framework utilizzato per lo sviluppo.
A fronte dello scaricamento di questo file è possibile poi iniziare a instaurare un dialogo
con la controparte per creare i messaggi, in particolare per creare il payload, il contenuto
da inserire all’interno dei messaggi di SOAP per invocare il servizio stesso.
È formato da cinque sezioni.
1. types.
2. message.
3. portType.
4. binding.
5. service.
<message name="execQueryRequest">
<part name="strSql" type="xsd:string"/>
</message>
<message name="execQueryResponse">
<part name="return" type="tns:MyType"/>
</message>
Esempio, il WS deve ricevere una stringa (input) e restituire (output) in formato MyType.
<xsd:complexType name="MyType">
<xsd:all>
TPSIT 5
434
um 128 di
<xsd:element name="ID" type="xsd:string" ID="ID"/>
<xsd:element name="Applicazione" type="xsd:string"
ID="ID" Applicazione="Applicazione"/>
</xsd:all>
</xsd:complexType>
WSDL definisce quattro primitive di trasmissione che un endpoint può supportare.
Modalità di trasmissione
Descrizione
One-way
Il client spedisce un messaggio al servizio.
Il client spedisce un messaggio al servizio e riceve una
Request-respone
risposta.
Solicit-response
Il servizio invia un messaggio ad un client.
Notification
Il servizio invia un messaggio ad un client e questo risponde.
È possibile creare binding differenti per la stessa porta astratta ottenendo così più di una
porta “concreta” che espone le stesse funzionalità con modalità diverse.
Il servizio vero e proprio è poi composto da un insieme di porte, ognuna definita tramite il
suo indirizzo di rete.
La sezione binding è estensibile consentendo di accettare la definizione di altri protocolli di
comunicazione.
SOAP
È un protocollo applicativo, basato su XML che fornisce le regole per permettere ad
applicazioni distribuite di scambiarsi informazioni in un formato comune e di richiedere
l’esecuzione di servizi remoti, tra un consumer e un provider.
Le applicazioni si scambiano messaggi attraverso un pacchetto informativo che prende il
nome di payload, una struttura XML complessa.
Progettato per garantire l’interoperabilità.
 Non è legato ad un particolare protocollo di trasporto sottostante, l’unico vincolo è che
sia in grado di trasportare il semplice testo, sebbene non esistano controindicazioni di
sorta verso altri protocolli, si usa HTTP per le caratteristiche simili a quelle di SOAP.
 Non è legato ad un particolare linguaggio o SO.
Un client SOAP è un’applicazione in grado di creare un documento XML che contiene le
informazioni per eseguire un’operazione su un server.
Un server SOAP è in grado d’interpretare i messaggi SOAP ricevuti eseguendo le
operazioni specificate, restituendo il risultato al client in formato XML.
I messaggi SOAP seguono il seguente formato.
Il SOAP Envelope è l’elemento radice del documento XML che rappresenta un messaggio
SOAP ed è composto da due elementi.
1. Header: opzionale, estende il messaggio contenendo informazioni utili al suo
TPSIT 5
434
um 129 di
processamento come ad esempio l’identità dell’utente, informazioni riguardo alla
cifratura del documento o informazioni utili per il routing.
2. Body: obbligatorio, è un contenitore dove sono presenti le informazioni applicative per
il destinatario del messaggio.
XML
I WS si basano su documenti XML e quindi sono un modo efficiente per creare dei servizi
leggeri, standard e utilizzabili da qualsiasi dispositivo.
Standard di tutte le applicazioni web, consente d’immagazzinare, trasportare e scambiare
dati in modo del tutto agnostico rispetto alla piattaforma.
Si può, inoltre, dire che esiste una fase di scoperta che è effettuata, ad esempio
interrogando i repositori UDDI.
È un PAC (Programmable Application Component) accessibile tramite i protocolli standard
d’Internet, sfrutta gli aspetti positivi sia dei componenti sia del web: tutte le volte che si
deve mettere in comunicazione un client con un server ciò di cui si ha bisogno è di un
canale di comunicazione, lungo il quale fluisca dell’informazione che per essere
comprensibile ad entrambi, occorre utilizzare un protocollo sul quale sono in accordo.
 Racchiude le funzionalità in una black box.
 Interagisce con altri servizi con un’interfaccia ben definita.
 Ha bisogno del supporto dei protocolli Internet, è stateless e utilizza un’interfaccia
basata su messaggi definiti da un contratto.
In COM questo tipo di protocollo esiste ma è un’implementazione di cui è proprietaria
Microsoft così come del canale di comunicazione.
Il modello dei WS è indipendente dal linguaggio di programmazione, dalla piattaforma e
dal modello a oggetti.
 È possibile creare un WS con qualsiasi linguaggio su qualsiasi piattaforma.
 È possibile usufruire di un WS con qualsiasi linguaggio su qualsiasi piattaforma.
Quando si porta in Internet questa metafora, si possono usare i canali di comunicazione
già esistenti e già utilizzati in maniera non dipendente dal SO e dalla piattaforma, per
esempio HTTP, SMTP (Simple Mail Transfer Protocol), FTP.
Ciò che serve è anche un protocollo in grado di dare significato al flusso d’informazioni
che transita lungo questo canale.
Questi protocolli sono basati su particolari grammatiche XML, basati quindi su file di testo.
TPSIT 5
434
um 130 di
SICUREZZA
Si usa la specifica WS-Security che è un’estensione standard dell’envelope SOAP e
consente di definire token di sicurezza mediante i quali verificare l’autenticità di un
consumer, oltre all’integrità e confidenzialità del messaggio SOAP.
L’autenticazione del consumer al provider avviene in due modi.
1. Token di sicurezza: username e password.
2. Binary token: firmare digitalmente l’envelope SOAP.
L’username token profile è una specifica che definisce i token di sicurezza di tipo
username.
Un elemento dell’header può avere alcuni attributi speciali, tra questi gli attributi
mustUnderstand e actor.
L’attributo mustUnderstand può assumere, nella specifica SOAP, i valori 1 o 0 (default),
oppure i valori true o false.
Se l’attributo mustUnderstand vale 1 o true, il destinatario del messaggio deve
obbligatoriamente analizzare, “capire” e processare l’header.
In caso contrario, il destinatario deve segnalare un errore o, più precisamente usando il
linguaggio dei WS, deve inviare una segnalazione di fault al consumer.
L’attributo actor consente di specificare l’endpoint, intermediario al quale è indirizzato
l’elemento header.
Infatti, un messaggio SOAP può viaggiare dal mittente al destinatario attraversando
differenti intermediari lungo il percorso e non tutti gli header del messaggio sono
necessariamente indirizzati al destinatario finale.
Il valore di quest’attributo è un’URI e, se non indicato, il default è l’ultimo nodo nel
message-path.
Di solito, un header di sicurezza che contiene le credenziali del consumer dovrà essere
obbligatoriamente processato, con attributo mustUnderstand uguale a 1 o true, dal
provider del servizio.
L’elemento XML <wsse:UsernameToken> è presente in un header WS-Security, per
specificare l’username e la password di un utente.
Elementi “figli” del TAG <wsse:UsernameToken> sono i TAG <wsse:Username> e
<wsse:Password>.
TPSIT 5
434
um 131 di
Esempio, un envelope SOAP è esteso con un header WS-Security.
<S11:Envelope xmlns:S11="..." xmlns:wsse="">
<S11:Header>
...
<wsse:Security>
<wsse:UsernameToken>
<wsse:Username>Andrea</wsse:Username>
<wsse:Password>
password
</wsse:Password>
</wsse:UsernameToken>
</wsse:Security>
...
</S11:Header>
...
</S11:Envelope>
L’utilizzo dell’username security token che, se da un lato consente di specificare in modo
standard e semplice le credenziali di un consumer, dall’altro presenta le seguenti
controindicazioni di sicurezza.
 La password è in chiaro.
 Il colloquio tra consumer e provider può essere soggetto ad attacchi di sicurezza di tipo
replay, ossia attacchi ripetuti al provider utilizzando lo stesso messaggio SOAP.
Due elementi opzionali sono introdotti nel TAG <wsse:UsernameToken> per fornire una
serie di contromisure agli attacchi di tipo replay.
Sono gli elementi <wsse:Nonce> e <wsu:Created>.
Un nonce è un valore casuale che il consumer del WS crea e include nel token di
sicurezza della chiamata SOAP.
Il provider del servizio conserva una cache dei nonce ricevuti, rigettando le invocazioni
SOAP che includono un valore di nonce già ricevuto in precedenza.
La gestione da parte del server di questa cache è un’attività onerosa in termini di risorse
computazionali e pertanto insieme al valore di nonce il consumer include nell’invocazione
SOAP anche un timestamp che riporta la data di creazione del nonce consentendo al
server di limitare la dimensione della cache rifiutando le invocazioni SOAP obsolete.
I soli elementi nonce e timestamp non sono però da soli sufficienti: per non inviare in
chiaro la password e per limitare gli attacchi di tipo replay il valore dell’elemento
<wsse:Password> è il digest, in codifica Base64, della concatenazione del nonce, del
timestamp e della password vera e propria.
Password_Digest = Base64 ( SHA-1 ( nonce + created + password))
Esempio, un envelope SOAP è esteso con un username token che usa nonce, timestamp
di creazione e il valore digest per la password.
<S11:Envelope xmlns:S11="..." xmlns:wsse="..." xmlns:wsu= "...">
<S11:Header>
...
<wsse:Security>
<wsse:UsernameToken>
<wsse:Username>pippo</wsse:Username>
<wsse:Password Type="...#PasswordDigest">
TPSIT 5
434
um 132 di
....
</wsse:Password>
<wsse:Nonce>.........</wsse:Nonce>
<wsu:Created>2013-07-07T07:27:37Z</wsu:Created>
</wsse:UsernameToken>
</wsse:Security>
...
</S11:Header>
...
</S11:Envelope>
L’attributo Type del TAG <wsse:Password> ha il valore di #PasswordDigest per indicare
che il valore del TAG è proprio il digest della password concatenata al nonce e al
timestamp in codifica Base64.
TPSIT 5
434
um 133 di
VISUAL STUDIO: WS BASATO SU XML
Aprire un progetto, File/Nuovo/Progetto… (CTRL+N), nella finestra Nuovo progetto,
modificare l’opzione e selezionare il .NET Framework 3.5; nel riquadro Modelli installati
selezionare Visual Basic/Web/Applicazione servizio Web ASP.NET.
Dopo aver scelto il nome del progetto e il percorso, confermare per creare l’applicazione.
Visual Studio mette a disposizione lo scheletro di un WS che non fa altro che restituire al
client la stringa “Ciao, mondo!”.
TPSIT 5
434
um 134 di
Per creare WS con il .NET Framework si usano i file ASMX di ASP.NET.
File MYWEBEXTENSION.VB
Definisce le proprietà disponibili nello spazio dei nomi My per i progetti web.
File ASSEMBLYINFO.VB
Informazioni generali dell’applicazione, dal nome del progettista al copyright.
RIFERIMENTI
Librerie esterne utilizzate dall’applicazione.
Cartella APP_DATA
Contiene file di dati dell’applicazione, inclusi i file MDF (MicroSoft Data File), i file XML e
altri file di archivio dati, è utilizzata da ASP.NET per archiviare un DB locale
dell’applicazione.
File WEB.CONFIG
File di configurazione dell’applicazione, nel quale si trovano i namespace importati e le
sezioni per impostare il livello di sicurezza.
FileSERVICE1.ASMX
Codice generato automaticamente da Visual Studio, definisce il metodo HelloWorld.
La dichiarazione namespace identifica il servizio, inoltre rende più chiare la finalità e la
modalità di utilizzo a chi dovrà interrogare il servizio.
Per creare un WS basta ereditare Inherits System.Web.Services.WebService.
Imports System.Web.Services
Imports System.Web.Services.Protocols
Imports System.ComponentModel
' Per consentire la chiamata di questo servizio Web dallo script utilizzando ASP.NET
' AJAX, rimuovere il commento dalla riga seguente.
' <System.Web.Script.Services.ScriptService()> _
<System.Web.Services.WebService(Namespace:="http://tempuri.org/")> _
<System.Web.Services.WebServiceBinding(ConformsTo:=WsiProfiles.BasicProfile1_1)> _
<ToolboxItem(False)> _
Public Class Service1
Inherits System.Web.Services.WebService
<WebMethod()> _
Public Function HelloWorld() As String
Return "Ciao, mondo!"
End Function
End Class
Il namespace http://tempuri.org/ è un URI temporaneo che dev’essere obbligatoriamente
indicato in assenza di un URI alternativo, per esempio il nome di dominio
dell’organizzazione ma non deve indicare necessariamente un dominio o un URL
effettivamente esistente.
La parte più importante di codice è costituita dalla classe che eredita da
System.Web.Services.WebService.
In OOP si decide quali metodi esporre come pubblici, protetti o privati.
Nei WS è necessario specificare anche quali metodi siano visibili “lato web”, ossia quali
metodi siano utilizzabili o meglio invocabili, dai client che si collegheranno.
TPSIT 5
434
um 135 di
All’interno di questa classe, sono inseriti metodi con un attributo <WebMethod()> per
indicare che si tratta di un metodo del WS.
Nel modello predefinito si ha a disposizione solo un metodo, HelloWorld che restituisce la
stringa ma si può modificarlo con uno diverso e aggiungere molti altri metodi al WS.
Le informazioni relative a nome e posizione, sono sufficienti a Visual Studio per generare il
codice di un WS, pronto per essere interrogato.
Visual Studio include un tool di sviluppo web, Visual Studio Development Server che
integra un server web per consentire di sperimentare subito le potenzialità di ASP.NET e
l’applicazione sarà visualizzata all’interno del browser web predefinito.
Il sito web del .NET Framework è noto come ASP.NET.
I mondi delle applicazioni desktop e delle applicazioni web convergono con .NET.
Per realizzare ed eseguire un’applicazione web è necessario che nel sistema sia installato
il .NET Framework e un web server in grado d’interpretare ed eseguire i file ASP.NET che
hanno estensione ASPX.
Le pagine non sono altro che classi ed ereditano le funzionalità di base dalla classe Page
come una Windows Form eredita dalla classe Form.
Contemporaneamente all’avvio del servizio, sarà avviata un’istanza d’Internet Explorer che
mostrerà la pagina, in altre parole l’indirizzo del servizio.
Il numero di porta utilizzato nell’indirizzo del servizio è generato in modo casuale.
Per impostare un numero di porta scelto dal programmatore, si deve configurarlo nelle
proprietà dell’applicazione.
Fare clic su Progetto/Proprietà di primoWs…, nella finestra delle proprietà, selezionare
la scheda Web: si vede una serie d’informazioni relative all’applicazione web.
La parte che interessa è quella centrale, nella sezione Server, infatti, è impostata
l’opzione Assegnazione automatica porta, mentre per impostare una porta fissa si deve
selezionare la voce Porta specifica e impostare il numero di porta da utilizzare.
È aperta una pagina web con Internet Explorer avente URL simile a quello seguente.
TPSIT 5
434
um 136 di
TPSIT 5
434
um 137 di
Si utilizza localhost al posto del dominio seguito dai due punti (:), dal numero della porta
del web server locale (1126), quindi il nome del servizio e la sua estensione
SERVICE1.ASMX.
Nella finestra del browser ci sono delle informazioni, consigli sulla configurazione del
namespace da assegnare al servizio e alcuni esempi di codice nei linguaggi Visual C#,
Visual Basic e Visual C++; nella parte inferiore, poi, ci sono alcuni link a documenti
riguardanti gli standard delle tecnologie indicate.
Questo servizio web utilizza http://tempuri.org/ come spazio dei nomi predefinito.
Consiglio: modificare lo spazio dei nomi predefinito prima della pubblicazione del
servizio web XML.
Per ogni servizio web XML è necessario uno spazio dei nomi univoco per consentire alle
applicazioni client di distinguerlo dagli altri servizi disponibili sul web.
Per i servizi web XML in fase di sviluppo è disponibile l'indirizzo http://tempuri.org/ ma
quelli pubblicati devono usare uno spazio dei nomi permanente.
Il servizio web XML dev’essere identificato da uno spazio dei nomi controllato
dall’amministratore.
Ad esempio, è possibile usare il nome di dominio Internet della propria società come parte
dello spazio dei nomi.
Anche se molti spazi dei nomi di servizi web XML sembrano URL, non fanno
necessariamente riferimento a risorse effettive sul web, gli spazi dei nomi dei servizi web
XML sono URI.
Per i servizi web XML creati con ASP.NET, lo spazio dei nomi predefinito può essere
modificato utilizzando la proprietà namespace dell’attributo WebService.
TPSIT 5
434
um 138 di
L’attributo WebService è applicato alla classe che contiene i metodi del servizio web XML.
Nell'esempio di codice sottostante, lo spazio dei nomi è impostato su.
http://microsoft.com/webservices/:
Visual C#
[WebService(Namespace="http://microsoft.com/webservices/")]
public class MyWebService {
// implementazione
}
Visual Basic
<WebService(Namespace:="http://microsoft.com/webservices/")> Public Class
MyWebService
' implementazione
End Class
C++
[WebService(Namespace="http://microsoft.com/webservices/")]
public ref class MyWebService {
// implementazione
};
Per ulteriori informazioni sugli spazi dei nomi XML, vedere la raccomandazione W3C
riguardante gli spazi dei nomi in XML (informazioni in lingua inglese).
Per ulteriori informazioni su WSDL, vedere la specifica WSDL (informazioni in lingua
inglese).
Per ulteriori informazioni sugli URI, vedere la RFC 2396 (informazioni in lingua inglese).
Per visualizzare le informazioni sul servizio, in formato WSDL, si deve aggiungere la
stringa ?WSDL all’URL precedente http://localhost:14658/Service1.asmx?WSDL oppure si
può semplicemente cliccare sul link descrizione del servizio.
Descrizione del servizio in formato WSDL.
<?xml version="1.0" encoding="UTF-8"?>
-<wsdl:definitions xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/"
targetNamespace="http://tempuri.org/" xmlns:http="http://schemas.xmlsoap.org/wsdl/http/"
xmlns:soap12="http://schemas.xmlsoap.org/wsdl/soap12/"
TPSIT 5
434
um 139 di
xmlns:s="http://www.w3.org/2001/XMLSchema" xmlns:tns="http://tempuri.org/"
xmlns:mime="http://schemas.xmlsoap.org/wsdl/mime/"
xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/"
xmlns:tm="http://microsoft.com/wsdl/mime/textMatching/"
xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/">
-<wsdl:types>
-<s:schema targetNamespace="http://tempuri.org/" elementFormDefault="qualified">
-<s:element name="HelloWorld">
<s:complexType/>
</s:element> -<s:element name="HelloWorldResponse">
-<s:complexType>
-<s:sequence>
<s:element name="HelloWorldResult" type="s:string" maxOccurs="1" minOccurs="0"/>
</s:sequence>
</s:complexType>
</s:element>
</s:schema>
</wsdl:types> -<wsdl:message name="HelloWorldSoapIn">
<wsdl:part name="parameters" element="tns:HelloWorld"/>
</wsdl:message> -<wsdl:message name="HelloWorldSoapOut">
<wsdl:part name="parameters" element="tns:HelloWorldResponse"/>
</wsdl:message> -<wsdl:portType name="Service1Soap">
-<wsdl:operation name="HelloWorld">
<wsdl:input message="tns:HelloWorldSoapIn"/>
<wsdl:output message="tns:HelloWorldSoapOut"/>
</wsdl:operation>
</wsdl:portType> -<wsdl:binding name="Service1Soap" type="tns:Service1Soap">
<soap:binding transport="http://schemas.xmlsoap.org/soap/http"/> -<wsdl:operation
name="HelloWorld">
<soap:operation style="document" soapAction="http://tempuri.org/HelloWorld"/> <wsdl:input>
<soap:body use="literal"/>
</wsdl:input> -<wsdl:output>
<soap:body use="literal"/>
</wsdl:output>
</wsdl:operation>
</wsdl:binding> -<wsdl:binding name="Service1Soap12" type="tns:Service1Soap">
<soap12:binding transport="http://schemas.xmlsoap.org/soap/http"/> -<wsdl:operation
name="HelloWorld">
<soap12:operation style="document" soapAction="http://tempuri.org/HelloWorld"/> <wsdl:input>
<soap12:body use="literal"/>
</wsdl:input> -<wsdl:output>
<soap12:body use="literal"/>
</wsdl:output>
</wsdl:operation>
</wsdl:binding> -<wsdl:service name="Service1">
-<wsdl:port name="Service1Soap" binding="tns:Service1Soap">
<soap:address location="http://localhost:1126/Service1.asmx"/>
</wsdl:port> -<wsdl:port name="Service1Soap12" binding="tns:Service1Soap12">
<soap12:address location="http://localhost:1126/Service1.asmx"/>
</wsdl:port>
TPSIT 5
434
um 140 di
</wsdl:service>
</wsdl:definitions>
Se invece, si fa clic sul link HelloWorld, nome che corrisponde al nome del metodo
esposto dal servizio, si ottiene un’altra pagina come questa.
La pagina contiene degli esempi di richiesta e di risposta.
SOAP 1.1
Di seguito è riportato un esempio di richiesta e risposta SOAP 1.1.
I segnaposto devono essere sostituiti con i valori appropriati.
POST /WebService1.asmx HTTP/1.1
Host: localhost
Content-Type: text/xml; charset=utf-8
Content-Length: length
SOAPAction: "http://tempuri.org/HelloWorld"
<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Body>
<HelloWorld xmlns="http://tempuri.org/" />
</soap:Body>
</soap:Envelope>
HTTP/1.1 200 OK
Content-Type: text/xml; charset=utf-8
Content-Length: length
<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Body>
TPSIT 5
434
um 141 di
<HelloWorldResponse xmlns="http://tempuri.org/">
<HelloWorldResult>string</HelloWorldResult>
</HelloWorldResponse>
</soap:Body>
</soap:Envelope>
SOAP 1.2
Di seguito è riportato un esempio di richiesta e risposta SOAP 1.2.
I segnaposto devono essere sostituiti con i valori appropriati.
POST /WebService1.asmx HTTP/1.1
Host: localhost
Content-Type: application/soap+xml; charset=utf-8
Content-Length: length
<?xml version="1.0" encoding="utf-8"?>
<soap12:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:soap12="http://www.w3.org/2003/05/soap-envelope">
<soap12:Body>
<HelloWorld xmlns="http://tempuri.org/" />
</soap12:Body>
</soap12:Envelope>
HTTP/1.1 200 OK
Content-Type: application/soap+xml; charset=utf-8
Content-Length: length
<?xml version="1.0" encoding="utf-8"?>
<soap12:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:soap12="http://www.w3.org/2003/05/soap-envelope">
<soap12:Body>
<HelloWorldResponse xmlns="http://tempuri.org/">
<HelloWorldResult>string</HelloWorldResult>
</HelloWorldResponse>
</soap12:Body>
</soap12:Envelope>
HTTP POST
Di seguito è riportato un esempio di richiesta e risposta HTTP POST.
I segnaposto devono essere sostituiti con i valori appropriati.
POST /WebService1.asmx/HelloWorld HTTP/1.1
Host: localhost
Content-Type: application/x-www-form-urlencoded
Content-Length: length
HTTP/1.1 200 OK
Content-Type: text/xml; charset=utf-8
Content-Length: length
<?xml version="1.0" encoding="utf-8"?>
<string xmlns="http://tempuri.org/">string</string>
TPSIT 5
434
um 142 di
Fare clic sul pulsante Richiama per ottenere il risultato fornito dal metodo HelloWorld che
restituisce il seguente risultato in formato XML.
Per installare un WS in IIS, basta copiare i file che lo compongono nella cartella che
ospiterà il servizio, tuttavia in IIS la cartella in questione dev’essere configurata come
applicazione, per far questo si può procedere in due modi.
1. Primo modo
Creare una cartella di sotto a C:\INETPUB\WWWROOT.
Fare clic su Start/Tutti i programmi/Strumenti di amministrazione e avviare Gestione
Internet Information Services (IIS).
Fare clic con il tasto destro sulla cartella e selezionare Converti in applicazione.
Nel riquadro Pool di applicazioni… fare clic sul pulsante Seleziona… nella casella a
discesa della versione di ASP.NET installata, selezionare quella relativa al WS sviluppato.
TPSIT 5
434
um 143 di
Per procedere all’installazione del WS anziché copiare i file, modificando manualmente
alcuni riferimenti, si ha a disposizione una modalità di pubblicazione molto più comoda,
veloce ed efficiente: la pubblicazione diretta da Visual Studio.
Nella finestra Esplora Soluzioni, fare clic con il tasto destro del mouse sul nome del
progetto e scegliere Pubblica…, apparirà una finestra di dialogo seguente.
Si è indicato il metodo di pubblicazione File system e si è indicato l’indirizzo dove trovare
il WS che, in questo caso, si trova nel web server C:\INETPUB\WWWROOT\WS.
Si è selezionata l’opzione Elimina tutti i file esistenti prima della pubblicazione, per
evitare che eventuali file preesistenti possano rimanere nella cartella dell’applicazione e
creare qualche problema.
Una volta concluso il completamento dei dati richiesti, premere il pulsante Pubblica.
2. Secondo modo
In questo caso la cartella che conterrà i file sarà messa al di fuori di WWWROOT.
Fare clic su Start/Tutti i programmi/Strumenti di amministrazione e avviare Gestione
Internet Information Services (IIS).
Portarsi nel sito web principale, cliccare con il tasto destro e selezionare Aggiungi
directory virtuale…
TPSIT 5
434
um 144 di
Si avvierà una procedura guidata.
Al secondo punto bisogna inserire un alias per la directory virtuale.
Usare un’etichetta significativa ad esempio il nome del progetto.
Nel passo successivo selezionare la cartella fisica cui la directory virtuale è associata.
Impostare i permessi per la nuova cartella, selezionare almeno il permesso d’esecuzione.
Se IIS restituisce errore, è probabile che non siano stati creati gli utenti corretti per
eseguire le applicazioni ASP.NET in Windows.
Per risolvere il problema portarsi nella cartella d’installazione del .NET Framework.
C:\Windows\Microsoft.NET\Framework\v4.0.30319>aspnet_regiis /?
Utilità di amministrazione (4.0.30319) per l'installazione e la
disinstallazione di ASP.NET nel computer locale.
Copyright (c) Microsoft Corporation. All rights reserved.
-- OPZIONI DI REGISTRAZIONE DI ASP.NET --i
Installazione della versione di ASP.NET e aggiornamento
della configurazione di IIS a livello radice per
l'utilizzo di questa versione di ASP.NET.
-ir
Installazione della versione di ASP.NET eseguendo solo la
registrazione. Non verranno apportate modifica alle
applicazioni Web per l'utilizzo di questa versione.
-iru
Installazione della versione di ASP.NET. Se sono presenti
applicazioni esistenti che utilizzano ASP.NET, la
configurazione di IIS non verrà modificata per l'utilizzo
di questa versione.
-enable
Se viene specificato -enable con -i, -ir o -r, ASP.NET
verrà abilitato nella console di sicurezza di IIS (IIS
6.0 o versione successiva).
-disable
Se viene specificato -disable con -i, -ir o -r, ASP.NET
verrà disabilitato nella console di sicurezza di IIS (IIS
6.0 o versione successiva).
-s <percorso>
Installazione degli scriptmap per la versione corrente
nel percorso specificato, in modo ricorsivo.
Es: aspnet_regiis.exe -s W3SVC/1/ROOT/SampleApp1
-sn <percorso>
Installazione degli scriptmap per la versione corrente
nel percorso specificato, in modo non ricorsivo.
-r
Installazione della versione corrente di ASP.NET e
aggiornamento degli scriptmap nella radice della metabase
di IIS e in tutte le sottodirectory. Gli scriptmap
esistenti vengono aggiornati alla versione corrente
indipendentemente dalle versioni originali.
TPSIT 5
434
um 145 di
-u
Disinstallazione della versione corrente di ASP.NET. Gli
scriptmap esistenti con tale versione vengono rimappati
alla più recente versione di ASP.NET ancora installata
nel computer.
-ua
Disinstallazione di tutte le versioni di ASP.NET sul computer.
-k <percorso>
Rimozione ricorsiva di tutti gli scriptmap di tutte le
versioni di ASP.NET dal percorso specificato, (opzione
non supportata su Windows Vista e versioni successive).
Es.: aspnet_regiis.exe -k W3SVC/1/ROOT/SampleApp1
-kn <percorso>
Rimozione non ricorsiva di tutti gli scriptmap di tutte
le versioni di ASP.NET dal percorso specificato (opzione
non supportata su Windows Vista e versioni successive).
-lv
Elenco di tutte le versioni di ASP.NET installate nel
computer, con stato e percorso di installazione.
-lk
Elenco dei percorsi di tutte le chiavi della metabase di
IIS in cui sono presenti scriptmap di ASP.NET,
indicandone la versione. Le chiavi che ereditano
scriptmap di ASP.NET da una chiave padre non vengono
visualizzate (opzione non supportata su Windows Vista e
versioni successive).
-c
Installazione degli script lato client per la versione
corrente nella sottodirectory aspnet_client della
directory di ogni sito di IIS.
-e
Rimozione degli script lato client per la versione
corrente dalla sottodirectory aspnet_client della
directory di ogni sito di IIS.
-ea
Rimozione degli script lato client per tutte le versioni
dalla sottodirectory aspnet_client della directory di
ogni sito di IIS.
-ga <utente>
Concessione all'utente specificato dei diritti di accesso
utente o gruppo alla metabase di IIS e alle altre
directory utilizzate da ASP.NET.
-- OPZIONI PER LA CRITTOGRAFIA DELLA CONFIGURAZIONE --pe sezione
Crittografia della sezione di configurazione. Argomenti facoltativi:
[-prov provider] Esecuzione della crittografia tramite il
provider specificato.
[-app percorso-virtuale] Esecuzione della crittografia al
percorso virtuale specificato. Il percorso virtuale deve
iniziare con una barra rovesciata. Se si utilizza il
carattere "/", viene fatto riferimento alla radice del
sito. Se -app non è specificato, verrà crittografato il
file web.config radice.
[-site nome-o-ID-sito] Il sito del percorso virtuale
specificato in -app. Se non è specificato viene
utilizzato il sito Web predefinito.
[-location sottopercorso] Sottopercorso.
[-pkm] Crittografia/decrittografia di machine.config
anziché di web.config.
-pd sezione
Decrittografia della sezione di configurazione. Argomenti facoltativi:
[-app percorso-virtuale] Decrittografia al percorso
virtuale specificato. Il percorso virtuale deve iniziare
con una barra rovesciata. Se si utilizza il carattere
TPSIT 5
434
um 146 di
"/", viene fatto riferimento alla radice del sito. Se
-app non è specificato, verrà decrittografato il file
web.config radice.
[-site nome-o-ID-sito] Il sito del percorso virtuale
specificato in -app. Se non è specificato viene
utilizzato il sito Web predefinito.
[-location sottopercorso] Sottopercorso.
[-pkm] Crittografia/decrittografia di machine.config
anziché di web.config.
-pef sezione dir-fisica-app-web
Crittografia della sezione di configurazione. Argomenti facoltativi:
[-prov provider] Esecuzione della crittografia tramite il
provider specificato.
-pdf section web-app-physical-dir
Decrittografia della sezione di configurazione.
-pc contenitore
Creazione di una coppia di chiavi RSA in ContainerName.
Argomenti facoltativi:
[-size dimensione-chiave] Dimensione della chiave. Il
valore predefinito è 1024.
[-pku] Utilizzo del contenitore dell'utente anziché
quello del computer.
[-exp] Impostazione delle chiavi private come
esportabili.
[-csp provider] Provider Csp da utilizzare.
-pz contenitore
Eliminazione di ContainerName. Argomenti facoltativi:
[-pku] Utilizzo del contenitore dell'utente anziché
quello del computer.
-pi file contenitore Importazione di una coppia di chiavi RSA dal file XML
specificato. Argomenti facoltativi:
[-pku] Utilizzo del contenitore dell'utente anziché
quello del computer.
[-exp] Creazione di chiavi esportabili.
[-csp provider] Provider Csp da utilizzare.
-px file contenitore Esportazione di una coppia di chiavi RSA nel file XML.
Argomenti facoltativi:
[-pku] Utilizzo del contenitore dell'utente anziché
quello del computer.
[-pri] Inclusione di chiavi private.
[-csp provider] Provider Csp da utilizzare.
-pa account contenitore
Aggiunta dei diritti di accesso dall'account al
contenitore. Argomenti:
[-pku] Utilizzo del contenitore dell'utente anziché
quello del computer.
[-csp provider] Provider Csp da utilizzare.
[-full] Aggiunta di diritti di accesso completo (per
impostazione predefinita è consentito solo l'accesso in
lettura).
-pr account contenitore
Rimozione dei diritti di accesso per l'account dal
contenitore. Argomenti:
[-pku] Utilizzo del contenitore dell'utente anziché
TPSIT 5
434
um 147 di
quello del computer.
[-csp provider] Provider Csp da utilizzare.
-- OPZIONI DI ACCESSO REMOTO DELLA CONFIGURAZIONE --config+
Abilitazione dell'accesso remoto per la configurazione.
-configDisabilitazione dell'accesso remoto per la
configurazione.
C:\Windows\Microsoft.NET\Framework\v4.0.30319>aspnet_regiis –i<INVIO>
Sono automaticamente creati gli utenti necessari a far funzionare IIS con ASP.NET.
Per utilizzare il WS, creare un nuovo progetto VB.NET Windows Form e trascinare un
pulsante sul form.
Fare clic con il tasto destro in Esplora soluzioni, sul nome del progetto, selezionare
Aggiungi riferimento al servizio…
Nella finestra che compare, indicare l’URL che espone il WS.
Fare clic su OK.
Fare doppio clic sul bottone per gestire il codice relativo all’evento.
Public Class Form1
Private Sub Button1_Click(sender As System.Object, e As System.EventArgs) Handles
Button1.Click
Dim ws As New ServiceReference1.WebService1SoapClient
End Sub
TPSIT 5
434
um 148 di
End Class
Microsoft commercializza MapPoint esponendo le funzionalità dell’applicazione come WS.
In altre parole, l’applicazione può interrogare MapPoint WS per conoscere ad esempio il
percorso stradale da compiere per arrivare da un luogo ad un altro.
Ciascun’interrogazione ha un costo per chi la compie.
Ad esempio, per utilizzare il servizio web di MapPoint, dopo aver ricevuto da Microsoft le
credenziali di accesso, si deve aggiungere un riferimento web al progetto.
Sono visualizzati tutti i metodi che il servizio espone e il nome da assegnare al riferimento
web: net.mappoint.staging.
Da questo momento è possibile usare tutti i metodi esposti dal servizio come se fossero
relativi ad un oggetto locale.
L’aggiunta del riferimento ha, infatti, creato una classe chiamata proxy che ha lo scopo di
rendere trasparente, per il programmatore, il WS.
Esempio.
File WEBSERVICE1.ASMX
Non basta avere un metodo che, senza accettare parametri, mandi un saluto ma occorre
modificare il codice con due metodi pubblici, di cui il primo accetta un parametro e ritorna
un valore.
I metodi devono essere richiamabili come servizi web, oltre che pubblici, devono essere
preceduti dalla dichiarazione <WebMethod()>.
TPSIT 5
434
um 149 di
Imports System.Web.Services
Imports System.Web.Services.Protocols
Imports System.ComponentModel
' Per consentire la chiamata di questo servizio Web dallo script utilizzando ASP.NET
AJAX, rimuovere il commento dalla riga seguente.
' <System.Web.Script.Services.ScriptService()> _
<System.Web.Services.WebService(Namespace:="http://www.ubertini.it/")> _
<System.Web.Services.WebServiceBinding(ConformsTo:=WsiProfiles.BasicProfile1_1)> _
<ToolboxItem(False)> _
Public Class WebService1
Inherits System.Web.Services.WebService
<WebMethod()> _
Public Function Saluta(ByVal saluto As String) As String
Return "Ciao " + saluto
End Function
<WebMethod()> _
Public Function Chisei() As String
Return "Sono il primo servizio web"
End Function
End Class
Compilare ed eseguire il servizio.
Per provare il servizio è sufficiente cliccare sul link Saluta, la pagina web che si aprirà
consentirà d’inserire un nome: in realtà si sta simulando, via web, il passaggio di parametri
che dovrà avvenire da codice.
TPSIT 5
434
um 150 di
Inserendo una stringa e cliccando sul pulsante Richiama, si ottiene il documento XML che
rappresenta la risposta del metodo all’invocazione.
Tutti i meccanismi di scambio dati SOAP rimangono nascosti al programmatore che non si
deve preoccupare d’implementare la parte più a basso livello del codice.
Esempio, DB.
<WebMethod()> _
Public Function query(ByVal strSql As String, ByVal strConnect As String) As DataSet
Dim oDataSet As New DataSet
Dim objConn As New OleDb.OleDbConnection(strConnect)
Try
objConn.Open()
Dim objcommand As New OleDb.OleDbCommand(strSql, objConn)
Dim oAdapter As New OleDb.OleDbDataAdapter(objcommand)
oAdapter.TableMappings.Add("Table", "Table0")
oAdapter.Fill(oDataSet)
Catch ex As Exception
TPSIT 5
434
um 151 di
Throw New Exception(ex.Message, ex)
Finally
objConn.Close()
End Try
Return oDataSet
End Function
Il <WebMethod()> riceve una stringa di connessione al DB e il comando SQL da eseguire.
Grazie ad ADO (ActiveX Data Objects) si può poi eseguire la query e restituire il dataset,
si può anche prevedere un metodo che esegua un inserimento o una modifica.
<WebMethod()>
Public Function nonQuery(ByVal command As String, ByVal strConnect As String) As
Integer
Dim objConn As New OleDb.OleDbConnection(strConnect)
Try
objConn.Open()
Dim objcommand As New OleDb.OleDbCommand(command, objConn)
Return objcommand.ExecuteNonQuery
Catch ex As Exception
Throw New Exception(ex.Message, ex)
Finally
' chiusa connessione
objConn.Close()
End Try
End Function
Il parametro restituito è il numero di record coinvolti nel comando.
Compilare ed eseguire il servizio.
Il WS è pronto per essere usato da una qualsiasi applicazione client.
TPSIT 5
434
um 152 di
VISUAL STUDIO: WS BASATO SU WCF
Creare il progetto WS
WCF è una nuova tecnologia basata su XML che ha un approccio uniforme rispetto a vari
tipi di comunicazione, non solo per i WS.
In questo modo si può scrivere codice che varia di poco tra un tipo di comunicazione e
l’altro, rendendo più semplice e produttiva l’attività di sviluppo.
Aprire un progetto, File/Nuovo/Progetto… (CTRL+MAIUSC+N), nella finestra Nuovo
progetto, nel riquadro Modelli selezionare Visual C#/WCF/Libreria del servizio WCF.
Visual Studio mette a disposizione lo scheletro di un WS.
File ISERVICE1.CS
È il codice dell’interfaccia usata per definire il servizio e i due metodi che esporrà al client.
1. GetData permette al servizio di ricevere un valore numerico e di restituire lo stesso
valore in formato stringa.
2. GetDataUsingDataContract permette al servizio di ricevere un tipo di dato composto da
più valori e di restituirlo al consumatore del servizio.
TPSIT 5
434
um 153 di
In sostanza, questo WS non è altro che un servizio “echo” che restituisce al mittente tutto
ciò che riceve.
Tutto questo è predisposto in modo automatico e non c’è la necessità di alcun intervento
da parte del programmatore, se non per personalizzare i metodi che il WS deve esporre.
using System.Runtime.Serialization;
using System.ServiceModel;
namespace PrimoWebServiceWCF {
/* ServiceContract descrive le operazioni che un client può effettuare su un
* servizio */
[ServiceContract]
public interface IService1 {
// OperationContract definisce le operazioni consentite per quel contratto
[OperationContract]
string GetData(int value);
[OperationContract]
CompositeType GetDataUsingDataContract(CompositeType composite);
// aggiungere qui le operazioni del servizio
}
/* DataContract descrive i dati scambiati con un servizio: definisce la classe che
* implementa un contratto di dati che potranno essere serializzati e scambiati fra
* servizio e client */
[DataContract]
public class CompositeType {
bool boolValue = true;
string stringValue = "Hello ";
// DataMember definisce le proprietà o i campi che devono essere serializzati
[DataMember]
public bool BoolValue
{ get { return boolValue; }
set { boolValue = value; }
}
[DataMembe]
public string StringValue
{ get { return stringValue; }
set { stringValue = value; }
}
}
}
Anche se possono essere applicati indistintamente su class, il loro utilizzo più comune si
ha con l’interface, perché nel rispetto di una corretta SOA in cui il contratto di servizio è
differente dalla sua implementazione.
File SERVICE1.CS
Implementa i metodi che devono essere esposti dal WS e definiti dall’interfaccia.
using System;
namespace PrimoWebServiceWCF {
public class Service1 : IService1 {
public string GetData(int value)
{ return string.Format("Hai inviato il valore seguente: {0}", value); }
TPSIT 5
434
um 154 di
public CompositeType GetDataUsingDataContract(CompositeType composite)
{ if (composite == null)
throw new ArgumentNullException("composite");
if (composite.BoolValue)
composite.StringValue += "Vero";
return composite;
}
}
}
Compilare ed eseguire il servizio, si avvia come se si trattasse di un’applicazione
Windows; cliccando sull’icona nell’area di notifica si ottiene l’apertura di una finestra che
fornisce informazioni sul servizio WCF: l’indirizzo del servizio e il suo stato.
Collaudare il WS
Si apre la finestra di dialogo che rappresenta un’applicazione client che è creata
automaticamente e in modo totalmente trasparente al programmatore, per consentirgli di
testare il WS, per esempio invocando i metodi.
Questo permette di sapere se il servizio web funziona correttamente o se si devono
apportare delle modifiche, senza bisogno di creare subito il client, con il dubbio che sia
TPSIT 5
434
um 155 di
quest’ultimo a non funzionare.
Nella parte sinistra della finestra ci sono i servizi attivati e i metodi che si possono
utilizzare con tali servizi; nella parte destra ci sono rispettivamente i dati della richiesta che
s’invia al WS attraverso il metodo chiamato e i dati che sono restituiti dal WS stesso.
Fare doppio clic sul nome del metodo GetData, poi modificare il valore nella
corrispondente cella del riquadro Richiesta e cliccare sul pulsante Richiama.
Apparirà un messaggio informativo sui rischi connessi sull’uso di servizi che inviano
informazioni in rete.
Fare clic su OK.
Nel riquadro Risposta appariranno i dati restituiti dal servizio, attraverso il metodo
GetData.
TPSIT 5
434
um 156 di
WCF permette di definire le caratteristiche del servizio in due modalità.
1. Amministrativamente: è il file di configurazione, eventualmente combinato con l’utilizzo
di attributi.
2. Programmaticamente: si configura il servizio completamente da codice.
Nella sezione che si trova a sinistra, nella finestra del client di prova, si trova anche la
voce File di configurazione, fare doppio clic per visualizzarlo.
Si vede la configurazione dell’endpoint: la sezione <configuration> descrive l’endpoint del
servizio BasicHttpBinding_IService1 che ha il seguente indirizzo e utilizzabile con un
binding di tipo <basicHttpBinding> e un contratto IService1.
http://localhost:8733/Design_Time_Addresses/PrimoWebServiceWCF/Service1
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<system.serviceModel>
<bindings>
<basicHttpBinding>
<binding name="BasicHttpBinding_IService1" sendTimeout="00:05:00" />
</basicHttpBinding>
</bindings>
<client>
<endpoint address =
"http://localhost:8733/Design_Time_Addresses/PrimoWebServiceWCF/Service1/"
binding="basicHttpBinding" bindingConfiguration="BasicHttpBinding_IService1"
contract="IService1" name="BasicHttpBinding_IService1" />
</client>
</system.serviceModel>
</configuration>
Le comunicazioni avvengono con l’utilizzo di documenti XML che sono scambiati nell’una
e nell’altra direzione, per specificare cosa è inviato dal client al servizio e viceversa.
La scheda XML visualizza il contenuto dei documenti XML scambiati al momento della
richiesta del client al WS e al momento della risposta del WS al web client.
Richiesta
<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
<s:Header>
TPSIT 5
434
um 157 di
<Action s:mustUnderstand="1"
xmlns="http://schemas.microsoft.com/ws/2005/05/addressing/none">http://tempuri.org/ISe
rvice1/GetData</Action>
</s:Header>
<s:Body>
<GetData xmlns="http://tempuri.org/">
<value>7</value>
</GetData>
</s:Body>
</s:Envelope>
Risposta
<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
<s:Header />
<s:Body>
<GetDataResponse xmlns="http://tempuri.org/">
<GetDataResult>Hai inviato il valore seguente: 7</GetDataResult>
</GetDataResponse>
</s:Body>
</s:Envelope>
Allo stesso modo, con un doppio clic sul nome di metodo GetDataUsingDataContract si
possono visualizzare i dati che sono inviati al corrispondente metodo del WS e cliccando
sul pulsante Richiama si possono ottenere i dati restituiti dal servizio.
Hosting in IIS
Il self-hosting di servizi WCF soffre di alcune limitazioni.
Per esempio, per scalare un’applicazione perchè deve gestire un maggiore carico di
richieste, si è costretti a implementare manualmente tutta la logica di gestione dei
processi, di multithreading, di sicurezza e identity.
La soluzione in questi casi è data da IIS che consente di ospitare servizi WCF all’interno
dei processi solitamente dedicati ad ASP.NET, ereditandone tutte le caratteristiche.
Un servizio WCF, per essere ospitato in IIS, dev’essere configurato attraverso l’utilizzo
dell’estensione SVC.
Per installare un WS in IIS, creare la cartella seguente.
TPSIT 5
434
um 158 di
C:\INETPUB\WWWROOT\PRIMOWEBSERVICEWCF
Quindi, nella finestra Esplora Soluzioni del progetto Visual Studio, fare clic con il tasto
destro del mouse sul nome del progetto e scegliere Pubblica….
Fare clic su Start/Tutti i programmi/Strumenti di amministrazione e avviare Gestione
Internet Information Services (IIS).
Fare clic con il tasto destro sulla cartella e selezionare Converti in applicazione.
Il sistema genera due file: WEB.CONFIG e PRIMOWEBSERVICEWCF.SERVICE1.SVC.
TPSIT 5
434
um 159 di
Il file con estensione SVC, similarmente a quanto è fatto nelle pagine ASPX o ASMX, ha
una direttiva @ ServiceHost il cui scopo è quello d’indicare qual è il servizio ospitato.
<%@ ServiceHost Service="PrimoWebServiceWCF.Service1" %>
L’applicazione funziona, il servizio è pronto per l’uso, è sufficiente aprire il browser e
digitare l’indirizzo seguente.
http://127.0.0.1/PrimoWebServiceWCF/PrimoWebServiceWCF.Service1.svc
È generato un file di configurazione e un file di codice contenente la classe Client.
Aggiungere i due file all’applicazione client e utilizzare la classe Client generata per
chiamare il servizio.
Visual C#
class Test {
static void Main() {
Service1Client client = new Service1Client();
/* utilizzare la variabile 'client' per chiamare le operazioni nel servizio
* chiudere sempre il client */
client.Close();
}
}
Visual Basic
Class Test
Shared Sub Main()
Dim client As Service1Client = New Service1Client()
' utilizzare la variabile 'client' per chiamare le operazioni nel servizio
' chiudere sempre il client
client.Close()
End Sub
TPSIT 5
434
um 160 di
End Class
Creare il client per il WS
Una volta definito l’endpoint, per interagire con esso è necessario progettare una classe
proxy che sul client si occupa d’inoltrare le richieste verso lo specifico endpoint,
rispettandone le caratteristiche.
Il proxy client, infatti, è la classe in grado di preparare le richieste e formattare i messaggi
nella modalità richiesta dal servizio e dev’essere il più disaccoppiato possibile dal servizio.
Ogni applicazione in grado d’inviare una richiesta HTTP può fungere da client per il WS e
non è necessario che sia un’applicazione .NET o Windows.
Ci sono due possibilità per generare i proxy client: CLI e Visual Studio.
C:\Program Files (x86)\Microsoft SDKs\Windows\v8.1A\bin\NETFX 4.5.1 Tools\svcutil.exe
targetNamespace associano tutti i
targetNamespaces senza un mapping
esplicito allo spazio dei nomi CLR.
Impostazione predefinita: deriva dallo
spazio dei nomi del documento di schema
per i contratti dati. Lo spazio dei nomi
predefinito viene utilizzato per tutti gli
altri tipi creati. (Forma breve: /n)
/messageContract
- Genera i tipi di contratto messaggio.
Forma breve: /mc)
/enableDataBinding
- Implementa l'interfaccia System.ComponentM
odel.INotifyPropertyChanged su tutti i
tipi di contratto dati per abilitare
l'associazione dati. (Forma breve: /edb)
/serializable
- Crea classi contrassegnate con l'attributo
Serializable. (Forma breve: /s)
/async
- Crea firme di metodi sincrone e asincrone
di inizio/fine. Impostazione predefinita:
crea firme di metodi sincrone e asincrone
basate su attività. (Forma breve: /a)
/internal
- Genera le classi contrassegnate come
interne. Impostazione predefinita: genera
classi pubbliche. (Forma breve: /i)
/reference:<file path>
- Tipi di riferimento nell'assembly
specificato. Durante la creazione dei
client, utilizzare questa opzione per
specificare assembly che potrebbero
contenere tipi che rappresentino i
metadati da importare. (Forma breve: /r)
/collectionType:<type>
- Nome completo o nome completo di assembly
del tipo da utilizzare come tipo di dati
delle raccolte quando un codice viene
generato da schemi. (Forma breve: /ct)
/excludeType:<type>
- Nome di tipo completo o completo di
assembly da tipi di contratto di
riferimento. (Forma breve: /et)
/noStdLib
- Non fa riferimento alle librerie standard.
Per impostazione predefinita mscorlib.dll
e system.servicemodel.dll sono di
TPSIT 5
434
um 161 di
riferimento.
/serializer:Auto
- Seleziona automaticamente il
serializzatore. Cerca di utilizzare il
serializzatore dei contratti dati e
utilizza XmlSerializer se l'operazione non
riesce. (Forma breve: /ser)
/serializer:DataContractSerializer - Crea i tipi di dati che utilizzano il
serializzatore dei contratti dati per la
serializzazione e la deserializzazione
/serializer:XmlSerializer
- Genera tipi di dati che utilizzano
XmlSerializer per la serializzazione e la
deserializzazione
/importXmlTypes
- Configura il serializzatore dei contratti
dati per importare tipi di contratto non
dati come tipi IXmlSerializable.
/useSerializerForFaults
- Questa opzione specifica se il
serializzatore specificato nell'opzione
'serializer' viene utilizzato per i tipi
di contratto di errore. Se l'opzione non è
specificata, DataContractSerializer viene
utilizzato per gli errori. (Forma breve:
/fault)
/targetClientVersion:Version30 - Genera un codice che fa riferimento a una
funzionalità presente negli assembly di
.NET Framework 3.0 e versioni precedenti.
Utilizzare questa opzione se si sta
generando un codice per client che
utilizzano .NET Framework versione 3.0.
(Forma breve: /tcv)
/targetClientVersion:Version35 - Genera un codice che fa riferimento a una
funzionalità presente negli assembly di
.NET Framework 3.5 e versioni precedenti.
Utilizzare questa opzione se si sta
generando un codice per client che
utilizzano .NET Framework versione 3.5.
(Forma breve: /tcv)
/wrapped
- Il codice generato non annullerà il
wrapping del membro "parameters" dei
messaggi document-wrapped-literal.
/serviceContract
- Genera il codice per i contratti dei
servizi. Non verranno generate la
configurazione e la classe del client.
(Forma breve: /sc)
/syncOnly
- Crea solo la firma del metodo sincrona.
Impostazione predefinita: crea firme di
metodi sincrone e asincrone basate su
attività.
-= ESPORTAZIONE METADATI =Descrizione: svcutil.exe può esportare metadati per tipi di dati, contratti e
servizi negli assembly compilati. Per esportare metadati per un servizio, è
necessario utilizzare l'opzione /serviceName per indicare il servizio da
esportare. Per esportare tutti i tipi di contratto dati in un assembly
TPSIT 5
434
um 162 di
utilizzare l'opzione /dataContractOnly. Per impostazione predefinita i
metadati vengono esportati per tutti i contratti di assistenza negli
assembly di input.
Syntax: svcutil.exe [/t:metadata] [/serviceName:<serviceConfigName>]
[/dataContractOnly] <assemblyPath>*
<assemblyPath> - Percorso di un assembly contenente tipi di contratto dati,
contratti o servizi da esportare. Per specificare più file
come input è possibile utilizzare caratteri jolly della riga
di comando standard.
Opzioni:
/serviceName:<serviceConfigName> - Nome della configurazione di un servizio da
esportare. Se questa opzione viene
utilizzata, è necessario passare come input
un assembly eseguibile con un file di
configurazione associato. Per la
configurazione del servizio Svcutil esegue
la ricerca tra tutti i file di
configurazione associati. Se i file di
configurazione contengono qualsiasi tipo di
estensione, gli assembly contenenti questi
tipi devono trovarsi nella cache globale o
essere specificati esplicitamente mediante
l'opzione /r.
/reference:<file path>
- Aggiunge l'assembly specificato al set di
assembly utilizzati per risolvere i
riferimenti di tipo. Se si sta esportando o
convalidando un servizio che utilizza
estensioni di terze parti (Behaviors,
Bindings e BindingElements) registrate nella
configurazione, utilizzare questa opzione
per trovare gli assembly delle estensioni
non presenti nella cache globale. (Forma
breve: /r)
/dataContractOnly
- Opera solo sui tipi di contratto dati. I
contratti di assistenza non verranno
elaborati. (Forma breve: /dconly)
/excludeType:<type>
- Nome completo o nome completo di assembly di
un tipo da escludere dall'esportazione. Per
escludere i tipi da esportare è possibile
utilizzare questa opzione durante
l'esportazione dei metadati per un servizio
o un set di contratti di assistenza. Non è
possibile utilizzare questa opzione con
l'opzione /dconly. (Forma breve: /et)
-= CONVALIDA SERVIZIO =Descrizione: la convalida è utile per rilevare gli errori nell'implementazione
di un servizio senza l'hosting del servizio. È necessario utilizzare
l'opzione /serviceName per indicare il servizio da convalidare.
Syntax: svcutil.exe /validate /serviceName:<serviceConfigName> <assemblyPath>*
<assemblyPath> - Percorso di un assembly contenente tipi di servizio da
convalidare. L'assembly deve avere un file di configurazione
associato per offrire la configurazione del servizio. È
TPSIT 5
434
um 163 di
possibile utilizzare caratteri jolly della riga di comando per
specificare più assembly.
Opzioni:
/validate
- Convalida l'implementazione di un servizio.
Per la convalida di un servizio è necessario
utilizzare l'opzione /serviceName per
indicare il servizio da convalidare. Se
questa opzione viene utilizzata, è
necessario passare come input un assembly
eseguibile con un file di configurazione
associato. (Forma breve: /v)
/serviceName:<serviceConfigName> - Nome della configurazione di un servizio da
convalidare. Per la convalida di un servizio
è necessario specificare questa opzione. Per
la configurazione del servizio Svcutil
esegue la ricerca tra tutti i file di
configurazione associati. Se i file di
configurazione contengono qualsiasi tipo di
estensione, gli assembly contenenti questi
tipi devono trovarsi nella cache globale o
essere specificati esplicitamente mediante
l'opzione /r.
/reference:<file path>
- Aggiunge l'assembly specificato al set di
assembly utilizzati per risolvere i
riferimenti di tipo. Se si sta esportando o
convalidando un servizio che utilizza
estensioni di terze parti (Behaviors,
Bindings e BindingElements) registrate nella
configurazione, utilizzare questa opzione
per trovare gli assembly delle estensioni
non presenti nella cache globale. (Forma
breve: /r)
/dataContractOnly
- Opera solo sui tipi di contratto dati. I
contratti di assistenza non verranno
elaborati. (Forma breve: /dconly)
/excludeType:<type>
- Nome completo o nome completo di assembly di
un tipo di servizio da escludere dalla convalida. (Forma breve: /et)
-= DOWNLOAD METADATI =Descrizione: svcutil.exe può essere utilizzato per scaricare i metadati da
servizi in esecuzione e salvarli in file locali. Per scaricare i metadati, è
necessario specificare esplicitamente l'opzione /t:metadata. In caso
contrario, verrà generato il codice client. Per schemi URL http e https,
svcutil.exe tenterà di recuperare i metadati mediante WS-Metadata Exchange e
DISCO. Per tutti gli altri schemi URL svcutil.exe utilizzerà solo
WS-Metadata Exchange. Per impostazione predefinita svcutil.exe utilizza
l'associazione definita nella classe
System.ServiceModel.Description.MetadataExchangeBindings. Per configurare
l'associazione utilizzata per WS-Metadata Exchange, è necessario definire un
endpoint client nella configurazione che utilizza il contratto
IMetadataExchange. Quest'ultimo può essere definito nel file di
configurazione svcutil.exe o in un altro file specificato mediante l'opzione /svcutilConfig.
Syntax: svcutil.exe /t:metadata <url>* | <epr>
TPSIT 5
434
um 164 di
<url> - Un URL a un endpoint del servizio che fornisce metadati o un URL che fa
riferimento a un documento di metadati con hosting online.
<epr> - Percorso di un file XML contenente WS-Addressing EndpointReference per
un endpoint del servizio che supporta WS-Metadata Exchange.
-= CREAZIONE TIPO XMLSERIALIZER =Descrizione: svcutil.exe può pregenerare il codice di serializzazione C#
richiesto per i tipi che possono essere serializzati utilizzando
XmlSerializer. svcutil.exe genera solo il codice per i tipi utilizzati dai
contratti di assistenza trovati negli assembly di input.
Syntax: svcutil.exe /t:xmlSerializer <assemblyPath>*
<assemblyPath> - Percorso di un assembly contenente tipi di contratto di
assistenza. I tipi di serializzazione verranno generati per
tutti i tipi serializzabili in XML in ciascun contratto
Opzioni:
/reference:<file path> - Aggiunge l'assembly specificato al set di assembly
utilizzato per risolvere i riferimenti di tipo. (Forma breve: /r)
/excludeType:<type> - Nome completo o nome completo di assembly del tipo da
escludere dall'esportazione o dalla convalida. È
possibile utilizzare questa opzione durante
l'esportazione dei metadati per un servizio o un set
di contratti di servizio per escludere i tipi da
esportare. Impossibile utilizzare questa opzione con
l'opzione /dataContractOnly. (Forma breve: /et)
/out:<file>
- Nome del file per il codice generato. Questa opzione
viene ignorata quando più assembly vengono passati
come input allo strumento. Impostazione predefinita:
deriva dal nome dell'assembly. (Forma breve: /o)
In Visual Studio, fare clic con il tasto destro in Esplora soluzioni, sul nome del progetto,
selezionare Aggiungi riferimento al servizio…, è possibile selezionare un WS tra quelli
registrati localmente o in una directory UDDI.
Nella finestra che si apre, inserire nella casella Indirizzo: il seguente valore del WS.
http://127.0.0.1/PrimoWebServiceWCF/PrimoWebServiceWCF.Service1.svc?WSDL
I metadati, il file WSDL, sono recuperati semplicemente accedendo allo stesso URI del
servizio con in più la querystring ?WSDL.
Selezionare il servizio e il contratto IService1, ci sono anche le operazioni ammesse dal
servizio, GetData e GetDataUsingDataContract, fare clic su OK.
Nel caso in cui il WS è presente nella stessa Soluzione dell’applicazione in Visual Studio,
è possibile fare clic sul pulsante Individua per rilevare automaticamente il WS.
TPSIT 5
434
um 165 di
Nella finestra Esplora Soluzioni si vede la cartella Service References, all’interno della
quale si trova il riferimento al servizio indicato con il nome ServiceReference1.
File APP.CONFIG
È definito un <endpoint> con i relativi parametri ABC.
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5.1" />
</startup>
TPSIT 5
434
um 166 di
<system.serviceModel>
<bindings>
<basicHttpBinding>
<binding name="BasicHttpBinding_IService1" />
</basicHttpBinding>
</bindings>
<client>
<endpoint address="http://pcmax/PrimoWebServiceWCF/PrimoWebServiceWCF.Service1.svc"
binding="basicHttpBinding" bindingConfiguration="BasicHttpBinding_IService1"
contract="ServiceReference1.IService1" name="BasicHttpBinding_IService1" />
</client>
</system.serviceModel>
</configuration>
File FORM1.CS
Il primo pulsante, Button1, contiene il codice necessario a richiamare il metodo GetData
del WS, passando il valore numerico rilevato dal controllo NumericUpDown.
L’istruzione successiva non fa altro che prendere il valore numerico, espresso con il tipo di
dato Decimal, convertirlo nel tipo Integer e passarlo al metodo GetData del servizio.
Si ottiene come risposta un dato di tipo stringa che è costituito dalle cifre del numero che
era stato inviato al WS.
Il risultato sarà poi assegnato alla casella di testo preposta alla visualizzazione dei risultati.
Il codice del secondo pulsante, Button2, crea un riferimento al tipo composito.
In realtà, non sarebbe necessario creare una seconda variabile-riferimento ct2: serve solo
a dimostrare che la stringa memorizzata nell’oggetto referenziato da ct2 è effettivamente il
risultato della risposta del WS e non il valore preesistente nello stesso oggetto.
using System;
using System.Windows.Forms;
namespace ClientPrimoWebServiceWCF {
public partial class Form1 : Form {
public Form1()
{ InitializeComponent(); }
private void btnGetData_Click(object sender, EventArgs e)
{ // prima di poter richiamare il metodo è necessario creare un riferimento al WS
ServiceReference1.Service1Client sr;
sr = new ServiceReference1.Service1Client();
string numero = sr.GetData((int)numericUpDown1.Value);
txtGetData.Text = numero;
}
private void btnGetUsingDataContract_Click(object sender, EventArgs e)
{ ServiceReference1.Service1Client sr;
sr = new ServiceReference1.Service1Client();
// crea un riferimento al tipo composito
ServiceReference1.CompositeType ct1 = new
ServiceReference1.CompositeType();
ServiceReference1.CompositeType ct2 = new
ServiceReference1.CompositeType();
ct1.StringValue = txtGetUsingDataContractInvia.Text;
ct2 = sr.GetDataUsingDataContract(ct1);
txtGetUsingDataContractRicevi.Text = ct2.StringValue;
TPSIT 5
434
um 167 di
}
}
}
Un strumento utile è l’Editor configurazione servizi, avviabile sia dal men Start sia dalla
cartella Microsoft Windows SDK Tools.
TPSIT 5
434
um 168 di
Aprire il file configurazione dell’applicazione APP.CONFIG.
MESSAGECONTRACT
È un particolare contratto che consente di definire direttamente il messaggio SOAP che è
scambiato tra il client e server.
È possibile indicare quali sono gli elementi che devono comporre l’header e quali sono
quelli che, invece, definiscono il body ottenendo un controllo completo del messaggio.
L’uso del MessageContracts si avvale sempre di attributi.
class SubmitBookRequestMessage {
/* MessageHeader indica uno o più elementi che compongono l’intestazione del
* messaggio */
[MessageHeader]
string Richiedente;
// MessageBody indica uno o più elementi che compongono il corpo del messaggio
[MessageBodyMember]
Libro Libro;
}
FAULTCONTRACT
Bisogna tenere presente che il concetto di eccezione è qualcosa di specifico, legato ad
una tecnologia, in SOA, invece, si deve pensare alla definizione di un qualcosa di astratto
rispetto alla tecnologia: ecco il perchè di un contratto di tipo FaultContract.
Un contratto che sia il servizio sia il suo fruitore sono in grado di riconoscere ed
interpretare.
In WCF una qualsiasi eccezione è ripresa e inviata verso il client sotto forma di un
TPSIT 5
434
um 169 di
messaggio SOAP formattato secondo la versione delle specifiche in uso.
Internamente, WCF fa uso della classe FaultException per generare le eccezioni.
[FaultContract(typeof(ArgumentException))]
void SubmitBook(SubmitBookRequestMessage request);
CALLBACKCONTRACT
Talvolta può essere necessario avviare una comunicazione bidirezionale nella quale client
e server si scambiano messaggi.
Il server è così in grado di contattare il client e d’inviare messaggi invertendo le posizioni.
Questo è un ServiceContract la cui implementazione, però, è a carico del client.
Dev’essere definito sul server e indicato sul servizio in questo modo.
[ServiceContract(CallbackContract = typeof(ICurrentBookCallback))]
Progettare un convertitore tra gradi Celsius e Fahrenheit.
Celsius è un’unità, espressa in gradi, con la quale si misura la temperatura e in cui il
valore 0° indica il punto di congelamento dell’acqua, mentre il valore 100° ne indica il
punto di ebollizione, fu proposta dal fisico e astronomo svedese Anders Celsius (Uppsala,
27 novembre 1701 – 25 aprile 1744) nel 1742.
Fahrenheit è un’unità, espressa in gradi, con la quale si misura la temperatura e in cui il
valore 32° indica il punto di congelamento dell’acqua, mentre il valore 212° ne indica il
punto di ebollizione, fu proposta dal fisico tedesco Gabriel Fahrenheit nel 1724.
Le seguenti formule sono usate per la conversione dei valori.
°C = (°F − 32) / 1.8
°F = °C X 1.8 + 32
TPSIT 5
434
um 170 di
File ISERVICE1.CS
Progettare il servizio il cui scopo è convertire la temperatura.
File SERVICE1.CS
Il contratto così dichiarato, per realizzare il servizio vero e proprio, dev’essere poi
implementato.
Testare il WS, invocando i metodi.
TPSIT 5
434
um 171 di
A questo punto il WS che espone funzioni per la conversione Farenheit/Celsius e
viceversa, potrà essere utilizzato da qualunque applicazione che abbia un accesso a
Internet e che sappia interpretare il documento WSDL prodotto.
Procedere ora alla creazione dell’applicazione client per la comunicazione con il WS.
Completa indipendenza dal linguaggio di programmazione.
 Applicazione client di tipo Windows Form, ClientVisualBasic.
 Applicazione client di tipo WPF, ClientVisualCS.
TPSIT 5
434
um 172 di
WEB SERVICE CREATOR
Fare clic su New…, si apre la finestra, selezionare il DB Access e fare clic su OK.
Nella finestra che si apre, selezionare il DB e fare clic su Next; in quella successiva,
selezionare le tabelle e le viste che si desiderano includere nel servizio web e fare clic su
Next; quindi digitare il nome di servizio, l’URL e il numero di porta, fare clic su Next.
Nella finestra che si apre, fare clic sul pulsante Submit Database Metadata XML.
TPSIT 5
434
um 173 di
Inserire la login e la password e fare clic OK.
Nella finestra che si apre, si visualizza il progetto e il segno di spunta verde indica che è
completato ed è scaricato; selezionare il progetto e scegliere la cartella dove
memorizzarlo, fare clic su Download Project.
TPSIT 5
434
um 174 di
La cartella BIN contiene tutti i file binari.
La cartella DOTNET contiene il servizio in codice sorgente.
La cartella CLIENTS contiene i client per il servizio web, in tutti i linguaggi gestiti
dall’applicazione.
La cartella SERVER contiene tutto il codice del servizio web e il WSDL.
Tool/Code Monkey…
TPSIT 5
434
um 175 di
TPSIT 5
434
um 176 di
Tool/Silverlight…
Selezionare il progetto e la configurazione, il pannello di destra visualizza l’anteprima.
Fare clic sul pulsante Parameters…, per visualizzare le relazioni.
TPSIT 5
434
um 177 di
Dopo aver fatto clic su OK, nella finestra compaiono due nuove schede: XAML e C#.
TPSIT 5
434
um 178 di
PHP (PHP: HYPERTEXT PREPROCESSOR)
L’uso dei WS si basa su librerie esterne quali ad esempio NUSOAP.
Occorre attivare l’estensione SOAP, in ambito Linux con il parametro –enablesoap, con
Windows decommentando la linea extension=php_soap .dll, nella sezione relativa alle
estensioni.
NUSOAP implementa un framework intermedio per l’accesso alle funzioni SOAP e di
conseguenza per creare e usare WS in PHP.
Non solo si tratta di una libreria affidabile ma è l’unica che consente di generare
automaticamente il WSDL.
In PHP esiste una serie di funzioni interne per utilizzare i WS che sono molto più veloci di
quelle implementate in NUSOAP, tuttavia rimangono ancora limitate per certi aspetti, in
pratica non consentono la generazione automatica del WSDL.
Esempio, creare un file INDEX.PHP all’interno di una cartella del server web e come prima
operazione sviluppare la funzione che s’interfaccerà al DB.
<?php
function execQuery($strSql)
{ $connessione = mysql_connect("localhost", "root", "password") or die("Connessione
nonriuscita: " . mysql_error());
mysql_select_db("mioDB") or die("Selezione del database non riuscita");
/* esecuzione di una query SQL */
$query = "SELECT * FROM file
where Applicazione like '%$strSql%'";
$risultato = mysql_query($query) or die("Query fallita: " . mysql_error() );
while ($riga = mysql_fetch_array($risultato, MYSQL_ASSOC)) {
$columns[] = $riga;
}
/* chiusura della connessione */
mysql_close($connessione);
return new soapval('return','tns:Idiot',$columns);
}
/* includere il file NUSOAP.PHP */
require_once('nusoap.php');
/* istanziare il server e aggiungere un tipo che servirà per descrivere i valori di ritorno che
devono essere contenuti nello schema XML SOAP */
$server = new soap_server();
// initialize WSDL support
$ns="http://localhost/~fabio/stock/services.php";
$server->configureWSDL('services', $ns);
$server->wsdl->schemaTargetNamespace=$ns;
$server->wsdl->addComplexType("MyType","complexType","array","all","",
array( "ID" =>
array("ID" => "ID","type" => "xsd:string"),"Applicazione" =>
array("Applicazione" => "Applicazione","type" => "xsd:string"),"File" =>
array("File" =>
"File", "type" => "xsd:string"),"Sottotitolo" =>
array("Sottotitolo" => "Sottotitolo", "type"=>
"xsd:string"),"Descrizione" =>
array("Descrizione" =>"Descrizione","type" =>
"xsd:string"),"NumeroRivista" =>
array("NumeroRivista" => "NumeroRivista","type" =>
TPSIT 5
434
um 179 di
"xsd:string"),"IDmc" =>
array("IDmc"=>
"IDmc", "type" => "xsd:string"),"Dim" =>
array("Dim" =>
"Dim", "type" => "xsd:string"),"posiz" =>
array("posiz" =>
"posiz", "type" =>
"xsd:string"),"Link" =>
array("Link" =>
"Link", "type" => "xsd:string"),"Evidenza" =>
array("Evidenza" => "Evidenza", "type" =>
"xsd:string"), "Indispensabili" =>
array("Indispensabili" => "Indispensabili","type" =>
"xsd:string")
)
);
Inserire le informazioni che si riferiscono al protocollo di trasporto e agli altri dati che
serviranno per la descrizione del WSDL.
$server->register('execQuery',
// method name
array('strSql' => 'xsd:string'),
// input parameters
array('return' => 'tns:Idiot'),
// output parameters
'urn:services',
// namespace
'urn:services#execQuery',
// soapaction
'rpc',
// style
'encoded',
// use
'Ritorna un array contenente i valori richiesti'
// documentation
);
Infine, invocare il WS.
$HTTP_RAW_POST_DATA = isset(
$HTTP_RAW_POST_DATA) ?
$HTTP_RAW_POST_DATA : '';
$server->service($HTTP_RAW_POST_DATA);
?>
Aprire un browser e connettersi al WS.
Ad esempio, http://localhost/WS/servizio/nomeservizio.php.
La pagina web contiene un link al file WSDL e uno per uno i link ai metodi esposti dal WS.
Cliccare sul link WSDL e si ottiene il file WSDL in formato leggibile.
Cliccare su uno dei metodi esposti dal WS per ottenere un riferimento alla
documentazione che si riferisce a quel particolare metodo.
Per installare un WS in Apache, è sufficiente copiare il file PHP e il relativo NUSOAP.PHP
nella cartella che esporrà il servizio.
Per utilizzare il WS, includere la libreria NUSOAP.PHP che contiene le varie classi che
consentono di lavorare in modo semplificato con il WS.
<?php
TPSIT 5
434
um 180 di
require_once('nusoap.php');
Creare un’istanza del client e tramite il file WSDL informare l’istanza di quali sono i metodi
che il WS espone e della sua descrizione.
Ovviamente il parametro passato al client corrisponde all’indirizzo del WS.
Nell’esempio, il WSDL è generato automaticamente dallo script, in altri casi si deve
referenziare direttamente il file WSDL.
$client = new soapclient_n("http://localhost/WS/services.php?wsdl", true);
Controllare se ci sono eventuali errori.
$err = $client->getError();
if ($err) {
echo '<h2>Errore ne costruttore</h2><pre>'. $err . '</pre>';
}
Richiamare il metodo esposto dal WS passandogli eventuali parametri sotto forma di
array.
$result2 = $client->call('execQuery', array('strSql' => 'test'));
Controllare eventuali errori e stamparli a video.
if ($client->fault) {
echo '<h2>Fault</h2><pre>';
echo '</pre>';
}
else {
$err = $client->getError();
if ($err) {
echo '<h2>Error</h2><pre>' . $err . '</pre>';
Se non ci sono errori, stampare il risultato a video.
} else {
// display the result
echo '<h2>Result</h2><pre>';
print_r($result2);
echo '</pre>';
}
}
Si può stampare anche il debug delle comunicazioni avvenute fra client e server.
echo '<h2>Request</h2>';
echo '<pre>' . htmlspecialchars($client->request, ENT_QUOTES) . '</pre>';
echo '<h2>Response</h2>';
echo '<pre>' . htmlspecialchars($client->response, ENT_QUOTES) . '</pre>';
// display the debug messages
echo '<h2>Debug</h2>';
echo '<pre>' . htmlspecialchars($client->debug_str, ENT_QUOTES) . '</pre>';
TPSIT 5
434
um 181 di
?>
WS senza WSDL
Esiste una tecnica che consente di non dover leggere il file WSDL ogni volta che si
richiama il servizio, si usano le funzioni __call.
Ha il vantaggio di non dovere parserizzare il file WSDL per ogni chiamata ma lo
svantaggio di essere meno semplice da utilizzare.
All’interno del file PHP.INI si trovano le seguenti linee che consentono di abilitare o meno
una cache per i file WSDL diminuendo il tempo di accesso.
[soap]
; Enables or disables WSDL caching feature.
; http://php.net/soap.wsdl-cache-enabled
soap.wsdl_cache_enabled=1
; Sets the directory name where SOAP extension will put cache files.
; http://php.net/soap.wsdl-cache-dir
soap.wsdl_cache_dir="/tmp"
; (time to live) Sets the number of second while cached file will be used
; instead of original one.
; http://php.net/soap.wsdl-cache-ttl
soap.wsdl_cache_ttl=86400
; Sets the size of the cache limit. (Max. number of WSDL files to cache)
soap.wsdl_cache_limit = 5
TPSIT 5
434
um 182 di
JAVA
Ha numerosi framework per lo sviluppo di WS.
Per esempio, Apache Axis è una libreria che s’integra in un’applicazione web basata sulla
tecnologia servlet.
Basta aggiungere i JAR (Java ARchive) di Axis all’applicazione, si programma estendendo
le classi e implementando le interfacce del framework, poi si pubblica il tutto servendosi di
specifici descrittori XML.
L’operazione, anche se svolta a mano, rimane semplice ma richiede comunque un certo
grado di confidenza con la tecnologia.
Eclipse facilita le cose: attraverso il progetto WTP (Web Tools Platform) la piattaforma è
dotata di template, editor e procedure guidate per un più rapido controllo dei servizi Axis.
Due diversi approcci
Quando si realizza un WS basato su SOAP, due diversi approcci sono possibili.
1. Se la logica del S/W è stata già scritta e il WS non deve rispettare un design imposto a
priori, allora si procede traducendo in SOAP l’interfaccia di programmazione Java già
esistente; in pratica si prende la classe Java esistente e si trasforma in un servizio
SOAP.
2. Se la logica del S/W non è stata già scritta, prima si definisce il WSDL che descrive e
dettaglia il servizio, poi si realizza l’applicazione Java da questo.
Esempio, implementare un WS che verifica se una carta di credito è valida.
JAX-WS (Java API per XML WS) è la tecnologia Java di riferimento che definisce un
modello che usa le annotazioni per sviluppare i provider e i consumer di servizi web.
public class WS {
@WebService (name="CartaCredito",
portName="Carta CreditoPort",
serviceName="CartaCreditoService",
targetNamespace="http://www.miosito.it")
public class CartaCreditoImpl
{
@WebMethod (operationName="verifica",action="che ck")
public @WebResult(name="risultatoVerifica")
boolean checkCC(@WebParam(name="codice")
final String cc,@WebParam(name="dataScadenza")
final Date scadenza)
{...}
}
La classe Java CartaCreditoImpl è dichiarata come WS annotandola con la notazione
@WebService.
Il metodo checkCC, annotato con @WebMethod, è un’operazione pubblica del servizio.
Nello specifico il metodo accetta come parametri d’input il codice della carta di credito, una
stringa e la data di scadenza, un oggetto java.util.Date e ritorna un risultato booleano che
indica la validità della carta.
I parametri d’input e di output di un metodo Java per essere serializzabili in XML all’interno
di un envelope SOAP devono soddisfare la specifica JAXB (Java Architecture per l’XML
Binding), è una tecnologia che definisce le regole e un set di annotazioni per mappare e
quindi serializzare i tipi e gli oggetti Java in XML e viceversa.
Le annotazioni @WebParam e @WebResult permettono di specificare il nome dei
parametri d’input e output di un WS.
TPSIT 5
434
um 183 di
Annotazione
Descrizione
Definisce una classe Java come WS, i principali attributi applicabili sono i
seguenti.
name: specifica il valore dell’attributo name dell’elemento wsdl:portType nel
documento WSDL.
Web Service targetNamespace: specifica il namespace per gli elementi wsdl:portType.
serviceName: specifica il nome del servizio, attributo name dell’elemento
wsdl:service nel documento WSDL.
portName: specifica il valore dell’attributo name dell’elemento wsdl:port nel
documento WSDL.
Consente di esporre un metodo di una classa Java come operazione del
servizio web, i principali attributi applicabili sono i seguenti.
operationName: specifica il valore dell’attributo name dell’elemento
wsdl:operation nel documento WSDL.
WebMethod
action: specifica l’azione per il metodo, attributo soapAction per i bindings
SOAP.
exclude: booleano che specifica se il metodo è esposto come operazione del
WS.
Definisce il mapping tra i parametri dei metodi Java e i parametri
dell’operazione del WS, i principali attributi applicabili sono i seguenti.
name: specifica il nome locale dell’elemento XML che rappresenta il
parametro.
partName: specifica il nome dell’elemento wsdl:part che rappresenta il
WebParam parametro.
targetNamespace: specifica il namespace XML del parametro.
mode: specifica se il parametro è d’input (IN), di output (OUT) o d’input e
output (INOUT).
header: booleano che specifica se il parametro è presente nel body del
messaggio (il default) o se è presente nell’header SOAP.
Definisce il mapping tra i parametri di ritorno dei metodi Java e i parametri
delle operazioni del WS, i principali attributi applicabili sono i seguenti.
name: specifica il nome locale dell’elemento XML che rappresenta il
parametro.
WebResult partName: specifica il nome dell’elemento wsdl:part che rappresenta il
parametro.
targetNamespace: specifica il namespace XML del parametro.
header: booleano che specifica se il parametro è presente nel body del
messaggio (il default) o se è presente nell’header SOAP.
Per installare un WS, basta eseguire il deploy di un’applicazione web che ha tra i sorgenti
Java la classe CartaCreditoImpl e nel deployment descriptor, il file WEB.XML, la
definizione di una servlet che dichiara come valore del TAG <servlet-class> proprio la
classe CartaCreditoImpl.
Il TAG <url-pattern> definisce l’URL relativa per invocare il servizio.
<servlet>
<servlet-name>CartaCreditoServlet</servlet-name>
<servlet-class>CartaCreditoImpl</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>CartaCreditoServlet< /servlet-name>
TPSIT 5
434
um 184 di
<url-pattern>/CarteCredito</url-pattern>
</servlet-mapping>
Il file WSDL del servizio è quindi visibile al seguente URL.
http://localhost:8080/WS/CarteCredito?wsdl
Per invocare il WS si usa il tool soapUI, installabile sia su piattaforma Window sia Linux,
S/W open source che semplifica il test dei WS.
È sufficiente referenziare il file WSDL presente all’URL e soapUI costruisce lo scheletro di
un envelope SOAP che è possibile inviare al WS dopo aver digitato i parametri d’input.
Questo è il messaggio SOAP inviato dal consumer al provider per richiedere la verifica
della carta di credito.
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"">
<soap:Header/>
<soap:Body>
<ns2:verifica xmlns:ns2="http://www.miosito.it"">
<codice>123456789</codice>
<dataScadenza>2001-12-28T00:00:00.000+01:00</dataScadenza>
</ns2:verifica>
</soap:Body>
</soap:Envelope>
Questa è la risposta del provider.
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Header/>
<soap:Body>
<ns2:verificaResponse xmlns:ns2="ttp://www.miosito.it"">
<risultatoVerifica>true</risultatoVerifica>
</ns2:verificaResponse>
</soap:Body>
</soap:Envelope>
Documento WSDL del WS di verifica della carta di credito.
Le sezioni types, message e portType danno una descrizione astratta del WS definendo le
operazioni offerte e il tipo di messaggi scambiati.
Viceversa, le sezioni binding e service danno una descrizione concreta “istanziando” le
precedenti definizioni su un’implementazione reale che è caratterizzata da specifici
protocolli di comunicazione e indirizzi di rete.
La sezione types definisce, mediante un XML schema, il tipo di dati utilizzati dal WS.
<wsdl:types>
<xs:schema ...>
<xs:element name="verifica" type="tns:verifica"/>
...
<xs:complexType name="verifica"">
<xs:sequence>
<xs:element minOccurs="0" name="codice" type="xs:string" />
<xs:element minOccurs="0" name="dataScadenza" type="xs:dateTime" />
</xs:sequence>
TPSIT 5
434
um 185 di
</xs:complexType>
...
</xs:schema>
</wsdl:types>
Il tipo complesso verifica è una sequenza di due elementi: il primo elemento, ossia il
codice della carta di credito, è una stringa, xs:string, mentre il secondo elemento, la data
di scadenza della carta di credito, è una data, xs:dateTime.
La sezione message definisce la struttura dei parametri d’ingresso e di risposta delle
operazioni del WS.
<wsdl:message name="verifica"">
<wsdl:part element="tns:verifica" name="parameters" />
</wsdl:message>
Il codice specifica come il messaggio verifica, parametro d’ingresso del WS, sia formato
da una sola “parte”, sezione wsdl:part e in particolare l’attributo element referenzia un tipo
complesso definito nella sezione wsdl:types.
La sezione portType definisce l’interfaccia del servizio descrivendo le operazioni e i
messaggi scambiati.
<wsdl:portType name="CartaCredito">
<wsdl:operation name="verifica">
<wsdl:input message="tns:verifica" name="verifica"/>
<wsdl:output message="tns:verificaResponse" name="verificaResponse""/>
</wsdl:operation>
</wsdl:portType>
Il portType di nome CartaCredito presenta una sola operazione di nome verifica che
accetta un messaggio d’input sempre di nome verifica e ritorna un messaggio di output di
nome verificaResponse.
La sezione binding definisce le tecnologie usate per implementare le operazioni
specificate nella sezione wsdl:portType.
Questa è la sezione wsdl:binding con un SOAP/HTTP binding per l’interfaccia
CartaCredito.
<wsdl:binding name="CartaCreditoServiceSoapBinding" type="tns:CartaCredito"">
<soap:binding style="document" transport="http://schemas.xmlsoap.org/soap/http"/>
<wsdl:operation name="verifica">
<soap:operation soapAction="check" style="document"/>
<wsdl:input name="verifica">
<soap:body use="literal"/>
</wsdl:input>
<wsdl:output ...> ...
</wsdl:operation>
</wsdl:binding>
L’attributo name dell’elemento wsdl:binding specifica un nome univoco per riferirsi al
binding nel resto del documento WSDL mentre l’attributo type riporta il nome di un
wsdl:portType al quale si sta applicando il binding.
L’elemento soap:binding descrive i dettagli del binding SOAP e in particolare l’attributo
transport specifica HTTP come protocollo di trasporto, mentre l’attributo style che può
TPSIT 5
434
um 186 di
assumere i valori RPC o document, definisce lo stile del servizio.
Il valore per l’attributo transport="http://schemas.xmlsoap.org/soap/http"/> corrisponde al
protocollo di trasmissione HTTP.
Altri valori per l’attributo transport possono essere specificati per indicare protocolli di
trasmissione diversi: SMTP e FTP.
L’elemento soap:operation definisce i dettagli SOAP di un’operazione esposta dal servizio
e in particolare, l’attributo soapAction specifica il valore del parametro SOAPAction
presente nell’header HTTP della richiesta.
La sezione soap:body descrive come le parti del messaggio devono apparire all’interno del
body dell’envelope SOAP.
La sezione wsdl:service dichiara un WS come una raccolta di porte “concrete”, elementi
wsdl:port.
<wsdl:service name="CartaCreditoService">
<wsdl:port binding="tns:CartaCreditoServiceSoapBinding" name="CartaCreditoPort"">
<soap:address location="http://localhost:8080/WS/CarteCredito"/>
</wsdl:port>
</wsdl:service>
Una porta, wsdl:port, è l’istanza di una porta astratta, wsdl:portType, ottenuta tramite un
binding.
Pertanto ogni elemento wsdl:port ha un nome univoco, attributo name e fa riferimento ad
un binding specifico, attributo binding.
Se poi il binding è di tipo SOAP allora l’elemento wsdl: port deve contenere un elemento
soap:address che specifica con l’attributo location l’indirizzo di rete della porta.
Esempio, modificare il WS di verifica della carta di credito, affinché sia invocabile solo da
consumers che forniscano le proprie credenziali di accesso.
Il servizio espone l’operazione di verifica e richiede come parametri d’input il codice della
carta di credito e la relativa data di scadenza e ritorna un risultato booleano per indicare la
validità o meno della carta.
L’operazione del WS è dichiarata nella sezione portType del file WSDL.
<wsdl:portType name="CartaCredito"">
<wsdl:operation name="verifica"">
<wsdl:input message="tns:verifica" name="verifica"/>
<wsdl:output message="tns:verificaResponse" name="verificaResponse"/>
</wsdl:operation>
</wsdl:portType>
La sezione types specifica, mediante un XML schema, il tipo di dati utilizzati dal WS.
verifica è un tipo complesso costituito da una stringa, il codice della carta di credito e da
una data, la data di scadenza della carta.
<wsdl:types>
…
<xs:complexType name="verifica">
<xs:sequence>
<xs:element minOccurs="0" name="codice" type="xs:string"/>
<xs:element minOccurs="0" name="dataScadenza" type="xs:dateTime"/>
</xs:sequence>
</xs:complexType>
TPSIT 5
434
um 187 di
…
<wsdl:types>
Il servizio dovrà ora richiedere obbligatoriamente l’username e la password del consumer
e le credenziali dovranno essere presenti in un header WS-Security.
Il messaggio SOAP di richiesta da parte del consumer dovrà quindi essere così strutturato.
<S11:Envelope xmlns:S11="http://schemas.xmlsoap.org/soap/envelope/">
<S11:Header>
<wsse:Security xmlns:wsse="..." S11:mustUnderstand="1">
<wsse:UsernameToken xmlns:wsu="..." wsu:Id="UsernameToken-256294">
<wsse:Username>pippo</wsse:Username>
<wsse:Password>pluto</wsse:Password>
</wsse:UsernameToken>
</wsse:Security>
</S11:Header>
<S11:Body>
<ns2:verifica xmlns:ns2="http://www.miosito.it">
<codice>123456789</codice>
<dataScadenza>2013-07-07T00:00:00.000+01:00</dataScadenza>
</ns2:verifica>
</S11:Body>
</S11:Envelope>
L’attributo mustUnderstand per l’header <wsse:Security> ha valore uno, ossia le
credenziali devono essere obbligatoriamente analizzate, “capite” e processate dal provider
del servizio.
Il provider del servizio sarà esteso affinché rifiuti i messaggi che non hanno una sezione
header di tipo WS-Security e verifichi la correttezza delle credenziali, per esempio su un
DB.
Per estendere il servizio si usa l’handler framework presente nella specifica JAX-WS.
Gli handler JAX-WS sono intercettori che possono essere collegati all’ambiente di
esecuzione JAX-WS per l’elaborazione dei messaggi in entrata e in uscita.
La specifica JAX-WS definisce due tipi di handler.
1. Logical handler: sono indipendenti dal protocollo e hanno accesso al solo payload del
messaggio.
2. Protocol hander: hanno accesso all’intero messaggio SOAP, inclusi gli header.
È possibile associare più di un handler al provider di un WS e questi sono assemblati in
una catena di gestori indicata con il termine di handler chain.
Un file di configurazione XML, referenziato con l’annotazione @HandlerChain, riporta
l’insieme degli handler associati al servizio.
Esempio, servizio di verifica con l’annotazione @HandlerChain.
@WebService (name="CartaCredito",
portName="Carta CreditoPort",
serviceName="CartaCreditoService",
targetNamespace="http://www.miosito.it")
@HandlerChain(file=""/security.xml")
public class CartaCreditoImpl {
@WebMethod (operationName="”verifica",action="check")
public @WebResult(name="risultatoVerifica")
TPSIT 5
434
um 188 di
boolean checkCC(@WebParam(name="codice")
final String cc;
@WebParam(name="dataScadenza")
final Date scadenza)
{...}
}
Il file SECURITY.XML definisce la lista di handler associati al servizio.
In questo caso, è definito un solo handler, indicato con il nome di GestoreSicurezza che
esegue i controlli di sicurezza.
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<javaee:handler-chains xmlns:javaee="http://java.sun.com/xml/ns/javaee"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"">
<javaee:handler-chain>
<javaee:handler>
<javaee:handler-name>GestoreSicurezza</javaee:handler-name>
<javaee:handler-class>it.wsecurity.Security</javaee:handler-class>
</javaee:handler>
</javaee:handler-chain>
</javaee:handler-chains>
L’handler GestoreSicurezza deve analizzare l’header WS-Security, recuperare le
credenziali del consumer e verificarle.
La classe it.wsecurity.Security che realizza l’handler GestoreSicurezza, dovendo avere
accesso all’intero messaggio SOAP e in particolare alla sezione degli header sarà un
protocol handler e implementerà l’interfaccia SOAPHandler.
public class Security implements SOAPHandler<SOAPMessageContext>
{ public boolean handleMessage(SOAPMessageContext context)
{ ...}
public boolean handleFault(SOAPMessageContext context)
{ ...}
public void close(MessageContext context)
{ ...}
public Set<QName> getHeaders()
{ ...}
}
Il metodo handleMessage consente di analizzare il messaggio SOAP sia in entrata sia in
uscita dal provider del servizio.
La direzione del messaggio è determinata attraverso un valore booleano ottenuto dal
SOAPMessageContext.
Boolean outbound = (Boolean)
context.get(MessageContext.MESSAGE_OUTBOUND_PROPERTY);
Se la direzione del messaggio è in entrata, dev’essere verificato l’header WS-Security.
public boolean handleMessage(SOAPMessageContext context)
{
Boolean outbound =...;
if (!outbound.booleanValue()) { // messaggio in entrata
TPSIT 5
434
um 189 di
SOAPMessage msg = context.getMessage();
SOAPHeader header = null;
try {
header = msg.getSOAPHeader();
}
catch (SOAPException e)
{...}
if (header != null) {
SOAPElement username = find("Username",header);
SOAPElement password = find("Password",header);
if (username != null && password !=null &&
"pippo".equals(username.getValue()) && "Staro".equals(password.getValue()))
return true;
else
throw
new SecurityException("Credenziali non corrette");
}
if header != null
if !outbound.booleanValue()
return true;
}
}
Dal contesto d’invocazione, è recuperato il messaggio SOAP e in particolare la sezione
dei SOAP header.
SOAPMessage msg = context.getMessage();
SOAPHeader header = msg.getSOAPHeader();
Dall’header devono essere recuperati i valori di Username e Password che sono verificati
rispetto a valori indicati nel codice.
Il metodo privato find cicla ricorsivamente sui nodi del SOAPElement e ritorna il
SOAPElement cercato, Username o Password o il valore null se non lo trova.
private SOAPElement find(String localName, SOAPElement element)
{
if (localName.equals(element.getLocalName())) return element;
if (element.getChildElements() == null || !element.getChildElements().hasNext())
return null;
Iterator childElements = element.getChildElements();
boolean founded = false;
SOAPElement soapElement = null;
while (childElements.hasNext() && !founded) {
// ciclo ricorsivamente sull’elemento
Object child = childElements.next();
if (child instanceof SOAPElement) {
SOAPElement _soapElement = (SOAPElement)child;
soapElement = find(localName, _soapElement);
if (soapElement == null)
founded = false;
else
founded = true;
}
TPSIT 5
434
um 190 di
}
return soapElement;
}
L’handler GestoreSicurezza implementa anche i metodi handleFault e close: il primo
ritorna true mentre il secondo è void.
L’ultimo metodo della classe handler Security è il metodo getHeaders, ha la seguente
implementazione.
public Set<QName> getHeaders()
{ Set<QName> header = newTreeSet<QName>();
String uri = "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecuritysecext-1.0.xsd";
header.add(new QName(uri,"Security"));
return header;
}
L’header WS-Security nel messaggio SOAP in ingresso ha l’attributo mustUnderstand
valorizzato ad uno.
Pertanto l’handler GestoreSicurezza deve necessariamente analizzare, “capire” e gestire
l’header WS-Security o in caso contrario restituire un messaggio di fault.
L’insieme degli header SOAP gestiti da un handler è individuato dall’elenco dei QName
che l’identificano.
Il metodo getHeaders dichiara che è in grado di processare la sezione WS-Security
identificata univocamente nel messaggio SOAP dalla URI che è proprio il namespace di
WS-Security versione 1.0.
TPSIT 5
434
um 191 di
GRID SERVICE
INTRODUZIONE
Si può immaginare Internet come una rete neurale (cervello umano), nella quale ogni
nodo rappresenta un neurone che scambia informazioni con gli altri e mette a disposizione
le proprie risorse di calcolo per eseguire elaborazioni complesse: è una griglia
computazionale, un’evoluzione del concetto di rete di calcolatori.
Infatti, sono progettate per lavorare su reti eterogenee, permettendo di mettere in
comunicazione macchine che si possono trovare ovunque nel mondo e che mettono a
disposizione una quantità di risorse variabile nel tempo.
I sistemi cluster, invece, usati nei centri di calcolo sono progettati per lavorare su una
rete tecnologicamente omogenea, in altre parole con nodi che hanno la stessa piattaforma
H/W e S/W, solitamente in locale e dove ogni macchina mette a disposizione delle risorse
di calcolo prestabilite.
I WS presentano molti vantaggi ma hanno un grosso limite: sono stateless, il servizio non
è in grado di mantenere informazioni sullo stato delle risorse.
Le griglie computazioni permettono di ovviare a questo problema grazie ai GS (Grid
Service) che si possono considerare come un’estensione dei WS che permette di gestire
delle risorse e il loro stato implementando lo standard WSRF (WS Resource Framework).
OGSA (Open Grid Service Architecture) definisce le caratteristiche fondamentali che un
GS dovrebbe avere: il modello di programmazione, informazioni su come costruire un
servizio e quali sono le componenti per costruire e distribuire un prodotto di GS.
OGSI (Open Grid Service Infrastructure), utilizzando le tecnologie che stanno alla base dei
GS e WS, definisce i meccanismi per la creazione, la gestione e lo scambio d’informazioni
TPSIT 5
434
um 192 di
tra i GS stessi.
Le specifiche OGSI estendono WSDL e XML al fine di gestire le seguenti possibilità.
 WS con stato.
 Estensione delle interfacce dei WS.
 Notifica asincrona dei cambiamenti di stato.
 Riferimenti a istanze di servizi.
 Collezione d’istanze.
In pratica, OGSA costituisce l’architettura, mentre WSRF costituisce l’infrastruttura sulla
quale l’architettura è stata costruita: infatti, WSRF fornisce i servizi stateful di cui OGSA ha
bisogno.
TPSIT 5
434
um 193 di
REST/POX
INTRODUZIONE
Lo stack SOAP definisce un proprio formato di messaggi.
I WS SOAP sono molto complessi: un protocollo di scambio “astratto” che sarà
implementato da un protocollo concreto HTTP, le funzionalità sono esposte tramite WSDL,
una serie di tipi che saranno mappati in tipi concreti dipendenti dal linguaggio di
programmazione.
Una direzione completamente diversa è stata presa, invece, da quelle architetture che
seguono l’approccio REST.
Descritto nel 2000 nella discussione di dottorato di Roy Fielding.
Considerare un tavolino da caffè sul quale si trovano un telecomando, una tazza, un
vassoio e un giornale: questi sono i nomi.
Prendi, elimina: questi sono i verbi.
È possibile usare lo stesso verbo per tutti questi nomi?
Sarebbe curioso inventare combinazioni per tutti questi elementi, come PrendiTazza,
EliminaTazza e PrendiTelecomando.
Un modello d’interfacce REST crea quindi un’URL che costituisce la rappresentazione di
un oggetto gestito nel sistema e ne consente la manipolazione con i verbi standard del
protocollo HTTP e in particolare i metodi GET, POST, PUT e DELETE per interrogare e
gestire dati attraverso il web.
In pratica, è un ritorno alle origini, da molto tempo, infatti, ancora prima di SOAP, esiste
uno stack in grado di garantire affidabilità, sicurezza e scalabilità: è il web stesso.
Riutilizzando la tecnologia del web è possibile integrare sistemi tra loro eterogenei.
I social network sono stati i primi che hanno reso disponibile le WebAPI esposte tramite
servizi REST/POX.
L’idea consiste nel fornire un URI di accesso ad un’entità, per esempio
http://www.miosito.it/bici e, se richiamato dal browser, restituisce la lista delle biciclette in
XML, in JSON, in RSS (Really Simple Syndication).
XML può essere letto con Visual C# e LINQ (Language INtegrated Query) to XML.
JSON può essere letto con JavaScript all’interno di pagine HTML.
In conclusione, i siti web, quindi, non restituiscono solo HTML ma offrono informazioni
attraverso API che i client possono consumare.
A differenza di un’API REST, una streaming API non restituisce dati immediatamente ma è
una connessione “live” e continua tra un client e un server o tra due server.
Una streaming API può essere assimilata ad una chiamata telefonica in corso, mentre
l’API REST è assimilabile ad una lettera che si riceve per posta.
Le API REST hanno un limite di 180 richieste ogni 15 secondi ma questo limite può variare
a seconda del tipo di richiesta.
WEBAPI
Fare clic su File/Nuovo/Progetto… (CTRL+N), nella finestra Nuovo progetto selezionare
Altri linguaggi/Visual C#/Web/Applicazione Web ASP.NET MVC 4, fare clic su OK.
Per progettare servizi REST, il .NET Framework di ASP.NET è stato arricchito con le
ASP.NET WebAPI, in pratica è una libreria che basa il suo funzionamento su ASP.NET.
Per iniziare usare il template WebAPI di Visual Studio che crea un’applicazione con servizi
REST.
TPSIT 5
434
um 194 di
Nella finestra successiva, selezionare WebAPI.
TPSIT 5
434
um 195 di
Visual Studio genera il progetto seguente.
TPSIT 5
434
um 196 di
Eseguire l’applicazione.
TPSIT 5
434
um 197 di
Esempio, nella finestra Nuovo progetto ASP.NET MVC 4 selezionare Vuoto.
Visual Studio genera il progetto seguente.
Selezionare la cartella Controllers e dal
Aggiungi/Controller… (CTRL+M, CTRL+C).
menu
contestuale
fare
clic
su
Selezionare Controller API vuoto e fare clic su Aggiungi.
Si ottiene una nuova classe vuota che si chiama Prodotti.Controllers ed eredita da
ApiController.
Il prefisso del controller, nell’esempio Prodotti, determina il percorso del servizio.
namespace Prodotti.Controllers {
public class Default1Controller : ApiController {
TPSIT 5
434
um 198 di
Tutti i servizi sviluppati con questa tecnologia si trovano sotto il percorso api.
Nell’esempio, il servizio, anche se non è ancora implementato, è raggiungibile all’indirizzo
http://localhost:1145//api/prodotti.
Inserire nella classe le funzioni che saranno invocate secondo il metodo HTTP usato per
chiamare l’indirizzo.
Per esempio, inserire le funzioni seguenti per fare sì che siano invocate su chiamate
HTTP di tipo GET o POST sull’indirizzo.
Get[esegui]
Post[esegui]
La funzione seguente, ritorna la lista dei prodotti.
public IEnumerable<Prodotto> GetProdotto()
{
return new Prodotto[] { new Prodotto {
ID = 1,
Nome = "prodotto 1"
} };
}
Se nel browser s’invoca l’indirizzo del servizio, siccome è una chiamata GET, si ottiene il
risultato da GetProdotto.
Il motore serializza automaticamente la lista in JSON e il risultato è il seguente.
[{"Nome":"prodotto 1","ID":1}]
Il motore legge, dagli header HTTP, quali formati di risposta il chiamante accetta, per
esempio aggiungendo l’header seguente: Accept : text/xml, si richiede la risposta in XML
che è serializzata con DataContractSerializer.
<ArrayOfProdotti xmlns:i="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://schemas.datacontract.org/2004/07/ProductsService">
<Prodotto>
<ID>1</ID>
<Nome>prodotto 1</Nome>
</Prodotto>
</ArrayOfProdotti>
I servizi possono rispondere secondo il formato preferito dal chiamante, inoltre si ha la
possibilità di creare formatter personalizzati.
Creata l’operazione per la lista, la regola di routing permette di aggiungere un’altra
funzione che accetta un parametro e ritorna un solo prodotto.
public GetProdotto(int id)
{ if ((id != 1))
throw new HttpResponseException(HttpStatusCode.NotFound);
return new Prodotto {
ID = 1,
Nome = "prodotto 1"
};
}
TPSIT 5
434
um 199 di
È possibile lanciare eccezioni con la classe HttpResponseException che il motore poi
traduce in status code del protocollo HTTP, permettendo d’informare il chiamante dell’esito
dell’operazione, nell’esempio per id diverso da uno ritorna l’errore 404 NotFound.
Implementazione di un’operazione di POST per aggiungere un nuovo prodotto.
public HttpResponseMessage PostProdotto(Prodotto p)
{ // assegno un id
p.ID = 2;
// salvo sul repository
HttpResponseMessage response=new HttpResponseMessage(HttpStatusCode.Created);
// URI della risorsa
dynamic link = Url.Link("DefaultApi", new { id = p.ID });
response.Headers.Location = new Uri(link);
return response;
}
Si riceve l’oggetto Prodotto direttamente come parametro della funzione perché sarà
inoltrato nella richiesta nel formato JSON o XML.
Anche per quanto riguarda la richiesta, quindi, il motore capisce il formato.
Nella funzione si esegue l’attività sul repository e ritorna l’oggetto HttpResponseMessage.
Quest’operazione è facoltativa ma permette di rispondere al chiamante secondo la filosofia
REST, nell’esempio si specifica lo status 201 Created e si restituisce nell’header location
l’indirizzo della risorsa.
HTTP/1.1 201 Created
Date: Fri, 07 Nov 2023 05:37:08 GMT
X-Aspnet-Version: 11.0.70711
Location: http://localhost:1229/api/prodotti/2
Content-Length: 0
Connection: Close
ODP (OPEN DATA PROTOCOL)
Con il passare del tempo è nata l’esigenza di uniformare il modo di accedere alle entità,
d’interrogarle e modificarle.
L’ODP ha come scopo l’accesso e la manipolazione di entità con chiamate REST/POX.
Microsoft ha implementato questi protocolli con i WCF Data Services.
TPSIT 5
434
um 200 di
Esempio, dato un progetto, mappare il DB ADVENTUREWORKSLT.MDF con ADO.NET
Entity Framework per avere a disposizione l’oggetto AdventureWorks come ObjectContext
e le varie entità.
Posizionarsi sul progetto e fare clic con il pulsante destro del mouse, dal menu contestuale
selezionare Aggiungi/Nuovo elemento…(CTRL+MAIUSC+A)/Visual C#/Web/WCF Data
Service.
Visual Studio crea automaticamente ADVENTUREWORKS.SVC e il relativo code-behind
ADVENTUREWORKS.SVC.CS.
TPSIT 5
434
um 201 di
File ADVENTUREWORKS.SVC.CS
Si ha un servizio già implementato: una classe che eredita da DataService<T> dove T è
l’unica informazione che si deve specificare.
Il metodo InitializeService configura il servizio, per esempio a quale entità dare accesso e
con quali diritti, "Products" e la versione di AtomPub, il formato di serializzazione usato
quando s’interroga il servizio.
Il resto dell’implementazione è demandato alla classe DataService che, in base al
contesto che il programmatore le ha fornito, conosce i metadati, le entità e le proprietà che
l’object model dispone ed è in grado di eseguire automaticamente query tramite
l’interfaccia IQueryable supportata da LINQ to Entities.
using System.Data.Services;
namespace DataServicesWebApp {
public class AdventureWorks : DataService<AdventureWorksLTEntities> {
// questo metodo è chiamato solo una volta per inizializzare i criteri a livello di servizio
public static void InitializeService(IDataServiceConfiguration config)
{ // impostare regole per indicare i set di entità e le operazioni del servizio visibili,
// Esempi:
// config.SetEntitySetAccessRule("MyEntityset", EntitySetRights.AllRead);
// testing utilizzare "*" per indicare tutti i set di entità/tutte le operazioni del servizio
// "*" NON dev’essere utilizzato in sistemi di produzione.
// espone solo i set di entità necessari per l'applicazione in fase di compilazione
// utilizza EntitySetRight.All che consente l'accesso sia in R sia in W al set di entità
config.SetEntitySetAccessRule("Products", EntitySetRights.All);
config.SetEntitySetAccessRule("ProductCategories", EntitySetRights.All);
config.SetEntitySetAccessRule("ProductDescriptions", EntitySetRights.All);
config.SetEntitySetAccessRule("ProductModelProductDescriptions",
EntitySetRights.All);
config.SetEntitySetAccessRule("ProductModels", EntitySetRights.All);
}
}
}
Fare clic sul file ADVENTUREWORKS.SVC, dal menu contestuale selezionare Imposta
come pagina iniziale, eseguire l’applicazione.
TPSIT 5
434
um 202 di
Si ottiene come risposta un feed AtomPub che elenca le entità disponibili.
I WCF Data Services supportano le specifiche di ODP e permettono quindi d’interrogare
l’entità "Products" navigando all’indirizzo seguente, ottenendo così, sotto forma di feed, la
lista dei prodotti.
Fare clic su File/Salva con nome… (CTRL+S) per ottenere il file PRODUCTS.XML.
TPSIT 5
434
um 203 di
<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<feed xml:base="http://localhost:50000/AdventureWorks.svc/"
xmlns:d="http://schemas.microsoft.com/ado/2007/08/dataservices"
xmlns:m="http://schemas.microsoft.com/ado/2007/08/dataservices/metadata"
xmlns="http://www.w3.org/2005/Atom">
<title type="text">Products</title>
<id>http://localhost:50000/AdventureWorks.svc/Products</id>
<updated>2023-07-31T03:02:50Z</updated>
<link rel="self" title="Products" href="Products" />
<entry>
<id>http://localhost:50000/AdventureWorks.svc/Products(680)</id>
<title type="text"></title>
<updated>22023-07-31T03:02:50Z</updated>
<author>
<name />
</author>
<link rel="edit" title="Product" href="Products(680)" />
<link
rel="http://schemas.microsoft.com/ado/2007/08/dataservices/related/ProductCategory"
type="application/atom+xml;type=entry"
title="ProductCategory" href="Products(680)/ProductCategory" />
<link
rel="http://schemas.microsoft.com/ado/2007/08/dataservices/related/ProductModel"
type="application/atom+xml;type=entry"
title="ProductModel" href="Products(680)/ProductModel" />
<category term="AdventureWorksLTModel.Product"
scheme="http://schemas.microsoft.com/ado/2007/08/dataservices/scheme" />
<content type="application/xml">
<m:properties>
<d:ProductID m:type="Edm.Int32">680</d:ProductID>
<d:Name>HL Road Frame - Black, 58</d:Name>
<d:ProductNumber>FR-R92B-58</d:ProductNumber>
<d:Color>Black</d:Color>
<d:StandardCost m:type="Edm.Decimal">1059.3100</d:StandardCost>
<d:ListPrice m:type="Edm.Decimal">1431.5000</d:ListPrice>
<d:Size>58</d:Size>
<d:Weight m:type="Edm.Decimal">1016.04</d:Weight>
<d:SellStartDate m:type="Edm.DateTime">1998-06-01T00:00:00</d:SellStartDate>
<d:SellEndDate m:type="Edm.DateTime" m:null="true" />
<d:DiscontinuedDate m:type="Edm.DateTime" m:null="true" />
<d:rowguid m:type="Edm.Guid">43dd68d6-14a4-461f-9069</m:properties>
</content>
</entry>
Le specifiche prevedono la possibilità di passare delle opzioni con operatori e usare delle
funzioni per filtrare, ordinare, paginare o aggregare.
Esempio, ricercare le biciclette con numero di telaio 58.
http://localhost:50000/AdventureWorks.svc/Products?$filter=Size eq '58'
TPSIT 5
434
um 204 di
Fare clic su File/Salva con nome… (CTRL+S) per ottenere il file PRODUCTS.XML.
$metadata permette di ottenere una descrizione del modello, delle entità e delle proprietà.
Se si referenzia l’indirizzo http://localhost:50000/AdventureWorks.svc/$metadata si ottiene
la classe seguente.
-<EntityContainer Name="AdventureWorksLTEntities" m:IsDefaultEntityContainer="true">
Da un punto di vista logico, è simile all’omonima classe di ADO.NET Entity Framework ma
l’oggetto IQueryable, sul quale è possibile lavorare e creare espressioni non è tradotto in
istruzioni SQL ma in un URI per l’interrogazione del servizio.
In modo simile al WSDL (Web Services Description Language), permette al consumatore
del servizio di eseguire chiamate REST e di conoscere quale sarà il tipo di risposta.
TPSIT 5
434
um 205 di
APPLICAZIONI ORIENTATE AI SERVIZI
INTRODUZIONE
I Windows Service, letteralmente servizi Windows, sono applicazioni progettate per
essere eseguite senza che vi sia un controllo diretto da parte dell’utente.
Nella maggior parte dei casi, sono avviati automaticamente da Windows, non appena il
SO ha completato la fase di boot.
Queste caratteristiche li rendono ideali per essere impiegati in ambito server, per eseguire
azioni complesse e per fornire funzionalità di supporto alle altre applicazioni.
Esempi, di servizi Windows.
 Spooler di stampa.
 Visualizzatore eventi.
 Windows Update.
 Gestione attività.
 Windows Firewall.
 IIS.
Un servizio può essere lanciato, fermato, sospeso o riavviato in tre modi, anche se non
presenta alcun tipo d’interfaccia utente e gira in background con un’identità indipendente
dall’utente eventualmente collegato al PC.
1. Start/Tutti i programmi/Strumenti di amministrazione/Servizi.
TPSIT 5
434
um 206 di
2. A linea di comando: NET START, NET STOP, NET PAUSE e NET CONTINUE.
Microsoft Windows [Versione 6.1.7601]
Copyright (c) 2009 Microsoft Corporation. Tutti i diritti riservati.
C:\Users\Massimo>net start
I seguenti servizi di Windows sono avviati:
Acquisizione di immagini di Windows (WIA)
Agente criteri IPsec
Agente mapping endpoint RPC
Alimentazione
Audio di Windows
avast! Antivirus
BFE (Base Filtering Engine)
Browser di computer
Centro sicurezza PC
Client di Criteri di gruppo
Client DNS
COM+ Event System
Connessioni di rete
EFS (Encrypting File System)
…
Esecuzione comando riuscita.
3. Programmazione: si usa la classe ServiceController.
PROGRAMMAZIONE
Un servizio Windows non è altro che una specializzazione della superclasse ServiceBase,
contenuta nel namespace System.ServiceProcess.
In pratica, per implementare un servizio bisogna definire un tipo che derivi dalla classe
ServiceBase, configurare le sue proprietà e implementare alcuni metodi affinché il servizio
possa essere eseguito.
Fare clic su File/Nuovo/Progetto… (CTRL+N), nella finestra di dialogo Nuovo Progetto
selezionare Modelli/Altri linguaggi/Visual C#/Windows/Servizio Windows.
TPSIT 5
434
um 207 di
Visual Studio genera il progetto seguente.
File PROGRAM.CS
È definita la classe statica Program con il metodo Main che è l’entry point del servizio.
using System.ServiceProcess;
namespace WindowsService1 {
static class Program {
static void Main()
{ ServiceBase[] ServicesToRun;
ServicesToRun = new ServiceBase[]
{ new Service1() };
ServiceBase.Run(ServicesToRun);
}
}
}
File SERVICE1.CS
A questo file sono associati un designer e una sezione di codice che contiene due metodi
TPSIT 5
434
um 208 di
OnStart (accetta parametri) e OnStop che sono eseguiti all’avvio e all’arresto del servizio.
using System.ServiceProcess;
namespace WindowsService1 {
public partial class Service1 : ServiceBase {
public Service1()
{ InitializeComponent(); }
protected override void OnStart(string[] args) { }
protected override void OnStop() { }
}
}
File SERVICE1.DESIGNER.CS
Il codice è generato dal designer in modo automatico, c’è la dichiarazione parziale della
classe che rappresenta il servizio.
namespace WindowsService1 {
partial class Service1 {
// variabile di progettazione necessaria
private System.ComponentModel.IContainer components = null;
// liberare le risorse in uso
protected override void Dispose(bool disposing)
{ if (disposing && (components != null))
components.Dispose();
base.Dispose(disposing);
}
#region Codice generato da Progettazione componenti
// Metodo necessario per il supporto della finestra di progettazione
// non modificare il contenuto del metodo con l'editor di codice
private void InitializeComponent()
{ components = new System.ComponentModel.Container();
this.ServiceName = "Service1";
}
#endregion
}
}
In compile-time il codice scritto dal programmatore, è unito a quello generato in modo
automatico dal designer per formare un’unica classe, eseguire l’applicazione.
Il programmatore deve implementare solo i metodi che sono eseguiti durante il ciclo di vita
TPSIT 5
434
um 209 di
del servizio: avvio, arresto e sospensione.
Metodo
OnContinue
OnCustomCommand
OnPause
OnPowerEvent
OnSessionChange
OnShutdown
Descrizione
Eseguito al riavvio del servizio dopo una sospensione.
Eseguito quando il SCM (Service Control Manager) inoltra
un comando personalizzato (numero intero) al servizio.
Eseguito quando il servizio è sospeso.
Eseguito al cambio di stato dell’alimentazione del PC.
Eseguito alla notifica di notifica ricevuta da una sessione
Terminal Server.
Eseguito prima dello spegnimento del PC.
CONFIGURAZIONE
Per rendere un servizio installabile, si deve creare un installer che va aggiunto allo stesso
progetto.
Fare clic con il tasto destro del mouse in Esplora soluzioni e selezionare la voce
Aggiungi/Aggiungi nuovo elemento… (CTRL+MAIUSC+A)/Classe installer.
Visual Studio aggiunge al progetto il file seguente.
File WINDOWSSERVICEINSTALLER1.CS
Anche in questo caso, al file è associato un designer.
using System.ComponentModel;
using System.Configuration.Install;
namespace WindowsService1 {
[RunInstaller(true)]
public partial class WindowsServiceInstaller1 : System.Configuration.Install.Installer {
public WindowsServiceInstaller1()
{ InitializeComponent(); }
}
}
L’installer di un servizio è una classe che deriva dalla superclasse Installer contenuta nel
namespace System.Configuration.Install.
Quest’ultimo include due componenti che sono generati automaticamente alla creazione
dell’installer.
1. Service Installer: di tipo ServiceInstaller per definire il comportamento del servizio e le
sue dipendenze.
2. Service Process Installer: di tipo ServiceProcessInstaller per configurare il contesto di
sicurezza del servizio.
È buona regola di programmazione, anche se non è obbligatorio, rinominarli con nomi che
facciano riferimento al servizio associato all’installer.
La proprietà Account di ServiceProcessInstaller specifica il tipo di utente usato dal servizio
e può assumere quattro valori.
1. User: utente Windows personalizzato, è il default.
2. LocalSystem: utente built-in di Windows con privilegi di amministratore.
3. NetworkService: utente built-in di Windows con bassi privilegi che sulla rete si
presenta come utente autenticato con le credenziali del PC locale.
4. LocalService: utente built-in di Windows con bassi privilegi che sulla rete si presenta
con credenziali anonime.
Se le credenziali non sono indicate all’interno dell’installer, saranno richieste durante
l’installazione.
TPSIT 5
434
um 210 di
È buona regola di programmazione, associare ad un servizio le credenziali più restrittive
tra quelle possibili in relazione al tipo di operazioni svolte dal servizio stesso.
INSTALLAZIONE
Una volta compilato il progetto di sviluppo, si deve usare l’utility a linea di comando
seguente con i privilegi di amministratore.
L’utility opera in modalità transazionale: cancella ogni azione parziale svolta durante
l’installazione in caso di errori imprevisti.
È usata anche per disinstallare i servizi ma affinché la disinstallazione abbia successo, il
servizio dev’essere prima arrestato.
C:\Program Files (x86)\Microsoft Visual Studio 11.0\VC>InstallUtil
Utilità di installazione di Microsoft (R) .NET Framework, versione 4.0.30319.17929
Copyright (C) Microsoft Corporation. Tutti i diritti riservati.
Uso: InstallUtil [/u | /uninstall] [option [...]] assembly [[option [...]] assembly] [...]]
InstallUtil esegue i programmi di installazione in ciascun assembly
specificato. Se si specifica l'opzione /u o /uninstall, gli assembly
vengono disinstallati, altrimenti vengono installati. Diversamente da
altre opzioni, /u può essere applicata a tutti gli assembly,
indipendentemente dalla posizione in cui si trova sulla riga di comando.
L'installazione viene effettuata in modo transazionale: se l'installazione
di un assembly non riesce, viene ripristinato lo stato precedente
dell'installazione di tutti gli altri assembly. La disinstallazione non avviene
in modo transazionale.
Le opzioni hanno il formato /switch=[valore]. Qualsiasi opzione che precede
il nome di un assembly verrà applicata all'installazione di quell'assembly.
Le opzioni sono cumulative ma sottoponibili a override. Le opzioni specificate
per un assembly verranno applicate anche al successivo, a meno che non sia
specificato un nuovo valore. Il valore predefinito per tutte le opzioni è empty
o false, a meno che non venga specificato diversamente.
Opzioni riconosciute:
Opzioni per l'installazione di qualsiasi tipo di assembly:
/AssemblyName
Il parametro dell'assembly viene interpretato come nome dell'assembly (nome,
impostazioni locali, PublicKeyToken, versione). Per impostazione predefinita
tale parametro viene interpretato come nome di file dell'assembly sul disco.
/LogFile=[nomefile]
File in cui viene registrato lo stato. Se non viene specificato, tali informazi
oni non verranno registrate nel log.
Il nome predefinito è <nomeassembly>.InstallLog
/LogToConsole={true|false}
Se si imposta false, non verrà generato l'output da visualizzare sulla console.
/ShowCallStack
Se durante l'installazione si verifica un'eccezione,
lo stack di chiamate verrà registrato nel log.
/InstallStateDir=[nomedirectory]
Directory in cui viene memorizzato il file InstallState. La directory
predefinita è quella dell'assembly.
I singoli programmi di installazione utilizzati in un assembly potrebbero ricono
scere altre opzioni. Per conoscere tali opzioni, eseguire InstallUtil
TPSIT 5
434
um 211 di
specificando i percorsi degli assembly sulla riga di comando con l'opzione /? o
/help.
Progettare un servizio per monitorare una particolare cartella del file system e registrare
nel Visualizzatore eventi tutte le modifiche dei file contenuti.
TPSIT 5
434
um 212 di
MODULO 3
PROTOCOLLI APPLICATIVI
Browser
WebClient
FTP
POP
SPAM
SNMP
Voice over IP
TV over IP
eMule
TOR
BT
IRC
Second Life
Facebook
Bing
RSS
Twitter
TPSIT 5
434
um 213 di
BROWSER
INTRODUZIONE
Qual è l’attività più dispendiosa e meno produttiva di un programmatore di applicazioni
desktop?
È il disegno della GUI.
Qual è la tecnologia che permette la massima flessibilità in campo di disegno della GUI?
È l’HTML arricchito dai CSS (Cascading Style Sheet) e da JavaScript.
Inoltre, ogni applicazione è sviluppata partendo dal concetto che i dati sono residenti su un
file o su un DB al quale l’applicazione accede direttamente.
Con Internet, invece, c’è la necessità di rendere disponibili all’esterno i dati del DB in modo
da essere consumati da applicazioni esterne.
Per standardizzare la distribuzione dei dati sono stati progettati protocolli di comunicazione
standard tra piattaforme eterogenee.
Quindi le applicazioni non accedono più direttamente ad un DB ma a dei servizi in rete:
LAN, intranet, extranet e Internet che espongono i dati del DB.
Accedere ai dati via rete è completamente diverso dall’accedere ad un DB locale.
Per recuperare i dati da un DB si usa una sola tecnica e si ha un solo formato: ADO.NET
(ActiveX Data Objects) che astrae la complessità di ogni DB.
Per recuperare i dati via rete si hanno a disposizione i protocolli di comunicazione TCP,
HTTP, UDP e WebSocket e i formati di serializzazione HTML, JSON, RSS e XML.
WEBBROWSER
Permette l’interazione tra applicazione host e il controllo WebBrowser, in pratica consente
di utilizzare, in tutto o in parte, una perfetta GUI web-like in modo semplice ed efficace.
Il controllo espone le seguenti proprietà.
CanGoBack
Determina se sia possibile tornare indietro nella cronologia.
CanGoForward
Determina se sia possibile andare avanti nella cronologia.
Document
Un oggetto di tipo HtmlDocument contenente tutte le informazioni sulla pagina.
DocumentStream
Permette di leggere la pagina web come da un file, restituisce un oggetto
System.IO.Stream.
DocumentText
Restituisce o imposta il codice della pagina, dopo aver caricato una pagina, contiene il suo
codice HTML, modificando questa proprietà, anche la pagina visualizzata sarà rielaborata
e ricaricata di conseguenza.
DocumentTitle
Il titolo del documento.
DocumentType
TPSIT 5
434
um 214 di
Il tipo del documento.
GoBack
Torna indietro alla pagina precedente.
GoForward
Procede alla pagina successiva.
GoHome
Ritorna alla pagina iniziale, per ottenere l’indirizzo di quest’ultima, ricercare nel registro di
sistema le preferenze che l’utente ha impostato per il browser predefinito.
GoSearch
Si reca alla pagina di ricerca predefinita.
IsBusy
Indica se il controllo sta caricando un nuovo documento.
IsOffline
Indica se il controllo è in modalità offline, sta processando pagine web su disco fisso.
IsWebBrowserContextMenuEnabled
Determina se sia attivo il menu contestuale predefinito per il browser.
Navigate(p)
Apre la pagina referenziata dall’indirizzo URL p.
ObjectForScripting
Consente di esporre un oggetto al codice JavaScript della pagina contenuta.
Print
Stampa il documento aperto con i settaggi impostati della stampante corrente.
ReadyState
Restituisce lo stato del controllo, l’enumeratore può assumere cinque valori.
1. Complete: pagina completa.
2. Interactive: le parti della pagina caricate sono sufficienti a garantire un minimo di
interazione con l’utente.
3. Loaded: il documento è caricato e inizializzato ma non tutti i dati sono ancora stati
ricevuti.
4. Loading: il documento è in caricamento.
5. Uninitialized: nessun documento è stato aperto.
ShowPageSetupDialog
Visualizza le impostazioni pagina con una finestra di dialogo Internet Explorer.
ShowPrintDialog
Visualizza la finestra di stampa d’Internet Explorer.
ShowPrintPreviewDialog
Visualizza l’anteprima di stampa in una finestra Internet Explorer.
TPSIT 5
434
um 215 di
ShowPropertiesDialog
Visualizza la finestra delle proprietà pagina come Internet Explorer.
ShowSaveAsDialog
Visualizza la finestra di dialogo di salvataggio d’Internet Explorer.
URL
Restituisce un oggetto URI rappresentante l’indirizzo della pagina caricata.
Version
La versione d’Internet Explorer installata.
Ci sono molti browser disponibili ma l’unico modo di far fare ad un’applicazione
quello che si vuole nel modo che si vuole è di scriversela!
Esempio, progettare un browser, caricare al proprio interno una pagina web e
visualizzarla.
Trascinare sul form il controllo WebBrowser, una volta posizionato si mostra come un’area
bianca perchè non contiene ancora alcuna pagina e aggiungere anche i controlli seguenti.
Inserire un ToolStrip.
Tutti i pulsanti seguenti sono di default disattivati, poiché all’inizio non è caricata alcuna
pagina e di conseguenza non si può effettuare alcuna operazione.
btnBack: per andare indietro.
btnForward: per andare avanti.
btnRefresh: per aggiornare la pagina.
btnVai: per avviare il caricamento della pagina.
txtUrl: per contenere l’indirizzo cui recarsi.
Public Class Form1
Private Sub Form1_Resize(ByVal sender As Object, ByVal e As System.EventArgs)
Handles Me.Resize
' per modificare in modo dinamico la dimensione del browser nella mia finestra
Dim altezza As Integer = Me.Height
Dim lunghezza As Integer = Me.Width
altezza -= 100
lunghezza -= 20
wbBrowser.Height = altezza
wbBrowser.Width = lunghezza
End Sub
Private Sub txtUrl_KeyDown(ByVal sender As System.Object, ByVal e As
System.Windows.Forms.KeyEventArgs)
' quando si preme invio durante la digitazione, naviga alla pagina indicata
TPSIT 5
434
um 216 di
If e.KeyCode = Keys.Enter Then
wbBrowser.Navigate(txtUrl.Text)
' poiché s'inizia a navigare, si ferma il caricamento, quindi attiva btnCancel
btnCancel.Enabled = True
End If
End Sub
Private Sub btnCancel_Click(ByVal sender As System.Object, ByVal e As
System.EventArgs)
' ferma l'attività del browser
wbBrowser.Stop()
btnCancel.Enabled = False
btnRefresh.Enabled = True
End Sub
Private Sub wbBrowser_DocumentCompleted(ByVal sender As System.Object, ByVal e
As System.Windows.Forms.WebBrowserDocumentCompletedEventArgs) Handles
wbBrowser.DocumentCompleted
' l'evento DocumentCompleted si verifica quando una pagina è stata completamente
' caricata, se anche una sola delle parti della pagina non è completa, l'evento non è
' generato, per evitare brutte soprese, potete utilizzare l'evento Navigated che si
' verifica dopo la navigazione indipendentemente dal successo o meno dell'operazione
btnCancel.Enabled = False
btnBack.Enabled = wbBrowser.CanGoBack
btnForward.Enabled = wbBrowser.CanGoForward
btnRefresh.Enabled = True
End Sub
Private Sub wbBrowser_Navigating(ByVal sender As System.Object, ByVal e As
System.Windows.Forms.WebBrowserNavigatingEventArgs) Handles
wbBrowser.Navigating
btnCancel.Enabled = True
' la proprietà StatusText contiene in forma leggibile
' un resoconto dell'operazione che il controllo sta svolgendo
lblStatus.Text = wbBrowser.StatusText
End Sub
Private Sub wbBrowser_Navigated(ByVal sender As System.Object, ByVal e As
System.Windows.Forms.WebBrowserNavigatedEventArgs) Handles
wbBrowser.Navigated
' l'evento Navigating si genera prima della navigazione
btnCancel.Enabled = False
btnBack.Enabled = wbBrowser.CanGoBack
btnForward.Enabled = wbBrowser.CanGoForward
btnRefresh.Enabled = True
lblStatus.Text = "Pagina caricata"
End Sub
Private Sub btnRefresh_Click(ByVal sender As System.Object, ByVal e As
System.EventArgs) Handles btnRefresh.Click
wbBrowser.Refresh()
End Sub
Private Sub btnBack_Click(ByVal sender As System.Object, ByVal e As
System.EventArgs) Handles btnBack.Click
wbBrowser.GoBack()
End Sub
Private Sub btnForward_Click(ByVal sender As System.Object, ByVal e As
TPSIT 5
434
um 217 di
System.EventArgs) Handles btnForward.Click
wbBrowser.GoForward()
End Sub
Private Sub btnVai_Click(ByVal sender As System.Object, ByVal e As
System.EventArgs) Handles btnVai.Click
If txtUrl.Text <> "" Then
wbBrowser.Navigate(txtUrl.Text)
Else
MessageBox.Show("Inserire l'indirizzo della pagina web.", "Errore!",
MessageBoxButtons.OK, MessageBoxIcon.Error)
End If
End Sub
End Class
Progettare un’applicazione che indica se la connessione di rete è attiva o meno.
Tutte le volte che lo stato della connessione di rete è modificato, è generato l’evento
Computer.Network.NetworkAvailabilityChanged.
La logica che aggiorna il pannello di stato è inclusa nel metodo SetConnectionStatus
dell’oggetto MainForm.
TPSIT 5
434
um 218 di
Startup
Generato all’avvio dell’applicazione, prima della creazione del form di avvio.
Shutdown
Generato dopo la chiusura di tutti i form dell'applicazione.
L’evento non è generato se l’applicazione termina in modo anormale.
UnhandledException
Generato se l’applicazione rileva un’eccezione non gestita.
StartupNextInstance
Generato quando si avvia un’applicazione a istanza singola che è già attiva.
NetworkAvailabilityChanged
Generato all’attivazione o alla disattivazione della connessione di rete, aggiorna lo stato di
connettività della rete nel form principale ogni volta che lo stato della connessione cambia.
All’avvio dell’applicazione, impostare lo stato della connessione nel controllo StatusStrip,
quindi aggiornarlo per indicare lo stato corrente della connessione.
TPSIT 5
434
um 219 di
WEBCLIENT
INTRODUZIONE
Nel protocollo TCP occorre realizzare una specifica controparte, il “server”, con la quale
instaurare una connessione per l’invio e la ricezione dei dati.
Con i protocolli HTTP, FTP o SMTP, la parte “server” è eseguita dal server web, per
esempio IIS, questo servizio integrato con il SO rende facile la distribuzione d’informazioni
pubbliche attraverso la rete Internet.
Un server web ben configurato e collegato alla rete, infatti, è accessibile da ogni client che
ne esegua specifiche richieste, per esempio, attraverso la porta 80, riservata proprio al
protocollo http, con il browser si ha la possibilità di consultare le informazioni presenti sul
server con il linguaggio HTML.
Per queste operazioni di base si ha a disposizione la classe WebClient che fornisce
metodi comuni per l’invio o la ricezione di dati da qualsiasi risorsa locale, rete intranet o
Internet identificata da un URI.
La classe WebClient utilizza la classe WebRequest per fornire l’accesso alle risorse.
Le istanze di WebClient possono accedere ai dati con qualsiasi classe WebRequest
discendente registrata con il metodo WebRequest.RegisterPrefix.
Esempio, leggere il contenuto di un file di testo in un sito web.
WebClient
File MAINWINDOW.XAML.CS
Il metodo OpenReadTaskAsync accede ad una risorsa presente in un sito web, apre uno
stream di dati e recupera il testo corrispondente.
using System;
using System.Windows;
TPSIT 5
434
um 220 di
using System.Net;
using System.IO;
namespace WebClient1 {
public partial class MainWindow : Window {
public MainWindow()
{ InitializeComponent(); }
private async void btnAvanti_Click(object sender, RoutedEventArgs e)
{ var client = new WebClient();
var dataStream = await client.OpenReadTaskAsync(new UriBuilder("http", "localhost",
1503, txbNome.Text).Uri);
var reader = new StreamReader(dataStream);
var data = await reader.ReadToEndAsync();
txbTesto.Text = data;
}
private async void btnsalva_Click(object sender, RoutedEventArgs e)
{ var client = new WebClient();
// invia il testo nella TextBox allo stream aperto con OpenWriteTaskAsync
var dataStream = await client.OpenWriteTaskAsync(String.Format(@"C:\{0}",
txbNome.Text));
var str = new StreamWriter(dataStream);
await str.WriteAsync(txbTesto.Text);
str.Close();
dataStream.Close();
}
}
}
File MAINWINDOW.XAML
<Window x:Class="WebClient1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="WebClient"
Height="200"
Width="300" WindowStartupLocation="CenterScreen">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<StackPanel Orientation="Horizontal">
<TextBox Height="23"
x:Name="txbNome" Text="Testo.txt" Width="180" />
<Button Content=">>"
Height="23"
x:Name="btnAvanti" Click="btnAvanti_Click" Width="35" />
<Button Content="Salva"
Height="23"
x:Name="btnSalva" Click="btnsalva_Click"
Width="35" />
</StackPanel>
<TextBox Grid.Row="1"
x:Name="txbTesto" />
TPSIT 5
434
um 221 di
</Grid>
</Window>
WebApplication1
Si utilizza il sito web del .NET Framework per rendere l’esempio più facilmente eseguibile,
senza dover effettuare particolari configurazioni.
File TESTO.TXT
File UPLOAD.ASPX.CS
Recupera lo stream in ingresso con la proprietà InputStream dell’oggetto Request, di tipo
HttpRequest e scrive sul file system con il metodo OpenWrite di FileStream.
Con il metodo Write dell’oggetto Response si scrive il testo del messaggio di risposta, per
recuperarlo nell’applicazione.
using System;
using System.Web;
using System.IO;
namespace WebApplication1 {
public partial class Upload : System.Web.UI.Page {
protected void Page_Load(object sender, EventArgs e)
{ if (this.Request.QueryString["path"] != null) {
string _physicalApplicationPath =
HttpContext.Current.Request.PhysicalApplicationPath;
string _path = string.Format(@"{0}\{1}", _physicalApplicationPath,
Convert.ToString(this.Request.QueryString["path"]));
using (FileStream stream = File.OpenWrite(_path))
{ byte[] dataByte = new byte[1024];
int i = 0;
TPSIT 5
434
um 222 di
do {
i = this.Context.Request.InputStream.Read(dataByte, 0, 1024);
if (i > 0)
stream.Write(dataByte, 0, i);
} while (i > 0);
}
this.Response.Clear();
this.Response.Write("OK");
this.Response.End();
}
else {
this.Response.Clear();
this.Response.Write("Errore!");
this.Response.End();
}
}
}
}
File UPLOAD.ASPX
<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Upload.aspx.cs"
Inherits="WebApplication1.Upload" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
<title></title>
</head>
<body>
<form id="form1" runat="server">
<div></div>
</form>
</body>
</html>
TPSIT 5
434
um 223 di
Si è richiamato con successo un file TXT, poichè si tratta di un tipo di risorsa il cui MIME è
mappato e quindi gestito da IIS.
Il registro di mappatura prevede una configurazione predefinita per la quale alcune risorse
entrano nel processo di gestione di ASP.NET, mentre altre sono semplicemente restituite
dal server web sotto forma di risposta.
Oltre a OpenReadTaskAsync, è possibile leggere i dati con i metodi seguenti.
DownloadData
Restituisce direttamente l’array di byte di una risorsa, è un metodo sincrono, ossia blocca
il funzionamento dell’applicazione fino a quando il download non è terminato.
DownloadDataTaskAsync
È la corrispondente modalità asincrona di DownloadData che esegue la richiesta e
svincola il thread dall’attesa della ricezione dei dati.
Le versioni asincrone dei metodi sono riconoscibili dal suffisso Async dopo il nome del
metodo, questi eseguono il download in un thread separato, perciò non interferiscono con
le normali operazioni dell’applicazione e si richiamano usando esattamente gli stessi
parametri delle versioni sincrone.
DownloadFile
Esegue il download di un file remoto su un file locale.
DownloadString
Restituisce direttamente il contenuto della risorsa, eseguendo automaticamente l’encoding
in stringa con la modalità specificata nella proprietà Encoding.
È possibile inviare testi, dati e file con i metodi seguenti.
OpenWrite
Apre uno stream in scrittura con la risorsa remota.
TPSIT 5
434
um 224 di
UploadData
Invia direttamente un array di byte ad una risorsa remota.
UploadFile
Esegue l’upload di un file locale.
UploadString
Invia una stringa ad una risorsa remota, previo encoding dei byte con la modalità
specificata nella proprietà Encoding.
UploadValues
Invia una collezione nome/valore d’invio POST di richieste HTTP.
Ognuno di questi metodi dispone della controparte asincrona che esegue la richiesta e
svincola il thread; l’accesso alle risorse del file system è vincolato alle autorizzazioni
dell’utente con cui si esegue l’applicazione.
La classe WebClient incapsula l’elaborazione con i protocolli applicativi relativi al tipo di
risorsa cui si ha accesso, in particolare, nell’invio di dati con il protocollo HTTP, è
automaticamente utilizzato il metodo POST, qualora non sia specificato come parametro
nei metodi che lo consentono.
Nell’aggiornamento dei dati di questo tipo di risorse, è necessario che il server web
intercetti tali richieste, recuperi lo stream ed esegua l’aggiornamento nel proprio contesto
applicativo.
FTP
INTRODUZIONE
Consente ai client FTP di caricare (upload), scaricare (download), cancellare, rinominare
e spostare file sul/dal server, si basa sul codice ASCII e in particolare ne usa, solo, la
parte inferiore, ASCII a 7 bit.
I servizi FTP sono richiesti al server sulla porta 21 (Porta FTP di comando) e poi il
trasferimento dei file avviene sulla porta 20 (Porta FTP dati).
Le entità coinvolte in una sessione FTP sono due.
1. Il PC remoto usa il client FTP che invia al server le richieste di trasferimento; sono
inclusi nei browser web e nei SO o sono forniti come applicazionii autonome.
2. Un server FTP attende le richieste del client, esistono numerosi server FTP per tutti i
SO e un numero ancora maggiore di client, sia testuali sia grafici.
Il modello del protocollo FTP è connesso ovvero, una volta stabilita la connessione, il
client può eseguire una serie di operazioni sul server prima di effettuare la
disconnessione.
Al server FTP è possibile accedere anche attraverso un browser, in questo caso l’indirizzo
da specificare è del tipo ftp://ftp.miosito.com, con questo tipo di richiesta le informazioni
visualizzate non saranno delle pagine HTML ma dei nomi di cartelle e di file.
In una configurazione client/server FTP, il server è un sistema in cui sono archiviati i file
organizzati in cartelle che attraverso un client FTP possono essere manipolati in remoto.
Sul server FTP sono definiti gli account dei client.
I client dopo aver specificato login e password possono accedere, in R/W, ad una parte
oppure a tutte, dipende dal tipo di account, le cartelle del server.
Su alcuni server, di solito, è definito l’account anonymous con password un indirizzo di
TPSIT 5
434
um 225 di
posta elettronica o guest che contiene le cartelle pubbliche del sito.
Il protocollo FTP, per la trasmissione di dati tra host basato su TCP che ha subìto una
lunga evoluzione negli anni, il primo meccanismo di trasmissione file risale al 1971, fu
sviluppato presso il MIT (Massachusetts Institute of Technology) è ancora utilizzato
soprattutto dagli host providers per garantire ai clienti l’accesso al proprio sito web.
Gli obiettivi principali di FTP sono i seguenti.
 Promuovere la condivisione di file, programmi o dati.
 Incoraggiare l’uso indiretto o implicito di computer remoti.
 Risolvere in maniera trasparente l’incompatibilità tra differenti sistemi di stoccaggio file.
 Trasferire dati in maniera affidabile ed efficiente.
La specifica di FTP non prevede alcuna cifratura per i dati scambiati tra client e server,
questo comprende nomi utenti, password, comandi, codici di risposta e file trasferiti i quali
possono essere visionati in determinate situazioni, per esempio in ambienti intranet.
Per ovviare al problema è stata definita una nuova specifica che aggiunge al protocollo
FTP originale un layer di cifratura SSL/TLS più una nuova serie di comandi e codici di
risposta, il protocollo prende il nome di FTPS (FTP Secure).
CLI
C:\Documents and Settings\Administrator>ftp -?
Trasferisce i file da e verso un computer che dispone di un servizio server FTP
(detto anche daemon). È possibile utilizzare FTP in modo interattivo.
FTP [-v] [-d] [-i] [-n] [-g] [-s:nomefile] [-a] [-w:dimensionifinestra] [-A] [host]
-v
Disabilita la visualizzazione delle risposte del server remoto.
-n
Disabilita l’autoconnessione dopo la connessione iniziale.
-i
Disattiva le richieste interattive durante i trasferimenti di più file.
-d
Attiva il debug.
-g
Disattiva il globbing su nomi file (vedere il comando GLOB).
-s:nomefile Specifica un file di testo contenente comandi FTP; i
comandi vengono eseguiti automaticamente all’avvio di FTP.
-a
Utilizza qualunque interfaccia locale nel binding delle connessioni dati.
-A
Connessione anonima.
-w:dimensionibuffer Prevale sulle dimensioni predefinite del buffer 4096.
host
Specifica il nome o l’indirizzo IP dell’host remoto a cui connettersi.
Note:
- i comandi mget e mput accettano y/n/q per sì/no/chiudi.
- Per annullare i comandi, premere CTRL+C.
I comandi FTP sono i seguenti.
C:\Documents and Settings\Administrator>ftp
ftp> help
I comandi possono essere abbreviati. I comandi sono:
!
delete
literal
prompt
send
?
debug
ls
put
status
append dir
mdelete pwd
trace
ascii
disconnect mdir
quit
type
bell
get
mget
quote
user
binary
glob
mkdir
recv
verbose
bye
hash
mls
remotehelp
cd
help
mput
rename
close
lcd
open
rmdir
ftp>quit
TPSIT 5
434
um 226 di
Esempio, sessione FTP, con interfaccia a caratteri, account utente pippo.
Host: alice.cs.unibo.it.
ftp alice.cs.unibo.it
Connected to alice.cs.unibo.it.
220 alice.cs.unibo.it FTP server (Version 6.2/OpenBSD/Linux-0.10) ready.
Name (alice.cs.unibo.it:pippo): pippo
331 Password required for pippo.
Password:
230- Linux alice 2.2.19pre17 #5 Wed Apr 4 15:05:17 CEST 2001 i686 unknown
230- SCIENZE DELL’INFORMAZIONE - UNIVERSITA’ DI BOLOGNA CS.UNIBO.IT
230- -------------------------------------230- cluster: Linux CS
230- -------------------------------------230-(lm 10.01.2003)
230- -------------------------------------230 User pippo logged in.
Remote system type is UNIX.
Using binary mode to transfer files.
ftp>
A questo punto l’host alice.cs.unibo.it è pronto per l’operazione FTP vera e propria.
Download del file PIPPO.TXT da alice.cs.unibo.it.
ftp> get pippo.txt local: pippo.txt remote: pippo.txt
227 Entering Passive Mode (130,136,2,13,17,206) 150 Opening BINARY mode data
connection for ‘pippo.txt’ (0 bytes).
226 Transfer complete. ftp>
L’operazione inversa, upload, è eseguita usando il comando put come segue.
ftp> put pippo.txt local: pippo.txt remote: pippo.txt
227 Entering Passive Mode (130,136,2,13,17,100) 150 FILE: pippo.txt
226 Transfer complete. ftp>
ARCHITETTURA
Il modello è il seguente.
PI (Protocol Interpreter) è l’interprete del protocollo, utilizzato da client, User-PI e server,
Server-PI, per lo scambio di comandi e risposte, in gergo comune si riferisce ad esso
come “canale comandi”.
DTP (Data Transfer Process) è il processo di trasferimento dati, utilizzato da client, UserDTP e server, Server-DTP, per lo scambio di dati, in gergo comune si riferisce ad esso
come “canale dati”.
FTP utilizza due connessioni separate per gestire comandi e dati.
Un server FTP rimane in ascolto sulla porta 21 TCP cui si connette il client.
La connessione da parte del client determinerà l’inizializzazione del canale comandi
attraverso il quale client e server si scambieranno comandi e risposte.
Lo scambio di dati richiede l’apertura del canale dati il quale può essere di due tipi.
1. In un canale dati di tipo attivo il client apre una porta tipicamente random (1023),
tramite il canale comandi rende noto il numero di tale porta al server e attende che
TPSIT 5
434
um 227 di
esso si connetta, una volta che il server ha attivato la connessione dati al client FTP,
quest’ultimo effettua il binding della porta sorgente alla porta 20 del server FTP, a tale
scopo possono essere impiegati i comandi PORT o EPRT, a seconda del protocollo di
rete utilizzato IPv4 o IPv6.
2. In un canale dati di tipo passivo il server apre una porta tipicamente random (1023),
tramite il canale comandi rende noto il numero di tale porta al client e attende che esso
si connetta, a tale scopo possono essere impiegati i comandi PASV o EPSV, a
seconda del protocollo di rete utilizzato IPv4 o IPv6.
Sia il canale comandi sia il canale dati sono delle connessioni TCP, FTP crea un nuovo
canale dati per ogni file trasferito all’interno della sessione utente, mentre il canale
comandi rimane aperto per l’intera durata della sessione utente, in altre parole il canale
comandi è persistente mentre il canale dati non lo è.
PROGRAMMAZIONE
Per gestire le risorse esposte da un server FTP, si usano le classi FtpWebRequest e
FtpWebResponse che ereditano da WebRequest.
Sono due classi comode da utilizzare ma hanno un limite: utilizzano il modello
disconnesso delle classi per la gestione delle richieste HTTP, in pratica, ad ogni richiesta
FTP, occorre inviare le credenziali di accesso al server e collegarsi.
Occorre instanziare un oggetto FtpWebRequest impostandone modalità di connessione e
URL, impostare il comando FTP con la proprietà Method dell’oggetto FtpWebRequest.
Grazie alla proprietà Method si specificano le diverse operazioni che si possono compiere
sia con una singola risorsa remota sia nell’intero contesto di una cartella.
Eventualmente scrivere sul flusso dei dati dal client al server recuperando lo stream in
uscita attraverso la funzione GetRequestStream dell’oggetto FtpWebRequest, questa
operazione serve solo per l’upload dei file verso il server.
Recuperare la risposta del server con la funzione GetResponse dell’oggetto
FtpWebRequest che restituisce un oggetto di tipo FtpWebResponse e, attraverso questo,
recuperare il flusso di dati dal server verso il client con il metodo GetResponseStream.
Si possono descrivere queste operazioni con i seguenti membri della classe
WebRequestMethods.Ftp.
AppendFile
DeleteFile
DownloadFile
GetDateTimestamp
GetFileSize
ListDirectory
ListDirectoryDetails
MakeDirectory
PrintWorkingDirectory
RemoveDirectory
Rename
UploadFile
UploadFileWithUniqueName
Esempio, upload di un file.
TPSIT 5
434
um 228 di
File MAINWINDOW.XAML.CS
C’è la possibilità si specificare le credenziali di accesso con la proprietà Credentials di tipo
ICredential grazie all’oggetto NetworkCredential.
In base a queste credenziali, il server FTP può o meno autorizzare le operazioni che si
andranno a eseguire.
using System.IO;
using System.Net;
using System.Windows;
using System.Windows.Media.Imaging;
using Microsoft.Win32;
namespace FtpApplication {
public partial class MainWindow : Window
{ public MainWindow()
{ InitializeComponent(); }
private void btnSfoglia_Click(object sender, RoutedEventArgs e)
{ OpenFileDialog fileDialog = new OpenFileDialog
{ Filter = "immagini (*.jpg)|*.jpg" };
bool? show = fileDialog.ShowDialog();
if (show.HasValue & show.Value) {
using (Stream fileStream = fileDialog.OpenFile())
{ this.txbNome.Text = fileDialog.SafeFileName;
this.txbNome.Tag = fileDialog.FileName;
MemoryStream dataStream = new MemoryStream();
byte[] dataByte = new byte[1024];
int i = 0;
do {
i = fileStream.Read(dataByte, 0, 1024);
if (i > 0)
dataStream.Write(dataByte, 0, i);
}
while (i > 0);
dataStream.Seek(0L, SeekOrigin.Begin);
BitmapImage bmpImage = new BitmapImage();
bmpImage.BeginInit();
bmpImage.StreamSource = dataStream;
bmpImage.EndInit();
this.imgImmagine.Source = bmpImage;
}
}
}
TPSIT 5
434
um 229 di
private async void btnInvia_Click(object sender, RoutedEventArgs e)
{ using (Stream fileStream = File.OpenRead(this.txbNome.Tag.ToString()))
{ var request =
(FtpWebRequest)WebRequest.Create(string.Format("ftp://127.0.0.1/{0}",
this.txbNome.Text));
request.Method = WebRequestMethods.Ftp.UploadFile;
request.Credentials = new NetworkCredential("username", "password");
Stream requestStream = await request.GetRequestStreamAsync();
byte[] sendBuffer = new byte[1024];
int bytesRead = 0;
do {
bytesRead = await fileStream.ReadAsync(sendBuffer, 0, 1024);
if (bytesRead > 0)
await requestStream.WriteAsync(sendBuffer, 0, bytesRead);
}
while (bytesRead > 0);
requestStream.Close();
this.txbNome.Text = ((FtpWebResponse)request.GetResponse()).StatusDescription;
}
}
}
}
File MAINWINDOW.XAML
<Window x:Class="FtpApplication.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Applicazione FTP" Height="350" Width="425"
WindowStartupLocation="CenterScreen">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<StackPanel Orientation="Horizontal"
FlowDirection="LeftToRight"
HorizontalAlignment="Center" >
<TextBlock Height="23"
x:Name="txbNome"
Width="180" />
<Button Content="Sfoglia"
Height="23"
Click="btnSfoglia_Click"
x:Name="btnSfoglia"
Width="55" />
<Button Content="Invia"
Height="23"
Click="btnInvia_Click"
x:Name="btnInvia"
Width="55" />
</StackPanel>
<Image Grid.Row="1"
TPSIT 5
434
um 230 di
x:Name="imgImmagine"
Stretch="Uniform" />
</Grid>
</Window>
Progettare un client FTP che utilizza il modello disconnesso per le richieste.
File FTPMANAGER.CS
Per la gestione delle operazioni FTP, il costruttore non fa altro che impostare il valore delle
proprietà.
Tutte le funzioni della classe FTPManager utilizzano un metodo base Connect che crea
una richiesta FTP al server tramite il metodo Create e associa alla richiesta le credenziali
di accesso da inviare al server.
Il metodo Create restituisce un oggetto di tipo WebRequest che si converte in
FtpWebRequest.
Il metodo GetFileList è usato per ottenere la lista dei file presenti in una cartella del server
FTP, restituisce una lista di stringhe in cui saranno presenti i nomi dei file presenti della
cartella scelta, come prima cosa costruire l’URI cui inviare la richiesta.
Eseguire il metodo Connect passandogli come parametro la variabile _Uri.
Impostare, quindi, il metodo della richiesta a ListDirectory in modo da ottenere in risposta
la lista delle cartelle.
Il metodo GetResponse della classe WebRequest fornisce un oggetto di tipo
WebResponse; a questo punto è sufficiente ottenere uno StreamReader tramite il metodo
GetResponseStream e leggerlo tramite un ciclo While.
Il metodo Dowload è usato per scaricare i file in locale.
Creare un FileStream in cui andare a salvare il file scaricato del server FTP.
Impostare, quindi, l’URI ed eseguire il metodo Connect.
Il metodo della richiesta dovrà essere impostato a DownloadFile.
Una volta ottenuto lo stream di risposta, leggere il contenuto e salvare i byte letti nel
FileStream creato in precedenza.
È istanziato un oggetto di tipo FTPManager e eseguito il metodo Download.
Difetti dell’applicazione: il modello disconnesso non è il massimo in fatto di performance e
sicurezza, esempio fare l’upload di cento file su una cartella: si devono effettuare cento
TPSIT 5
434
um 231 di
connessioni e cento disconnessioni invece di una soltanto.
TPSIT 5
434
um 232 di
Progettare un client FTP che vive all’interno di Internet Explorer per uploadare file in
remoto senza scomodare applicazioni di terze parti.
Il problema è quello della pubblicazione di applicazioni web presso provider, dove non si
ha il controllo sui permessi assegnati alle cartelle, il provider, infatti, prevede soltanto una
TPSIT 5
434
um 233 di
cartella nella quale l’utente web ha anche diritti di scrittura: la cartella PUBLIC del sito.
Alcune applicazioni invece richiedono percorsi di scrittura diversi, purtroppo non
parametrizzati, grazie all’API FTP è possibile scrivere non attraverso il file system ma
attraverso l’account FTP che il provider mette a disposizione.
L’applicazione permette di svolgere i seguenti compiti.
 Consentire il browsing delle cartelle di un server FTP.
 Consentire la cancellazione, il download e l’upload di file.
Configurare la cartella virtuale del servizio FTP, con il nome ftp, di IIS puntandolo sul disco
C:\ del PC che quindi risponde all’URL ftp://localhost/ftp.
La maschera di login chiede la URL del server FTP, il nome utente e la password per la
connessione, una volta fornite queste informazioni, l’applicazione web presenta una vista
sulla cartella principale, root, del server FTP.
Cliccando sulle cartelle si apre la cartella inferiore, mentre cliccando sui file si procede al
download, inoltre, nel browser è presente un campo upload per i file da caricare, un link
per cancellare il file, una barra degli indirizzi e un bottone per il logout.
Inserire le classi nella cartella App_Code e usare i file ASPX per la sola logica di
presentazione perché il codice deve risiedere in file separati: modello code-behind.
La cartella App_Code contiene le classi deputate a gestire il colloquio FTP.
File FTPCLIENT.VB
Si crea la classe base che sarà utilizzata dal lato ASP.NET per colloquiare con il server
FTP, sono stati dichiarati quattro campi.
1. Credentials di tipo NetworkCredential che rappresenta le credenziali di autenticazione
presso il server.
2. Port di tipo Integer che rappresenta la porta TCP per la connessione, la 21.
3. Proxy di tipo IWebProxy che rappresenta l’eventuale proxy da utilizzare nella
connessione.
4. Url di tipo Uri che rappresenta l’URL della richiesta FTP.
Tali variabili sono valorizzate nei costruttori di cui sono fornite due versioni.
Nel secondo costruttore, più semplice e di uso più comune, si omette il proxy e si
costruisce direttamente la credenziale a partire da userName e password.
È stata poi aggiunta una funzione, di uso comune a tutti i metodi, per creare la richiesta al
server FTP, il parametro method, rappresenta il comando FTP da inviare al server.
Non è necessario scrivere manualmente la stringa del comando in quanto è possibile
utilizzare le costanti esposte dalla classe System.Net.Web RequestMethods.Ftp.
TPSIT 5
434
um 234 di
I metodi pubblici esposti dalla classe sono i seguenti.
 DeleteFile invia un comando di cancellazione del file.
 DownloadFile scarica un file dal server come flusso.
 List ottiene una lista di oggetti contenuti in una directory del server.
 UploadFile invia un file al server come matrice di byte.
Parsing
L’API FTP ha due comandi per ottenere la lista di oggetti di una cartella nel server.
1. WebRequestMethods.Ftp.List Directory fornisce soltanto il nome degli oggetti senza
dire, ad esempio, se si tratta di file o cartelle.
2. WebRequestMethods.Ftp.ListDirectory Details fornisce la lista comprendente
informazioni su: natura dell’oggetto file o cartella, dimensioni e data di creazione.
Il problema è che la struttura della lista dipende dal server, nei server Unix/Linux la
risposta sarà di un tipo, mentre per i server Windows di un altro tipo.
Per questo motivo si è dovuto creare una classe astratta, FtpListParserBase che poi sarà
ereditata dalle classi deputate ad interpretare la risposta del server.
Si è implementato FtpListParserIIS e FtpListParserUnix ognuna di queste sotto classi deve
avere un metodo pubblico, Match, che, partendo dalla prima linea di testo ricevuta dal
server, determina se è essa a poter effettuare il parsing oppure no.
Il parsing è effettuato con le Regular Expressions, le classi specializzate di
FtpListParserBase si comportano tutte nello stesso modo prendono una matrice di linee di
testo, ne interpretano il contenuto e creano una lista corrispondente di oggetti.
Questi oggetti sono basati su un’altra classe, FtpListItem che raccoglie le informazioni
dalla linea di testo inviata dal server dimensioni, data, nome.
TPSIT 5
434
um 235 di
Tutti gli oggetti FtpListItem sono raccolti dal parser in un insieme FtpList che dispone di
metodi per l’ordinamento e la contestualizzazione degli elementi contenuti.
Il metodo List della classe FtpClient legge le linee trasmesse dal server, sceglie il parser
adatto e restituisce un insieme di oggetti FtpListItem.
Il metodo DeleteFile cancella un file dal server FTP.
Il metodo DownloadFile restituisce un flusso, stream, dei dati provenienti dal server FTP.
Il metodo UploadFile restituisce la stringa contenente il messaggio di ritorno del server,
tuttavia richiede, come parametro la matrice di byte che rappresenta il file da caricare.
Lato Web
La pagina ASP.NET utilizza la classe FTPclient, per l’implementazione è stato utilizzato il
flusso Request Response che è sempre alla base del ciclo della pagina.
Su OnLoad della pagina, quindi, s’innesta tutto il ciclo delle operazioni.
Si controlla se la Request o la Session contengono i valori di autenticazione, se non li
contengono sarà mostrata la maschera di login, altrimenti sarà effettuato il listing
richiamando il metodo.
GetList richiama una serie di metodi definiti nello script della pagina volti a formattare
l’output, anche per richiamare la funzione List di FtpClient si è utilizzato una versione
statica del metodo nella classe FtpClient.
I metodi statici, shared, sono molto comodi perché permettono di richiamare al volo una
funzione senza dover tutte le volte instanziare un nuovo oggetto.
Prima di effettuare il browsing della cartella corrispondente all’URL corrente, OnLoad
dovrà però valutare se tra le operazioni richieste c’è una cancellazione o un upload di file,
queste operazioni sono racchiuse in due metodi.
EvalDelete funziona esaminando se nella Request esiste il valore del che sarà
corrispondente alla URL del file da cancellare, se esiste richiama la funzione statica
DeleteFile di FtpClient.
EvalUpload, invece, valuta se il flusso Request contenga o meno un Post di file ed
eventualmente richiama la funzione statica UploadFile di FtpClient.
File DOWNLOADFILE.ASPX
Per effettuare il download del nuovo file è necessario progettare questa pagina ASP.NET,
alla quale tutti i nomi dei file che compaiono nella tabella saranno linkati.
In pratica in OnLoad si richiama la funzione statica DownloadFile di FtpClient e si legge il
flusso che restituisce scrivendolo nella Response.
Il browser capisce che si tratta di un file da scaricare e non di una pagina da mostrare in
quanto gli si è passato, nella Response, un apposito Header contentdisposition e un
ContentType application/xdownload.
File SHFILEICON.VB
Le icone delle cartelle e dei vari tipi di file, senza dover ricercare i file grafici
corrispondenti, sono state ottenute predisponendo un file che utilizza l’API Win32
richiamando la funzione SHGetFileInfo di SHELL32.DLL del SO, questa consente di
ricavare la bitmap con l’icona del file passandole un nome di file con la relativa estensione,
il file non dev’essere fisicamente presente su disco, basta il nome; la stessa API consente
anche di ottenere le bitmap delle icone utilizzate dal SO per le cartelle.
File ICON.ASPX
Recupera la bitmap dal nome di file passato nella Request e la salva nel flusso Response.
Per ottenere la GIF (Graphics Interchange Format) corrispondente nella pagina è
sufficiente utilizzare ICON.ASPX come src di un TAG HTML img.
TPSIT 5
434
um 236 di
<img src=”icon.aspx?n=file.txt”>
TPSIT 5
434
um 237 di
POP (POST OFFICE PROTOCOL)
INTRODUZIONE
È Il protocollo che consente di ricevere la posta, la gestione è implementata in Internet
attraverso la cooperazione di due categorie di sottosistemi.
1. MUA (Mail User Agent)
È l’applicazione di gestione della posta, per esempio Outlook e Eudora installata sul PC,
deve possedere alcune caratteristiche fondamentali.
 Interfaccia utente per la gestione dei messaggi di posta elettronica, ossia deve
consentire la composizione, la ricezione e la lettura dei messaggi.
 Dev’essere in grado di “colloquiare” con i mail server che provvedono all’invio dei
propri messaggi all’MTA.
 Riconoscere la sintassi di composizione dei messaggi.
2. MTA (Mail Transport Agent)
È il canale di comunicazione tra due MUA, il suo scopo principale è quello di consentire la
ricezione di tutti i messaggi e il loro recapito, può essere.
 Un server SMTP che si occupa della spedizione e della ricezione dell’email da e verso
altri server SMTP.
 Un server POP3 che si occupa di spedire i messaggi ad ogni client.
 Un server IMAP4 che consente la gestione dei messaggi direttamente sul server.
Naturalmente, ognuno di questi servizi è attivo su una porta TCP/IP diversa, ognuno di
essi potrebbe risiedere su di uno stesso server o su server diversi.
Protocollo SMTP
TPSIT 5
434
um 238 di
È usato dai client per inviare la posta al loro server locale che poi decide se la casella
destinataria è locale come il mittente o se la posta va inoltrata ad un altro server, sempre
usando SMTP sulla Porta 25.
Quando un server SMTP invia la posta, un altro server SMTP la riceve e la smista,
dispatch, ad un server POP3 o IMAP.
I messaggi di posta possono anche essere recapitati dopo essere stati incapsulati in altri
protocolli applicativi, quali HTTP, come avviene nei servizi di web mail.
L’utente destinatario si connette al server POP3 e scarica la posta tramite username e
password.
L’indirizzo preciso del server è restituito in un record MX attraverso una ricerca su DNS.
Record MX
È un tipo d’informazione contenuto nel sistema di nomi dei domini DNS che indica come
dev’essere reindirizzata la posta Internet; quando un messaggio è inviato ad un server di
posta, questo esegue una query DNS richiedendo il record MX del destinatario.
I comandi principali di questo protocollo sono i seguenti.
HELO
Dà inizio al colloquio con il server SMTP e serve ad identificare il client, mediante il
parametro Hostname.
MAIL FROM
Inizializza la vera e propria transazione, si deve indicare il mittente, reversepath, cui è
riferita la creazione della mail, un generico formato del comando è MAIL.
RCPT TO
Serve a identificare i destinatari del messaggio.
RSET
Consente di “resettare” le impostazioni correnti dell’email che si sta configurando per
l’inoltro, permettendo di ricominciare con un nuovo messaggio di posta elettronica.
DATA
Lanciato senza parametri, offre la possibilità d’inserire il testo dell’email che dev’essere
necessariamente completato da questa sequenza: <CRLF><CRLF>, se tutto è andato a
buon fine, il server risponde con un messaggio di esito positivo in cui s’indica che il
messaggio è stato accettato per l’invio.
VRFY
Consente di verificare l’email di un qualunque destinatario di posta elettronica.
HELP:
Mostra l’elenco dei comandi accettati dal server.
TPSIT 5
434
um 239 di
QUIT
Chiude il colloquio tra client e server SMTP.
Elenco dei Reply Number ossia i messaggi possibili che il server SMTP restituisce al client
quando quest’ultimo gli invia un qualunque comando.
211 System status, o system help reply.
214 Messaggio di Help.
220 <domain> Servizio pronto.
221 <domain> Servizio ha chiuso il canale.
250 Azione completata, OK.
251 Utente non locale; si spedirà a <forward-path>.
252 Non è possibile verificare la mail ma è comunque accettata.
354 Inizia l’input dei dati; termina con <CRLF>.<CRLF>.
421 Servizio non avviabile.
450 Richiesta di azione non avviabile.
451 Richiesta di azione abortita: errore locale durante il processo.
452 Richiesta di azione abortita: spazio di sistema insufficiente.
500 Errore di sintassi, comando non riconosciuto.
501 Errore di sintassi nei parametri o negli argomenti.
502 Comando non implementato.
503 Cattiva sequenza del comando.
504 Parametri del comando non implementati.
550 Richiesta di azione non presa.
551 Utente non locale; tentare con <forward-path>.
552 Richiesta di azione abortita: si eccede l’allocazione di spazio.
553 Richiesta di azione non presa: nome della mailbox non permesso.
554 Transazione fallita.
Nella nuova versione di SMTP, ESMTP (Extended SMTP), il client che desidera farne uso
invia, all’inizio della comunicazione con il server, la stringa EHLO anziché HELO.
Se il server con cui si sta comunicando supporta ESMTP, la risposta inviata al client sarà
un certo numero di righe con prefisso/codice 250.
Questa risposta è di solito multilinea, poichè ognuna contiene una parola chiave e,
opzionalmente, un argomento che specifica le estensioni SMTP supportate dal server.
In caso contrario, ossia qualora il server non riconoscesse il comando EHLO, invierà al
client un codice di errore.
Esempio di sessione SMTP.
biagio@venus> telnet localhost 25
Trying 127.0.0.1...
Connected to localhost.
Escape character is ‘^]’.
220 venus. Sendmail SMI-8.6/SMI-SVR4 ready at Mon, 21 Aug 2000 11:00:59 +0200
HELO
250 venus. Hello localhost [127.0.0.1], pleased to meet you
mail from: [email protected]
250 [email protected]... Sender ok
rcpt to: davide
250 davide... Recipient ok
data
TPSIT 5
434
um 240 di
354 Enter mail, end with "." on a line by itself
Subject: In Microsoft we trust
Why don’t you?
250 LAA00879 Message accepted for delivery
quit
221 venus. closing connection
Connection closed by foreign host.
La mail ricevuta è la seguente:
biagio@venus> Mail
mailx version 5.0 Tue Jul 15 21:29:48 PDT 1997 Type ? for help.
"/var/mail/biagio": 2 messages 2 new
N 1 bill.gates@microso Mon Aug 21 11:09 15/403 In Microsoft we trust
?1
Message 1:
From [email protected] Mon Aug 21 11:09:35 2000
Date: Mon, 21 Aug 2000 11:08:50 +0200
From: [email protected]
Subject: In Microsoft we trust
Why don’t you?
?
Per testare l’invio di email usare il S/W fakeSMTP (https://nilhcem.github.io/FakeSMTP)
che emula il funzionamento di un server SMTP, basta indicare la porta di ascolto e la
cartella dove salvare l’email.
Protocollo POP3
È usato dai client per scaricare la posta dal loro server locale, sulla porta 110; per default
non conserva la posta anche sul server ma può farlo.
In realtà esistono più versioni di questo protocollo, il numero 3 indica la terza versione.
L’implementazione del protocollo POP3 è più complessa di quella impiegata per SMTP.
Una generica conversazione in POP3 prevede essenzialmente tre fasi distinte.
1. Autenticazione: il client invia al server le proprie credenziali, username e password, per
richiedere l’accesso alla propria casella postale, qualora la risposta del server sia
positiva, quest’ultimo ne concede l’accesso, bloccandone l’utilizzo da parte di altre
applicazioni.
2. Transizione: il client scarica effettivamente i messaggi dalla propria casella postale,
l’utente termina questa conversazione con il comando QUIT.
3. Aggiornamento: il server procede ad effettuare un aggiornamento delle informazioni,
eliminando gli eventuali messaggi contrassegnati per l’eliminazione nella Transaction
Phase, sbloccando la casella postale e, infine, chiudendo la connessione
definitivamente.
Analogamente a quanto accadeva con SMTP, ogni utente che desidera scaricare i propri
messaggi di posta elettronica, deve stabilire una connessione TCP sulla porta 110 del
server POP3.
Subito dopo aver stabilito la connessione, il server si “presenta” ed è pronto ad accettare i
comandi ad esso inviati.
Il protocollo POP3 prevede diversi modi con cui il client può identificarsi ma quello più
utilizzato consiste nell’utilizzo dei comandi USER e PASS.
Attraverso il primo, il client invia al server il nome utente registrato corrispondente ad una
casella postale ben definita.
Se il server accetta il nome allora è possibile spedire anche la password mediante il
comando PASS, seguito dall’opportuna parola d’ordine associata al nome in questione.
TPSIT 5
434
um 241 di
A questo punto, avvenuta l’autenticazione, il client ha accesso alla propria casella postale
e può, quindi, effettuare operazioni su di essa.
Ha inizio la seconda fase del collegamento, Transaction Phase, durante lo scambio delle
informazioni tra client e server, è possibile osservare due soli possibili indicatori di stato
che, posti all’inizio di ogni riga, identificano immediatamente la risposta del server, oltre ad
un’azione richiesta dal client: +OK e –ERR.
Una volta entrati nella fase di transazione, il server assegna a ogni messaggio un
identificativo numerico partendo dal numero uno, mentre il client può iniziare, a sua volta,
la spedizione delle proprie richieste utilizzando una serie di comandi a sua disposizione.
I comandi principali di questo protocollo sono i seguenti.
RETR n
Questo comando, seguito dal numero di messaggio, consente di scaricare una generica
email e leggerla.
STAT
Restituisce il numero di messaggi presenti nella propria casella postale e la dimensione
totale della stessa.
LIST
Restituisce l’elenco dell’email contenute all’interno della casella postale, numerate in
ordine progressivo e la dimensione di ciascuna.
NOOP
Restituisce semplicemente +OK, controlla lo “stato” del server per assicurare che esso sia
ancora raggiungibile e in ascolto.
DELE n
Contrassegna per la cancellazione un determinato messaggio.
RSET
Reimposta il contrassegno che identifica, per ogni email, la richiesta di eliminazione dal
server.
TOP n
Intestazione di un determinato messaggio.
PAS password
Invio della password.
USER username
Invio del nome utente.
Protocollo IMAP
È tipo POP3 ma mantiene la posta scaricata anche sul server, risponde alle richieste
indirizzate dai client alla porta 143.
Supporre che due colleghi di lavoro devono accedere alla stessa casella di posta, ad
esempio quella relativa all’assistenza clienti.
Se il primo collega scaricasse la posta in locale il secondo collega non troverebbe alcun
messaggio sul server.
Se entrambi decidono di lasciare la posta sul server sarebbe impossibile sapere chi dei
due ha risposto ad un messaggio, a meno che non lo si comunichi verbalmente l’uno
TPSIT 5
434
um 242 di
all’altro.
Allo stesso modo non rimarrebbe traccia della risposta, quindi nessuno dei due potrebbe
sostituire l’altro in caso di emergenza.
IMAP4 è un protocollo che nelle intenzioni doveva sostituire POP3 e fornire delle soluzioni
anche per questo tipo di problema, prevede che la posta rimanga centralizzata sul server
e prevede che i client che vi accedano possano condividere le informazioni relative alla
posta elettronica.
Se uno dei due marca un messaggio come “risposto” l’altro ne avrà visione.
Si tratta di un protocollo più lento, complesso e macchinoso di POP3.
Si tratta di un metodo utile da utilizzare in reti aziendali che svolgano tramite posta
elettronica anche funzioni di gestione della produzione.
Formato MIME
Inizialmente il protocollo per la rappresentazione dei documenti di posta elettronica
specificava il formato per i messaggi di posta, limitandosi a messaggi esclusivamente di
tipo testo ASCII, senza alcun riferimento ad altri formati.
Una delle principali limitazioni del protocollo risiede nel fatto che il contenuto dei messaggi
è limitato ai soli caratteri codificati in sette bit.
Quest’imposizione obbliga, prima dell’invio, a convertire ogni messaggio che non contenga
esclusivamente semplice testo ASCII.
Per ovviare a questo inconveniente fu introdotto lo standard MIME.
 Definizione del formato di messaggi testuali ASCII e non.
 Definizione del formato di messaggi multimediali contenenti video, suono, immagini.
Il MIME e, nello specifico, S/MIME, è stato decisivo per l’implementazione dei servizi di
sicurezza che offrono all’utente la possibilità d’inviare messaggi corredati di firma digitale,
di criptografia o di autenticazione.
Il documento è costituito “semplicemente” da una stringa di testo formata da due parti
distinte, separate da una linea vuota.
1. Header
Racchiude in sé le informazioni per il trasporto della mail, può essere così schematizzato.
TO: indica la lista dei destinatari.
FROM: mittente della mail.
CC: lista dei destinatari per conoscenza.
BCC: lista nascosta di destinatari per conoscenza.
DATE: data dei spedizione della mail.
REPLY-TO: indirizzo alternativo del mittente cui inviare le risposte.
SUBJECT: oggetto del messaggio.
2. Body
Con lo standard MIME è possibile inserire, in una qualsiasi email, oltre al testo, anche file
contenenti immagini, segnali audio e video.
In particolare, è bene ricordare che il S/W che dovrà gestire la posta non deve
preoccuparsi del contenuto del messaggio, poichè è l’utilizzatore finale a dover applicare
l’opportuna decodifica in base alle “specifiche di tipo“ inserite nel messaggio stesso.
TPSIT 5
434
um 243 di
Un documento MIME contiene una testata in cui si trovano i seguenti campi.
MIME version
Identifica la versione dello standard MIME usato nell’email.
Content-Transfer-Encoding
Specifica un modo di codifica dei dati accessorio a quello principale, i possibili valori che
può assumere questo attributo sono 7 bit, 8 bit, binary, quoted-printable e base64.
Content-Type
Consente di specificare il tipo e il sottotipo di dati contenuti all’interno del messaggio,
MIME type, attraverso esso, il client che riceve l’email è in grado di rilevare in che maniera
sono stati codificati i dati ricevuti, questo attributo, insieme a quello precedente,
identificano la parte fondamentale di questo standard e, nel contempo, l’aspetto più
complesso da gestire.
Content-ID
Identifica il messaggio in modo univoco, questo attributo è opzionale.
Content-Description
Rappresenta una descrizione testuale del contenuto del messaggio, questo attributo è
opzionale.
Codifica UUENCODE
Un mail client qualunque consente di aggiungere degli allegati al messaggio, permettendo
anche di salvarli una volta ricevuta la mail.
Ciò, ovviamente, significa anche che non è possibile inserirlo “immediatamente” all’interno
del messaggio poiché una generica email è costituita “necessariamente” da testo puro.
Un modo per risolvere il problema è quello di applicare al file un algoritmo tale da
convertirlo in una forma che possa essere inclusa all’interno della struttura dell’email ma
che non “interferisca” con il testo stesso.
L’algoritmo che consente di ottenere tutto questo esiste ed è definito con il nome
UUENCODE e presuppone che il file d’input sia di tipo binario.
Stabilito questo, estrae dal file binario 3 byte per volta e li converte in 4 caratteri ASCII.
Algoritmo.
 Considerare i 3 byte come un’unica parola da 24 bit.
 Prendere 6 bit alla volta creando 4 nuovi byte.
 Aggiungere 32 ad ogni nuovo byte per ottenere un carattere di testo in formato ASCII.
Il metodo è semplice e sicuramente efficiente poiché consente di ottenere, al termine di
questa elaborazione, un file contenente soltanto caratteri di testo, anche se un pò più
grande di quello originario.
Esempio, un messaggio decodificato secondo questa tecnica.
begin 664 Allegato.txt
%3TLN+BX@
‘
end
Il valore 664 rappresenta un codice che consente d’indicare espressamente l’utilizzo della
codifica Base64.
TPSIT 5
434
um 244 di
DIVERSI TIPI DI APPLICAZIONI
Applicazioni abilitate alla posta
La funzione principale non è specificatamente relativa all’email ma contiene alcuni servizi
di messaggistica di posta elettronica, spesso in maniera trasparente per l’utente.
Per esempio, un’applicazione che controlla macchine utensili e invia per email le
informazioni che si riferiscono alla loro efficienza.
Applicazioni di posta
La funzione principale è l’email.
Applicazioni sensibili alla posta
La funzione principale non riguarda l’email, per esempio un elaboratore di testi permette di
comporre un documento e d’inviarlo per posta ad altre persone.
Microsoft Visual C#
MailReader
È un protocollo che garantisce l’accesso ad account di posta elettronica tramite user e
password.
Il server funziona implementando un Listener TCP che resta, in genere, in attesa sulla
porta 110.
I client comunicano con il server inviandogli dei comandi.
 USER Nuome_Utente: invia la username per la connessione.
 PASS Password_Utente: invia la password.
 LIST: legge la lista dei messaggi su server e restituisce ID e dimensione.
 RETR Id_Messaggio: restituisce il contenuto del messaggio.
 DELE Id_Messaggio: cancella un messaggio.
 QUIT: esce.
Implementare un client di posta elettronica, dev’essere capace d’instaurare una
connessione con un server POP, inviargli dei comandi e leggere le risposte.
Fare clic su File/Nuovo/Progetto… (CTRL+N), nella finestra Nuovo progetto.
File POP3CLIENT.CS
È la classe che si occupa della comunicazione con il server di posta Pop3Client, eredita
da TcpClient.
Definisce le proprietà per gestire la connessione, inviare messaggi al server e leggerne le
TPSIT 5
434
um 245 di
risposte.
La classe TcpClient fornisce dei metodi per creare un client TCP.
Per funzionare ha bisogno di avere un server TCP in ascolto per collegarsi al quale può
utilizzare il metodo Connect.
Fornisce, inoltre, il metodo GetStream per ottenere un NetworkStream da cui poter
leggere i dati ricevuti dal server.
Metodi.
 Connect: effettua la connessione.
 Disconnect: effettua la disconnessione.
 ListMessages: restituisce la lista messaggi sul server.
 ReadMessage: legge un singolo messaggio.
 DeleteMessage: cancella un messaggio.
Tutti i metodi della classe utilizzano le seguenti funzioni per comunicare con il server di
posta.
 SendServerMessage: invia un messaggio al server.
 ReadServerResponse: riceve la risposta.
La funzione ListMessages recupera la lista dei messaggi presenti in una casella di posta.
Il metodo SendServerMessage invia dei messaggi contenenti dei comandi al server.
Riceve in ingresso un messaggio in forma di stringa, lo codifica in un array di byte con il
metodo GetBytes e lo invia tramite un NetworkStream al server.
Lo stream è ottenuto con il metodo GetStream della classe System.Net.Sockets.TcpClient
da cui la classe eredita.
Il metodo ReadServerResponse legge i messaggi inviati dal server in risposta alle
richieste.
Ottiene dal server uno stream tramite il metodo GetStream, utilizza, quindi, un ciclo While
per leggere losStream con il metodo Read della classe Stream e restituire una stringa
contenente la risposta.
File POP3MESSAGE.CS
Si definisce la struttura di un messaggio POP3 tramite la classe Pop3Message, questa ha
delle proprietà che definiscono il numero del messaggio, il mittente, la data di ricezione.
Il costruttore non fa altro che valorizzare le proprietà del messaggio.
Metodi.
 GetFrom: restituisce il mittente.
 GetFromName: restituisce il nome del mittente.
 GetDate: restituisce la data.
 GetSubject: restituisce l’oggetto.
 GetBody: restituisce il corpo.
File MAINFORM.CS
using System;
using System.Collections.Generic;
using System.Windows.Forms;
namespace MailReader {
public partial class MainForm : Form {
private List<Pop3Message> MessageList;
public MainForm()
{ InitializeComponent(); }
private void btn_test_Click(object sender, EventArgs e)
{ try {
Pop3Client pop = new Pop3Client(txt_popServer.Text, txt_userName.Text,
txt_password.Text, Convert.ToInt32(txt_port.Text));
TPSIT 5
434
um 246 di
try {
pop.Connect();
if (pop.Connected)
MessageBox.Show("Connesso.");
else
MessageBox.Show("Non connesso!");
}
finally
{ if (pop.Connected)
pop.Disconnect();
}
}
catch (Exception ex)
{ MessageBox.Show("Errore: " + ex.Message); }
}
private void btn_readMessages_Click(object sender, EventArgs e)
{ try {
dgv_messages.DataSource = null;
dgv_messages.AutoGenerateColumns = false;
Pop3Client pop = new Pop3Client(txt_popServer.Text, txt_userName.Text,
txt_password.Text, Convert.ToInt32(txt_port.Text));
try {
pop.Connect();
if (pop.Connected) {
MessageList = pop.ListMessages();
dgv_messages.DataSource = MessageList;
}
}
finally
{ if (pop.Connected)
pop.Disconnect();
}
}
catch (Exception ex)
{ MessageBox.Show("Errore" + ex.Message); }
}
private void dgv_messages_CellClick(object sender, DataGridViewCellEventArgs e)
{ if (e.RowIndex > -1) {
long id = Convert.ToInt64(dgv_messages.Rows[e.RowIndex].Cells[2].Value);
if (e.ColumnIndex == 0) {
foreach (Pop3Message msg in MessageList)
if (msg.MessageNumber == id)
rtb_message.Text = msg.Message;
}
else if (e.ColumnIndex == 1) {
Pop3Client pop = new Pop3Client(txt_popServer.Text, txt_userName.Text,
txt_password.Text, Convert.ToInt32(txt_port.Text));
try {
pop.Connect();
if (pop.Connected) {
pop.DeleteMessage(id);
dgv_messages.DataSource = null;
TPSIT 5
434
um 247 di
List<Pop3Message> tmp = new List<Pop3Message>();
tmp.AddRange(MessageList);
foreach (Pop3Message msg in MessageList)
if (msg.MessageNumber == id)
tmp.Remove(msg);
MessageList = tmp;
dgv_messages.DataSource = MessageList;
}
}
finally
{ if (pop.Connected)
pop.Disconnect();
} }
}}
}
}
Mail Sender
Il protocollo SMTP consente d’inviare dei messaggi ad un host, utilizza la porta 25.
Il .NET Framework fornisce il namespace System.Net.Mail che contiene già tutte le classi
e i metodi che servono.
File MAILSENDER.CS
Il metodo SendMessage crea un oggetto di tipo MailMessage, ne imposta le proprietà e
poi lo invia.
Proprietà del messaggio.
 Priority: priorità del messaggio.
 From: mittente di tipo MailAddress.
 ReplyTo: indirizzo di risposta di tipo MailAddress.
 To: lista destinatari di tipo MailAddress.
TPSIT 5
434
um 248 di
 Bcc: lista destinatari in copia nascosta di tipo MailAddress.
 Subject: oggetto.
 Body: corpo.
 Attachments: la lista di allegati di tipo Attachment.
 Headers: una lista di headers inviati con il messaggio coppie chiave-valore.
 IsBodyHtml: se la mail è in formato HTML.
L’invio è eseguito istanziando un oggetto di tipo SmtpClient e invocandone il metodo
Send.
TPSIT 5
434
um 249 di
Progettare un client SMTP che invia e riceve email.
 Selezione di account multipli.
 Invio e ricezione allegati.
 Salvare i dati nel formato XML.
 Il progetto usa la libreria CODERDLL.DLL scritta in C++ Unmanaged Code.
File ACCOUNTXML_DATA.CS
Dati di account.
File ATTACHMENTS.CS
Gestione degli allegati.
File BASECODER.CS
Classe per la gestione della decodifica degli allegati di posta.
File EMAILDATA.CS
Struttura dati di un’email.
File FORM2.CS
Finestra per la composizione della posta.
File LOADMAIL_XMLFILE.CS
Legge e salva i dati di un’email dal file XML.
File POP.CS
TPSIT 5
434
um 250 di
Legge l’email dal server POP3.
File CODERDLL.CPP
extern "C" void _declspec(dllexport) DECODE_BUFFER_TO_FILE(LPCTSTR mBuffer ,
LPCTSTR mFileName )
extern "C" LPTSTR _declspec(dllexport) _cdecl BrowseForFolder()
TPSIT 5
434
um 251 di
TPSIT 5
434
um 252 di
Microsoft Visual Basic
Inviare un messaggio di posta elettronica.
Fare doppio clic sul pulsante Invia, si apre la finestra del codice con la struttura della
routine di gestione dell’evento btnInvia_Click, al suo interno fare clic con il pulsante destro
e selezionare Inserisci frammento di codice… (CTRL+K, CTRL+X).
È inserito il seguente codice.
Imports System.Net.Mail
Dim message As New MailMessage("sender@address", "from@address", "Subject",
"Message Text")
Dim emailClient As New SmtpClient("Email Server Name")
emailClient.Send(message)
Progettare un’applicazione che include un solo form con controlli analoghi a quelli del
client di posta elettronica Microsoft Outlook, quando il form è caricato, è verificato che nel
PC dell’utente il servizio SMTP sia installato e in esecuzione.
La proprietà SmtpServer dev’essere impostata sull’indirizzo IP o sul nome del server in cui
TPSIT 5
434
um 253 di
è in esecuzione il server SMTP, generalmente è il PC localhost 127.0.0.1.
Il Server virtuale SMTP predefinito dev’essere in esecuzione, il servizio può essere in
esecuzione ma il Server virtuale SMTP predefinito in IIS può essere interrotto.
Vi è una differenza tra il servizio stesso e il server effettivo eseguito dal servizio.
Se il Server virtuale SMTP predefinito non è in esecuzione, sul relativo nodo sarà
visualizzata un’icona a forma di X rossa.
TPSIT 5
434
um 254 di
Impostare le limitazioni per l’inoltro, nella finestra fare clic con il pulsante destro del mouse
su Server virtuale SMTP predefinito e scegliere Proprietà.
Nella finestra di dialogo fare clic sulla scheda Accesso, quindi su Inoltro….
Nella finestra di dialogo selezionare la casella Tutti tranne quelli indicati nell’elenco.
È indifferente che la casella di controllo in basso sia selezionata o deselezionata.
Impostare il nome di SmtpServer che può essere uno degli elementi seguenti, secondo le
impostazioni di sicurezza locale.
 Indirizzo IP locale presumendo che il server SMTP del PC locale abbia il diritto
d’inviare messaggi tramite un firewall locale, se presente.
 127.0.0.1, loopback del PC locale.
 Smarthost, il nome o l’indirizzo IP del server di Exchange utilizzato per l’invio di
messaggi.
Utilizzare la gestione strutturata degli errori per tentare d’inviare il messaggio di email e
fornire una notifica all’utente sull’esito positivo o negativo del tentativo.
TPSIT 5
434
um 255 di
TPSIT 5
434
um 256 di
Progettare un plugin per Outlook.
Visual Studio mette a disposizione una serie di strumenti per creare plugin per la suite di
Office, sono i VSTO (Visual Studio Tools for Office).
Outlook conserva i messaggi di posta elettronica in file chiamati file delle cartelle personali
con estensione PST; utilizzando intensivamente la posta elettronica, dopo qualche tempo,
questi file crescono a dismisura, ciò è dovuto ai vari file allegati ai messaggi che sono
salvati anch’essi nel file PST.
Il plugin dovrà dotare Outlook di funzionalità aggiuntive.
1. Estrarre i file allegati dai messaggi e salvarli in una cartella su disco locale o in rete.
2. Cancellare i file fisici allegati al messaggio.
3. Sostituirli, nel messaggio, con dei collegamenti al file precedentemente salvato.
Fare clic su File/Nuovo/Progetto… (CTRL+N), nella finestra Nuovo progetto selezionare
Visual Basic/Office/2007/Componente aggiuntivo per Outlook 2007
È stato creato automaticamente il file THISADDIN.VB che rappresenta il plugin.
TPSIT 5
434
um 257 di
In pratica, si tratta dello scheletro vuoto che gestisce le due fasi principali della vita del
plugin: l’avvio, Startup e la chiusura, Shutdown.
In un plugin queste due fasi servono a integrare la GUI dell’applicazione host, nel progetto
Outlook con gli elementi menu, pulsanti che consentono di richiamare le funzionalità
dell’applicazione.
Inserire due nuove voci al menu strumenti di Outlook.
1. La voce Salva allegati che serve a richiamare la funzionalità principale.
2. La voce Opzioni salvataggio allegati che farà aprire una finestra di dialogo per
consentire l’impostazione della cartella che conterrà gli allegati salvati.
Le voci dei menu File/Modifica sono oggetti di tipo CommandBarPopup contenuti nella
barra del menu che è un oggetto di tipo CommandBar.
Le voci contenute sotto quelle principali sono oggetti di tipo CommandBarButton.
Ogni oggetto CommandBarPopup, CommandBarButton ha un ID identificativo univoco.
Esempio.
30002 - File
30003 - Modifica
30004 - Visualizza
30328 - Vai
30007 - Strumenti
30131 - Azioni
30010 - ?
Occorrerà trovare il riferimento al menu Strumenti, codice 30007, crearvi le due voci di
tipo CommandBarButton e riunire tutto nel metodo AddMenuItems.
File THISADDIN.VB
Richiama AddMenuItems e RemoveMenuItems, all’interno del gestore di avvio.
Così facendo, all’avvio, saranno eliminate e ricreate le voci di menu.
La logica vorrebbe che AddMenuItems fosse contenuto nel gestore dell’evento Startup
mentre RemoveMenuItems in quello dell’evento Shutdown, tuttavia a volte capita che
Shutdown non sia eseguito, rischiando così di trovarsi al successivo riavvio con le voci di
menu duplicate, per cui è consigliabile ricrearle a ogni avvio.
Me.Application rappresenta l’applicazione Office in esecuzione.
La gestione dell’evento clic sulla voce di menu è realizzata impostando un handler che
punta a metodi ButtonMain_Click e ButtonOpt_Click che andranno impostati all’interno
della classe.
Assegnare alle voci di menu dei TAG univoci, menuTag e menuTag2, per poterle poi
ritrovare con il metodo FindControl.
AddMenuItems è il metodo che crea i pulsanti.
RemoveMenuItems è il metodo che rimuove i pulsanti, cerca le due voci, identificate per
mezzo dei TAG e, se le trova, le elimina.
Salvare gli allegati, tutto il processo parte da una lista di messaggi selezionati dall’utente
nella finestra di Outlook.
Tali messaggi saranno esaminati sostituendo gli allegati; in primis, quindi, serve una
funzione che restituisca la lista di messaggi selezionati dall’utente:
GetSelectedItems restituisce un insieme di MailItem corrispondente ai messaggi
selezionati.
SaveAttach è il metodo più importante del plugin.
Se il messaggio non ha allegati il metodo s’interrompe.
Nella cartella dove andranno salvati gli allegati crea, se non esiste, la cartella relativa a
quel mittente, ricavando il nome dal mittente del messaggio.
Scorre l’insieme degli allegati, se un allegato non è un collegamento quindi è un file
TPSIT 5
434
um 258 di
incluso nel file di dati di Outlook, lo salva nella path di destinazione.
Se il salvataggio è andato a buon fine conserva le informazioni del nome da visualizzare,
DisplayName e della path di destinazione in un Dictionary e cancella l’allegato.
Scorre il Dictionary reinserendo gli allegati ma questa volta come collegamenti
OlAttachmentType.olByReference al file salvato.
Il metodo SaveAttach
Il metodo SaveAttach va richiamato per ogni messaggio della lista che si è recuperato,
questo compito è assegnato a SaveAttachs.
ButtonMain_Click è attivato al clic sulla voce di menu Salva allegati che è inserita
automaticamente dal plugin sotto il menu Strumenti di Microsoft Outlook 2007.
Gestione dell’apertura della finestra di dialogo delle impostazioni, al clic sulla voce
Opzioni salvataggio allegati, il plugin apre la Windows Form che consente all’utente di
specificare il percorso dove salvare gli allegati.
Fare clic su Progetto/Proprietà di OutlookAddIn….
Selezionare la scheda Impostazioni, inserire il valore SavePath, impostando l’ambito
come Utente in questo modo il valore sarà modificabile a run-time.
In questo modo,Visual Studio creerà automaticamente tutto il codice necessario per
gestire le impostazioni con il namespace My.
È necessario provvedere a creare un’UI per cambiare le impostazioni.
Creare quindi nel progetto una nuova Window Form scegliendo come modello quello delle
finestre di dialogo.
File FORMOPTIONS.VB
Usando questo plugin si può notare che, sebbene i file allegati ai messaggi siano estratti e
salvati in altra posizione, il file delle cartelle di Outlook resta sempre della stessa
dimensione; per ridurre effettivamente le dimensioni di tale file occorre compattarlo con
l’opzione dell’applicazione.
TPSIT 5
434
um 259 di
Per distribuire il plugin, si usa ClickOnce, fare clci su Compila/Pubblica OutlookAddIn.
TPSIT 5
434
um 260 di
JAVA
Progettare un client CLI che invia email.
File INVIO.JAVA
package posta;
import java.util.Properties;
import java.util.Date;
import javax.mail.*;
import javax.mail.internet.*;
public class Invio {
public static void main(String[] args) throws Exception {
// il server SMTP da impiegare per l’invio
String smtp = "fauser.edu";
// il mittente della mail
String mittente = "\"Massimo Ubertini\" <[email protected]>";
// il destinatario della mail
String destinatario = "\"Massimo Ubertini\" <[email protected]>";
// l’oggetto della mail
String oggetto = "Prova JavaMail";
// il corpo della mail
String corpo = "Ciao!\nQuesta è una prova...";
// acquisisco le proprietà del sistema
Properties props = System.getProperties();
// aggiungo alle proprietà un record per l’host smtp da impiegare
props.put("mail.fauser.edu", smtp);
// creo un oggetto java.mail.Session con le proprietà elaborate
Session session = Session.getDefaultInstance(props, null);
// preparo il messaggio da inviare (javax.mail.internet.MimeMessage)
MimeMessage message = new MimeMessage(session);
// imposto il mittente
message.setFrom(new InternetAddress(mittente));
// imposto il destinatario
message.addRecipient(Message.RecipientType.TO, new
InternetAddress(destinatario));
// imposto l’oggetto
message.setSubject(oggetto);
// imposto la data di invio
message.setSentDate(new Date());
// imposto il corpo della mail
message.setText(corpo);
// invio il messaggio
TPSIT 5
434
um 261 di
Transport.send(message);
// avviso l’utente
System.out.println("Mail inviata!");
}
}
Progettare un client GUI che invia email.
File INVIO.JAVA
package posta;
import java.awt.*;
import java.awt.event.*;
import java.util.*;
import javax.swing.*;
import javax.mail.*;
import javax.mail.internet.*;
public class Invio extends JFrame {
// componenti della GUI
JLabel label1 = new JLabel("Host SMTP:");
JLabel label2 = new JLabel("Mittente:");
JLabel label3 = new JLabel("Destinatario:");
JLabel label4 = new JLabel("Oggetto:");
JTextField text1 = new JTextField("");
JTextField text2 = new JTextField("");
JTextField text3 = new JTextField("");
JTextField text4 = new JTextField("");
JTextArea text5 = new JTextArea("");
JButton button1 = new JButton("Invia");
// un sinonimo di this, per convenienza
Invio myself = this;
// la finestra per far attendere l’utente durante l’invio
SendDialog sendDialog;
// costruttore della classe
public Invio() {
super("Invio");
// imposto le preferenze dei componenti
text1.setPreferredSize(new Dimension(300, text1.getPreferredSize().height));
text2.setPreferredSize(new Dimension(300, text2.getPreferredSize().height));
text3.setPreferredSize(new Dimension(300, text3.getPreferredSize().height));
text4.setPreferredSize(new Dimension(300, text4.getPreferredSize().height));
text5.setLineWrap(true);
text5.setWrapStyleWord(true);
JScrollPane scrollPane = new JScrollPane(text5);
scrollPane.setPreferredSize(new Dimension(400, 300));
// assemblo la GUI
JPanel row1 = new JPanel(new FlowLayout(FlowLayout.RIGHT));
row1.add(label1);
row1.add(text1);
JPanel row2 = new JPanel(new FlowLayout(FlowLayout.RIGHT));
row2.add(label2);
row2.add(text2);
JPanel row3 = new JPanel(new FlowLayout(FlowLayout.RIGHT));
TPSIT 5
434
um 262 di
row3.add(label3);
row3.add(text3);
JPanel row4 = new JPanel(new FlowLayout(FlowLayout.RIGHT));
row4.add(label4);
row4.add(text4);
JPanel north = new JPanel(new GridLayout(4, 1));
north.add(row1);
north.add(row2);
north.add(row3);
north.add(row4);
JPanel south = new JPanel(new FlowLayout(FlowLayout.RIGHT));
south.add(button1);
JPanel all = new JPanel(new BorderLayout());
all.setBorder(new javax.swing.border.EmptyBorder(3, 3, 3, 3));
all.add(north, BorderLayout.NORTH);
all.add(scrollPane, BorderLayout.CENTER);
all.add(south, BorderLayout.SOUTH);
getContentPane().add(all);
// compatto la finestra
pack();
// posiziono al centro dello schermo
Dimension screen = Toolkit.getDefaultToolkit().getScreenSize();
Dimension frame = getSize();
setLocation((screen.width - frame.width) / 2,(screen.height - frame.height)/2);
// aggiungo il listener al pulsante di invio
button1.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
sendDialog = new SendDialog(myself);
sendDialog.sendMessage();
}
});
// alla chiusura della finestra termina il programma
setDefaultCloseOperation(EXIT_ON_CLOSE);
}
// la finestra che fa attendere l’utente durante l’invio dalla posta
private class SendDialog extends JDialog implements Runnable {
// il thread per l’invio della mail
Thread thread;
// costruttore
public SendDialog(JFrame owner) {
super(owner, "Invio in corso...", true);
// assemblo la GUI
JPanel all = new JPanel(new GridLayout(5, 1));
all.setBorder(new javax.swing.border.EmptyBorder(3, 3, 3, 3));
all.add(new JPanel());
all.add(new JLabel("Invio in corso", JLabel.CENTER));
all.add(new JPanel());
all.add(new JLabel("Per favore attendi", JLabel.CENTER));
all.add(new JPanel());
getContentPane().add(all);
// compatto la finestra
pack();
TPSIT 5
434
um 263 di
// posiziono il componente
setLocationRelativeTo(owner);
// non permetto il ridimensionamento della finestra
setResizable(false);
// non chiudere questa finestra su richiesta dell’utente
setDefaultCloseOperation(DO_NOTHING_ON_CLOSE);
}
// il metodo richiamato per invocare l’invio della mail
@SuppressWarnings("deprecation")
public void sendMessage() {
// avvio il thread che cura l’invio della mail
thread = new Thread(this);
thread.start();
// mostro la finestra di attesa
show();
}
// l’effettivo invio della mail
public void run() {
// acquisisco le proprietà del sistema
Properties props = System.getProperties();
// aggiungo alle proprietà un record per l’host smtp da impiegare
props.put("mail.smtp.host", text1.getText());
// creo un oggetto java.mail.Session con le proprietà elaborate
Session session = Session.getDefaultInstance(props, null);
// preparo il messaggio da inviare (javax.mail.internet.MimeMessage)
MimeMessage message = new MimeMessage(session);
try {
// imposto il mittente
message.setFrom(new InternetAddress(text2.getText()));
// imposto il destinatario
message.addRecipient(Message.RecipientType.TO,
new InternetAddress(text3.getText())
);
// imposto l’oggetto
message.setSubject(text4.getText());
// imposto la data di invio
message.setSentDate(new Date());
// imposto il corpo della mail
message.setText(text5.getText());
// invio l’e-mail
Transport.send(message);
// chiudo la finestra d’attesa
dispose();
// avviso l’utente del successo dell’operazione
JOptionPane.showMessageDialog(
myself,"La lettera è stata spedita","Invio eseguito!",
JOptionPane.INFORMATION_MESSAGE);
} catch (Exception e) {
// chiudo la finestra d’attesa
dispose();
// avviso l’utente in caso di errore
JOptionPane.showMessageDialog(
TPSIT 5
434
um 264 di
myself,"Impossibile inviare la lettera.\n" + "Controllare i campi e
ritentare.","Errore!",JOptionPane.ERROR_MESSAGE);
}
}
}
// punto d’ingresso dell’applicazione
public static void main(String[] args) {
Invio mailSender = new Invio();
mailSender.show();
}
}
Progettare un client SMTP che invia e riceve email, inclusi file allegati.
TPSIT 5
434
um 265 di
TPSIT 5
434
um 266 di
SPAM (SPICY PORK AND HAM)
INTRODUZIONE
Sono messaggi pubblicitari, chiamati anche spamming che non sono stati richiesti.
Letteralmente è definito “spalmare” ma l’origine del termine è americano, infatti esiste un
prodotto spalmabile chiamato SPAM, è un impasto di carne di maiale in scatola prodotto
dalla Hormel Foods Corporation, nato nel 1937.
Il termine deriva da una scena dello spettacolo televisivo “Monty Python’s Flying Circus”,
dove una coppia tentava inutilmente di ordinare al ristorante un piatto senza SPAM che
invece la cameriera proponeva sempre.
Per proteggersi da questi messaggi indesiderati sono nati diversi strumenti S/W.
Le tecniche utilizzate per implementare dei sistemi anti SPAM possono riassumersi nelle
tre seguenti categorie.
1. Ricerca di elementi chiave
Il testo del messaggio è analizzato e per ogni caratteristica sospetta è assegnato un
punteggio.
2. White/black list
L’utente costruisce, in modi più o meno automatici, una lista di mittenti conosciuti e
accreditati e/o una lista di mittenti non accreditati.
Il filtro di posta sui messaggi in arrivo farà passare solo i messaggi i cui mittenti sono in
white list, mentre cancellerà quelli in black list.
Restringendo i parametri di filtro si rischia di ottenere falsi positivi, ovvero messaggi
erroneamente indicati come posta spazzatura.
Lato client
Esempio, in Outlook si definiscono delle regole per l’elaborazione automatica dei
messaggi.
Prima si verificano le condizioni, poi le scelte relative alle azioni; quindi, prima di
memorizzare la regola conviene assegnarle un nome.
È possibile impostare più regole.
Alcune condizioni non hanno bisogno d’informazioni aggiuntive, per esempio Il
messaggio contiene allegati, mentre altre richiedono l’impostazione di qualche
parametro.
Per creare una regola, fare clic sul pulsante Regole del gruppo Sposta della scheda
HOME.
Nella finestra che si apre, selezionare la scheda Regole posta elettronica e fare clic su
Nuova regola…, inizia la creazione guidata.
TPSIT 5
434
um 267 di
Lato server
È invisibile all’utente finale, la cui conoscenza è però importantissima per chi gestisce un
mail server o per chi ricevendo molta posta spazzatura desidera fare in modo che allo
spammer sia negato l’utilizzo della rete per fini non legittimi.
L’idea è molto semplice, alcuni siti web raccolgono le segnalazioni degli utenti in relazione
a “denunce” di SPAM.
I server SMTP degli ISP (Internet Service Provider) quando ricevono un’email interrogano
questi siti web e, se il mittente del messaggio non è elencato nelle liste degli spammer,
allora è regolarmente inviato all’utente, viceversa è bloccato.
In sostanza, un utente non riceverà mai i messaggi di SPAM perché questi messaggi
saranno filtrati a livello di server e non a livello utente.
Perché una black list abbia effetto, è necessario che l’elenco dei mittenti sia il più possibile
completo.
Nella maggioranza di questi non è memorizzato il mittente ma l’indirizzo IP del server che
invia il messaggio.
Se il server che invia è inserito in black list e il server di posta che riceve supporta la
gestione della black list, il messaggio è rifiutato.
Le black list non possono essere scaricate integralmente, perché si compongono di
megabyte d’informazioni, l’accesso avviene per interrogazione.
Open Relay
È una funzione dei server SMTP che consente a utenti anonimi d’inviare la posta, una
TPSIT 5
434
um 268 di
protezione è quella di limitare l’invio della posta ai soli utenti che utilizzano un indirizzo IP
sulla stessa rete su cui è posizionato il server SMTP.
L’autenticazione al server SMTP avviene quando l’utente fornisce uno username e una
password per inviare la posta.
Non confondere password e utente per l’invio con quello per la ricezione.
Relay multi-hop
Se gli ISP usano più server SMTP, alcuni per l’invio di posta tra utenti dello stesso dominio
e altri MTA “di confine” usati per inoltrare la posta verso l’esterno, ovviamente, gli MTA di
confine accettano il relay da parte dei server interni.
Se lo spammer ha accesso ad uno dei server interni o se quest’ultimo non è ben
configurato, può inviare messaggi di SPAM tramite il MTA di confine che, pur non facendo
Open Relay, accetta di rispedirli verso l’esterno perché gli sembra che provengano da un
mittente autorizzato.
Dynamic addressing e recapito No Relay
Gli ISP assegnano ai loro clienti indirizzi IP dinamici, questa prassi ha dato agli spammer
un altro modo di aggirare il blocco dell’Open Relay: lo spammer recapita i messaggi di
SPAM direttamente ai server SMTP dei destinatari, usando il suo indirizzo IP dinamico.
Ogni volta che l’indirizzo IP dinamico dello spammer è notato e elencato su una black list,
lo spammer può scollegarsi da Internet, riconnettersi e ricevere un nuovo indirizzo IP
dinamico, il costo di eseguire uno spamming di questo tipo è alto anche per lo spammer,
soprattutto in termini di tempo ma l’inoltro di SPAM con questa tecnica, detta No Relay, è
molto efficace e combatterlo è estremamente difficile.
Connection Sharing e recapito Open Proxy
Molte organizzazioni usano proxy sui loro server connessi a Internet per consentire a altri
PC della loro rete locale di condividere la connessione, se sono mal configurati
permettono a host “parassiti” di attivare connessioni proxy di tipo Open Proxy.
3. Filtri statistici o bayesiani
L’idea iniziale del filtraggio orientato al contenuto fu sfruttare il fatto che la maggior parte
degli spammer inviava a tutti i destinatari una copia dello stesso messaggio.
Anche se in un primo tempo il filtraggio collaborativo fu efficace, ci si accorse subito che gli
spammer potevano aggirarlo usando tecniche di partizionamento e di personalizzazione
delle liste dei destinatari, il List Splitting, in modo da aggiungere ai messaggi di SPAM
delle porzioni variabili dipendenti dal destinatario.
La soluzione bayesiana è basata sullo studio statistico del contenuto dei messaggi.
Un filtro bayesiano decide se un messaggio è SPAM o non in base alle parole contenute
nei messaggi ricevuti da uno specifico utente.
Proposto per la prima volta nel 1998 nel AAAI-98 Workshop on Learning for Text
Categorization e reso popolare da un articolo di Paul Graham nel 2002 “A plan for SPAM”.
Thomas Bayes (Londra, 1702 – Tunbridge Wells, 17 aprile 1761) è stato un matematico e
ministro presbiteriano britannico, deve la sua fama ai suoi studi nel campo della
matematica e della filosofia; è noto soprattutto nella statistica per il suo teorema sulla
probabilità condizionata, pubblicato postumo nel 1763.
Teorema di Bayes
Sia data un’osservazione O, un messaggio contiene la parola “sesso” e un’ipotesi H, un
messaggio è SPAM.
P(O|H), in pratica la probabilità che O accada dato H, ovvero la probabilità che un
TPSIT 5
434
um 269 di
messaggio di SPAM contenga la parola “sesso” è facile da stimare, ad esempio
esaminando la cartella in cui l’utente destinatario mette lo SPAM e contando quanti dei
messaggi che vi si trovano già contengono “sesso”.
Per il futuro, interessa però sapere P(H|O), in pratica la probabilità che H accada, dato O e
in pratica che un messaggio indirizzato a quell’utente e che contiene la parola “sesso” sia
effettivamente SPAM.
Secondo il teorema di Bayes tale probabilità è la seguente.
P (H|O) = P (O|H) * P (H)/P(O)
Dove sia P(H) la probabilità che un messaggio sia SPAM, sia P(O) la probabilità che un
messaggio contenga la parola “sesso” possono essere stimate esaminando le caselle di
posta dell’utente.
P(H) si stima esaminando comparativamente la cartella dove l’utente mette lo SPAM e la
casella di posta generale dell’utente e contando quanti sono i messaggi di SPAM rispetto
al totale dei messaggi.
P(O) si stima contando quanti messaggi contengono “sesso” sul totale dei messaggi
ricevuti dall’utente.
Purtroppo, però, questa tecnica è praticamente impotente contro lo SPAM grafico.
Infatti, molti spammer hanno reagito a queste tecniche evolute di riconoscimento
spostando la parte informativa dei loro messaggi all’interno d’immagini raster, da inviare
poi come allegati MIME o agganciare ai messaggi scrivendoli in formato HTML.
I messaggi ritenuti fidati dal server di posta, dai filtri, sono recapitati al destinatario, mentre
i messaggi sospetti sono eliminati dal server di posta.
Le operazioni di verifica sui messaggi sono applicate sia ai messaggi in ingresso sia ai
messaggi in uscita e possono essere riassunte in sei passi.
1. Verifica della presenza del mittente nella white list.
2. Verifica della presenza del mittente nella black list.
3. Verifica dell’impronta digitale del messaggio.
TPSIT 5
434
um 270 di
4. Verifica mediante filtri euristici.
5. Verifica mediante filtri bayesiani.
6. Verifica mediante antivirus.
I filtri bayesiani eseguono un’analisi dei messaggi che l’utente indica essere posta
spazzatura e costruiscono un dizionario di termini “sospetti”.
Poi analizzano la posta accettata e costruiscono un dizionario di termini “legittimi”.
Sulla base di questi dizionari, sono in grado d’identificare quali messaggi potrebbero
essere spazzatura o meno.
Questa analisi è svolta all’inizio e prende il nome di “allenamento”.
In sostanza, appena dopo aver installato il filtro nell’applicazione di posta, è necessario
passare qualche giorno indicando al sistema quale dei messaggi ricevuti è buono e quale
invece è posta spazzatura.
Quando la fase di allenamento è conclusa, il sistema filtra autonomamente i messaggi,
marcandoli come spazzatura o posta accettata.
Per implementare un filtro bayesiano occore costruire un dizionario che contiene una serie
di parole, con il numero di occorrenze delle stesse passate per la fase di allenamento.
PROGRAMMAZIONE
Il sistema da progettare è costituito da un server contenente le definizioni e da un client il
cui scopo è duplice: scaricare l’email e accedere alle definizioni di segnalazione di quelle
sospette.
Il client filtra l’email in arrivo, sulla base delle definizioni scaricate dal server in formato
XML e marca quelle sospette con la dicitura “SPAM”.
Gli utenti abilitati al servizio segnalano al server, direttamente dal client, eventuali mail
indesiderate, il server immagazzina l’informazione all’interno di un DB che contiene le
segnalazioni di tutti gli utenti registrati.
Ciò che sarà archiviato sul server è solo l’indirizzo del mittente dell’email sospetta.
Qualora lo stesso indirizzo fosse segnalato da più utenti, è incrementato un contatore che
definirà il “rating” dell’indirizzo.
Più il rating è alto, più è probabile che l’email ricevuta sia effettivamente SPAM.
La lista degli indirizzi “sospetti” è scaricata in formato XML dal client di posta che,
confrontando gli indirizzi dei mittenti dell’email in arrivo con quelli contenuti nel file delle
definizioni, può marcare opportunamente quelle riconosciute come SPAM aggiungendo
successivamente il valore del rating.
L’interazione tra il client e il server avviene tramite un WS predisposto ad accettare le
richieste degli utenti abilitati.
Il progetto è una libreria di classi, quindi priva d’interfaccia utente ma il servizio è esposto
attraverso un sito web, per farlo è possibile grazie a WSE 2.
SoapService, usato per realizzare i servizi web, deriva infatti da SoapReceiver che
implementa l’interfaccia I’HttpHandler.
L’indirizzo cui le classi saranno raggiungibili si definisce attraverso un HttpHandler
direttamente nel WEB.CONFIG del progetto web.
Amministrazione del server
Avviene attraverso un tool di gestione separato da installare sul PC dell’amministratore
che fa uso di un secondo servizio web per comunicare con il server.
TPSIT 5
434
um 271 di
Digitare UserName e Password nella form di login e cliccare sul relativo pulsante.
Una volta autenticati, si può eseguire una qualsiasi delle operazioni per cui il tool è
preposto: inserimento di nuovi utenti, visualizzazione e cancellazione di utenti,
visualizzazione delle segnalazioni di SPAM.
Tutti i metodi pubblici marcati con l’attributo [SoapMethod()] sono descritti nel contratto
WSDL e possono essere utilizzati nei progetti.
Server
CREATE TABLE tblUsers (
ID_Utente int IDENTITY (1,1),
UserName varchar (50),
TPSIT 5
434
um 272 di
Password varchar (50),
EMail varchar (50),
Livello int,
PRIMARY KEY(ID_Utente)
);
CREATE TABLE tblEmail (
ID_Mail int IDENTITY (1,1),
Email varchar (50),
Rating int,
PRIMARY KEY(ID_Mail)
)
INSER INTO tblUsers VALUES ('Admin', 'Admin', 1)
INSER INTO tblUsers VALUES ('User', 'User', 0)
Dopo aver realizzato il DB, il primo problema da affrontare è quello di rendere disponibili i
dati in esso contenuti verso l’esterno.
Il server deve permettere le seguenti operazioni.
1. Segnalare un’email come SPAM.
2. Trasmettere l’elenco degli indirizzi email in formato XML.
3. Registrare nuovi utenti abilitati al servizio.
4. Eliminare utenti dal DB.
Queste operazioni possono essere divise in due famiglie.
1. Gestione utenti.
2. Gestione SPAM.
Si mantiene quindi la stessa distinzione logica anche sul server, realizzando due distinti
WS, per la loro realizzazione si usa il riferimento Microsoft.Web.Services2 che si ottiene
installando nel PC il pacchetto MICROSOFT WSE 2.0 SP3.MSI, perché avendo la
necessità di proteggere l’accesso al servizio, implementa le specifiche WS-Security.
Nel corso di tutto lo sviluppo si utilizzerà una sola soluzione in cui inserire tutti i progetti
che compongono il sistema anti SPAM.
TPSIT 5
434
um 273 di
AntiSpamServerEngine
File SPAMMANAGER.CS
Definizione del servizio che si chiamerà SpamManager.
Il metodo che consente di segnalare un indirizzo di posta da considerare come SPAM si
chiama SegnalaMail e accetta come parametro d’ingresso l’email da segnalare.
L’attributo [SoapMethod("urn:SegnalaMail")] non fa altro che comunicare a SoapService
che il metodo SegnalaMail sarà disponibile via Soap Message.
SoapMethod sostituisce il WebMethod dei servizi web tradizionali.
SpamManagerDB è la classe che astrae la logica di accesso ai dati dal servizio web.
Prima d’inserire l’indirizzo nel DB, bisogna assicurarsi che sia sintatticamente valido con il
metodo IsValidAmail che, usando una Regular Expression, restituisce true se l’indirizzo è
sintatticamente corretto, false in caso di indirizzo errato.
File USERMANAGER.CS
C’è la dichiarazione del [SoapMethod()] e quella del metodo stesso AddNewUser che
accetta come parametro in ingresso un tipo complesso, ovvero un semplice oggetto che
descrive l’utente che si vuole inserire nel DB.
Leggere il SoapContext del messaggio SOAP in arrivo, è un meccanismo per applicare dei
filtri al messaggio SOAP in uscita.
Il primo controllo che si fa è relativo alla presenza o meno di un context.
Le credenziali dell’utente per effettuare il login sono passate appunto nel context.
Il controllo appena effettuato taglia tutte le richieste che non lo hanno.
Il secondo tipo di controllo effettuato ha lo scopo di ciclare all’interno di tutti gli eventuali
context presenti nel messaggio, recuperare il token con le credenziali dell’utente e
verificarne il ruolo.
CustomSecurityTokenManager
Il comportamento di default del sistema di autenticazione si basa sugli account del SO.
Questo vuol dire che è necessario creare tanti utenti sul server quanti sono quelli abilitati
all’utilizzo del servizio.
Questo progetto ha lo scopo di sostituire il meccanismo di autenticazione di default di
WSE 2 con uno sviluppato ad hoc.
Avendo predisposto una tabella nel DB per immagazzinare i dati degli utenti, si validano le
credenziali ricevute direttamente sul DB.
Un progetto separato offre il vantaggio non indifferente di poter sostituire il meccanismo di
autenticazione in qualsiasi momento, senza dover intervenire sul resto della soluzione.
CustomSecurityTokenManager estrae i dati dell’utente che “chiama” il servizio dal
contex.
I dati estratti, nome utente e password, saranno confrontati con quelli contenuti nel DB e,
in caso di riscontro positivo, la chiamata al servizio potrà essere effettuata.
DataLayer
File SPAMMANAGER.CS
La prima cosa da verificare è se l’email è stata segnalata qui; questo si fa richiamando il
metodo EmailCounter passando come parametro l’email che si desidera controllare,
assegnando il valore di ritorno ad una variabile chiamata Rating: se il rating è 0 allora
l’indirizzo non è presente nel DB.
È il caso in cui un indirizzo sia stato segnalato per la prima volta.
TPSIT 5
434
um 274 di
Il metodo AddNewEmail si occupa d’inserire l’email all’interno del DB.
Se invece il rating è maggiore di 0, l’indirizzo passato come parametro al metodo
EmailCounter è già presente nel DB e quindi va incrementato solo il rating.
L’ultima cosa che manca è il download delle definizioni in formato XML.
Il client utilizza un file XML come file di definizione dell’email di SPAM, questo file
dev’essere generato sul server e salvato direttamente sul PC dell’utente.
Per la generazione di questo file si usa la funzione di SQL Server for xml che restituisce
un result set direttamente in formato XML.
Nel DataLayer si ha un metodo chiamato MailList che interroga il DB.
TestClient
File CLASS1.CS
Il result set generato è composto da TAG XML e sarà inviato al client in formato stringa.
Una volta ricevuto dal client, la stringa dev’essere estratta dal messaggio SOAP e
utilizzata per creare l’XML con le definizioni, per poi essere salvato sul disco.
Ora inviare le credenziali dell’utente, per permettere al CustomSecurityTokenManager di
controllarle.
Iniziare col definire il messaggio che sarà di tipo SoapEnvelope.
Il corpo di questo messaggio conterrà l’oggetto userInfo creato sulla base delle
informazioni dell’utente che si vogliono inserire nel DB.
ASService
Usando WSE 2.0 è possibile esporre le classi in svariati modi, si può utilizzare un server
web insieme al protocollo HTTP, oppure si può realizzare un servizio di Windows o
un’applicazione di tipo CLI da interrogare via TCP.
La soluzione usata è quella di utilizzare un sito web, esporlo su un sito dà il vantaggio di
avere un servizio localizzabile attraverso un URL e, lo stesso sito potrebbe essere usato
per promuovere il servizio e far registrare gli utenti.
Creare un progetto chiamandolo ASService, si deve far sì che il sito intercetti le richieste
SOAP in arrivo e le rimandi alle classi del progetto UserMamanager e SpamManager.
TPSIT 5
434
um 275 di
Le classi del progetto AntiSpamServerEngine sono ora accessibili attraverso il sito in
ASService.
File WEB.CONFIG.
Si devono configurare due HTTP handler per reindirizzare le richieste HTTP alle classi.
Usare quindi due nomi fittizi UserManager e SpamManager associandoli all’estensione
ASHX già mappata su IIS.
Questa configurazione permette di girare le richieste alla pagina USERMANAGER.ASHX
alla classe programmo.AntiSpamServerEngine.UserManagerService che rappresenta il
servizio web.
Per rendere attivo il meccanismo di autenticazione personalizzato, si deve aggiungere un
riferimento a questa classe nella sezione <security>del nodo Microsoft.Web.Service2.
Salvare e compilare, quindi richiamare la pagina seguente.
Quello che si vede nella pagina è il “contratto SOAP” WSDL esposto dal servizio web,
sarà poi utilizzato per creare i metodi per interrogare il servizio.
Copiare la cartella ASService sul disco e renderla una cartella virtuale in IIS.
TPSIT 5
434
um 276 di
Client
Implementare un client, capace di comunicare con il server e gestire l’email, dove una
classe gestisce il protocollo POP3, POP3.CS, dove ci sono tutti i metodi che si occupano
della creazione del canale di comunicazione, dell’invio dei comandi al server e della lettura
dei risultati che dovranno poi essere gestiti in modo opportuno dal client.
In Visual Studio, scegliere la voce File/Nuovo/Progetto (CTRL+N), nella finestra Nuovo
Progetto selezionare Altri linguaggi/Progetti Visual C# e Windows/Applicazione per
Windows Form come modello.
Nel form principale si devono inserire tutti gli elementi che compongono l’interfaccia utente
del client.
Per la barra di navigazione di sinistra è stata scelta una ListView.
Nella parte centrale alta posizionare un DataGrid mentre in quella bassa un Panel.
Il nostro client, oltre che dal form principale, sarà composto da altri elementi tra cui il
DataSet tipizzato per la memorizzazione dei messaggi, tutti gli elementi sono raggruppati
sotto un unico progetto.
TPSIT 5
434
um 277 di
La logica del S/W è implementata nel form principale.
File FORM1.CS.
Il metodo che permette di scaricare la posta sul client è DownloadMails, dove è istanziata
la classe POP3.
È effettuata la connessione al server e sono inviate le credenziali.
Se la connessione ha esito positivo, il server è raggiungibile e le credenziali inviate sono
valide, quindi si possono inviare i comandi supportati dal protocollo.
Quello che interessa è scaricare i messaggi sul PC in modo da poterli leggere anche in
modalità disconnessa.
Dovendoli scaricare tutti, per prima cosa si deve conoscere quanti messaggi ci sono.
Il comando da inviare è STAT.
La risposta, qualora ci fossero dei messaggi, sarà ad esempio la seguente.
+OK 7 5678
Dove.
+OK indica che il comando inviato è corretto e non ha causato errori.
7 indica il numero dei messaggi presenti.
5678 rappresenta la dimensione totale dei messaggi.
Per facilitare l’estrazione del numero dei messaggi dalla risposta al comando STAT, nella
classe POP3 è stata implementata una proprietà che ritorna direttamente il numero dei
messaggi, MailNumber che fa riferimento al metodo ParseSTAT.
int totmail = pop3.MailNumber;
TPSIT 5
434
um 278 di
Dato il numero, si deve inviare il comando RETR per ogni messaggio presente sul server:
Quello che si riceve dal comando RETR è una stringa contenente l’intero messaggio,
header compresi.
Da questa stringa si devono estrarre le informazioni che permettono di rendere più
presentabile il messaggio nonché di memorizzarlo sul disco.
Questo problema si risolve con un parser cui passare la stringa ricevuta dal server di
posta, il risultato sarà quello di memorizzare in una serie di variabili i campi seguenti.
 FROM il mittente del messaggio.
 TO il destinatario.
 SUBJECT oggetto del messaggio.
 BODY corpo del messaggio.
 DATE data del messaggio.
 MESSAGEID ID del messaggio.
Il codice del parser è contenuto nel file PARSEEMAILMESSAGE.CS.
Il problema, relativo alla presentazione e memorizzazione dei dati, vista la semplicità delle
informazioni da memorizzare si risolve con un DataSet tipizzato, è una rappresentazione
dei dati residente in memoria.
Sebbene un DataSet è utilizzato in accoppiata ad un DB per la gestione dei dati in
modalità disconnessa, nulla impedisce di usarlo come entità a sé stante.
Per aggiungere un DataSet al progetto è sufficiente cliccare con il tasto destro del mouse
sull’icona relativa al progetto e selezionare la voce Aggiungi/Aggiungi nuovo
elemento… (CTRL+MAIUSC+A).
Nell’elenco degli elementi che possono essere aggiunti, selezionare il Dataset,
chiamandolo dsMail e fare clic su Aggiungi.
Visual Studio offre uno strumento visuale per realizzare i DataSet.
Aprire il file DSMAIL.XSD e dalla casella degli strumenti aggiungere gli elementi che
interessano, il primo sarà un Element di nome Mail che rappresenta la tabella, come se si
operasse su un DB, aggiungere all’elemento appena inserito i vari campi che interessano.
TPSIT 5
434
um 279 di
Ora che si ha il parser e il contenitore per i messaggi scaricati, creare una nuova istanza
del DataSet.
dsMail dsmail = new dsMail();
Creare una nuova DataRow, in altre parole una nuova riga nel DataSet, valorizzandone i
campi con il risultato del parser e aggiungerli al DataSet.
Dato che il ciclo sarà ripetuto per tutti i messaggi presenti, ogni riga del DataSet conterrà
un singolo messaggio scaricato dal server.
Ora si deve implementare nel client le funzionalità che consentono di ricevere il file delle
definizioni e di usarlo.
Il primo passo sarà quello di prendere la classe Proxy relativa alla gestione del server anti
SPAM, ANTISPAMMANAGERSERVICES.CS e includerla nel progetto.
Tale classe, infatti, contiene il metodo GetSpamList che ritorna l’elenco delle definizioni.
Dato che il download delle definizioni richiede un accesso sicuro al server anti SPAM,
includere nel progetto anche la classe per la gestione del token di autorizzazione
AUTHMANAGER.CS.
Nel client, il metodo che si occupa di ricevere la lista delle definizioni e di salvarla sul disco
si chiama DownloadSpamList.
Dopo aver creato il token con le credenziali dell’utente, costruire il messaggio SOAP da
inviare al server anti SPAM con la richiesta del file con le definizioni.
Il metodo GetSpamList della classe proxy ritorna una stringa che si trasforma in XML con
XmlDocument.LoadXml e si salva il risultato sul disco.
Il client dovrà “filtrare” l’email ricevute e anteporre a quelle che ritiene SPAM una dicitura
che le distingua dalle altre mail.
In particolare, a differenza degli altri campi, il Subject dell’email è filtrato prima di essere
inserito nel DataSet di appoggio.
Ora si può scaricare la posta e salvarla sul disco, si può scaricare e usare il file con le
definizioni di SPAM ma non si può ancora segnalare nuovo SPAM.
La classe proxy ANTISPAMMANAGERSERVICES.CS contiene il metodo SegnalaMail
che accetta come input una stringa, l’indirizzo da segnalare.
Questo metodo richiamerà il corrispettivo del server anti SPAM che si occuperà
dell’inserimento dell’indirizzo nel DB.
Per recuperare l’indirizzo da segnalare dal DataSet popolato con i dati dell’email ricevute
occorre abilitare l’evento CurrentCellChanged del DataGrid che mostra l’elenco dell’email,
passare lo stesso DataGrid alla metodo GetCurrentBindedObject che si occuperà di
estrarre dal DataSet associato i dati che interessano.
Si usa il GetCurrentBindedObject invece di recuperare i dati direttamente dal DataGrid,
perché conviene sempre prelevare i dati dal DataSet associato in quanto il DataGrid
potrebbe nascondere alcune delle colonne del DataSet.
Ritornando al codice dell’evento CurrentCellChanged, si può vedere che, oltre a
TPSIT 5
434
um 280 di
valorizzare il contenuto di alcune TextBox utili a visualizzare il contenuto dell’mail, si
valorizza anche un campo di tipo stringa denominato mail_From.
tbxFrom.Text = drv["From"].ToString();
mail_From = tbxFrom.Text;
Che sarà esattamente quello che si passa al metodo SegnalaMail della classe Proxy.
Il client ora ha tutte le funzionalità che servono per scaricare i messaggi di posta, filtrarli e,
eventualmente, segnalare nuovo SPAM.
Scegliendo il sistema di memorizzazione dei dati, si è preferito un DataSet ad un DB.
A differenza di un DB però, l’istanza di un DataSet “vive” finché è mantenuto un riferimento
a essa.
Dopo di che, il GC (Garbage Collector) libererà la memoria e i dati andranno persi.
Occorre quindi a “salvare” il DataSet per ritrovarlo integro a ogni riavvio dell’applicazione.
Si sfrutta una funzionalità fornita dal .Net Framework: la serializzazione.
Usare il BinaryFormatter che risulta essere semplice da usare e veloce nelle operazioni di
serializzazione e deserializzazione.
Come prima cosa quindi creare un’istanza del Formatter che si vuole utilizzare.
Dovendo salvare i dati sul disco, predisporre uno StreamWriter chiamato dat che scriva il
dato nella cartella Mails contenuta nella stessa cartella di esecuzione dell’applicazione.
Nella cartella Mails si trova ora un nuovo file chiamato MAILS.DAT che conterrà la
rappresentazione binaria del DataSet.
Per recuperarlo si deve avviare il processo inverso, chiamato deserializzazione.
Lo stesso principio è stato utilizzato per la persistenza dei dati relativi alla casella di posta
da controllare e ai dati di login al server anti SPAM solo che, al posto di serializzare un
DataSet, è stato serializzato un normale oggetto.
File SPAMFILTER.CS
Il metodo statico Filter si occupa di valutare l’email in arrivo basandosi sul file XML
contenente le definizioni.
Il primo step è quello di “leggere” il file XML delle definizioni, l’oggetto XmlTextReader
fornisce un rapido accesso al contenuto di tale file.
L’applicazione del filtro consiste nel valutare se il mittente dell’email, mailFrom, è
contenuto nell’elenco delle definizioni.
Qualora il riscontro dovesse avere esito positivo, la funzione passa alla lettura del rating e
antepone al Subject originale la dicitura seguente.
return "[***SPAM*** " + rating +" ] " + mailSubject;
In questo modo, tutte l’email ritenute SPAM saranno contrassegnate e saranno facilmente
distinguibili da quelle che non lo sono.
Alla prima esecuzione del client di posta è chiesto d’impostare i dati di accesso sia al
server POP3 sia al server anti SPAM.
Per recuperare questo parametro fare riferimento all’ISP che fornisce la casella di posta
elettronica.
UserName e Password sono quelle che si usano per leggere la posta.
Nella scheda Settaggi server antispam bisogna inserire i dati di accesso al servizio, di
default UserName = User e Password = User.
Una volta inseriti questi dati sarà possibile utilizzare il client.
Sulla barra di sinistra è presente una serie d’icone, ognuna delle quali avvia un comando
specifico, ogni comando si attiva con un doppio clic sull’icona relativa.
Per scaricare l’email quindi, sarà sufficiente fare doppio clic sull’icona Scarica Mail.
TPSIT 5
434
um 281 di
L’email scaricate con questo client non saranno cancellate dal server.
TPSIT 5
434
um 282 di
JAVA
Per implementare un filtro bayesiano occore costruire un dizionario che contenga una
serie di parole, con il numero di occorrenze delle stesse passate per la fase di
allenamento.
Nel progetto questa fase è simulata da due file.
1. File NORMALE.MBOX che contiene posta legittima.
accesso=1.0
account=9.0
acquisizione=5.0
commerciale=3.0
comunicabile=0.0
comunicato=1.0
TPSIT 5
434
um 283 di
compagnia=17.0
File JUNK.MBOX che contiene una serie di messaggi spazzatura.
lots=1.0
lou=5.0
love=5.0
viagra=9.0
cialis=9.0
levitra=9.0
sesso=7.0
low=3.0
File DIZIONARIO.JAVA
package bayes;
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.StringTokenizer;
public class Dizionario {
public final static String TOKENS = "!#* \t\n,:";
Map dizionario = new HashMap();
String filename;
// crea un dizionario di termini e loro occorrenze
// @param filename file che contiene la casella da analizzare
public Dizionario( String filename) {
this.filename = filename;
}
È definita la classe Dizionario che dispone di tre attributi: una costante che indica quali
sono i separatori di parola, una Map che conterrà le parole e le occorrenze e una stringa
per ospitare il nome del file da caricare.
La classe ha un costruttore che permette di specificare il nome del file da leggere.
// importa la casella di testo
public void importa() throws IOException {
FileReader fr = null;
try {
fr = new FileReader( filename );
BufferedReader br = new BufferedReader( fr );
while( true ) {
String line = br.readLine();
if (line==null)
break;
StringTokenizer st = new StringTokenizer( line, TOKENS );
while( st.hasMoreTokens() ) {
increment( st.nextToken() );
}
}
}
finally {
TPSIT 5
434
um 284 di
if (fr == null)
fr.close();
}
System.out.println("Importato il dizionario " +
filename + " per un totale di " + dizionario.size() + " token"
);
}
La seconda fase prevede la lettura del file di testo, utilizzando un oggetto FileReader
associato ad un BufferedReader, in questo modo è letta una riga alla volta e spezzata in
token attraverso un oggetto StringTokenizer.
Questo è costruito con la costante TOKENS definita al passo precedente.
In questo modo è considerata una parola unica tutto quanto non separato dai caratteri
sopra indicati; per ciascuna parola così ottenuta è chiamato il metodo increment che si
occupa di aumentare il conteggio di questa parola, se non esiste.
// incrementa il conteggio di un token
void increment( String token ) {
Float count = (Float)dizionario.get(token);
if (count == null) {
count = new Float(1);
}
else {
count = new Float( count.floatValue() + 1 );
}
dizionario.put( token, count );
}
Il metodo increment verifica se nel dizionario è già presente la parola.
In questo caso ne ottiene il numero di occorrenze che aumenta di 1, per poi reinserire i
dati aggiornati nel dizionario.
Nel caso la parola non esista, è inserita nel dizionario con contatore a 1.
// ritorna la probabilità di un token
float get( String token ) {
Float value = (Float)dizionario.get(token);
if (value == null)
return 0.0f;
else
return value.floatValue();
}
La classe è poi integrata con due funzioni aggiuntive che permettono d’investigare i dati
del dizionario da un’altra classe.
Il metodo get ritorna il numero di occorrenze di una parola passata come parametro.
Se questa non esiste nel dizionario è ritornato 0.0f.
Il dato è restituito come tipo primitivo float per agevolare le operazioni successive.
// numero di parole nel dizionario
int size()
return dizionario.size();
TPSIT 5
434
um 285 di
File FILTROBAYESIANO.JAVA
Una volta in possesso di dizionari, è possibile creare il filtro di posta che è implementato
con una singola classe.
package bayes;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.StringTokenizer;
import java.util.TreeMap;
public class FiltroBayesiano {
Dizionario postaAccettata;
Dizionario postaRifiutata;
// crea un nuovo filtro, utilizzando i dizionari di posta
// accettata e rifiutata indicati
public FiltroBayesiano( Dizionario postaAccettata,Dizionario postaRifiutata ) {
this.postaAccettata = postaAccettata;
this.postaRifiutata = postaRifiutata;
}
}
Il codice prevede un singolo costruttore che si aspetta i due dizionari di riferimento, il
metodo che esegue il filtro del contenuto di un file non fa altro che caricare il file.
// filtra un messaggio contenuto in un file
public float filtra( File file ) throws IOException {
FileReader fr = new FileReader(file);
char[] buffer = new char[ (int) file.length() ];
fr.read( buffer );
fr.close();
return filtra( new String(buffer) );
}
Il file è caricato in un unico blocco di caratteri, memorizzati in un array di char.
Da questo è costruita una stringa che è passata al metodo filtra(string).
Questo contiene la logica di valutazione del messaggio che è composta da tre passaggi.
1. Estrazione di tutte le parole presenti nel messaggio, in modo similare a quanto fatto per
la costruzione dei dizionari e calcolo delle probabilità dei singoli token.
2. Estrazione delle parole che hanno probabilità più lontana dal punto medio, identificato
da una probabilità dello 0.5.
3. Calcolo della probabilità combinata di tutti i termini che produce la probabilità che il
messaggio sia posta indesiderata.
// filtra un messaggio, indicando se potrebbe essere posta spazzatura
public float filtra( String messaggio ) {
Map tokenProb = new HashMap();
StringTokenizer st = new StringTokenizer(
messaggio, Dizionario.TOKENS );
while( st.hasMoreTokens() ) {
TPSIT 5
434
um 286 di
String token = st.nextToken();
tokenProb.put( token, calcolaProbabilita(token));
}
System.out.println( tokenProb );
float[] pb = estraiProbabilita( tokenProb );
return probabilitaCombinata( pb );
}
Il primo passo è espletato tramite l’oggetto di tipo StringTokenizer.
L’approccio è simile alla classe Dizionario ma nella mappa, per ogni parola, non è
presente il numero di occorrenze ma la probabilità calcolata con il metodo
calcolaProbabilità.
Questo metodo mette in relazione le occorrenze della parola nei dizionari di posta
accettata e rifiutata, calcolando la probabilità che la mail che contiene questa parola sia
indesiderata.
Se una parola non è presente nei dizionari assume un valore di 0.4.
// calcola la probabilità di un singolo token
Float calcolaProbabilita( String token ) {
float result = 0.4f;
float g = postaAccettata.get(token) * 2;
float b = postaRifiutata.get(token);
if ( !((g+b) < 5) ) {
float temp1 = b / postaRifiutata.size();
temp1 = Math.min( temp1, 1 );
float temp2 = g / postaAccettata.size();
temp2 = Math.min( temp2, 1 );
float temp = temp1 / (temp1+temp2);
result = Math.min( 0.99f, temp );
result = Math.max( 0.1f, result );
}
return new Float(result);
}
Una volta ottenuto il dizionario dei token del messaggio da valutare, sono considerati i 15
elementi con maggior discostamento dal punto medio 0.5: il discostamento deve
considerare il segno.
Ad esempio, un valore 0.8 si discosta di 0.3 dal punto medio (0.8 – 0.5 = +0.3), mentre un
valore 0.3 si discosta di -0.2 (0.3 – 0.5 = –0.2).
Per ottenere l’elenco dei token ordinati per discostamento, è costruita una mappa ordinata.
Nella mappa la chiave è il punteggio, mentre il valore è la probabilità.
Da questa mappa sono estratti i primi 15 elementi e ritornati sotto forma di array di float.
// estrae le 15 probabilità più lontane dal valore medio 0.5f
float[] estraiProbabilita( Map dizionario ) {
final int size = 15;
float[] pb = new float[ size ];
// crea una mappa con le probabilità e i punteggi relativi
// cioè la distanza dal valore medio
Map punteggi = new TreeMap();
for( Iterator iter = dizionario.keySet().iterator();
iter.hasNext(); ) {
TPSIT 5
434
um 287 di
String key = (String)iter.next();
Float currentObject = (Float)dizionario.get(key);
float current = currentObject.floatValue();
float punteggio = Math.abs( current - 0.5f );
punteggi.put( new Float( punteggio ), currentObject );
}
// la mappa è ordinata per chiave, dunque estraendo i
// primi 15 elemeni si anno le probabilità più lontane dal valore medio
int i = 0;
for( Iterator iter = punteggi.keySet().iterator();
iter.hasNext(); ) {
Float key = (Float)iter.next();
Float currentObject = (Float)punteggi.get(key);
pb[ i++ ] = currentObject.floatValue();
if (i>=size) {
break;
}
}
return pb;
}
Una volta in possesso di questo array di probabilità, è possibile calcolare la probabilità
combinata.
In pratica la probabilità che tutti i casi si verifichino, la formula relativa è la seguente.
abc + (1 - a)(1 - b)(1 - c)
Dove a, b e c sono valori di probabilità.
Ovviamente la formula si espande fino a contemplare tutte e 15 le probabilità estratte al
passo precedente.
La probabilità combinata è implementata nel metodo seguente.
// calcola la probabilità combinata di un insieme di valori di numerosità arbitraria
float probabilitaCombinata( float[] pb ) {
float result = 0;
// calcola il prodotto delle probabilitˆ
float prodotto = pb[0];
for( int i=1; i<pb.length; i++ ) {
prodotto *= pb[i];
}
// calcola il prodotto di 1-x
result = (1 - pb[0]);
for( int i=1; i<pb.length; i++ ) {
result *= (1 - pb[i]);
}
return prodotto / ( prodotto + result );
}
L’array di float passato come parametro potrebbe essere di qualsiasi lunghezza.
Per prima cosa è calcolato il prodotto delle probabilità, in seguito è calcolato il prodotto di
tutte le probabilità (1 – x).
Il valore ritornato dal metodo filtra è un float che può assumere un valore vicino allo 0.
TPSIT 5
434
um 288 di
In questo caso il messaggio è accettato.
Se il valore è invece superiore a 0.9, il messaggio è molto probabilmente posta
indesiderata.
Nella classe FiltroBayesiano sono presenti metodi di utilità per eseguire queste valutazioni
e stampare un messaggio a video.
Il metodo main contiene del codice di prova che esegue il filtro a fronte di messaggi
spazzatura CATTIVO.TXT o legittimi BUONO.TXT.
A questo punto è possibile costruire i due dizionari necessari all’operatività di un filtro
bayesiano.
public static void main(String[] args) throws IOException {
Dizionario dizionarioJunk = new Dizionario("junk.mbox");
dizionarioJunk.importa();
Dizionario dizionarioNormale = new Dizionario("normale.mbox");
dizionarioNormale.importa();
FiltroBayesiano filtro = new FiltroBayesiano(dizionarioNormale, dizionarioJunk);
filtro.stampaEsitoFile("cattivo.txt");
filtro.stampaEsitoFile("cattivo1.txt");
filtro.stampaEsitoFile("buono.txt");
filtro.stampaEsitoFile("buono1.txt");
}
L’output che si ottiene è il seguente.
Importato il dizionario I:/junk.mbox per un totale di 19 token
Importato il dizionario I:/normale.mbox per un totale di 7 token
>>> Il messaggio è posta accettata con una probabilità di 0.0
TPSIT 5
434
um 289 di
SNMP
INTRODUZIONE
È il protocollo standard per il network management su reti IP, opera al livello 7 del modello
ISO/OSI, controlla qualunque tipo di device che lo supporti, compresi router, PC e
stampanti.
L’architettura di cui il protocollo SNMP fa parte, è definita con il nome NMF (Network
Management Framework) e consente la gestione dei device gestibili attraverso l’utilizzo di
agenti ossia moduli S/W che risiedono su ciascuno di essi.
Ciascun agente non fa altro che comunicare con almeno una stazione di gestione
centralizzata NMS (Network Management Station): il manager, attraverso la quale è
possibile leggere e/o scrivere informazioni e raccogliere eventuali segnalazioni, trap,
inviatele da essi.
Le informazioni che si possono gestire per un’apparecchiatura, mediante il protocollo
SNMP, sono dette MO (Management Objects) e sono contenute all’interno di file chiamati
MIB (Management Information Base) secondo la struttura definita nella SMI (Structure
Management Information) che è costituita da un insieme di strutture comuni e da uno
schema identificativo usato per far riferimento alle variabili del MIB.
Componenti dell’architettura.
 SNMP: protocollo per la comunicazione tra agenti e sistema centrale.
 NMS: sistema centrale.
 Agente: sistema S/W residente su ogni device.
 MIB: formato per la descrizione delle caratteristiche di ogni device.
 Trap: allarmi, ossia particolari pacchetti inviati dagli agenti all’NMS per la notifica di
certe situazioni.
Progettare un sistema in grado di monitorare un certo numero di periferiche permettendo,
nel contempo, di ricevere opportuni allarmi nel caso di malfunzionamento.
Il S/W deve analizzare il traffico che passa su una scheda di rete, oppure determinare
l’occupazione della memoria o il regime d’uso dei dischi.
L’architettura è costituita dai seguenti componenti.
 Un sistema centrale in grado di raccogliere informazioni sullo stato di ogni device da
monitorare e capace d’interpretare i messaggi che sono inoltrati da essi.
 Un linguaggio comune in grado di fare da ponte tra le periferiche e il sistema centrale.
TPSIT 5
434
um 290 di
Problema: il sistema chiederà le informazioni sullo stato alle periferiche oppure saranno le
periferiche a inviare autonomamente le informazioni al sistema.
Affinché il sistema funzioni in maniera bidirezionale, è necessario che ogni device sappia
in che modo interpretare le richieste ricevute dalla centrale e, allo stesso modo, sappia
come avvertirla relativamente agli eventi che la riguardano.
La bidirezionalità è, ovviamente, una caratteristica necessaria in questi sistemi perché se
per qualche ragione il device non riuscisse a comunicare con la stazione centrale per
assenza di collegamento o per altre ragioni, quest’ultima deve poter tentare in qualche
modo di contattare il device a determinate scadenze di tempo.
Una volta stabilito che le due componenti debbano comunicare tramite un protocollo
comune, sarà necessario implementare un S/W che gestisca questo genere di
comunicazione: questo tipo di S/W prende il nome di agente.
Ogni device, diverso da tipo a tipo, deve esporre le proprie caratteristiche che devono,
peraltro, essere messe a conoscenza di ogni agente e, ancora, del sistema centrale.
Occorre quindi anche un DB che contenga la descrizione delle caratteristiche esposte da
ogni periferica.
FORMATO MIB
È un’astrazione di un generico DB, se per un device non si ha disponibile il MIB è
paragonabile ad un device che non esiste poiché non si sa come interpretare le
informazioni da richiedere o ricevute da un agent.
Un MIB è un file di testo, scritto secondo una notazione particolare che permette di
descrivere tutte le variabili alle quali si può accedere via SNMP e relative a quel
determinato device.
Tutti i MIB fanno parte di una struttura gerarchica simile a quella utilizzata dai DNS per i
nomi dei domini, costituita da una radice senza nome e all’interno della quale ogni nodo
contiene un identificatore univoco, tipi di dati e modalità di accesso per ciascuna variabile
in essi contenuta.
Al di sotto della radice di quest’albero, sono inseriti i MIB relativi alle diverse
organizzazioni standard e quelli che non risultano standard, solitamente posizionati
all’interno del ramo experimental.
Le modalità di lettura di una determinata variabile sono analoghe a quanto è fatto per lo
spazio dei nomi DNS.
Si parte dalla radice, indicata con un punto e si scorre l’intera gerarchia sino al
raggiungimento dell’informazione voluta.
In questa maniera, ogni foglia dell’albero può essere identificata da una sequenza di nomi,
Object Name o, analogamente, di numeri OID (Object IDentifier) separati da un (.).
Ad esempio, .iso.org.dod.internet.mgmt.mib-2.system e .1.3.6.1.2.1.1 identificano la
stessa cosa.
Ovviamente, ciascun identificativo dev’essere necessariamente unico e ciò implica che
all’interno di un qualunque sotto albero non possono esistere più oggetti aventi il
medesimo OID.
Se si parte dalla radice della struttura, scorrendola si nota che esiste il ramo dod (6) (US
DEPARTEMENT OF DEFENSE), dal quale deriva il nodo internet (OID = 1.3.6.1).
Al di sotto di esso si trovano diversi nodi tra cui i seguenti.
management: questo sotto albero contiene le definizioni delle MIB standard.
experimental: utilizzato per identificare oggetti che sono in fase sperimentale.
private: utilizzato per identificare gli oggetti creati dai vari vendor come Cisco, IBM per la
gestione delle variabile che sono specifiche di un loro prodotto.
In conclusione, un agent deve possedere, per ogni device che intende gestire, il relativo
MIB.
TPSIT 5
434
um 291 di
COMUNITÀ
L’insieme degli apparati di rete che possono essere gestiti attraverso l’SNMP
appartengono necessariamente ad una comunità.
La comunità rappresenta un identificativo attraverso il quale si cerca di garantire un
minimo di sicurezza quando avvengono i colloqui, via SNMP, tra NMS ed agenti.
Questo vuol dire che un agent SNMP risponde solamente alle richieste d’informazioni
effettuate da un NMS appartenente alla sua stessa comunità.
Esistono tre tipi di comunità.
1. Monitor: permette di effettuare solamente interrogazioni agli agent quindi
esclusivamente operazioni di lettura.
2. Control: permette, attraverso gli agent SNMP, di effettuare delle operazioni di
lettura/scrittura sul device.
3. Trap: permette ad un agent d’inviare un opportuno messaggio (trap) SNMP alla
management station per notificarle una determinata situazione.
Bisogna tener presente che i nomi che definiscono una comunità sono formati da 32
caratteri e sono case sensitive.
Esistono dei nomi predefiniti per i diversi tipi di comunità e molto spesso sono public per i
casi di sola lettura e private per quelle in lettura/scrittura.
Per aumentare il grado di sicurezza, naturalmente, è opportuno modificare queste
impostazioni prima possibile.
La maniera più valida per imparare a conoscere SNMP ma soprattutto per vedere se la
configurazione delle macchine è a posto, è quella di provare praticamente a leggere
alcune informazioni, interrogando direttamente il proprio PC o, meglio ancora, un’altra
macchina in rete.
Per far questo, sono necessari due passi.
1. Configurazione del protocollo SNMP.
2. Utility per lo scorrimento delle informazioni e per l’invio/la lettura delle trap.
TPSIT 5
434
um 292 di
In Windows ma la procedura è analoga anche su altri SO, occorre avviare i seguenti
servizi: Start/Tutti i programmi/Strumenti di amministrazione/Servizi e verificare se il
servizio SNMP è avviato con i parametri di default.
Ora non serve che un’applicazione in grado d’interrogare, via SNMP, il PC di destinazione
alla ricerca delle variabili dei MIB supportati.
Esempio, leggere la variabile sysDescr, il cui percorso è identificato come system.1 o,
.1.3.6.1.2.1.1.1.1 da PC configurato per la community di default public.
TPSIT 5
434
um 293 di
WMI (WINDOWS MANAGEMENT INSTRUMENTATION)
È un componente del SO Windows ed è l’implementazione Microsoft di WBEM (WebBased Enterprise Management) che è un’iniziativa industriale per lo sviluppo di una
tecnologia standard di accesso alle informazioni di gestione in un ambiente aziendale.
WMI può essere utilizzato per automatizzare attività amministrative, quali la modifica del
registro di sistema, in linguaggi di script che dispongono di un modulo di gestione per
Windows e che supportano la gestione di oggetti ActiveX.
WMI include un archivio di oggetti conforme agli standard CIM (Common Information
Model) e il servizio CIMOM (CIM Object Manager) che gestisce l’acquisizione, la
manipolazione di oggetti nell’archivio e la raccolta delle informazioni fornite dai provider
WMI, il CIM offre, in definitiva le seguenti possibilità.
 Gestire gli oggetti come classi astratte, in questo modo, si può operare su una
qualunque periferica o su di un processo qualsiasi attraverso esse.
 Una serie di proprietà e metodi che consentono la gestione delle suddette classi.
 namespace per gestire le classi di oggetti.
In particolare, lo scopo di un namespace è quello di raggruppare tutte le classi e le istanze
che riguardano un determinato sistema da monitorare.
Si può anche definirlo come una porzione del CIM Schema.
In particolare, il CIMV2 namespace che costituisce quello di default per WMI, raggruppa
tutte le classi e le istanze che riguardano il sistema Windows locale.
II provider WMI costituisce un concetto importante e il compito è quello di fungere da
intermediario tra i componenti del SO e le applicazioni.
Un esempio è proprio quello del provider SNMP che fornisce dati ed eventi dalle
periferiche.
In sostanza, quando il CIMOM riceve una richiesta da un’applicazione, management
application, per dati che non sono disponibili nel CIM oppure per notifiche di eventi non
supportati dal CIMOM stesso, inoltra tali richieste direttamente al provider WMI
interessato.
Start/Pannello di controllo/Installazione applicazioni
Nella finestra Installazione applicazioni selezionare Installazione componenti di
Windows
TPSIT 5
434
um 294 di
Selezionare la voce Provider WMI SNMP e confermare con OK.
Selezionare la voce SNMP (Simple Network Management Protocol) e OK.
I provider WMI altro non sono che delle DLL localizzate all’interno della cartella seguente:
%SYSTEMROOT%\SYSTEM32\WBEM.
All’interno di questa cartella ci sono file di tipo testo con estensione MOF contenenti le
definizioni necessarie a registrare un provider WMI e, quindi, il set di classi che gli
appartengono, all’interno del CIM repository.
File NCPROV.MOF
// Copyright (c) 1997-2001 Microsoft Corporation, All Rights Reserved
TPSIT 5
434
um 295 di
#pragma namespace("\\\\.\\root\\cimv2")
class MSFT_NCProvEvent : __ExtrinsicEvent
{
string Namespace;
string ProviderName;
uint32 Result;
};
class MSFT_NCProvNewQuery : MSFT_NCProvEvent
{
string QueryLanguage;
string Query;
uint32 ID;
};
class MSFT_NCProvCancelQuery : MSFT_NCProvEvent
{
uint32 ID;
};
class MSFT_NCProvAccessCheck : MSFT_NCProvEvent
{
string QueryLanguage;
string Query;
uint8 Sid[];
};
class MSFT_NCProvClientConnected : MSFT_NCProvEvent
{
boolean Inproc;
};
class MSFT_WMI_GenericNonCOMEvent : __ExtrinsicEvent
{
string PropertyNames[];
string PropertyValues[];
string ProviderName;
uint32 ProcessId;
};
#pragma deleteinstance("MSFT_WMI_NonCOMEventProvider.Name=\"Standard NonCOM Event Provider\"",NOFAIL)
Instance of __Win32Provider as $NonCOMProvider
{ Name = "Standard Non-COM Event Provider";
HostingModel = "Decoupled:NonCOM";
};
Instance of __EventProviderRegistration
{ Provider = $NonCOMProvider;
EventQueryList = {"select * from MSFT_WMI_GenericNonCOMEvent", "select * from
MSFT_NcProvEvent"};
};
#pragma namespace("\\\\.\\root\\cimv2")
instance of __namespace{ name="ms_409";};
#pragma namespace("\\\\.\\root\\cimv2\\ms_409")
#pragma deleteinstance("MSFT_WMI_NonCOMEventProvider.Name=\"Standard NonCOM Event Provider\"",NOFAIL)
File WINMGMT.EXE
TPSIT 5
434
um 296 di
Tester di strumentazione gestione Windows, denominato anche WBEMTest, è un’utilità di
uso generale per visualizzare o modificare classi, istanze o metodi dello schema CIM
durante lo sviluppo di provider di strumentazione gestione Windows e di applicazioni WMI.
Un consumer è un’applicazione che sfrutta i dati offerti da un provider WMI e rappresenta
una management application.
In particolare, esso interagisce con le WMI COM API, un set di oggetti COM utilizzabili da
diversi linguaggi di programmazione.
I provider WMI sono installati assieme al SO ma la lista di quelli messi a disposizione può
essere allungata attraverso la definizione di propri.
SNMP Provider funge da ponte tra i sistemi di monitoring e i device che devono essere
controllati, consentendo la lettura e la scrittura di MIB SNMP e la rimappatura, in eventi
WMI, di trap SNMP.
Ovviamente affinché ciò sia possibile, è anche vero che ci debba essere un gruppo di
classi, all’interno di WMI che consenta di rappresentare questo tipo d’informazioni.
Per ottenere questo risultato e, quindi, interagire con una periferica della quale si possieda
il proprio MIB, è necessario inserire tale DB nello schema di WMI affinché le sue
caratteristiche siano visibili come oggetti astratti WMI.
Un’utility che consente di effettuare questa operazione è SNMP Information Module
Compiler SMI2SMIR.EXE che, opportunamente utilizzata, permette di mappare i MIB
voluti nel formato equivalente CIM, consentendo d’interagire con un determinato device,
sfruttando la sua Management Information Base ma attraverso WMI.
In particolare, tali informazioni sono destinate all’interno di SMIR (SNMP Module
Information Repository).
smi2smir /a <Nome file MIB>
Attraverso questo comando, s’inseriscono i dati di un MIB all’interno dell’SMIR per poterli
utilizzare all’occorrenza.
Ci sono anche consumer built-in che consentono di perfezionare il funzionamento di
questa architettura.
TPSIT 5
434
um 297 di
Infatti, WMI dispone di diverse management application in grado di compiere determinate
azioni a seguito del verificarsi di alcuni eventi.
 SMTP Event Consumer: invia una mail al verificarsi di un determinato evento.
 Command Line Event Consumer: avvia un processo al verificarsi di un determinato
evento.
 Log File Event Consumer: scrive una stringa all’interno di un file di log al verificarsi di
un determinato evento.
Per poter accedere a oggetti contenuti all’interno del CIM Repository, sono necessarie tre
informazioni.
1. Il namespace e il nome della macchina alla quale ci si riferisce ad esempio root\CIMV2.
2. La classe alla quale ci si riferisce ad esempio Win32_Service.
3. L’istanza alla quale ci si riferisce ad esempio Win32_Service.Name="SNMP".
Il primo passo può essere portato a termine attraverso due possibili modi.
Creare un oggetto SWBemLocator e utilizzando il metodo ConnectServer per indicare il
namespace che interessa.
Microsoft Visual Basic 6
Il metodo ConnectServer ritorna un oggetto di tipo SwbemServices.
A questo si è agganciato il namespace, ora si deve interrogare il CIM Repository alla
ricerca delle informazioni, per far questo, utilizzare WQL.
Questa query WQL non fa altro che cercare, all’interno della classe Win32_Service, tutti i
servizi down, riportando una collezione di elementi, denominata ServiceSet e costituita
dallo Stato e dal DisplayName dei servizi trovati.
Conoscendo namespace e classi da interrogare si possono cercare informazioni come
“tutte le partizioni del disco rigido con meno del 10% di spazio” oppure “tutti i servizi che
sono in modalità Avvio Automatico”.
Il progetto è costituito da un solo form e da moduli che raccolgono funzioni utili
all’applicazione stessa.
DISKS
Raccoglie le procedure e le funzioni utili al controllo dello spazio disco libero su tutte le
partizioni del proprio disco.
PING
Implementa una semplice funzione che consente di “pingare” un host remoto.
PORTE
Implementa una procedura che, sfruttando l’SNMP Provider, consente il controllo delle
porte TCP/IP e UDP aperte.
All’avvio, l’evento Load del form principale compie una serie d’operazioni.
Le più importanti riguardano il controllo sulla prima esecuzione dell’applicazione e
l’impostazione delle variabili che rispecchiano le scelte precedentemente fatte dall’utente.
Se l’applicazione è stata avviata per la prima volta, è richiamata una serie di procedure
che si preoccupano di creare, all’interno del registro, una struttura che memorizza diverse
impostazioni utili al corretto funzionamento dello stesso.
La chiave che fa da root a queste informazioni è denominata MasterAgent sotto
HKEY_CURRENT_USER\Software\VB and VBA ProgramSettings e al suo interno si
trovano le seguenti sotto chiavi.
Drives: per il controllo dello spazio disco sulle partizioni.
Eventi: per il controllo di nuovi eventi nell’Event Viewer.
TPSIT 5
434
um 298 di
Install: per verificare se si tratta di primo avvio dell’applicazione.
Interfacce: per il controllo d’interfacce di rete.
Processi: per il controllo sulla creazione o eliminazione di processi.
Servizi: per il controllo di servizi che vanno giù.
Fatta eccezione per la sotto chiave Drives ed Install, le altre controllano se l’utente ha o
meno attivato quel determinato tipo di controllo e lo attivano all’avvio.
La chiave Drives, invece, conserva anche altre informazioni tra le quali la soglia di allarme
sullo spazio disco rimasto libero, espresso come valore percentuale.
Una volta determinate le impostazioni lette all’interno del registro, attraverso un’apposita
procedura denominata ReadFromREG, l’evento Load del form principale inizia ad
impostare le variabili e i controlli all’interno del form.
Successivamente, valorizza un’array denominato WQLQuery che non fa altro che
memorizzare le varie stringhe WQL che serviranno per i controlli, imposta i flag relativi
all’attivazione delle procedure, lanciandole se necessario e visualizza il form.
Essa può essere suddivisa in tre parti principali.
1. Una lista di pulsanti alla sinistra.
2. Una serie di pannelli sovrapposti nella parte centrale.
3. Una serie di LED nella parte inferiore sinistra.
Ogni pulsante attiva un determinato pannello attraverso il metodo ZOrder che consente di
visualizzare eventuali allarmi scattati, impostare o meno il controllo ed eliminare i log
creati.
Codice per controllare lo stato dell’interfaccia di rete
Le procedure implementate sfruttano l’SNMP Provider per ottenere le informazioni che
servono.
La prima notazione risiede nel namespace specificato, ossia \ROOT\SNMP\LOCALHOST.
È ovvio che, se si fosse caricato all’interno della struttura esposta da WMI il MIB di un
qualunque altro device, si sarebbe specificato, al posto di localhost, il device considerato.
Gli oggetti di tipo SwbemNamedValueSet sono importanti perché rappresentano delle
collezioni di oggetti SWbemNamedValue attraverso i quali è possibile inviare ai provider
che si stanno sfruttando, informazioni aggiuntive.
In questo caso si sta sfruttando l’SNMP Provider e considerato che SNMP protegge i
pacchetti inviati e ricevuti attraverso il nome della community di riferimento, è
indispensabile specificare quest’informazione, impostandolo come ultimo parametro
all’interno della chiamata ExecNotificationQueryAsync.
Si è considerato come nome di community configurata all’interno del servizio SNMP della
macchina locale la stringa Pippo.
Ricordare che senza quest’informazione, il provider SNMP non è in grado di ritornare
alcuna informazione e che la query WQL fallirà senza ritornare alcun dato.
Codice per il controllo dei servizi
È implementato grazie a due procedure: ServiceStatus e ServiceSink_OnObjectReady.
L’utilizzo delle istruzioni seguenti.
objWmiServices.Security_.ImpersonationLevel=3
objWmiServices.Security_.Privileges.AddAsString ("SeSecurityPrivilege")
Consentono di definire le modalità di accesso, per motivi di sicurezza, alle informazioni
che si stanno cercando di ottenere.
Istruzioni come queste sono necessarie per impedire errori del tipo “Accesso negato”.
La seconda particolarità è il modo con cui è avviata la query WQL.
TPSIT 5
434
um 299 di
SELECT * FROM __InstanceModificationEvent
WITHIN 1
WHERE TargetInstance ISA 'Win32_Service' AND (PreviousInstance.State <>
TargetInstance.State) AND TargetInstance.State='Stopped'
La classe __InstanceModificationEvent consente di tener traccia di eventuali modifiche
avvenute all’interno di altre, per esempio si sta chiedendo di essere avvertiti quando
avviene un cambiamento all’interno di dell’istanza Win32_Service.
La clausola WITHIN costituisce l’intervallo di polling, mentre TargetInstance permette di
specificare l’istanza della classe da controllare.
La query nella stringa prima dell’AND, controlla qualunque cambiamento nell’istanza
Win32_Service, impegnando le risorse di sistema.
Tuttavia, per restringere il campo d’azione e, quindi, sacrificare meno risorse, si può
migliorare la query attraverso la seconda parte della stringa che chiede di monitorare ogni
cambiamento avvenuto all’interno di Win32_Service ma considerando solo i casi in cui ci
sia un cambio di stato di un qualunque servizio e lo stato attuale sia pari a Stopped.
Le due proprietà PreviousInstance e TargetInstance della __InstanceModificationEvent
rappresentano lo stato precedente e attuale dell’istanza della classe, ora bisogna essere
avvertiti quando la query impostata è soddisfatta, si usa l’oggetto: SWBemSink attraverso
il quale è possibile definire le procedure che riceveranno tali notifiche.
Attraverso WMI sono riconosciute le seguenti procedure predefinite: OnCompleted,
OnObjectPut, OnObjectReady e OnProgress.
Di queste è gestita solo la OnObjectReady che è chiamata in causa non appena l’evento,
definito dalla query WQL, è pronto.
Lanciare la query asincrona sfruttando il metodo ExecNotificationQueryAsync di
objWmiServices specificando, però, come primo parametro, un oggetto SWBemSink
attraverso i cui eventi si gestiranno gli allarmi.
Nel momento in cui la query è soddisfatta ossia, un servizio è fermato, scatta l’evento
ServiceSink_OnObjectReady che consentirà di gestire l’allarme.
In particolare, il codice implementato agisce sul primo parametro passato alla procedura
ossia StatusEvent che, nel caso specifico della classe Win32_Service, in corrispondenza
dello stop del servizio Centro Sicurezza PC.
Attraverso l’utilizzo della funzione Split, quindi, non si fa altro che spezzare nelle varie
righe l’intero testo e prelevare solo le parti che interessano.
Codice per il controllo dei processi e degli eventi
Consente di controllare sia la creazione sia la distruzione di qualunque processo.
La principale differenza con la precedente è l’utilizzo di due oggetti SWBemSink,
ProcessCreateSink e ProcessDeleteSink e, di conseguenza, di due query WQL.
Codice per il controllo dei dischi
Le variabili per la gestione degli allarmi sullo spazio disco delle partizioni del sistema
controllato sono inizializzate all’avvio dell’applicazione e servono a impostare
correttamente i parametri di allarme sui dischi.
La variabile ThresholdDiskFlag consente di tener traccia di cambiamenti alle soglie di
allarme dei dischi permettendo di richiedere all’utente il salvataggio all’interno del registro.
Le procedure ReadFromREG e CaricaDrives, consentono di leggere le impostazioni dal
registro e preparare l’UI con tutti i parametri corretti, numero di partizioni, soglie di allarme.
La procedura CaricaDrives ricava tutte le lettere di unità dei soli dischi rigidi oltre alla
dimensione e allo spazio disco di ognuna di esse.
Una volta ottenute queste informazioni, se le impostazioni dell’utente prevedono il
TPSIT 5
434
um 300 di
controllo dello spazio disco, è lanciata la procedura ControlloSpazioDisco, ad intervalli
prestabiliti.
TPSIT 5
434
um 301 di
VOICE OVER IP
SIP (SESSION Initiation Protocol)
La società Skype, con sede in Lussemburgo, è stata fondata nel 2003, nel 2005 è stata
acquisita da eBay, nel 2009 è stata acquistata da Silver Lake.
Il prodotto denominato Skype è un S/W di comunicazione gratuito e liberamente
scaricabile che permette di utilizzare i servizi di VoIP per la telefonia e per la
videoconferenza.
Nel 2011 è arrivato l’accordo con Microsoft che si è impegnata a mantenere gli
investimenti per lo sviluppo del prodotto anche su piattaforme non Microsoft: il prodotto è
disponibile anche per Linux e per Mac.
Skype ha progettato un insieme di API per controllare il S/W Skype da una propria
applicazione: SkypeKit.
Invece, Skype Public API è un insieme di API che possono essere utilizzate per
connettere il proprio H/W e il proprio S/W a Skype.
Skype4COM è un ActiveX che espone le API di Skype in termini di oggetti, proprietà,
comandi, eventi e notifiche e può essere utilizzato nei seguenti linguaggi, Visual Studio,
Delphi, PHP, JavaScript e VBScript.
In questo modo, invece di dover utilizzare direttamente la tecnologia COM, per connettere
un’applicazione .NET, si può utilizzare questo ActiveX che è un “wrapper” alle Skype API.
Esempio, aprire, in VBScript, una chiamata all’utente “echo123” e verificare che Skype sia
installato e configurato correttamente.
// creare un oggetto Skype:
Set oSkype = WScript.CreateObject("Skype4COM.Skype", "Skype_")
// effettuare una chiamata utilizzando l’oggetto Skype:
Set oCall = oSkype.PlaceCall("echo123")
// sospendere una chiamata:
oCall.Hold
// recuperare una chiamata sospesa:
oCall.Resume
// terminare una chiamata:
oCall.Finish
L’operazione preliminare che è necessaria, prima di poter utilizzare Skype, è l’installazione
del S/W VoIP sul PC degli utenti e la registrazione di ogni utente al servizio gratuito di
Skype.
Comandi
Per ogni attività è necessario inviare un comando in formato testuale a Skype: per aprire e
chiudere una chiamata o una chat, per ottenere informazioni su un altro utente e per
disconnettersi da Skype.
A sua volta, Skype restituirà un’altra stringa di testo con le informazioni richieste o con
altre informazioni di supporto, come il numero identificativo della chiamata.
Questo <id> è importantissimo per riuscire a inviare dei comandi corretti a Skype nel corso
di una chiamata.
Il comando Skype è formato dalle seguenti parti.
 Una dichiarazione del comando formata da un simbolo di cancelletto (#) e da alcuni
TPSIT 5
434
um 302 di
caratteri in sequenza, a propria scelta, per esempio (#cmd).
 Una parola che indica un’azione: GET, SET, CALL, PING e OPEN.
 Altre parole che integrano il comando ed eventuali parametri, nome utente da
chiamare, numero identificativo o <id> della chiamata.
Per chiamare un utente di Skype si utilizza uno dei seguenti comandi.
#cmd CALL echo123
#cmd CALL +393331234567
Nel primo caso si apre una chiamata vocale, eventualmente anche video.
Nel secondo caso si chiama un numero di un cellulare, con prefisso internazionale, può
avvenire solamente nel caso in cui si è abbonati a Skype.
In caso contrario, chiamando un numero di telefono reale si ottiene un errore e si può solo
chiamare utenti Skype, con il loro nome utente.
All’avvio della chiamata, si riceve una sequenza di messaggi simile alla seguente.
(Comando) #abc CALL echo123
#abc CALL 162221 STATUS UNPLACED
CALL 162221 STATUS UNPLACED
CALL 162221 STATUS ROUTING
CALL 162221 STATUS RINGING
CALL 162221 STATUS INPROGRESS
CALL 162221 DURATION 0
CALL 162221 VIDEO_RECEIVE_STATUS AVAILABLE
CALL 162221 DURATION 1
CALL 162221 DURATION 2
CALL 162221 DURATION 3
...
(Comando) #cmd SET CALL 162221 STATUS FINISHED
#cmd CALL 162221 STATUS FINISHED
CALL 162221 DURATION 12
CALL 162221 STATUS FINISHED
I messaggi con il testo DURATION <n> indicano la durata della chiamata in corso e il
parametro <n> è un numero che indica il numero di secondi attuale.
Per chiudere la chiamata in corso, si deve utilizzare il comando seguente.
#cmd SET CALL <id> STATUS FINISHED
<id> è il parametro numerico che si è ricevuto quando è stata aperta la chiamata.
Si deve utilizzare lo stesso numero anche per chiudere la chiamata in corso.
Un comando che si può inviare per verificare se si è connessi al servizio Skype è il
seguente.
#cmd PING
Skype risponde con il comando seguente.
#cmd PONG
Confermando la ricezione del comando.
Per verificare la versione di Skype attualmente installata, si utilizza il comando seguente.
TPSIT 5
434
um 303 di
#cmd GET SKYPEVERSION
Si ottiene in risposta il messaggio seguente.
#cmd SKYPEVERSION 5.1.0.112
Per verificare lo stato come utente Skype, si utilizza il comando seguente.
#cmd GET USERSTATUS
Si riceve un risultato, dove <stato> è lo stato Skype corrente.
#cmd USERSTATUS <stato>
Il valore di <stato> può essere uno dei seguenti: ONLINE (in linea), AWAY (assente), DND
(occupato), INVISIBLE (invisibile) oppure OFFLINE (non in linea).
Per cambiare stato, si utilizza il comando seguente.
#cmd SET USERSTATUS <stato>
Per verificare lo stato di un altro utente Skype, si utilizza il comando seguente.
#cmd GET USER pippo ONLINESTATUS
PROGRAMMAZIONE
Si utilizzano le API di Skype dall’interno di un’applicazione Visual Basic per vedere quali
sono le possibilità di utilizzo che sono messe a disposizione.
L’applicazione deve gestire una chiamata in audio/video sul servizio Skype, senza dover
andare appositamente ad aprire la finestra di Skype.
Dopo aver creato il tipo di applicazione e dopo aver aggiunto il progetto SkypeControl alla
soluzione, inserire un nuovo form di nome ChiamaOperatore.
Il form principale contiene solamente un pulsante per l’apertura del form secondario.
La soluzione è formata da due progetti.
SkypeControl, crea una libreria di classi che funziona da wrapper delle funzionalità offerte
da Skype.
VB_Skype è l’applicazione che utilizza la DLL, apre direttamente una chiamata Skype,
TPSIT 5
434
um 304 di
senza che l’utente debba preoccuparsi di aprire la finestra principale di Skype, cercare
l’utente, in pratica il supporto tecnico del S/W ed effettuare la chiamata.
L’utente da chiamare potrà essere impostato in modo predefinito e la chiamata potrà
essere effettuata in automatico: basterà premere un pulsante nell’applicazione.
File MAIN.VB
Public Class Main
Private Sub cmdChiamaOperatore_Click(sender As System.Object, e As
System.EventArgs) Handles cmdChiamaOperatore.Click
ChiamaOperatore.Show()
End Sub
End Class
File CHIAMAOPERATORE.VB
Il form contiene quattro controlli: una PictureBox, due Button e una TextBox.
La casella di testo serve per visualizzare un log delle operazioni svolte all’utente.
All’apertura del form si deve creare una connessione all’applicazione di Skype, per gestire
le sue funzionalità direttamente dal form.
Il gestore dell’evento Load contiene due istruzioni simili tra loro che creano un gestore
degli eventi che è agganciato rispettivamente agli eventi pubblici SkypeAttach e
SkypeResponse, esposti dall’oggetto aSkype.
SkypeAttach permette di aprire la connessione a Skype dall’applicazione, mentre
SkypeResponse permette di ottenere uno o più messaggi da Skype.
Il tipo di messaggi che si ricevono da Skype sono di tutti i tipi.
Infatti, nella finestra di log sono elencati dei messaggi che rispecchiano tutto quello che
avviene a Skype, compresa la modifica dell’aspetto della sua finestra.
 WINDOWSTATE MINIMIZED.
 WINDOWSTATE MAXIMIZED.
 WINDOWSTATE NORMAL.
Quello che conta è che, dopo aver eseguito le due istruzioni inserite nel gestore
dell’evento Load, si può gestire Skype direttamente dall’applicazione.
Al momento dell’avvio della connessione a Skype, si ottengono due messaggi consecutivi.
1. PendingAuthorizaion.
2. Success indica che tutto è andato per il verso giusto.
Nel codice del gestore dell’evento SkypeResponse: le prime due istruzioni prendono il
messaggio ricevuto e lo accodano alla casella di “log”.
Nel blocco If ... End If si verifica se il messaggio contiene contemporaneamente le stringhe
CALL e STATUS INPROGRESS: se la risposta è positiva, significa che si è chiesto a
Skype di aprire una chiamata ad un utente Skype o ad un numero telefonico.
In tal caso si eliminano entrambe le stringhe dal messaggio originario, compreso lo spazio
iniziale o finale e si memorizza la stringa rimanente che è costituita dalla stringa composta
solo da cifre numeriche, nella variabile id.
A questo punto si è preso il numero di riferimento della chiamata in corso e lo si deve
memorizzare per poterlo utilizzare al momento opportuno: per chiudere la conversazione
in corso.
Gestore dell’evento Click del pulsante cmdChiama.
La prima istruzione chiama il metodo Conect dell’oggetto aSkype, per agganciare
l’applicazione di Skype, subito dopo si definisce il comando per chiamare un utente.
Chiamando il metodo Command dell’oggetto aSkype e passando a questo metodo la
stringa di comando, si ottiene l’avvio effettivo della chiamata vocale e/o video.
Gestore dell’evento Click del pulsante cmdRiaggancia.
Per chiudere la conversazione in corso, si deve inviare un altro comando testuale.
TPSIT 5
434
um 305 di
SET CALL <id> STATUS FINISHED
Nel comando s’inserisce il valore della variabile <id> salvata nel momento di apertura della
chiamata, una volta inviato il comando di termine conversazione, si può disconnettersi da
Skype, utilizzando il metodo Disconnect dell’oggetto aSkype.
' importare il namespace SkypeControl per utilizzare le funzionalità offerte dalla DLL creata
' con il progetto Visual C#
Imports SkypeControl
Partial Public Class ChiamaOperatore
' inserisce un ritorno a capo nel testo che è visualizzato nella casella di testo
Private nl As String = Environment.NewLine
' serve a memorizzare l’identificativo della chiamata attuale
Private id As String = ""
' comando da inviare a Skype
Dim comando As String = ""
' la classe SkypeProxy è definita in SkypeControl nel file SkypeProxy.cs
Dim aSkype As SkypeProxy = New SkypeProxy()
Private Sub Form1_Load(sender As System.Object, e As System.EventArgs) Handles
MyBase.Load
' connessione all’applicazione di Skype
AddHandler aSkype.SkypeAttach,
AddressOf aSkype_SkypeAttach
AddHandler aSkype.SkypeResponse,
AddressOf aSkype_SkypeResponse
End Sub
Public Sub aSkype_SkypeAttach(theSender As Object, theEventArgs As
SkypeAttachEventArgs)
' stato della connessione
TextBox1.AppendText(String.Format("Attach: {0}", theEventArgs.AttachStatus) & nl)
End Sub
Public Sub aSkype_SkypeResponse(theSender As Object, theEventArgs As
SkypeResponseEventArgs)
Dim risposta As String = theEventArgs.Response
TextBox1.AppendText(String.Format("Response: {0}", risposta) & nl)
If risposta.Contains("CALL") And risposta.Contains("STATUS INPROGRESS") Then
risposta = risposta.Replace("CALL ", "").
Replace(" STATUS INPROGRESS", "")
id = risposta
End If
End Sub
Private Sub cmdChiama_Click(sender As System.Object, e As System.EventArgs)
Handles cmdChiama.Click
aSkype.Conect()
comando = "#cmd CALL echo123"
TextBox1.AppendText(comando & nl)
aSkype.Command(comando)
End Sub
Private Sub cmdRiaggancia_Click(sender As System.Object, e As System.EventArgs)
Handles cmdRiaggancia.Click
comando = String.Format("#cmd SET CALL {0} STATUS FINISHED", id)
TPSIT 5
434
um 306 di
TextBox1.AppendText(comando & nl)
aSkype.Command(comando)
aSkype.Disconnect()
TextBox1.AppendText("Disconnesso" & nl)
End Sub
End Class
JAVA
Considerando i diversi dispositivi che compongono la struttura di una chiamata VoIP, Java
ha sviluppato diverse tipologie di API chiamate JAIN (Java APIs for Integrated Networks)
sono sviluppati da JCP (Java Community Process) e sono classificati in progetti JSR
(Java Specification Request), i progetti sono i seguenti.
 JSR 180 Sip per J2ME (Java Micro Edition), per lo sviluppo di applicazioni mobile.
 JSR 32 Jain-Sip per sviluppo su piattaforma J2SE (Java2 Platform, Standard Edition).
 JSR 116 Sip-Servlet, per piattaforma J2EE (Java Enterprise Edition).
In realtà, a queste API complete, c’è anche una libreria aggiuntiva Jain-Sip Lite con delle
classi di più alto livello che servono per astrarre dalle difficoltà e dai dettagli implementativi
e consentono di sviluppare applicazioni con estrema facilità e velocità.
Permette di sviluppare applicazioni di tipo UA (User Agent), in pratica terminali S/W che si
connettono a reti SIP.
Possono essere viste come involucro, wrapper, per accedere alle librerie Jain-Sip che
sono complete e richiedono un programmatore che conosca il protocollo SIP.
Per avere, invece, maggior controllo su cosa accade e per guidare il protocollo SIP, si
deve fare riferimento alle altre API.
SIP per J2ME definisce un set d’interfacce per dispositivi cellulari e PDA (Personal Digital
Assistant) che trattano la comunicazione SIP con factory comuni alle SocketConnection e
HttpConnection per semplificarne l’uso, in questo modo, infatti, si ha la gestione integrata
degli header, response automatizzate e supporto ai dialoghi SIP.
Sip-Servlet è un insieme di API basate sulle esistenti Servlet e funziona in maniera
analoga, come le Servlet, devono girare in un container adatto, Servlet Container, ad
esempio Tomcat.
Le Sip-Servlet hanno anche l’obiettivo di rendere standard alcuni aspetti del container:
l’associazione delle richieste SIP con le Servlet, il modello di sicurezza, il Servlet
Deployment Descriptor, documento che accompagna la Servlet e che contiene le
TPSIT 5
434
um 307 di
informazioni per il container su come dev’essere gestita la Servlet e il formato del file di
distribuzione analogo al JAR, simile al formato WAR (Web Archives Repository) utilizzato
dalle Servlet HTTP.
In questo modo si può rispondere alle richieste SIP con interfacce semplici per realizzare
sia UA sia proxy.
I dettagli di basso livello del protocollo, invece, sono demandate al Servlet Container.
È, infatti, il container che si occuperà di gestire le ritrasmissioni dei messaggi in caso di
funzionamento come proxy, di scegliere la risposta migliore tra più risposte a seguito di
richieste concorrenti, generazioni di numeri di sequenza, identificativi di chiamata e
gestione degli header.
La differenza con le altre API è l’uso necessario del Servlet Container e del proxy SIP.
Jain-Sip è il primo progetto proposto per la standardizzazione Java del protocollo SIP.
Le interfacce sviluppate consentono la realizzazione di tutti gli attori coinvolti in una rete
SIP: UAC (User Agent Client), UAS (User Agent Server), proxy, registrar server e redirect
server.
Si basano su eventi e hanno vari oggetti factory per la rapida creazione di request,
response e header.
Eclipse IDE for Java
Un’applicazione che voglia realizzare il VoIP dovrebbe basarsi sul modello a eventi.
In questo modo si definiscono un Listener e un Provider che scambiano, appunto, eventi.
In particolare il Listener si troverà in ascolto degli eventi generati dal Provider.
Gli eventi incapsulano Request e Response e il SipListener rappresenta colui che ascolta
e consuma l’evento.
L’evento è un’entità astratta: quello che c’è dentro l’evento è il messaggio SIP.
Il SipProvider è il fornitore dell’evento che riceve il messaggio dalla rete e lo passa
all’applicazione sempre sotto forma di evento.
Questo modello serve per capire come sono strutturati le interfacce delle API.
Indipendentemente dal tipo di API che si usano, infatti, gli obiettivi e i servizi offerti da Java
sono: fornire metodi per formattare il messaggio in formato SIP; inviare e ricevere i
messaggi; scompattare i messaggi in arrivo; gestire la transazione con timeout, stati e
ciclo di vita.
Resta all’applicazione il compito d’implementare il SipListener per interagire con lo stack
SIP; di registrarsi con il SipProvider per tutti i messaggi e gestire la transazione in modalità
sincrona o asincrona; accedere agli oggetti dello stack e ricevere i messaggi dallo stack
sotto forma di eventi.
Il flusso di un’applicazione dovrebbe essere come quello in figura nella quale sono
coinvolte le classi realizzate in Jain-Sip.
TPSIT 5
434
um 308 di
L’interfaccia SipStack serve per gestire i Provider SIP, può registrare molti listener, è
instanziato dalla SipFactory con un insieme di parametri, definisce le proprietà di
ritrasmissione, le informazioni sui router ed eventuali nuovi metodi realizzati.
In particolare, coordinando la ritrasmissione, solleva il programmatore da una notevole
complessità di gestione.
I parametri da passare per il corretto funzionamento del SipStack sono il suo indirizzo IP, il
nome, il path del router per l’instradamento dei messaggi prima che il dialogo sia iniziato e
il filtro per la ritrasmissione.
L’interfaccia SipProvider ha invece il compito di ricevere gli eventi e notificarli al
SipListener registrato e gestisce le transazioni con scambio di request e response.
L’intrerfaccia SipListener è creata e associata ad un unico SipStack, e tutti i SipProvider
associati al SipStack hanno il medesimo SipListener; gestisce i timeout della transazione e
ritrasmette gli eventi.
Queste interfacce si trovano nel package generale javax.sip di Jain-Sip che definisce
l’architettura generale, le transazioni e gli eventi.
Gli altri package sono relativi al messaggio SIP.
Il package address contiene un wrapper per le URI, definendo interfacce per URI di tipo
Sip e Tel.
Gli indirizzi di un utente, infatti, possono essere ad esempio sip:[email protected],
simili a quelli di posta elettronica, oppure come numero di telefono tel:123456.
Il package message definisce le interfacce per i messaggi request e response.
I messaggi request contengono il tipo di richiesta e l’URI; quelli di response il codice dello
stato della transazione.
Il body di un messaggio può contenere anche la descrizione della sessione in formato
String o Object, come specificato nel SDP (Session Description Protocol).
I tipi di messaggio della request seguono il protocollo SIP e, a livello di codice, sono delle
costanti.
Il package header, naturalmente, contiene tutti gli header supportati e loro possibili
estensioni.
Sono simili agli header HTTP sia per sintassi sia per significato.
In questo package si è deciso di sviluppare ogni possibile header invece di uno solo
generico che gestisse tutte le informazioni, per rendere più esplicita la gestione dei
molteplici header definiti negli standard.
TPSIT 5
434
um 309 di
Per poter scegliere, di volta in volta, tra le diverse alternative, ci sono quattro classi che
implementano il pattern factory.
1. AddressFactory.
2. HeaderFactory.
3. MessageFactory
4. SipFactory.
Per creare indirizzi URI sip e tel, gli header, i messaggi con request e response e gli
oggetti Stack.
I vantaggi di usare questa suddivisione sono la facilità di aggiungere nuovi metodi e
header.
Un’applicazione che voglia far comunicare due utenti si chiama 3PCC (Third Party Call
Control), il dialogo è un’associazione P2P, punto punto, tra due utenti che stabiliscono una
connessione SIP e rappresenta, in maniera astratta, il contesto nel quale interpretare i
messaggi SIP.
Si può osservare che tra i due utenti si stabilisce una connessione RTP (Real-time
Transport Protocol).
Nel codice c’è l’inizializzazione dello stack, usando la SipFactory e impostando i parametri
dello stack.
try {
Properties properties = new Properties();
properties.setProperty("javax.sip.IP_ADDRESS","129.8.9.78");
// inizializzazione altre proprietà
}
try
{ sipStack = sipFactory.createSipStack(properties); }
catch(SipException e)
{ System.exit(-1); }
L’inizializzazione della request specificando, ad esempio, l’URI e il tipo di messaggio,
INVITE.
try {
SipURI requestURI = addressFactory.createSipURI (toUser, toSipAddress);
TPSIT 5
434
um 310 di
// creazione altri header
Request request = messageFactory.createRequest
(requestURI, Request.INVITE, callIdHeader,
cSeqHeader, fromHeader, toHeader, viaHeaders, maxForwards);
}
L’invio del messaggio di request usando una ClientTransaction.
try {
// creazione ClientTransaction
ClientTransaction inviteTid = sipProvider.getNewClientTransaction(request);
// invio request
sipProvider.sendRequest(inviteTid,request);
}
La gestione dei messaggi come eventi usando una ServerTransaction.
try {
public void processRequest(RequestEvent requestEvent)
{ Request request = requestReceivedEvent.getRequest();
ServerTransaction st = requestEvent.getTransaction();
// gestione specifica della request
}
La voce non può essere trattata come i dati e allora serve un protocollo che garantisca
una certa priorità ai pacchetti vocali.
Per realizzare il protocollo RTP, Java si appoggia a JMF (Java Media Framework) che
fornisce un’architettura unificata per la cattura, emissione e manipolazione di dati
multimediali.
JMF permette la riproduzione e trasmissione di stream RTP attraverso l’uso di classi e
interfacce definite in javax.media.rtp, javax.media.rtp.event e javax.media.rtp.rtcp che
sfruttano il modello a eventi.
SIP si occupa di trovare e stabilire una connessione tra gli utenti, specificando le
caratteristiche della comunicazione grazie al SDP presente nel body del messaggio.
RTP permette d’inviare lo stream dei pacchetti della voce e RTPC ne controlla la qualità.
JMF ha il compito di definire la sorgente dalla quale prelevare la voce, convertirla da
formato analogico a digitale, per poterla avere come flusso di byte e stabilire il protocollo
RTP verso il destinatario.
JMF necessita di tutti i dati relativi all’indirizzo dell’utente al quale inviare lo stream della
voce che sono contenuti nel body del messaggio SIP, conformi al SDP.
Sviluppare un’applicazione vuol dire creare un oggetto che permetta, prima di stabilire una
connessione SIP tra utenti e prendere i dati relativi ad un tipo di streaming usando JainSip e poi chiamare JMF per iniziare il protocollo RTP creando così la sequenza dei
pacchetti voce su Internet.
Si deve quindi costruire un oggetto con le API JMF cha cattura la voce, la codifica in
formato digitale opportuno, campionandola ad esempio alla frquenza di 44 KHz e la
associa a una lista di dispositivi audio.
Vector audioDevices=CaptureDeviceManager.getDeviceList(new
AudioFormat(AudioFormat.LINEAR, 44100, 16, 2));
A questo punto si apre il canale di stream per la voce usando i parametri SDP come
TPSIT 5
434
um 311 di
indirizzo e porta del destinatario.
public void openVoiceStream (String sdpData) throws MediaException
{…
SessionDescription sessionDescription=sdpFactory.createSessionDescription(sdpData);
Connection sessionConnection = sessionDescription.getConnection();
// avvio oggetti per inviare e ricevere lo stream voce
}
Per scambiare il flusso voce si usano le classi JMF PushBufferStream e PullBufferStream
per sfruttare le capacità offerte dal Buffer, PushBufferDataSource per catturare il flusso
dati e RTPManager e SendStream per l’invio.
Quindi le API JMF si usano per creare un’applicazione client/server che apre una
connessione e invia uno stream.
Emulare un telefono, è un’applet Java che stabilisce connessioni con altri utenti e scambia
flussi di pacchetti voce.
Il package principale è denominato, applet.phone.
Un livello al di sotto della gerarchia principale ci saranno due cartelle, UA e MEDIA che
costituiranno il nucleo dell’applicazione.
All’interno di ua si trovano le classi per la gestione del telefono e in media quelle per il
flusso dei pacchetti voce.
Per stabilire una connessione con un utente, l’applicazione usa il protocollo SIP.
La classe principale sarà MessengerManager che ha lo scopo di regolare tutte le chiamate
e il loro stato.
Entrando nei dettagli del costruttore della classe, è invocato il gestore delle chiamate,
inizializzata la lista dei contatti e impostato l’indirizzo SIP URI dell’utente.
Il gestore delle chiamate: CallManager che si trova nel package applet.phone.call, ha il
compito di regolare i vari tipi di chiamata.
Infatti, in questo caso, si tratta di una chiamata audio ma è possibile anche semplicemente
avviare una chat testuale.
Se si vuole ampliare lo sviluppo anche alla video chiamata, si dovranno aggiungere nel
CallManager la gestione e l’oggetto specifico.
// classe MessangerManager
public MessengerManager(Configuration configuration,NISTMessengerGUI appletHandle)
{
MediaManager.detectSupportedCodecs();
contactList = new Vector();
callManager = new CallManager();
// create a new instance of the sip Listener
messageListener = new MessageListener(this, configuration,appletHandle);
messageListener.start();
String userURI = messageListener.getConfiguration().userURI;
try {
// create the SIP URI for the user URI
String user = userURI.substring(0,userURI.indexOf("@"));
String host = userURI.substring(userURI.indexOf("@") + 1, userURI.length());
userSipURI = MessageListener.addressFactory.createSipURI(user, host);
} catch (ParseException ex) {
System.out.println(userURI + " is not a legal SIP uri! " + ex);
}
}
TPSIT 5
434
um 312 di
Da sottolineare anche la creazione del MessageListener che invia e si pone in ascolto dei
messaggi SIP.
Come tutti i protocolli che si basano sui messaggi sincroni tra utenti, anche SIP si basa
sull’invio di richieste e risposte, per esempio questo avviene quando un utente manda un
messaggio, INVITE, al secondo utente al quale inizia a squillare il telefono.
Appena l’utente chiamato decide di rispondere manderà un OK al chiamante che
risponderà a sua volta con un ACK, a questo punto può iniziare la sessione voce.
A livello di codice questa sequenza si traduce in una serie di chiamate per creare e
ricevere i messaggi SIP e poi il controllo passa al gestore degli eventi multimediali.
La prima parte da considerare è quindi l’invio dei messaggi SIP tra gli utenti.
Per mandare la richiesta di INVITE si usa il metodo SendInvite che prende in ingresso le
informazioni sul destinatario della telefonata calee-URI e sul tipo di sessione multimediale
da scambiare, sdpBody, ed esegue la creazione della richiesta impostando header e body
del messaggio e gestendo la transazione con l’utente.
// classe MessengerManager
private void sendInvite(String calleeURI, String sdpBody)
{
// creazione messaggio richiesta INVITE
Request invite = createRequest(Request.INVITE,contactURI, userSipURI);
// creazione header e body del messaggio
acceptHeader=MessageListener.headerFactory.createAcceptHeader("audio","x-gsm");
// gestione transazione
ClientTransaction inviteTransaction = null;
inviteTransaction = messageListener.sipProvider.getNewClientTransaction(invite);
// invio richiesta
inviteTransaction.sendRequest();
}
TPSIT 5
434
um 313 di
L’oggetto ClientTransaction appartiene alle API standard contenute nella libreria Jain-Sip.
Da questo punto in poi si occuperanno loro di gestire la richiesta.
Quando il chiamato riceve la chiamata e vuole rispondere, devono essere invocati i metodi
per la gestione della sessione multimediale, il principale è processInviteOK che è
contenuto nella classe MessageProcessor ed è invocato dal MessageListener.
// classe MessageProcessor
public void processInviteOK(ClientTransaction clientTransaction,Response inviteOK)
{
CallManager callManager=messageListener.sipMeetingManager.getCallManager();
// ricerca della chiamata Audio
AudioCall call = callManager.findAudioCall(callee);
// invio ACK
try {
TPSIT 5
434
um 314 di
Request ack=(Request)clientTransaction.getDialog().createRequest(Request.ACK);
clientTransaction.getDialog().sendAck(ack);
if (type.equals("application") && subType.equals("sdp")) {
// inizio sessione multimediale
MediaManager mediaManager =call.getMediaManager();
call.setVoiceMesaging(false);
mediaManager.prepareMediaSession(new String(inviteOK.getRawContent()));
mediaManager.startMediaSession(true);
}
}
}
// gestione degli stati della chiamata
call.setStatus(AudioCall.IN_A_CALL);
messageListener.sipMeetingManager.notifyObserversNewCallStatus(call);
}
Come per la gestione dei messaggi esiste il metodo MessageManager, così esistono i
gestori per le chiamate CallManager e per la sessione multimediale MediaManager.
L’interfaccia Call contiene gli elementi che devono essere implementati nella classe,
proprio come fa l’oggetto AudioCall.
In altri messaggi del protocollo, un ruolo di spicco assume anche il body Sdp che serve
per far capire agli utenti le informazioni multimediali da scambiare, come il tipo di codifica
supportata e su quale porta viaggerà la voce.
Dopo aver permesso agli utenti di scambiarsi informazioni sulla modalità con la quale
scambiarsi i pacchetti voce, i due utenti possono finalmente iniziare la conversazione.
Per avviare uno stream con i pacchetti voce sono stati scelti i protocolli RTP e RTCP che,
insieme, riescono a gestire contenuti multimediali impostando velocità di trasmissione e
qualità del servizio, la chiamata che avvia questo processo è startMediaSession ed è
gestita dal MediaManager.
Le classi che realizzano il protocollo sono in un package a parte applet.phone.media.
L’oggetto MediaManager si occupa di gestire la sessione multimediale aprendo e
chiudendo gli oggetti receiver e transmitter che gestiscono il flusso voce.
La chiamata per avviare questi oggetti avviene con il metodo startMediaSession.
// classe MediaManager
public void startMediaSession(boolean transmitFirst)
{ if (!started) {
startReceiving();
startTransmitting();
}
}
In questo modo si arriva a instanziare i vari oggetti, come Transmit che serve per
realizzare il protocollo RTP sfruttando JMF che a sua volta, usa gli oggetti JMF come
RTPManager, MediaLocator, SessionAddress e Socket per il flusso relativo ai protocolli
RTP e RTCP; per gestire il flusso voce serve a catturarla e poi trasmetterla.
Per il primo compito, la classe Trasmit usa il metodo createProcessor che prende la
sorgente dati che si desidera utilizzare ed è gia predisposto per audio, video, o entrambi
per il secondo, usa createTrasmitter.
// classe Transmit
private String createProcessor()
TPSIT 5
434
um 315 di
{DataSource audioDS=null;
DataSource videoDS=null;
DataSource mergeDS=null;
StateListener stateListener=new StateListener();
// creazione del DataSource
if (audioLocator != null) {
// creazione DataSource tipo audio
audioDS= javax.media.Manager.createDataSource(audioLocator);
}
if(videoDS!=null && audioDS!=null) {
try {
// creazione merge audio-video
mergeDS = javax.media.Manager.createMergingDataSource(new DataSource []
{audioDS, videoDS}); }
catch (Exception e)
{ System.out.println("Non si connettte ad audio o device di cattura video"); }
// configurazione del processor
boolean result = stateListener.waitForState(processor, Processor.Configured);
// tracce del processor
TrackControl [] tracks = processor.getTrackControls();
// impostazione dei parametri delle tracce
conformi a RTP
// output data source del processor
dataOutput = processor.getDataOutput();
}
Anche gli oggetti di tipo TrackControl fanno parte di JMF e sono gestiti dal framework.
Il metodo createTransmitter si appoggia a RTPManager per creare sessioni per ogni
traccia del processor e gestisce il flusso della voce a livello di buffer.
// classe Transmit
private String createTransmitter(String localIpAddress)
{PushBufferDataSource pbds =(PushBufferDataSource)dataOutput;
PushBufferStream pbss[] = pbds.getStreams();
rtpMgrs = new RTPManager[pbss.length];
SessionAddress localAddr, destAddr;
InetAddress ipAddr;
SendStream sendStream;
SourceDescription srcDesList[];
…
for (int i = 0; i < pbss.length; i++) {
try {
rtpMgrs[i] = RTPManager.newInstance();
// impostazione parametri connessione con host remoto per RTP
while(!connected) {
socketRTPTransmit = new Socket(ipAddr,destPort);
connected=true;
}
// connessione con host remoto per RTCP
while(!connected) {
socketRTCPTransmit = new Socket(ipAddr, rtcpDestPort);
connected=true;
TPSIT 5
434
um 316 di
}
rtpMgrs[i].initialize(new TCPSendAdapter(socketRTPTransmit,socketRTCPTransmit));
}
// inizio trasmissione con host remoto
sendStream = rtpMgrs[i].createSendStream(dataOutput, i);
sendStream.start();
}
catch (Exception e)
{ e.printStackTrace(); }
Una volta aperti i socket, avviene l’invio dei dati voce per mezzo dell’oggetto SendStream
anch’esso appartenente a JMF.
RTPManager gestisce i protocolli RTP e RTCP e c’è anche la gestione di TCP e UDP a
seconda delle caratteristiche del protocollo di trasporto che si desidera impostare.
In modo del tutto analogo al trasmettitore lavora l’oggetto Receiver che si occupa di
ricevere lo stream voce.
JMF viene incontro anche alla modalità di cattura delle sorgenti voce.
Infatti, nella classe MediaManager, il metodo detectSupportedCodecs serve proprio ad
ottenere una lista di tutti i dispositivi audio e video disponibili con le loro caratteristiche.
TPSIT 5
434
um 317 di
TV OVER IP
INTRODUZIONE
È la trasmissione di audio e video su protocollo TCP/IP, per proiettare contenuti
multimediali su Internet.
Tv Over IP significa visualizzare sul proprio televisore di casa una trasmissione televisiva,
scegliendola quando e come si vuole da una televisione basata su Internet.
Protocollo MMS (Microsoft Media Services)
È un protocollo a livello applicazione, utilizzato per lo streaming audio/video.
Usa il protocollo UDP ma non è raro il suo utilizzo sopra TCP.
La porta MMS di default è 1755.
Protocollo RTSP (Real Time Streaming Protocol)
È stato sviluppato da RealNetworks, Netscape Communications e Columbia University.
Ottimizza il flusso di dati, è stato progettato per essere simile a HTTP.
Il protocollo RTSP non si occupa direttamente della trasmissione dello streaming.
La maggior parte dei server utilizza RTP per il trasporto del flusso multimediale ma alcuni
produttori utilizzano protocolli di trasporto proprietari.
La porta RSTP di default è 554.
Protocollo RTP
È un protocollo del livello applicazioni che permette la distribuzione di servizi che hanno
bisogno di trasferimento in tempo reale, come l'interattività audio e video.
WMS (WINDOWS MEDIA SERVICES)
È un servizio installabile su Windows, il cui complemento è il Windows Media Player.
Per visualizzare contenuti video provenienti da un server di streaming WMS si usa
Windows Media Player, basta fare clic su File/Apri URL… (CTRL+U) e digitare l’indirizzo
seguente per connettersi al server in questione e poter visualizzare il file.
mms://indirizzodelserver/indirizzodipubblicazione/<opzionale indirizzo del video>
Un client che apre un file tramite mms:// prova prima a utilizzare RTSP su UDP e se
fallisce, prova RTSP su TCP.
Dopo aver fallito con RTSP, prova MMS su UDP, quindi MMS su TCP.
Se MMS fallisce prova una versione modificata di HTTP su TCP.
Qual è il vantaggio di utilizzare un servizio di streaming rispetto a consentire ad un utente
di scaricare un file per visualizzarlo in un secondo momento?
Prima di tutto un server di streaming consente la diretta, inoltre nel caso in cui si volessero
mettere a disposizione contenuti registrati in precedenza, l’utente sarebbe sempre
costretto a visualizzarli sul sito.
In pratica, un video o un file MP3 (Motion Picture Expert Group-1/2 Audio Layer 3) non
può essere copiato, può essere solo riprodotto.
È possibile anche proteggere i contenuti con una password e fornire l’accesso solo agli
utenti registrati al servizio.
Infine, un server di streaming consente di gestire la pubblicità, il palinsesto e la banda.
TPSIT 5
434
um 318 di
TPSIT 5
434
um 319 di
WME (WINDOWS MEDIA ENCODER)
WME consente di catturare audio e video da una fonte esterna, comprimere i dati in tempo
reale e inviarli ad un WMS che si occuperà di renderli disponibili a tutti i client che ne
vorranno fare uso.
Per esempio, se al PC è collegata una telecamera, WME catturerà il segnale e lo invierà
verso il WMS.
A questo punto un qualunque utente dotato di Windows Media Player potrà connettersi al
servizio e visualizzare in tempo reale i dati trasmessi dalla telecamera.
Due tipi di trasmissione.
1. Live streaming: diretta.
2. TV On Demand: trasmissione preregistrata.
Singolo filmato
S’intende quello che comunemente è indicato come servizio, per esempio un filmato
singolo di un telegiornale.
Trasmissione televisiva
S’intende un insieme di servizi, per esempio un telegiornale.
Emittente televisiva
S’intende un soggetto che propone una serie di trasmissioni televisive organizzate in
palinsesti, per esempio la RAI (RAdiotelevisione Italiana).
S’indica una trasmissione televisiva come PP (Publishing Points), ciascuno sarà composto
da uno o più filmati, i PP potranno trasmettere due modalità.
1. Broadcasting: s’intende che il video sarà trasmesso sequenzialmente e tutti i client
che si connetteranno per guardarlo lo visualizzeranno in maniera sincrona, in altre
parole ciascun client vedrà scorrere le stesse immagini nello stesso tempo, utilizzata
nelle dirette.
2. Unicasting: consente a ciascun client di avviare la ripresa del video in modalità
asincrona, scegliendo quando e come avviare il video indipendentemente dagli altri,
utilizzata per la TV On Demand.
Ciascun PP può avere delle caratteristiche, per esempio una sigla iniziale e una sigla
finale che devono essere sempre visualizzate qualunque sia il servizio che è richiesto
dall’utente.
Oppure pubblicità agganciata ad un particolare servizio sotto la forma di banner.
Di tutte queste operazioni si occupa il WMS.
PROGRAMMAZIONE
È disponibile il WME SDK che permette d’interagire con gli strumenti dell’encoder.
using System.Windows.Forms;
using WMEncoderLib;
public partial class Form1 : Form {
WMEncoder Encoder;
IWMEncSourceGroupCollection SrcGrpColl;
IWMEncSourceGroup SrcGrp;
IWMEncSource SrcAud;
IWMEncVideoSource SrcVid;
IWMEncProfileCollection ProColl;
IWMEncProfile Pro;
IWMEncPushDistribution PushDist;
TPSIT 5
434
um 320 di
string strServerName,strPubPoint,strPubTemplate,MyNSCFile,MyNSCURL,MyASXFile;
public Form1()
{ InitializeComponent(); }
private void Form1_Load_1(object sender, System.EventArgs e)
{ // crea l'oggetto WMencoder
Encoder = new WMEncoder();
}
private void Button1_Click(System.Object sender, System.EventArgs e)
{ if (OpenFileDialog1.ShowDialog() == Windows.Forms.DialogResult.OK)
ListBox1.Items.Add(OpenFileDialog1.FileName);
}
private void Button2_Click(System.Object sender, System.EventArgs e)
{
SrcGrpColl = Encoder.SourceGroupCollection;
// crea un gruppo chiamato SG_1.
SrcGrp = SrcGrpColl.Add("SG_1");
// specifica la sorgente
// commentata la linea che recupera una sorgente audio
//SrcAud = SrcGrp.AddSource(WMENC_SOURCE_TYPEWMENC_AUDIO)
SrcVid = SrcGrp.AddSource(WMENC_SOURCE_TYPE.WMENC_VIDEO);
// specifica il path del file sorgente, lo recuperiamo dalla listbox
//SrcAud.SetInput("c:\audio.wav")
SrcVid.SetInput(ListBox1.Items(0).ToString());
// se vogliamo prendere come sorgente un device
//SrcAud.SetInput("device://default_audio_device")
//SrcVid.SetInput("device://default_video_device")
// Stabilice il profilo da utilizzare per la codifica.
ProColl = Encoder.ProfileCollection;
Pro = ProColl.Item(2);
SrcGrp.Profile = Pro;
// definisce l'encoding in broadcast
PushDist = Encoder.Broadcast;
// definisce il nome del PP e la porta
strServerName = "localhost:8888";
strPubPoint = "publicationpoint";
// rimuove il PP quando la trasmissione è finita
PushDist.AutoRemovePublishingPoint = true;
PushDist.ServerName = strServerName;
PushDist.PublishingPoint = strPubPoint;
Encoder.PrepareToEncode(true);
// start encoding.
Encoder.Start();
MsgBox("Click OK to stop broadcasting.");
}
}
TPSIT 5
434
um 321 di
EMULE
INTRODUZIONE
La tecnologia che si nasconde dietro al file sharing, P2P, è raffinata quanto semplice.
La storia di eMule inizia il 13 maggio 2002, quando Hendrik Breitkreuz, un programmatore
tedesco, conosciuto come Merkur, insoddisfatto del client originale per la rete
eDonkey2000, inizia a lavorare al progetto.
eMule consente di connettersi sia alla rete eDonkey sia alla rete Kad.
Fra le caratteristiche che lo hanno portato ad essere uno dei client P2P maggiormente
utilizzati, vi sono lo scambio diretto fra le sorgenti localizzate nei PC client, recupero dei
download interrotti o corrotti, l’utilizzo di un sistema a crediti che incoraggia la condivisione
e l’upload, la trasmissione in formato compresso ZLIB, per risparmiare larghezza di banda.
eMule è stato sviluppato e continua ad esserlo, in Visual C++ è rilasciato sotto licenza
GPL (GNU General Public License), ed è disponibile, tramite progetti paralleli da esso
derivati, anche sotto altre piattaforme, per esempio esiste il client xMule per Linux.
Sempre più lunga anche la lista di altri progetti, detti Mod che apportano modifiche
all’originale: eMule Extreme, eMule plus, aMule.
In inglese Donkey significa “asino”, quindi Merkur scelse un nome simile: Mule, significa
“mulo”, la e iniziale di eDonkey ed eMule indica il termine inglese electronic, letteralmente
elettronico.
ARCHITETTURA
Una rete eMule è costituita da centinaia di macchine che agiscono da server eMule e
milioni di PC nelle case degli utenti che svolgono il ruolo di client eMule.
I client devono come primo passo connettersi ad un server, per ottenere accesso alla rete
eMule.
La connessione al server rimarrà attiva per tutto il tempo in cui il client resterà connesso
alla rete eMule.
Un server eMule esegue compiti d’indicizzazione dei contenuti e non comunica mai con
altri server eMule.
Ogni client invece possiede una lista di server eMule, oppure può essere aggiornato con
TPSIT 5
434
um 322 di
un nuovo elenco, ottenibile sia sui siti specializzati, oppure richiedendolo mediante
apposito comando ad un server cui esso possa connettersi.
Ogni client inoltre possiede un proprio elenco di file che mette a disposizione della
comunità eMule, vale a dire la lista dei file condivisi.
La connessione di un client ad un server eMule avviene utilizzando il protocollo TCP,
tramite esso si ottiene l’elenco dei file disponibili e dei client sui quali questi sono presenti.
Dopo aver effettuato l’accesso, mediante altre centinaia di connessioni TCP, un client
comunica con i suoi pari, per effettuare l’upload o il download di file.
Tale processo è regolato mediante code di accesso ad ogni determinato file.
Ogni coda è formata dai client che hanno richiesto lo stesso file e che mano a mano che
uno di essi completa il download permette l’avanzamento di un altro client.
Per migliorare le prestazioni e velocizzare il download, ogni client non effettua il download
di un intero file da un singolo altro client.
Ogni file è invece suddiviso in parti, ognuna delle quali può essere richiesta e scaricata da
un client diverso.
In questo modo possono essere sfruttate centinaia di sorgenti differenti e
contemporaneamente il client può fornire le parti che ha già scaricato mettendole a
disposizione di altri client.
Il protocollo eMule prevede, inoltre, un sistema di crediti che avvantaggia i client che
mettono a disposizione un maggiore numero di file, incoraggiando la condivisione.
Ogni server eMule mantiene un DB, con le informazioni relative ai client e ai file condivisi
ma non memorizza alcun file, agendo solo da indicizzatore.
Architettura di alto livello di una rete di server e client Emule per la condivisione di file.
L’elenco dei server utilizzati da eMule è contenuto in un file SERVER.MET, presente nella
cartella di avvio dell’applicazione.
L’elenco può essere aggiornato sia fornendo l’URL di un SERVER.MET presente in
Internet, oppure è previsto dal protocollo eMule un comando OP_SERVERLIST (32H) che
permette di effettuare una richiesta al server eMule cui un client è connesso per richiedere
TPSIT 5
434
um 323 di
una lista dei server aggiornata.
PROGRAMMAZIONE
Formato dei messaggi
Ogni messaggio ha un’intestazione lunga 6 byte, suddivisi in tre parti.
Il primo byte indica il protocollo utilizzato ed ha il valore E3H per eDonkey, oppure C5H
per il protocollo eMule.
I successivi 4 byte indicano la lunghezza del messaggio in byte, escluso l’header stesso,
quindi un messaggio formato solo dall’intestazione ha come dimensione il valore zero.
Il terzo campo dell’intestazione, in pratica l’ultimo byte, è l’identificatore univoco del
messaggio stesso, un ID che indica il comando.
Classe che rappresenta un’intestazione di messaggio.
public class MessageHeader {
public Protocol.ProtocolType protocolID;
public uint packetSize;
public byte Command;
public MessageHeader(byte command):base()
{ protocolID = Protocol.ProtocolType.eDonkey;
Command = command;
packetSize = 6;
TPSIT 5
434
um 324 di
}
public void Serialize(BinaryWriter writer)
{ writer.Write((byte)protocolID);
writer.Write(packetSize);
writer.Write(Command);
}
}
Il metodo Serialize serve per scrivere l’intestazione su uno stream da spedire in rete.
Per appendere informazioni aggiuntive ai messaggi eMule, sono utilizzate delle strutture
chiamate TAGS e strutturate secondo una sequenza Tipo-Lunghezza-Valore, per questo
sono detti anche TAG TLV.
Esistono diversi TAG differenti, documentati nel protocollo di eMule liberamente
scaricabile dalla rete.
Ogni TAG è formato da 4 campi, non tutti però sono sempre inclusi nel corpo del
messaggio.
Tipo
Nome
Valore
Special
1 byte, indica il tipo del TAG.
Può essere formato da una stringa di lunghezza variabile o da
un intero di 1 byte.
Può essere un intero di 4 byte, un numero floating di 4 byte,
oppure una stringa di lunghezza variabile.
È un intero di 1 byte che identifica un TAG speciale.
TAG con valori interi sono detti integer TAGS, si hanno string TAGS e float TAGS.
Il tipo di un string TAG è 2, di un integer TAG è 3 e di un float TAG è 4.
Quando i TAG sono codificati per essere spediti in rete, è seguito l’ordine seguente: prima
il tipo, poi il nome e infine il valore.
I nomi dati ai TAG non hanno un significato particolare per il protocollo, sono solo
assegnati per un più semplice ed immediato riferimento nella descrizione dei messaggi.
Per leggere un TAG occorre implemetare una classe TagReader.
Analogamente si deve implementare la classe per scrivere un TAG.
Comandi e OpCode
Ogni comando eMule è identificato da uno specifico codice esadecimale, detto OpCode.
Ogni comando inoltre prevede una serie di parametri da specificare.
La connessione avviene inviando un comando con OpCode OP_LOGINREQUEST, pari al
valore 01H.
Per evitare di specificare ogni OpCode di volta in volta e rendere più facilmente gestibili i
comandi s’implementano delle enumerazioni.
Per esempio, per i comandi da inviare al server un’enumerazione ServerCommand con
relativi OpCode.
public enum ServerCommand : byte {
LoginRequest = 0x01,
ServerMessage = 0x38,
IDChange = 0x40,
GetServerList = 0x14,
OfferFiles = 0x15,
SearchRequest = 0x16,
ServerList = 0x32,
SearchResult = 0x33,
TPSIT 5
434
um 325 di
GetSources = 0x19,
QueryMoreResults = 0x21,
FoundSources = 0x42,
CallBackRequest = 0x1C,
ServerState = 0x34,
ServerIdent = 0x41,
CallBackRequested = 0x35,
}
Allo stesso modo si possono implementare enumerazioni per i comandi client o per i valori
dei campi di un TAG.
Per un server eMule per esempio si possono specificare e ottenere le seguenti
informazioni.
public enum ServerTag : byte {
Name = 0x01,
Description = 0x0B,
Ping = 0x0C,
Priority = 0x0E,
Fails = 0x0D,
DynIP = 0x85,
LastPing = 0x86,
MaxUsers = 0x87,
}
Connessione ad un server
Per connettersi ad un server eMule è necessario inviare l’apposito comando di login, con
OpCode LoginRequest e stabilire una connessione TCP.
Per rappresentare una connessione al server si deve progettare una classe
ServerConnection, derivata da una generica Connection che utilizzerà i socket per creare
una comunicazione TCP ad un indirizzo IP su una data porta.
La classe Connection possiede i seguenti campi.
public class Connection {
protected Socket m_socket;
protected IPEndPoint m_EPremote;
}
Al metodo CreateConnection si passeranno l’indirizzo IP e la porta verso cui effettuare la
connessione.
Il metodo Connect invierà una richiesta asincrona di connessione, indicando il metodo
OnConnected come metodo che si occuperà di gestire la risposta del server una volta
ricevuta una risposta.
All’interno del metodo OnConnected è innanzitutto impostato l’istante dell’avvenuta
connessione e azzerati i tentativi falliti.
Un’altra classe, Server, si occuperà d’inviare e ricevere i messaggi dai server eMule.
Il metodo SendLoginRequest invia il messaggio di login suddetto, costruendo un oggetto
ServerLoginRequest che si occupa di riempire e formattare un buffer contenente la
richiesta di login e spedendo poi il messaggio mediante il metodo SendPacket.
La classe ServerLoginRequest serve a formattare il messaggio di login, costruendone
l’intestazione, mediante l’utilizzo degli appropriati TAG.
Il messaggio di login, come specificato nella documentazione del protocollo eMule, deve
TPSIT 5
434
um 326 di
contenere innanzitutto un’intestazione e poi una serie di altre informazioni come l’ID
utente, versione del client, nome del client.
L’intestazione è costruita mediante la classe MessageHeader al cui costruttore è passato il
valore ServerCommand.LoginRequest.
La lunghezza del messaggio varia quindi in base alla configurazione dell’utente, per
esempio dal suo nickname.
Al ricevimento di un messaggio di risposta, dopo aver letto e processati i byte del
messaggio.
A connessione avvenuta, il metodo ProcessIncomingPacket verifica il comando specificato
nell’intestazione del messaggio e intraprende l’azione opportuna.
Nel caso del messaggio di login, sarà ricevuto come risposta un messaggio
ServerMessage.
Tale messaggio può avere lunghezza ed è spedito da un server in diverse occasioni, non
solo dopo una richiesta di login.
Un singolo ServerMessage può contenere diverse righe di testo separate da caratteri new
line, (‘\r’,’\n’ o entrambe).
Messaggi che iniziano con il testo ”server version”, ”warning”, ”error” e ”[emDynIP:” hanno
un significato per il client e possono essere trattati in maniera speciale.
Gli altri messaggi possono semplicemente essere mostrati all’utente.
Nel metodo ProcessIncomingPacket, esiste un case per elaborare un ServerMessage,
all’interno del quale è invocato il metodo SetMessage della classe Server.
Tale metodo semplicemente interpreta i byte ricevuti ed estrae il testo del messaggio.
Oltre all’intestazione il messaggio, infatti, conterrà due byte che specificano la lunghezza
della stringa successiva e appunto una stringa di testo, il messaggio può poi essere
mostrato all’utente, mediante un’interfaccia grafica simile a quella di eMule.
ID
Basta leggere un intero per ottenere l’ID e poi verificare se il server ha specificato altri flag.
Attualmente è possibile che il server spedisca infatti altri 4 byte e se il valore convertito in
intero è uno indica che è supportata la compressione.
In risposta alla richiesta di login, un server che accetta la connessione invierà anche un
messaggio chiamato ID Change che assegna un ID al client in base a determinati
parametri.
TPSIT 5
434
um 327 di
Il client ID è un identificatore a 4 byte ed è valido solo per il ciclo di vita di una sessione di
connessione client-server, i client ID possono essere distinti in alti e bassi.
Un ID basso è in genere assegnato da un server quando questi si accorge che il client non
può accettare connessioni in ingresso.
Avere un ID basso diminuisce le potenzialità di utilizzo della rete eMule, diminuendo
notevolemente le capacità di download, in quanto il server dovrà agire da intermediario
nello scambio dei file.
L’ID è calcolato in base all’indirizzo IP, in quanto è questo che influenza la capacità da
parte di altri client di connettersi direttamente ad un’altra macchina su una data porta.
Se il server cui ci si connette non riesce ad aprire una connessione TCP verso il client,
esso assegnerà un ID basso.
Ciò accade generalmente se si utilizza un firewall per bloccare determinate porte.
Un ID basso si avrà anche se ci si connette attraverso NAT (Network Address Translation)
o proxy o se il server è troppo occupato.
Un ID è calcolato secondo la seguente formula.
Se l’IP del client è X.Y.Z.W, l’ID sarà pari al valore seguente.
X + 28*Y + 216*Z + 224*W
Se il valore risultante è inferiore a 16777216 (0x1000000) si tratta di un ID basso.
Per controllare più agevolmente lo stato dell’ID, si controllano le frecce in diagonale poste
sul piccolo globo azzurro posizionato nella barra di stato del client, in basso a destra.
La freccia inferiore sinistra indica il collegamento al server, quella superiore destra indica il
collegamento alla rete Kad.
La freccia rossa indica: non connesso.
La freccia verde indica: connesso con ID alto.
La freccia gialla indica: connesso con ID basso, firewalled per la rete Kad.
Da non confondersi con le frecce poste in verticale, rosse o verdi che indicano
un’attività/inattività di download/upload.
I crediti sono uno dei modificatori per il calcolo del punteggio nella coda di upload.
In eMule un client accumula crediti verso un altro client in base alla quantità di dati
scambiati reciprocamente e variano da 1 a 10 calcolati con le seguenti formule.
Dove.
C rappresenta i crediti.
Bi indica i byte inviati.
Br indica i byte ricevuti.
MBi i MB inviati.
eMule assegnerà i crediti scegliendo il risultato minore tra le due precedenti formule.
Qualche mod di eMule fa scaricare pochi KB e poi blocca l’invio del file, cercando di
guadagnare gli stessi crediti rispetto ai client “leali” che fanno scaricare l’intera parte.
Per questo motivo è stato modificato il calcolo dei crediti da riconoscere agli altri utenti per
i primi 9 MB ricevuti, non permettendo questa ingiustizia.
La priorità di upload di un file condiviso descrive il livello d’importanza di condivisione,
può essere impostato manualmente oppure in automatico da eMule.
TPSIT 5
434
um 328 di
Il variare del valore associato va a modificare il punteggio nella coda di upload.
Queste sono le proprietà e i valori utilizzati da eMule.
Release
Alta
Normale
Bassa
Molto Bassa
1.8
0.9
0.7
0.6
0.2
I download in eMule funzionano grazie ad un meccanismo di code.
Ogni richiesta di upload è posta in una “lista d’attesa” propria di ogni eMule alla quale è
assegnato un punteggio calcolato nel modo seguente.
punteggio = tempo di attesa * priorità del file * crediti
Il tempo di attesa, espresso in secondi, indica da quanto tempo un client è entrato in coda
per il download di un file.
Esempio, di due client che si trovano in coda fra di loro da 20 minuti, senza crediti fra loro:
il primo ha il file in priorità release, l’altro ha lo stesso file ma in priorità bassa,
naturalmente hanno tutti e due il file incompleto.
1. Punteggio del client con il file in priorità release che richiede il file dell’altro client in
priorità bassa: 1200 * 0.6 * 1 = 720.
2. Punteggio del client con il file in priorità bassa che richiede il file dell’altro client in
priorità release: 1200 * 1.8 * 1 = 2160.
Si può notare che più si rimane connessi, più il punteggio aumenta.
Comunque è meglio lasciar gestire le priorità a eMule in automatico e usare la priorità
release per file più unici che rari.
Durante il download di un file eMule può essere in coda solamente per un file alla volta, se
un client possiede più file tra quelli scaricati eMule utilizza quindi le fonti A4AF (Asked For
Another File), in altre parole Contattato Per Un Altro File per entrare nella coda di un file
quando il download del precedente è terminato.
eMule assegna le fonti A4AF ad un file in base alla sua priorità.
Vi sono 9 livelli distinti di priorità determinati dalla priorità della categoria e del file.
Priorità finale
1
2
3
4
5
6
7
8
9
Priorità della categoria
Alta
Alta
Alta
Normale
Normale
Normale
Bassa
Bassa
Bassa
Priorità del file
Alta
Normale
Bassa
Alta
Normale
Bassa
Alta
Normale
Bassa
Ricerca dei file
Per avviare la ricerca di un file nella rete eMule basta utilizzare il comando SearchRequest
cui il server risponderà con un comando SearchResult contenente l’elenco degli eventuali
risultati.
Nel comando di ricerca è possibile specificare i diversi parametri da utilizzare, come la
TPSIT 5
434
um 329 di
dimensione massima o minima dei file, il tipo di contenuto da ricercare e l’estensione.
La risposta del server è se tutto va bene un comando SearchResult, in cui è specificato il
numero n di risultati e quindi di seguito n elementi in un determinato formato.
Il metodo SearchResult mostra come è possibile interpretare il messaggio dei risultati, il
costruttore legge il numero di risultati nRes e poi esegue un ciclo for per ricavare i dati dei
file trovati.
Il metodo ExtractResult legge la parte seguente del messaggio interpretando i singoli
TAG.
Per ogni risultato ricava dunque il nome, la dimensione, il numero di sorgenti, l’ID del file o
altri parametri come il bitrate o l’artista se si tratta per esempio di un MP3.
TPSIT 5
434
um 330 di
TOR (THE ONION ROUTER)
INTRODUZIONE
È un S/W open source, il cui acronimo significa “instradamento a cipolla”, il motivo di un
nome così originale dipende dall’algoritmo su cui è basato il funzionamento della rete.
I nodi, relay, si occupano di smistare i messaggi da e verso gli altri nodi della rete.
I messaggi non seguono un percorso logico o diretto dalla sorgente alla destinazione ma
percorrono la rete in modo disordinato e tortuoso, cancellando periodicamente le tracce.
Grazie al sofisticato algoritmo adottato dalla rete nessun osservatore situato in un singolo
punto può capire da dove proviene e dove va ogni singolo messaggio, in pratica ogni nodo
conosce solo il nodo da cui ha ricevuto il pacchetto e a quale nodo deve inoltrarlo.
Per ogni singolo hop da un relay all’altro è usato un insieme di chiavi diverse per la
cifratura, così da evitare che qualche nodo possa tracciare le connessioni.
Quando la rete è riuscita a stabilire un percorso tra una sorgente e una destinazione
continua ad usare questo “circuito” per tutte le comunicazioni che avvengono tra i due
nodi, per 10 minuti.
Dopo questo periodo di tempo è creato automaticamente un nuovo circuito.
L’installazione e la configurazione del client è semplice e richiede pochi passaggi.
Una volta che il S/W si è collegato alla rete bisogna configurare tutti i S/W che devono
collegarsi a Internet per l’uso di un server proxy basato sul protocollo Socks, con indirizzo
127.0.0.1 e porta TCP 9051.
TPSIT 5
434
um 331 di
Nella sua versione di base il client è un eseguibile a riga di comando che si limita ad
avviarsi e a collegarsi, restando in attesa di connessioni sulla porta 9051.
TPSIT 5
434
um 332 di
Il progetto TOR ha sviluppato anche un’UI per la configurazione e gestione del client,
chiamata Vidalia, questo curioso nome deriva da una varietà di cipolle rosse dolci,
apprezzate in America, originarie di Vidalia, in Georgia.
L’uso dell’interfaccia grafica permette di tenere sotto controllo lo stato della rete, di
visualizzare e modificare le principali impostazioni e persino di visualizzare la rete TOR su
una mappa del pianeta, per vedere dove si trovano tutti i nodi che ne fanno parte.
TPSIT 5
434
um 333 di
BT (BITTORRENT)
INTRODUZIONE
È un protocollo di tipo P2P finalizzato alla distribuzione e condivisione di file nella rete.
In realtà, non è un vero e proprio protocollo P2P, visto che l’architettura di BT prevede la
presenza di un server.
L’omonimo client originale e il protocollo sono stati sviluppati da Bram Cohen, un
programmatore di San Francisco, nel 2002.
Un file con estensione TORRENT è un piccolo file che può essere pubblicato su un
qualunque sito web.
Ne esistono migliaia che offrono funzioni di ricerca per genere, per tipo, per categoria, si
chiamano Torrent Finder, i principali sono i seguenti.
Btjunkie.org
Bittorrent.com
Torrentreactor.net
Isohunt.com
Meganova.org
Mininova.org
ThePirateBay.org
Torrentportal.com
Torrentspy.com
Torrentz.com
Il punto di partenza è trovare un file TORRENT da prelevare, tramite esso il client potrà
sapere di quali e quanti pacchetti è composto il file originale, incluse le informazioni
necessarie a confermare l’integrità e la “bontà” dello stesso.
Naturalmente il file TORRENT contiene anche l’indirizzo URL del server che conosce le
sorgenti dei vari frammenti del file originale.
Il server è chiamato Tracker ed è esso che si occupa di coordinare le varie richieste dei
client che richiederanno di scaricare il file.
Il tracker fornisce informazioni sui file TORRENT che gestisce, per esempio quanti client
posseggono una copia del file, intera o parziale o quante volte esso è stato scaricato.
Una volta che un client richiede un file e inizia a scaricarne un frammento, esso può
diffonderlo agli altri pretendenti, grazie alla collaborazione del tracker.
Per il tracker non transita neppure un byte dei file che si stanno scambiando, è su ciò che
si basa la difesa della legalità di questi server: i contenuti sono scambiati direttamente da
P2P.
TPSIT 5
434
um 334 di
In questo modo ogni file è diffuso in maniera estremamente rapida ed efficiente, perché
tutti i client e non solo i server contribuiscono alla diffusione stessa e inoltre è anche
eliminato alla base il problema dovuto al fatto che molti utenti scaricano il file ma poi, una
volta completato si disconnettono impedendo ad altri di poterne ottenere una copia.
Il file Seed, seme, è una copia completa online del file che può essere ospitato su un
qualunque sito web purchè connesso in banda larga.
Nel momento in cui un client invia una richiesta di download del file, il trasferimento non
parte fino a che non sia trovato il file Seed.
Questo sistema incrementa la velocità di download, soprattutto quando ci sono molti utenti
che stanno scaricando uno stesso file.
Un client che ottiene una copia completa del file da scaricare diverrà anch’esso Seeder.
Anche client che non hanno il file completo possono condividere la loro parte, tali client
sono chiamati Peer e contribuiscono alle prestazioni del protocollo come i Seed.
Chi scarica un file e si disconnette non permettendo la diffusione dello stesso è Leech.
Maggiore è il numero di Seed e Peer, maggiore sarà la velocità di download.
Allo stesso modo se ci sono moltissimi Peer e pochi Seed, inizialmente si avrà una
maggiore velocità, mentre man mano che si procede meno client avranno il file completo e
minore sarà la velocità di download per riuscire a completare il file.
Le specifiche di BT sono state rese pubblicamente fruibili e grazie a ciò molti
programmatori le hanno potute implementare liberamente.
PROGRAMMAZIONE
I progettisiti di Mono per gestire il protocollo Torrent hanno realizzato la libreria
MONOTORRENT.DLL, un set completo di API, grazie al quale, è possibile creare un client
senza dover scrivere nulla riguardante il protocollo stesso o che abbia a che fare con la
comunicazione di rete di basso livello.
Al progetto aggiungere il riferimento all’assembly MONOTORRENT.DLL.
La classe da cui partire si chiama ClientEngine, quella del progetto MyTorrentController
con un campo private di tale tipo m_clientEngine che permetterà di trattare caricamento e
download dei file TORRENT.
File MYTORRENTCONTROLLER.CS
Mostra il costruttore che si occupa d’inizializzare tutti i campi privati della classe e la
relativa inizializzazione.
Il dizionario m_itemTorrents conterrà tutti i file TORRENT trattati in ogni dato momento.
Il metodo Init si occupa di creare l’istanza di ClientEngine, passandogli come parametro un
oggetto EngineSettings, contentente tutte le impostazioni viste nel costruttore.
TPSIT 5
434
um 335 di
Dopo di che, se non esistono, crea le cartelle che conterrenno i download, leggendo dalle
stesse eventuali file TORRENT in sospeso.
Il primo evento sottoscritto è l’evento StatsUpdate, al verificarsi del quale si procederà
all’aggiornamento di tutte le statistiche e informazioni sui file TORRENT: velocità,
dimensione, byte scaricati, avanzamento.
Il primo passo per iniziare il download di un file TORRENT è procurarsi il suo URL, oppure
avere scaricato il file TORRENT.
A questo punto è verificato se il file non è già nella cartella dei TORRENT e lo si salva.
Quindi è invocato il metodo Torrent.Load.
Dopo aver creato graficamente un elemento della ListView, in maniera da dare un
riscontro all’utente finale, inizia il trattamento vero e proprio del TORRENT, registrandolo
nel ClientEnginee creando un nuovo oggetto TorrentManager a esso dedicato.
Il TorrentManager è aggiunto al dizionario m_itemTorrents, facendolo corrispondere
all’item della ListView che funge da chiave per ricavarlo in ogni momento.
Il metodo Start inizia il download dei file TORRENT in lista.
Tale metodo scorre il dizionario e invoca su ogni TorrentManager trovato il metodo Start
della classe stessa.
Di ogni file TORRENT è possibile conoscere lo stato in download, in pausa, la percentuale
di avanzamento, il numero di Seed e Leech, la velocità di download e upload, la quantità
di byte già scaricati e, quindi, quella rimanente, i byte caricati e, mediante un calcolo
basato sulla dimensione, tempo trascorso e byte scaricati, nonché la velocità di download
attuale.
Per creare un TORRENT a partire da un file esistente si usa la classe TorrentCreator.
Basta poi trovare un sito su cui caricare il TORRENT creato, in pratica un Tracker che dice
a ogni utente interessato a chi e come connettersi per scaricarlo.
TPSIT 5
434
um 336 di
TPSIT 5
434
um 337 di
JAVA
Vuze, noto come Azureus fino alla versione 2.0, è un client BT scritto in Java sotto licenza
GNU GPL.
Nella cartella dove è installato Vuze si trova il file AZUREUS2.JAR.
Copiarlo e aggiungerlo al progetto Java: si ha accesso a tutte le classi di Vuze.
La prima cosa da fare è accedere alla classe com.aelitis.azureus.core.AzureusCore,
costituisce il core, in altre parole il nucleo di Vuze/Azureus.
Per utilizzare qualsiasi funzione collegata alla libreria di Vuze, quindi, bisogna ottenere
un’istanza di questa classe.
L’istanza è costruita passando per la classe seguente e il suo metodo statico create.
AzureusCore core = AzureusCoreFactory.create();
Una volta creata l’istanza di AzureusCore bisogna avviarla con il suo metodo start.
core.start();
Ora è possibile usare il core di Vuze/Azureus per scaricare un torrent.
Si deve chiamare in gioco il componente global manager che corrisponde alla classe
org.gudy.azureus2.core3.global.GlobalManager.
Un’istanza può essere recuperata dal core, nel modo seguente.
GlobalManager globalManager = core.getGlobalManager();
Con il GlobalManager è possibile mettere in download un TORRENT.
Il metodo da invocare si chiama addDownloadManager e vuole due argomenti sotto forma
di stringa: il percorso del file TORRENT e quello della cartella in cui posizionare i file
scaricati.
globalManager.addDownloadManager("test.torrent","F:\\MiaDirectory");
Si possono aggiungere più download, chiamando più volte addDownloadManager con
differenti argomenti.
Le operazioni di download possono quindi essere avviate invocando il codice seguente.
globalManager.startAllDownloads();
Nella maggioranza dei casi, è importante conoscere lo stato di ciascun download in corso.
È possibile monitorare ciascuno dei download passando per la classe seguente
org.gudy.azureus2.core3.download.DownloadManager.
Il metodo addDownloadManager restituisce un’istanza della classe, utile per monitorare il
download appena aggiunto al global manager.
DownloadManager manager = globalManager.addDownloadManager("test.torrent",
"F:\\MiaDirectory");
Al download manager ottenuto si può aggiungere un listener.
manager.addListener(listener);
Il listener fornito dev’essere un oggetto che implementi l’interfaccia seguente.
TPSIT 5
434
um 338 di
org.gudy.azureus2.core3.download.DownloadManagerListener.
Questa richiede i seguenti metodi.
public void stateChanged(DownloadManager manager,int state)
public void downloadComplete(DownloadManager manager)
public void completionChanged(DownloadManager manager, boolean bCompleted)
public void filePriorityChanged(DownloadManager manager, DiskManagerFileInfo info)
public void positionChanged(DownloadManager manager, int oldPosition, int newPosition)
Il metodo stateChanged è richiamato quando lo stato del download cambia.
Il download manager fornito è quello corrispondente al download in corso, mentre il nuovo
stato dell’operazione è simboleggiato dall’intero state.
Il valore sarà sicuramente una delle costanti di DownloadManager il cui nome comincia
per STATE, ad esempio DownloadManager.STATE_DOWNLOADING che indica che il
download è in corso.
Il metodo downloadComplete è richiamato per segnalare che il download è completato.
Al termine della sessione di utilizzo del core di Vuze/Azureus, bisogna arrestare
quest’ultimo invocandone il metodo stop.
core.stop();
Scaricare da riga di comando un TORRENT alla volta, il nome del file TORRENT
dev’essere fornito come argomento, mentre la cartella corrente sarà usata come locazione
per il download dei file del TORRENT.
File SIMPLEDOWNLOADER.JAVA
Nel main dell’applicazione è controllata la presenza del parametro che corrisponde al
percorso del file TORRENT da scaricare.
La cartella corrente è risolta interrogando la proprietà di sistema user.dir.
Si è inserito nella classe uno speciale metodo chiamato waitDownloadCompleted che
serve per mettere in attesa chi lo chiama fin quando il download non sarà completato.
L’attesa è coordinata servendosi delle tecniche di sincronizzazione di Java basate sui
metodi wait e notify.
Nella definizione del metodo stateChanged si resta in attesa che il download cominci.
Quando lo stato diventa DownloadManager.STATE_DOWNLOADING, si lancia un thread
secondario, il cui compito è monitorare il progresso dell’operazione.
TPSIT 5
434
um 339 di
Nel metodo downloadComplete si ferma il thread di controllo e si risveglia chi si era messo
in attesa dell’evento.
package sd;
import java.io.File;
import org.gudy.azureus2.core3.download.DownloadManager;
import org.gudy.azureus2.core3.global.GlobalManager;
import com.aelitis.azureus.core.AzureusCore;
import com.aelitis.azureus.core.AzureusCoreException;
import com.aelitis.azureus.core.AzureusCoreFactory;
public class SimpleDownloader {
public static void main(String[] args) {
if (args.length != 1) {
System.out.println();
System.out.println("Utilizzo:");
System.out.println();
System.out.println("SimpleDownloader torrentDaScaricare.torrent");
System.out.println();
System.exit(1);
}
File file = new File(args[0]);
String filePath = file.getAbsolutePath();
String dirPath = System.getProperty("user.dir");
if (!file.exists()) {
System.out.println("Impossibile trovare " + filePath);
System.exit(1);
}
try {
AzureusCore core = AzureusCoreFactory.create();
core.start();
GlobalManager globalManager = core.getGlobalManager();
DownloadManager downloadManager =
globalManager.addDownloadManager(filePath, dirPath);
SimpleDownloadListener listener = new SimpleDownloadListener();
downloadManager.addListener(listener);
globalManager.startAllDownloads();
listener.waitDownloadCompleted();
core.stop();
} catch (AzureusCoreException e) {
System.out.println("Errore nel core di Vuze/Azureus scaricare");
e.printStackTrace();
System.exit(1);
}
}
}
File SIMPLEDOWNLOADCHECKER.JAVA
È un metodo che cicla fin quando non è interrotto, facendo pause di cinque secondi tra un
ciclo e il successivo.
Ad ogni ciclo controlla lo stato di avanzamento del download.
Per farlo interagisce con il DownloadManager assegnato al costruttore della classe.
Attraverso il metodo getStats estrapola dal download manager un oggetto di tipo
TPSIT 5
434
um 340 di
org.gudy.azureus2.core3.download.DownloadManagerStats.
Questo genere di oggetti riporta informazioni statistiche sul download in corso.
Con getCompleted se ne estrae il valore di progresso, espresso come intero da 0 a 1000.
È sufficiente dividere questo valore per 10 per avere una percentuale di completamento
del download.
package sd;
import org.gudy.azureus2.core3.download.DownloadManager;
import org.gudy.azureus2.core3.download.DownloadManagerStats;
class SimpleDownloadChecker implements Runnable {
// il manager da controllare
private DownloadManager manager;
public SimpleDownloadChecker(DownloadManager manager) {
this.manager = manager;
}
@Override
public void run() {
int lastValue = -1;
while (!Thread.interrupted()) {
DownloadManagerStats stats = manager.getStats();
int completed = stats.getCompleted();
if (completed != lastValue) {
lastValue = completed;
float percent = completed / 10F;
System.out.println("Scaricato: " + percent + "%");
}
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
break;
}
}
}
}
File SIMPLEDOWNLOADLISTENER.JAVA
package sd;
import org.gudy.azureus2.core3.download.DownloadManager;
import org.gudy.azureus2.core3.download.DownloadManagerStats;
class SimpleDownloadChecker implements Runnable {
// il manager da controllare
private DownloadManager manager;
public SimpleDownloadChecker(DownloadManager manager)
{ this.manager = manager; }
@Override
public void run()
{
int lastValue = -1;
while (!Thread.interrupted()) {
DownloadManagerStats stats = manager.getStats();
int completed = stats.getCompleted();
if (completed != lastValue) {
lastValue = completed;
TPSIT 5
434
um 341 di
float percent = completed / 10F;
System.out.println("Scaricato: " + percent + "%");
}
try
{ Thread.sleep(5000); }
catch (InterruptedException e)
{ break; }
}
}
}
Avviare la classe principale SimpleDownloader, passandole come argomento il percorso
del TORRENT da scaricare PROVA.TORRENT.
Fare clic su Run/Run Configurations…
Fare clic su Run.
Da console.
I:\>java -cp Azureus2.jar;SimpleDownloader.jar sd.SimpleDowloader prova.torrent
Eclipse IDE for Java
Progettare un client BT, l’utente dovrà fornire all’applicazione soltanto due informazioni:
qual è il file TORRENT con i dettagli dei contenuti da scaricare e quale la cartella in cui
sono salvati i dati scaricati.
TPSIT 5
434
um 342 di
package bt;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Insets;
import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.io.File;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JFileChooser;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JProgressBar;
import javax.swing.JTextField;
import javax.swing.UIManager;
import javax.swing.filechooser.FileFilter;
import org.gudy.azureus2.core3.disk.DiskManagerFileInfo;
import org.gudy.azureus2.core3.download.DownloadManager;
import org.gudy.azureus2.core3.download.DownloadManagerListener;
import org.gudy.azureus2.core3.download.DownloadManagerStats;
import org.gudy.azureus2.core3.global.GlobalManager;
import org.gudy.azureus2.core3.security.SESecurityManager;
import com.aelitis.azureus.core.AzureusCore;
import com.aelitis.azureus.core.AzureusCoreFactory;
Classe BTDownLoader.
Per gestire i download BT, si ha bisogno di un’istanza della classe AzureusCore.
Una volta ottenuta questa istanza, la si utilizza per creare un DownloadManager, in altre
parole un componente utile per avviare e gestire il download e la condivisione di un
TORRENT.
Una volta avviato il download è possibile ciclicamente interrogare il DownloadManager,
per chiedergli come stanno andando le operazioni in corso.
TPSIT 5
434
um 343 di
Si fa avviando un thread parallelo che si occuperà di estrarre continuamente dal
DownloadManager le informazioni di stato aggiornate.
Per far ciò si devono aggiungere tre proprietà alla classe BTDownLoader.
Per ricevere eventi dal DownloadManager si deve usare un DownloadManagerListener,
mentre per far girare del codice nel thread secondario occorre un oggetto Runnable.
// interfaccia grafica per il download dei torrent
public class BTDownLoader extends JFrame implements DownloadManagerListener,
Runnable {
private static final long serialVersionUID = 1L;
// la casella di testo per specificare il percorso del file torrent
private JTextField torrentFilePathField = new JTextField("");
// la casella di testo per specificare il percorso della directory in cui
// scaricare i contenuti del torrent
private JTextField downloadDirectoryPathField = new JTextField("");
// il bottone per la selezione guidata del percorso del file torrent
private JButton torrentFilePathBrowseButton = new JButton("Sfoglia");
// il bottone per la selezione guidata del percorso della directory di download
private JButton downloadDirectoryPathBrowseButton = new JButton("Sfoglia");
// il bottone per l’avvio del download / upload del torrent
private JButton startButton = new JButton("Avvia");
// il bottone per l’arresto del download / upload del torrent
private JButton stopButton = new JButton("Arresta");
// la barra di progresso usata per mostrare l’avanzamento del download
private JProgressBar downloadProgressBar = new JProgressBar();
// l’etichetta usata per mostrare una descrizione dello stato del torrent
private JLabel statusLabel = new JLabel("Seleziona un file .torrent per cominciare",
JLabel.CENTER);
// l’etichetta che riporta la velocit&agrave; di download
private JLabel downloadRateLabel = new JLabel("Download: 0 KB/s",
JLabel.CENTER);
// l’etichetta che riporta la velocit&agrave; di upload
private JLabel uploadRateLabel = new JLabel("Upload: 0 KB/s", JLabel.CENTER);
// un riferimento al core di Azureus
private AzureusCore core = AzureusCoreFactory.create();;
// il download manager per il torrent in download / upload
private DownloadManager downloadManager = null;
// il thread che durante il download / upload controlla e mostra le statistiche
private Thread statsThread = null;
// costruttore privato (inibisce l’uso dall’esterno della classe)
private BTDownLoader() {
setTitle("BitTorrent Downloader");
// costruisce l’interfaccia grafica
JComponent contentPane = buildContentPane();
setContentPane(contentPane);
// applica le dimensioni ottimali
pack();
// posizione al centro dello schermo.
Dimension screen = Toolkit.getDefaultToolkit().getScreenSize();
Dimension frame = getSize();
int x = (screen.width - frame.width) / 2;
int y = (screen.height - frame.height) / 2;
TPSIT 5
434
um 344 di
setLocation(x, y);
// alla chiusura della finestra termina l’applicazione
addWindowListener(new WindowAdapter() {
@Override
public void windowClosing(WindowEvent e) {
SESecurityManager.exitVM(0);
}
});
}
L’UI è costituita da una finestra di dimensioni ridotte, all’interno della quale l’utente avrà
due aree di testo per specificare i percorsi del file TORRENT e della cartella di download.
A fianco delle aree di testo ci sono due pulsanti Sfoglia che permettono all’utente di
selezionare un percorso su disco.
L’interfaccia è poi dotata di due pulsanti Avvia e Arresta, utili rispettivamente per avviare
e interrompere il download e la condivisione dei contenuti del torrent, in questa maniera
l’utente potrà anche mettere in pausa il download, per poi riprenderlo successivamente.
Al termine c’è la barra di progressione, utile per mostrare all’utente la percentuale di
completamento raggiunta durante il download e alcune label per notificare lo stato del
torrent e per informare l’utente sulle velocità di download e upload dei dati.
Il costruttore della classe assembla l’UI chiamando il metodo buildContentPane.
La finestra è compattata alle sue dimensioni ideali e messa al centro dello schermo.
Quando si chiude la finestra, un WindowAdapter cattura l’evento e lo gestisce invocando il
metodo exitVM della classe org.gudy.azureus2.core3.security.SESecurityManager.
Per uscire dall’applicazione si deve passare per la classe SESecurityManager.
// costruisce il pannello con i contenuti della finestra
// @return Il pannello con i contenuti della finestra
private JComponent buildContentPane() {
JComponent pathsPane = buildPathsPane();
JComponent actionsPane = buildActionsPane();
JComponent statusPane = buildStatusPane();
JComponent ratesPane = buildRatesPane();
JPanel contentPane = new JPanel();
contentPane.setLayout(new GridBagLayout());
GridBagConstraints c = new GridBagConstraints();
c.insets = new Insets(3, 3, 3, 3);
c.fill = GridBagConstraints.HORIZONTAL;
c.anchor = GridBagConstraints.CENTER;
c.weightx = 1;
c.weighty = 0;
c.gridx = 0;
c.gridy = 0;
contentPane.add(pathsPane, c);
c.gridy = 1;
contentPane.add(actionsPane, c);
c.gridy = 2;
contentPane.add(statusPane, c);
c.gridy = 3;
contentPane.add(ratesPane, c);
return contentPane;
}
TPSIT 5
434
um 345 di
Il pannello principale della finestra è suddiviso in quattro sotto aree: pathsPane,
actionsPane, statusPane e ratesPane.
Nel pannello pathsPane s’inseriscono i widget utili per indicare i percorsi del file
TORRENT e della cartella per il download, insieme con i corrispettivi pulsanti Sfoglia che
sono collegati a dei listener che rimandano ai metodi browseTorrentFilePath e
browseDownloadDirectoryPath.
// costruisce il pannello per la selezione dei percorsi
// @return Il pannello per la selezione dei percorsi
private JComponent buildPathsPane() {
torrentFilePathField.setPreferredSize(
new Dimension(300,
torrentFilePathField.getPreferredSize().height));
downloadDirectoryPathField.setPreferredSize(
new Dimension(300,
downloadDirectoryPathField.getPreferredSize().height));
JLabel torrentFilePathLabel = new JLabel("File .torrent:");
JLabel downloadDirectoryPathLabel = new JLabel("Scarica in:");
torrentFilePathBrowseButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
browseTorrentFilePath();
}
});
downloadDirectoryPathBrowseButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
browseDownloadDirectoryPath();
}
});
JPanel pathsPanel = new JPanel();
pathsPanel.setLayout(new GridBagLayout());
GridBagConstraints c = new GridBagConstraints();
c.insets = new Insets(3, 3, 3, 3);
c.fill = GridBagConstraints.NONE;
c.anchor = GridBagConstraints.EAST;
c.weightx = 0;
c.weighty = 0;
c.gridx = 0;
c.gridy = 0;
pathsPanel.add(torrentFilePathLabel, c);
c.fill = GridBagConstraints.HORIZONTAL;
c.anchor = GridBagConstraints.CENTER;
c.weightx = 1;
c.gridx = 1;
pathsPanel.add(torrentFilePathField, c);
c.fill = GridBagConstraints.NONE;
c.weightx = 0;
c.gridx = 2;
pathsPanel.add(torrentFilePathBrowseButton, c);
c.fill = GridBagConstraints.NONE;
TPSIT 5
434
um 346 di
c.anchor = GridBagConstraints.EAST;
c.weightx = 0;
c.weighty = 0;
c.gridx = 0;
c.gridy = 1;
pathsPanel.add(downloadDirectoryPathLabel, c);
c.fill = GridBagConstraints.HORIZONTAL;
c.anchor = GridBagConstraints.CENTER;
c.weightx = 1;
c.gridx = 1;
pathsPanel.add(downloadDirectoryPathField, c);
c.fill = GridBagConstraints.NONE;
c.weightx = 0;
c.gridx = 2;
pathsPanel.add(downloadDirectoryPathBrowseButton, c);
return pathsPanel;
}
Nel pannello actionsPane ci sono i pulsanti Avvia e Arresta che sono associati ai metodi
startTorrent e stopTorrent.
// costruisce il pannello con i bottoni per le azioni di start e stop
// @return Il pannello con i bottoni per le azioni di start e stop
private JComponent buildActionsPane() {
stopButton.setEnabled(false);
startButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
startTorrent();
}
});
stopButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
stopTorrent();
}
});
JPanel actionsPane = new JPanel();
actionsPane.setLayout(new FlowLayout(FlowLayout.CENTER, 5, 3));
actionsPane.add(startButton);
actionsPane.add(stopButton);
return actionsPane;
}
Nel pannello statusPane ci sono, la label di stato e la barra di progressione che indicano
rispettivamente l’operazione corrente e la percentuale di completamento della stessa.
// costruisce il pannello con le informazioni di stato sul download / upload del torrent
// @return Il pannello con le informazioni di stato sul download / upload del torrent
private JComponent buildStatusPane() {
downloadProgressBar.setStringPainted(false);
downloadProgressBar.setMinimum(0);
TPSIT 5
434
um 347 di
downloadProgressBar.setMaximum(1000);
downloadProgressBar.setValue(0);
JPanel statusPane = new JPanel();
statusPane.setLayout(new GridBagLayout());
GridBagConstraints c = new GridBagConstraints();
c.insets = new Insets(3, 3, 3, 3);
c.anchor = GridBagConstraints.CENTER;
c.fill = GridBagConstraints.HORIZONTAL;
c.weightx = 1;
c.weighty = 0;
c.gridx = 0;
c.gridy = 0;
statusPane.add(downloadProgressBar, c);
c.gridy = 1;
statusPane.add(statusLabel, c);
return statusPane;
}
L’ultimo pannello è ratesPane che mostra le velocità correnti di download e upload.
// costruisce il pannello con le informazioni sulle velocità di upload e download dei dati.
// @return Il pannello con le informazioni sulle velocità di upload e download dei dati
private JComponent buildRatesPane() {
JPanel ratesPane = new JPanel();
ratesPane.setLayout(new GridBagLayout());
GridBagConstraints c = new GridBagConstraints();
c.insets = new Insets(3, 3, 3, 3);
c.anchor = GridBagConstraints.CENTER;
c.fill = GridBagConstraints.HORIZONTAL;
c.weightx = 1;
c.weighty = 0;
c.gridx = 0;
c.gridy = 0;
ratesPane.add(downloadRateLabel, c);
c.gridx = 1;
ratesPane.add(uploadRateLabel, c);
return ratesPane;
}
L’UI è dotata di due pulsanti Sfoglia, utili per selezionare graficamente file e cartelle senza
dover digitare manualmente per intero il loro percorso.
Il primo di questi due pulsanti, quello a fianco della casella di testo con il percorso del file
TORRENT da aprire, utilizza un oggetto Swing javax.swing.JFileChooser, al quale è
abbinato un filtro utile per mostrare solo i file con estensione TORRENT, lasciando così
nascosti quelli di altro tipo.
A selezione confermata, il percorso del file scelto dall’utente è scritto nella casella di testo
corrispondente.
Il metodo controlla se l’utente ha già specificato una cartella di download, se non lo ha
fatto, l’applicazione propone automaticamente di scaricare i dati nella stessa cartella che
ospita il file TORRENT appena selezionato.
// sfoglia il file system per selezionare il file torrent.
TPSIT 5
434
um 348 di
private void browseTorrentFilePath() {
JFileChooser fc = new JFileChooser(System.getProperty("user.home"));
fc.setFileFilter(new FileFilter() {
@Override
public String getDescription() {
return "File .torrent";
}
@Override
public boolean accept(File f) {
// se cartella, va bene sempre
if (f.isDirectory()) {
return true;
}
// se file, va bene solo se ha estensione .torrent
if (f.getName().toLowerCase().endsWith(".torrent")) {
return true;
} else {
return false;
}
}
});
int c = fc.showOpenDialog(this);
if (c == JFileChooser.APPROVE_OPTION) {
File file = fc.getSelectedFile();
torrentFilePathField.setText(file.getAbsolutePath());
torrentFilePathField.setCaretPosition(0);
String aux = downloadDirectoryPathField.getText().trim();
if (aux.length() == 0) {
File dir = file.getParentFile();
if (dir != null) {
downloadDirectoryPathField.setText(dir.getAbsolutePath());
downloadDirectoryPathField.setCaretPosition(0);
}
}
}
}
Il metodo associato al secondo tasto Sfoglia, in altre parole quello utile proprio per
specificare o modificare il percorso della directory di download.
// sfoglia il file system per selezionare la directory di download
private void browseDownloadDirectoryPath() {
JFileChooser fc = new JFileChooser(System.getProperty("user.home"));
fc.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);
int c = fc.showOpenDialog(this);
if (c == JFileChooser.APPROVE_OPTION) {
File file = fc.getSelectedFile();
downloadDirectoryPathField.setText(file.getAbsolutePath());
downloadDirectoryPathField.setCaretPosition(0);
}
}
TPSIT 5
434
um 349 di
Il metodo startTorrent avvia le operazioni di download e condivisione dei dati.
Prima di avviare il download, controlla che i dati forniti dall’utente siano validi.
Sono pertanto esaminati i percorsi del file .TORRENT e della cartella di download,
appurando preventivamente la loro esistenza e la loro correttezza.
Passati i controlli, la GUI è aggiornata e il core di Vuze/Azureus avviato, se non è già
attivo, il metodo infatti potrebbe essere richiamato più di una volta.
Tramite il core è possibile generare il DownloadManager e avviare il download e la
condivisione.
Per ultimo si avvia anche il thread che controllerà le statistiche e aggiornerà la percentuale
di progresso e le velocità di download e upload.
// avvia o riprende il download / upload del torrent
private void startTorrent() {
// controlla il file torrent
String torrentFilePath = torrentFilePathField.getText().trim();
if (torrentFilePath.length() == 0) {
JOptionPane.showMessageDialog(this, "Specificare il file .torrent");
return;
}
File torrentFile = new File(torrentFilePath);
if (!torrentFile.exists() || !torrentFile.isFile()) {
JOptionPane.showMessageDialog(this, "File .torrent non valido");
return;
}
// controlla la cartella di download
String downloadDirectoryPath = downloadDirectoryPathField.getText().trim();
if (downloadDirectoryPath.length() == 0) {
JOptionPane.showMessageDialog(this, "Specificare la directory di download");
return;
}
File downloadDirectory = new File(downloadDirectoryPath);
if (!downloadDirectory.exists() || !downloadDirectory.isDirectory()) {
JOptionPane.showMessageDialog(this, "Directory di download non valida");
return;
}
// normalizza i percorsi
torrentFilePath = torrentFile.getAbsolutePath();
downloadDirectoryPath = downloadDirectory.getAbsolutePath();
// cambia lo stato della GUI.
torrentFilePathField.setEnabled(false);
torrentFilePathBrowseButton.setEnabled(false);
downloadDirectoryPathField.setEnabled(false);
downloadDirectoryPathBrowseButton.setEnabled(false);
startButton.setEnabled(false);
stopButton.setEnabled(true);
downloadProgressBar.setStringPainted(true);
downloadProgressBar.setValue(0);
downloadRateLabel.setText("Download: 0 KB/s");
uploadRateLabel.setText("Upload: 0 KB/s");
// se necessario, avvia il core di Azureus
if (!core.isStarted()) {
TPSIT 5
434
um 350 di
core.start();
}
GlobalManager globalManager = core.getGlobalManager();
downloadManager = globalManager.addDownloadManager(torrentFilePath,
downloadDirectoryPath);
downloadManager.addListener(this);
globalManager.startAllDownloads();
// avvia il thread delle statistiche
statsThread = new Thread(this);
statsThread.start();
}
Per arrestare il torrent si usa il metodo stopTorrent che rimuove il download dalla pila di
Vuze/Azureus e aggiorna la GUI di conseguenza.
Il core è mantenuto attivo, in modo che sia possibile riusarlo al riavvio successivo, il
download, infatti, può essere ripreso azionando di nuovo il bottone Avvia.
// arresta il download / upload del torrent
private void stopTorrent() {
// ferma il thread delle statistiche
statsThread.interrupt();
// ferma e rimuove il download
GlobalManager globalManager = core.getGlobalManager();
globalManager.stopAllDownloads();
try {
globalManager.removeDownloadManager(downloadManager);
} catch (Exception e) {
e.printStackTrace();
}
// cambia lo stato della GUI
torrentFilePathField.setEnabled(true);
torrentFilePathBrowseButton.setEnabled(true);
downloadDirectoryPathField.setEnabled(true);
downloadDirectoryPathBrowseButton.setEnabled(true);
startButton.setEnabled(true);
stopButton.setEnabled(false);
downloadProgressBar.setValue(0);
downloadProgressBar.setStringPainted(false);
downloadRateLabel.setText("Download: 0 KB/s");
uploadRateLabel.setText("Upload: 0 KB/s");
statusLabel.setText("Torrent arrestato");
}
Implementazione dei metodi in grado d’intercettare gli eventi del TORRENT.
Si possono seguire due vie per estrapolare altrettante distinte categorie d’informazioni.
I cambi di stato si possono intercettare dando implementazione ai metodi richiesti
dall’interfaccia DownloadManagerListener.
Le informazioni sulla percentuale di completamento e sulle velocità di download e upload
dei dati, invece, si possono recuperare ciclicamente con il metodo statsThread.
@Override
public void completionChanged(DownloadManager manager, boolean bCompleted) {
TPSIT 5
434
um 351 di
}
@Override
public void downloadComplete(DownloadManager manager) {
}
@Override
public void filePriorityChanged(DownloadManager download, DiskManagerFileInfo file) {
}
@Override
public void positionChanged(DownloadManager download, int oldPosition, int
newPosition) {
}
Il metodo stateChanged intercetta e annota i cambi di stato del TORRENT, così da poter
illustrare all’utente cosa sta accadendo all’interno del S/W.
@Override
public void stateChanged(DownloadManager manager, int state) {
switch (state) {
case DownloadManager.STATE_ALLOCATING:
statusLabel.setText("Allocazione in corso...");
break;
case DownloadManager.STATE_CHECKING:
statusLabel.setText("Verifica in corso...");
break;
case DownloadManager.STATE_CLOSED:
statusLabel.setText("Torrent chiuso");
break;
case DownloadManager.STATE_DOWNLOADING:
statusLabel.setText("Download in corso...");
break;
case DownloadManager.STATE_ERROR:
statusLabel.setText("Errore: " + manager.getErrorDetails());
break;
case DownloadManager.STATE_INITIALIZED:
statusLabel.setText("Inizializzato...");
break;
case DownloadManager.STATE_INITIALIZING:
statusLabel.setText("Inizializzazione in corso...");
break;
case DownloadManager.STATE_QUEUED:
statusLabel.setText("In attesa...");
break;
case DownloadManager.STATE_SEEDING:
statusLabel.setText("Download completato. Condivisione in corso...");
break;
case DownloadManager.STATE_STOPPED:
statusLabel.setText("Arrestato");
break;
case DownloadManager.STATE_STOPPING:
statusLabel.setText("Arresto in corso...");
break;
}
TPSIT 5
434
um 352 di
}
Il metodo run, richiesto dall’interfaccia Runnable, è in esecuzione su thread secondario
mentre il download e la condivisione del TORRENT sono attivi.
La routine rimane attiva fin quando il thread non è interrotto, evento che avviene nel
metodo stopTorrent.
Finché attivo, il thread interroga continuamente il DownloadManager corrente,
estraendone il livello corrente di completamento dell’operazione, valore da 0 a 1000 e le
velocità di download e upload dei dati.
Ad ogni ciclo il thread esegue una pausa di 1000 millisecondi, un secondo, in modo da
aggiornare le statistiche.
@Override
public void run() {
while (!Thread.interrupted()) {
DownloadManagerStats stats = downloadManager.getStats();
int completed = stats.getCompleted();
long dataReceiveRate = stats.getDataReceiveRate();
long dataSendRate = stats.getDataSendRate();
downloadProgressBar.setValue(completed);
downloadRateLabel.setText("Download: " + (dataReceiveRate / 1024) + " KB/s");
uploadRateLabel.setText("Upload: " + (dataSendRate / 1024) + " KB/s");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
break;
}
}
}
Il metodo main compie tre differenti operazioni.
Per prima cosa forza l’utilizzo del look & feel di sistema, ove disponibile, in modo da far
apparire l’applicazione più familiare per l’utente che la utilizza.
In Swing c’è la possibilità di modificare l’aspetto e il comportamento dei widget grafici
come i pulsanti, le caselle di testo, attraverso la classe javax.swing.UIManager e il suo
metodo setLookAndFeel.
La VM ingloba diversi temi ma uno è speciale, definito di sistema e richiamabile con il
metodo getSystemLookAndFeelClassName di UIManager.
Questo tema cerca la maggiore integrazione possibile con il SO sottostante, usando
l’aspetto e le dimensioni standard che i vari widget hanno nel sistema ospite.
Subito dopo imposta una cartella di lavoro per le API di Vuze/Azureus.
La piattaforma ha bisogno di spazio su disco per memorizzare informazioni temporanee.
La cartella utilizzata da Vuze può essere stabilita annotandone il percorso sulla proprietà
di sistema azureus.config.path.
La routine usa l’accortezza di generare una cartella temporanea diversa a ogni avvio del
S/W, poiché il suo nome è stabilito ogni volta in maniera differente, usando l’orologio di
sistema.
Questa accortezza permette l’esecuzione simultanea di più istanze del S/W senza che le
stesse vadano in conflitto tra di loro, per esempio si possono scaricare più torrent insieme,
semplicemente avviando più di una volta l’applicazione.
public static void main(String[] args) {
TPSIT 5
434
um 353 di
// applica il look & feel di sistema (se disponibile)
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (Exception e) {
}
// fa lavorare Azureus in una cartella temporanea
final String workdir = System.getProperty("java.io.tmpdir") + File.separator + "bt."
+ System.currentTimeMillis();
System.setProperty("azureus.config.path", workdir);
// avvia la GUI
BTDownLoader gui = new BTDownLoader();
gui.setVisible(true);
}
}
TPSIT 5
434
um 354 di
IRC (INTERNET RELAY CHAT)
INTRODUZIONE
Sviluppato nel 1988 è un sistema di chat che si basa su un server centrale e su diversi
client che si connettono a questo server.
A loro volta i diversi server entrano in comunicazione fra loro formando una specie di
circuito condiviso dagli stessi utenti connessi però a server diversi.
Ogni utente sceglie un nickname con il quale è identificato univocamente su tutti i server.
Esistono due tipi di moderazione.
1. OP
Gli OP di un canale sono i proprietari o comunque quelli che gestiscono il canale,
cambiano il TOPIC, l’argomento, settano limiti e decidono se una persona può rimanere
nel canale o non.
2. VOICE
I VOICE sono le uniche persone che possono parlare, oltre agli OP, quando il canale è
settato in un modalità “moderata”.
PircBot è un framework open source per sviluppare in Java dei bot IRC, il S/W “gestirà” un
canale quando non si è connessi.
Nel package org.jibble.pircbot si trova la definizione delle classi base del framework.
La classe base da cui partire è PircBot, per far partire il primo bot e collegarlo al server
IRC, in un canale, il codice è il seguente.
import org.jibble.pircbot.*;
public class HelloWorldBot extends PircBot {
public HelloWorldBot(){
this.setName("Ciao, mondo bot!");}
}
Con il metodo setName s’impone il nickname che il bot utilizzerà sul server IRC.
Il prossimo passo sarà collegare questo bot ad un server, farlo entrare in un canale e
chiudere la connessione.
import org.jibble.pircbot.*;
public class BotExample {
public static void main(String a[]){
HelloWorldBot hwb=new HelloWorldBot();
hwb.connect("irc.azzurra.org");
hwb.joinChannel("#javastaff");
hwb.sendMessage("#javastaff","Ciao a tutti");
hwb.disconnect(); }
}
Con il metodo connect si dice al bot a quale server si deve connettere.
Una volta che la connessione è avvenuta si fa entrare il bot nel canale #javastaff.
Il simbolo # è un prefisso che identifica i canali su IRC, ad esempio #java, #italia, #roma.
Appena il bot entra nel canale si manda un messaggio pubblico su #javastaff con il
TPSIT 5
434
um 355 di
metodo sendMessage.
Infine, ci si disconnette dal server e termina l’esecuzione.
La cosa più interessante in un bot è la possibilità d’interagire con le persone che sono
presenti nel canale.
Per fare ciò si devono poter controllare i messaggi del canale dove il bot si trova e parsarli
in maniera corretta.
Il formato con cui i messaggi passano sul canale e i messaggi è il seguente.
1113949468186:[email protected] PRIVMSG #javastaff :ciao
DeLiRiUm
1113949480624:[email protected] PRIVMSG #javastaff :che
fai?
Un messaggio di questo genere non è adatto a essere comprensibile e andrebbe “parsato”
e “scomposto” per ottenere solo le informazioni che servono.
Non è necessario riprogrammare il parser usando PircBot, di fatto esiste già il metodo
OnMessage nella classe base che implementa un parser a tutti gli effetti.
Attraverso il metodo OnMessage è facile usare dei trigger per riprogrammare i
comportamenti del bot.
public void onMessage(String channel, String sender,String login, String hostname,
String message) {
if (message.equals("Africa"))
sendMessage(channel, "Celestino");
}
In questo modo il bot quando intercetta la parola Africa all’interno di un messaggio,
scriverà come reazione nel canale la parola Celestino, usando la primitiva sendMessage
definita nella classe PircBot.
Eclipse IDE for Java
Per testare questo progetto si deve avere un collegamento a Internet.
Creazione del bot
TPSIT 5
434
um 356 di
Dopo aver incluso le librerie, creare la classe che estende PircBot e nel main
dell’applicazione inizializzare il bot.
package bot;
import org.jibble.pircbot.*;
import java.util.*;
import java.io.*;
public class Delirium extends PircBot{
static Delirium bot;
Vector canali,utenti,utentiIdentificati;
File f;
public Delirium() {
utenti=new Vector();
caricaUtenti();
f=new File("regole.txt");
utentiIdentificati=new Vector();
}
public static void main(String[] args) throws Exception {
bot = new Delirium();
bot.setVerbose(true);
bot.setName("DeLiRiUm");
bot.connect("localhost");
bot.channelJoin();
}
Connessione al server
Collegare il bot al server IRC passato come parametro al metodo connect.
Effettuata la connessione si entra in tutti i canali che sono presenti in un testo.
public void channelJoin() {
canali=new Vector();
try{
BufferedReader br=new BufferedReader(new FileReader("I:/canali.txt"));
String linea=br.readLine();
while(linea!=null) {
bot.joinChannel("#"+linea);
linea=br.readLine();
canali.add(linea);
}
br.close();
}
catch(Exception e) {
}
}
La prima parola
Implementare il metodo onMessage, decidere un trigger, in risposta al quale il bot
risponde nel canale.
In questo caso si permette al bot di essere attivato dal trigger !REGOLE per poter inviare
un file con le regole del canale a chi le ha richieste.
public void onMessage(String channel, String sender,String login, String hostname,
TPSIT 5
434
um 357 di
String message) {
StringTokenizer st=new StringTokenizer(message," ");
String action=st.nextToken();
if (action.equals("!OP")) {
opAction(st,sender,channel);
}
else if(action.equals("!DEOP")) {
deopAction(st,sender,channel);
}
else if (action.equals("!REGISTER")) {
sendMessage(channel,sender+" queste cose si fanno in query :)");
}
else if (action.equals("!REGOLE")) {
dccSendFile(f,sender,120000);
}
else if (action.equals("!GOOGLE")) {
searchAction(st,sender,channel);
}
else if (action.equals("Africa")) {
sendMessage(channel,"Celestino");
}
else if (action.equals("!JAVA")) {
javaAction(st,sender,channel);
}
else if (action.equals("!DIE")) {
dieAction(st,sender,channel);
}
}
Una volta definito come sarà rappresentato l’utente si deve decidere un metodo per far sì
che gli utenti possano registrarsi.
Per fare ciò l’utente dovrà scrivere in query, messaggio privato, al bot.
Ridefinire il metodo onPrivateMessage, creando prima di tutto uno StringTokenizer basato
sullo spazio, per ottenere così tutte le singole parole presenti nel messaggio che è
passato.
public void onPrivateMessage(String sender, String login, String hostname, String
message) {
StringTokenizer st=new StringTokenizer(message," ");
String action=st.nextToken();
// INVIO EMAIL
if (action.equals("!EMAIL")) {
emailAction(st,sender);
}
// REGISTRAZIONE UTENTE
else if(action.equals("!REGISTER")) {
registerAction(st,sender);
}
else if(action.equals("!IDENTIFY")) {
identifyAction(st,sender);
}
}
TPSIT 5
434
um 358 di
Caricare gli utenti
Caricare una lista di utenti che si sono precedentemente registrati, si legge il vettore degli
utenti direttamente da un file, salvati nella precedente sessione.
public void caricaUtenti() {
try {
FileInputStream in = new FileInputStream("I:/user.ser");
ObjectInputStream s = new ObjectInputStream(in);
utenti =(Vector) s.readObject();
s.close();
}
catch(Exception e) {
System.out.println(e.toString());
e.printStackTrace();
}
}
Gestione file
Utilizzando IRC è possibile effettuare uno scambio diretto di file che è chiamato DCC
(Direct Client to Client), utilizzato anche per parlare.
Chiaramente anche il bot può utilizzare queste funzionalità, essendo lui stesso un client
che si collega al server IRC.
Esiste un metodo definito all’interno della classe PircBot che permette di avere la notifica
del file in arrivo è onIncomingFileTransfer, dovrà essere implementato nel bot, per poter
accettare i file in arrivo.
Per l’invio si utilizza dccSendFile, indicando l’utente, il file e il timeout.
public void onIncomingFileTransfer(DccFileTransfer transfer) {
File file = transfer.getFile();
transfer.receive(file, true);
}
Il file accettato e ricevuto sarà copiato nella cartella dove è in esecuzione il bot, in questo
modo si può anche creare un repository per tutti gli utenti in un canale.
In generale la tecnica per inviare file è simile a quella della ricezione solo che si deve
indicare una persona alla quale inviare il file.
Gestione canali
Il metodo joinChannel fa entrare il bot nel canale che è passato come parametro.
Se si volesse far entrare automaticamente in una serie di canali, definiti in un file di testo,
non si deve fare altro che scrivere un metodo che, chiamato dopo la connessione al
server, legge da un file i nomi dei canali e richiede, uno per uno, di entrare nei canali.
public void channelJoin() {
canali=new Vector();
try{
BufferedReader br=new BufferedReader(new FileReader("I:/canali.txt"));
String linea=br.readLine();
while(linea!=null) {
bot.joinChannel("#"+linea);
linea=br.readLine();
TPSIT 5
434
um 359 di
canali.add(linea);
}
br.close();
}
catch(Exception e) {
}
}
Così tutti i canali che avranno l’onore di avere come ospite il bot saranno facilmente
gestibili tramite questo file di testo.
Altre funzionalità
Si deve permettere al bot di svolgere le normali funzioni di utente IRC.
Si può comandarlo per rendere altri utenti OP del canale o levare l’OP del canale a
qualcuno.
Nei canali IRC dove non si ha una gestione tramite bot o tramite particolari funzionalità di
registrazione del canale sull’IRCD ci sono elevate probabilità di perderlo a causa di
persone che cercano di rubarlo.
Per gestire queste funzionalità base ci sono semplici metodi, già definiti in PircBot che
devono essere richiamati, quindi basta inserire dei trigger nel metodo onMessage.
Richiamando il metodo opAction e il metodo deopAction si comunica al server di rendere
moderatore l’utente indicato.
Tutti gli utenti possono registrarsi e identificarsi, quindi potrebbero rubare il canale.
Infatti, si può pensare a una registrazione lato web oppure ad un solo utente, il primo che
può registrare pian piano altri utenti.
Questo dipende dalle esigenze del canale che si desidera gestire.
public void opAction(StringTokenizer st,String sender,String channel) {
String who=st.nextToken();
if (utentiIdentificati.contains(sender))
op(channel,who);
}
public void deopAction(StringTokenizer st,String sender,String channel) {
String who=st.nextToken();
if (utentiIdentificati.contains(sender))
deOp(channel,who);
}
public void dieAction(StringTokenizer st,String sender,String channel) {
if (utentiIdentificati.contains(sender)) {
salvaUtenti();
disconnect();
}
}
public void identifyAction(StringTokenizer st,String sender) {
try{
String pass=st.nextToken();
for (int i=0;i<utenti.size();i++) {
User temp=(User)utenti.elementAt(i);
if
((sender.equals(temp.getUsername()))&&(pass.equals(temp.getPassword()))) {
if (!utentiIdentificati.contains(sender))
utentiIdentificati.add(sender);
TPSIT 5
434
um 360 di
sendMessage(sender,"Utente identificato");
return;
}
}
sendMessage(sender,"Errore nell’identificazione");
}
catch(Exception e) {
}
}
Con il metodo salvaUtenti si registra l’utente aggiungendolo al vettore degli utenti, è
richiamato prima della disconnessione del bot dal server IRC, per salvare sul file il vettore
di utenti aggiornati.
public void salvaUtenti() {
try {
FileOutputStream out = new FileOutputStream("I:/user.ser");
ObjectOutputStream s = new ObjectOutputStream(out);
s.writeObject(utenti);
s.close();
}
catch(Exception e) {
System.out.println(e.toString());
}
}
Ricerche con Google
Sviluppare un’estensione che s’interfacci al motore di ricerca Google, per poter fare le
ricerche anche quando si è in chat con altre persone.
Si usano le Google WebAPI, ovvero delle API che permettono di effettuare ricerche su
Google grazie ad un WS che è esposto.
Una volta registrati sul sito http://www.google.com/apis/ è inviata via email una chiave da
utilizzare nell’applicazione che serve per limitare l’accesso al WS, offrendo un massimo di
1000 ricerche al giorno.
Quindi prima d’interfacciare Google con il bot costruire una classe WebSearch per
effettuare la vera e propria ricerca, come parametro si passano le parole inviate dall’utente
nel canale.
Si comunicano i risultati tramite la primitiva sendMessage.
Oltre a istanziare GoogleSearch bisogna settare la chiave che è stata inviata via email con
il metodo setKey.
Poi si deve settare un proxy e relativa porta, attraverso il quale si desidera effettuare la
ricerca e infine con il metodo setQueryString si setta la stringa che sarà ricercata su
Google.
File WEBSEARCH.JAVA
package bot;
import com.google.soap.search.GoogleSearch;
import com.google.soap.search.GoogleSearchResult;
import com.google.soap.search.GoogleSearchResultElement;
import com.google.soap.search.GoogleSearchFault;
import java.util.*;
public class WebSearch{
TPSIT 5
434
um 361 di
int tipo;
GoogleSearch search;
Vector toReturn;
public WebSearch(int tipo) {
this.tipo=tipo;
search = new GoogleSearch();
// search.setProxyHost("192.168.1.1");
// search.setProxyPort(3128);
search.setKey("DACAMBIARECONLACHIAVECHEVIENEINVIATA");
toReturn=new Vector();
}
public Vector search(String toSearch) {
try {
if (tipo==0) {
search.setQueryString(toSearch);
GoogleSearchResult result = search.doSearch();
GoogleSearchResultElement[] re = result.getResultElements();
for ( int i = 0; i < re.length; i++ ) {
toReturn.add(re[i].getURL());
}
return toReturn;
}
else
return searchAdvanced(toSearch);
}
catch(Exception e) {
return toReturn;
}
}
public Vector searchAdvanced(String toSearch){
String toSearch1="site:www.javaworld.com "+toSearch;
String toSearch2="site:www.javalobby.org "+toSearch;
String toSearch3="site:www.alphaworks.ibm.com "+toSearch;
tipo=0;
Vector temp=search(toSearch1);
temp=search(toSearch2);
temp=search(toSearch3);
return temp;
}
}
Definire il metodo searchAction richiamando questa classe appena implementata.
Praticamente si ha un vettore come risultato, quindi con questo metodo basterà eseguire
un ciclo for e stampare nel canale tutti i risultati.
public void searchAction(StringTokenizer st,String sender,String channel) {
try{
String toSearch=st.nextToken();
while(st.hasMoreTokens())
toSearch=toSearch+" "+st.nextToken();
WebSearch ws=new WebSearch(0);
Vector result=ws.search(toSearch);
TPSIT 5
434
um 362 di
sendMessage(channel,sender+" ecco i risultati della tua ricerca");
for (int i=0;i<result.size();i++) {
String temp=(String)result.elementAt(i);
sendMessage(channel,temp);
}
}
catch(Exception e) {
}
}
Riconoscere gli utenti
Uno dei principali motivi per cui si crea un bot su dei canali IRC è per gestire il canale,
anche quando non sono presenti i veri moderatori del canale.
Allora bisogna implementare un vero e proprio sistema d’identificazione che permetterà
soltanto a determinati utenti di utilizzare il bot.
Occorre una classe dove implementare i metodi get e set per ogni variabile, inoltre,
bisogna implementare l’interfaccia Serializable, per serializzare un vettore di utenti e
ricaricarlo all’avvio.
File USER.JAVA
package bot;
import java.util.*;
import java.io.*;
public class User implements Serializable{
String username,password,email,dir;
int level;
public User(String username,String password,String email,int level,String dir) {
this.username=username;
this.password=password;
this.email=email;
this.level=level;
this.dir=dir;
}
public String getMail() {
return email;
}
public void setMail(String email) {
this.email=email;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username=username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password=password;
}
public int getLevelAut() {
TPSIT 5
434
um 363 di
return level;
}
public void setLevelAut(int level) {
this.level=level;
}
public void setDir(String dir) {
this.dir=dir;
}
public String getDir() {
return dir;
}
}
File EMAILSENDER.JAVA
package bot;
import java.util.*;
import javax.mail.*;
import javax.mail.internet.*;
public class EmailSender {
String smtp,destinatario,mittente,oggetto,corpo;
public EmailSender(String smtp,String destinatario, String mittente,String oggetto,
String corpo) {
this.smtp=smtp;
this.destinatario=destinatario;
this.mittente=mittente;
this.oggetto=oggetto;
this.corpo=corpo;
}
public String send() {
try{
Properties props = System.getProperties();
props.put("mail.smtp.host", smtp);
Session session = Session.getDefaultInstance(props, null);
Message msg = new MimeMessage(session);
msg.setFrom(new InternetAddress(mittente));
msg.setRecipients(Message.RecipientType.TO,InternetAddress.parse(destinatario,
false));
msg.setSubject(oggetto);
msg.setText(corpo);
msg.setHeader("X-Mailer", "Delirium Irc Bot");
msg.setSentDate(new Date());
Transport.send(msg);
return "Mess inviato";
}
catch(Exception e) {
return e.toString();
}
}
}
TPSIT 5
434
um 364 di
TPSIT 5
434
um 365 di
SECOND LIFE
INTRODUZIONE
È un mondo virtuale 3D, progettato nel 2003 dalla società americana Linden Lab fondata
nel 1999 da Philip Rosedale la sua sede è a San Francisco, interamente costruito e
posseduto dai suoi residenti, raggiungibile all’indirizzo http://www.secondlife.com, costruito
con dei “mattoncini” in cui vivono gli alter ego, gli “avatar”.
“Un nuovo mondo, una seconda vita!”
Il sistema fornisce ai propri utenti, i “residenti”, gli strumenti per aggiungere e creare nel
mondo nuovi contenuti grafici: oggetti, fondali, fisionomie dei personaggi e audiovisivi.
Second LIFE gira su più di 2000 server basati su processori INTEL (INTegrated
ELectronics) o AMD (Advanced Micro Devices), utilizzano come SO la distribuzione Linux
Debian e come DB MySQL.
L’architettura è di tipo client server, il client è stato rilasciato dalla Linden Lab in licenza
GPL, il server, Simulator o SIM, è di proprietà della Linden Lab.
Il protocollo di comunicazione tra i server e i client costituisce la SecondLifeGrid che mette
in comunicazione i simulator tra loro, anche la Grid è di proprietà della Linden Lab.
La peculiarità del mondo di Second LIFE è quella di lasciare agli utenti la libertà di
usufruire dei diritti d’autore sugli oggetti che essi creano che possono essere venduti e
scambiati tra i residenti utilizzando una moneta virtuale, il Linden Dollar che può essere
convertito in veri dollari statunitensi e anche in euro.
Avatar
È un’immagine scelta per rappresentare l’utenza nelle comunità virtuali, luoghi di
aggregazione, discussione o di gioco online.
La parola che è in lingua sanscrita, è originaria della tradizione induista, nella quale ha il
significato d’incarnazione, di assunzione di un corpo fisico da parte di un dio, Avatar è
“Colui che discende” per traslazione metaforica, nel gergo di Internet s’intende che una
persona reale che scelga di mostrarsi agli altri, lo faccia attraverso una propria
rappresentazione, un’incarnazione: un avatar appunto.
Tale immagine che può variare per tema e per grandezza può raffigurare un personaggio
di fantasia, della realtà, o anche temi più vari, come vignette comiche, testi e altro.
TPSIT 5
434
um 366 di
Titolo originale
Paese
Anno
Durata
Genere
Regia
Sceneggiatura
Casa di produzione
Fotografia
Montaggio
Musiche
Scenografia
Interpreti e personaggi
Sam Worthington:
Zoë Saldaña:
Sigourney Weaver:
Giovanni Ribisi:
Michelle Rodríguez:
Avatar
Stati Uniti
2009
162 min
Fantascienza, avventura, drammatico
James Cameron
James Cameron
20th Century Fox, Giant Studios, Lightstorm Entertainment
Mauro Fiore
James Cameron, John Refoua, Stephen E. Rivkin
James Horner
Rick Carter, Martin Laing, Robert Stromberg
Jake Sully
Neytiri
Dr. Grace Augustine
Parker Selfridge
Trudy Chacón
Tutto ciò che che si vede nel mondo è costruito a partire da componenti elementari, i prim
e reso dinamico utilizzando il linguaggio di programmazione LSL.
Se si vuole creare una vita virtuale, si deve scaricare e installare sul PC il client che si
trova all’indirizzo http://http://secureweb11.secondlife.com/community/downloads.php e
iscriversi, fornire le “seconde” generalità nome e cognome, la password, quindi premere
sul tasto Connect....
In Second LIFE ciò che si vede è l’avatar che si può personalizzare nell’aspetto e si può
muovere utilizzando le frecce direzionali della tastiera.
LSL (Linden Scripting Language)
Il client contiene al suo interno tutto ciò che serve, se si fa clic sul pulsante Build, in
basso, il mouse si trasforma in “bacchetta magica”; se si fa un clic sul terreno di fronte,
appare un cubo di legno e il relativo dialogo di progettazione; se si fa clic sulla linguetta
Content, ci si posiziona nella zona in cui porre gli script.
Fare clic sul pulsante New Script... il sistema crea lo script che si può analizzare se si fa
clic sulla relativa icona.
LSL è un linguaggio per la programmazione di una macchina a stati finiti guidata dagli
TPSIT 5
434
um 367 di
eventi, la sintassi è C-like.
Di ogni oggetto, infatti, si deve programmare la sequenza degli stati che l’oggetto potrà
assumere e gli eventi che saranno scaturiti dal/nell’oggetto.
LSL offre una libreria di oltre 300 funzioni che consentono di manipolare in ogni modo gli
oggetti del mondo.
Il client consente da solo di sviluppare codice ma è meglio utilizzare un editor, per
esempio LSLEditor che fornisce un intellisense tipo Visual Studio, colorazione del codice e
un debugger, si trova all’indirizzo http://www.lsleditor.org/.
È definito un solo stato, default che è lo stato predefinito e che dev’essere sempre
presente in uno script LSL.
Sono definiti due gestori degli eventi.
state_entry
Il primo evento è scatenato quando si entra nello stato default, ovvero quando lo script è
compilato; in questo caso, dato che non sono definiti ulteriori stati, il codice del metodo è
eseguito solo la prima volta, normalmente si utilizza questo metodo per inizializzare
piuttosto che per modificare l’aspetto dell’oggetto stesso.
touch_start
Restando nello stato default, il metodo è eseguito ogni qualvolta è toccato l’oggetto; per
reagire al tocco si utilizza una delle funzioni di libreria.
Tutte le funzioni di libreria cominciano per ll (Linden Library); la funzione llSay è utilizzata
per comunicare con l’esterno, detto in altre parole per eseguire una azione di chat.
Il primo parametro è il canale su cui è inviato il messaggio che si passa al metodo come
secondo parametro.
Ci sono ben 4294967294 canali disponibili nell’intervallo che va da 2147483648 a
2147483647; il canale 0 è l’unico canale utilizzabile per mandare messaggi tra avatar e
corrisponde alla chat.
Tutti gli altri canali sono utilizzati dagli script per la comunicazione tra oggetti.
Se si esce dalla modalità di progettazione dell’oggetto, clic sulla X in alto a destra del
dialogo di progettazione e si fa clic sull’oggetto, nella zona in basso a sinistra appare il
messaggio che c’invia l’oggetto.
TPSIT 5
434
um 368 di
Per coloro che non posseggono un proprio terreno in cui fare le prove, esistono delle zone
franche, dette sandbox, in cui si possono creare oggetti e provare script.
Una di queste sandbox è raggiungibile al seguente indirizzo: Volksland 9, 248, 21 gli
oggetti che si creano sono posti nella cartella Lost And Found dell’Inventory, dopo alcuni
minuti di utilizzo al fine di mantenere la sandbox pulita; per continuare le prove basta
trascinare nuovamente l’oggetto sul terreno.
File PORTA SCORREVOLE.LSL
Codice per programmare una porta scorrevole a chiusura automatica, si possono vedere
l’uso degli stati e le varie transizioni.
La porta può assumere quattro diversi stati.
Lo stato predefinito, default, corrisponde alla porta chiusa.
La porta si deve aprire se si fa clic sull’anta, il metodo touch_start effettua una transizione
di stato ponendo la porta nello stato in apertura, opening.
Sia per cambiare stato sia per definire uno stato si usa la parola chiave state.
default
{
// evento scatenato dal tocco dell’avatar
touch_start(integer total_number)
{
// cambiamo stato
state opening;
}
}
Dichiarare un nuovo stato racchiudendo tra parentesi graffe ({}), i gestori degli eventi che
interesseranno l’oggetto nello stato che si sta definendo.
Lo stato opening rappresenta l’atto dell’apertura della porta.
La porta resta in questo stato solo il tempo necessario alla sua apertura.
Si deve precisare che le animazioni relative al cambio di posizione degli oggetti, sono
eseguite automaticamente dal simulatore di Second LIFE.
// la porta si sta aprendo
state opening
{
state_entry()
{
// ricaviamo la posizione corrente della porta
vector pos = llGetPos();
// togliamo dalla componente trasversale la lunghezza della porta
pos.y = pos.y - 2.000;
// imponiamo la nuova posizione
llSetPos(pos);
// cambiamo stato
state open;
}
}
Per lo stato opening si deve codificare solo il metodo state_entry in cui si va a ricavare la
posizione corrente della porta attraverso la funzione di libreria llGetPos; la posizione è
rappresentata da un vettore che esprime le coordinate cartesiane del centro dell’oggetto.
Quindi si va a togliere dalla componente trasversale della porta la sua lunghezza; infine, si
setta la nuova posizione della porta con un’altra funzione di libreria, llSetPos.
La porta sarà spostata nella nuova posizione con un effetto movimento creato dal
TPSIT 5
434
um 369 di
simulatore.
Dopo aver imposto la nuova posizione, si cambia nuovamente stato, ponendo la porta
nello stato aperto, open.
La componente da aggiungere dipende dall’orientamento iniziale della porta; ciò vuol dire
che se si orienta la porta lungo l’asse delle ascisse X, la componente su cui operare sarà,
appunto, la componente X e il codice sarà modificato in: pos.x = pos.x - 2.000;.
// la porta è aperta
state open
{
state_entry()
{
// creiamo un timer di 2 secondi
llSetTimerEvent(2.0);
}
// evento scatenato dal timer
timer()
{
// cancelliamo il timer
llSetTimerEvent(0);
// cambiamo stato
state closing;
}
}
Lo stato open usa la funzione di libreria: llSetTimerEvent che consente d’implementare la
chiusura automatica della porta; la funzione è utilizzata per scatenare un evento di tipo
timer ogni qualvolta trascorre l’intervallo di tempo specificato come parametro della
funzione.
In questo caso, dopo due secondi è invocato il metodo timer che, in primo luogo annulla il
timer, passando alla funzione llSetTimerEvent il valore zero, quindi esegue una nuova
transizione di stato che pone la porta in chiusura, closing.
// la porta si sta chiudendo
state closing
{
state_entry()
{
// ricaviamo la posizione corrente della porta
vector pos = llGetPos();
// aggiungiamo alla componente trasversale la lunghezza della porta
pos.y = pos.y + 2.000;
// imponiamo la nuova posizione
llSetPos(pos);
// cambiamo stato
state default;
}
}
Nello stato closing non si deve fare altro che compiere il processo inverso rispetto al
metodo opening.
Impostare quindi la posizione della porta al valore iniziale e portarsi nuovamente nello
stato di default.
File SEMAFORO BASE.LSL
L’esempio precedente usava uno script associato ad un singolo elemento, prim ma gli
oggetti più complessi necessitano una composizione di più prim.
TPSIT 5
434
um 370 di
Per esempio, un semaforo è composto da un supporto più le tre lampade dei colori.
La composizione grafica del semaforo è semplice; basta creare tre cilindri e porli sopra
una base rappresentata da un parallelepipedo.
Per creare un unico oggetto è sufficiente selezionare ogni prim, quindi selezionare la voce
Tools/Link.
È importante definire l’ordine con cui si selezionano i prim dato che l’ultimo selezionato
prima dell’operazione di link sarà il root prim che regolerà il comportamento dell’intero
oggetto.
Programmare il semaforo affinché riproduca il ciclo semaforico dei segnali verde, giallo e
rosso.
In questo caso, si vuole che i singoli prim interagiscano tra loro per sincronizzarsi a
vicenda.
In questo modo, il rosso comunica al verde quando accendersi; stessa cosa fa il verde nei
confronti del giallo che, infine, a sua volta comunica con il rosso.
Esistono diverse modalità di comunicazione tra prim; in alcune situazioni si utilizza la
funzione di libreria llSay o llShout se si vuole comunicare a distanze superiori.
Si deve specificare un canale diverso dallo 0 usato per la chat.
Normalmente questa modalità è utilizzata per mettere in comunicazione prim fisicamente
distanti come nel caso di un ascensore.
Nel caso in cui i prim sono linkati si preferisce utilizzare la funzione di libreria
llMessageLinked che invia messaggi solo ai prim che compongono l’oggetto; ciò mette al
riparo da eventuali interferenze che potrebbero sorgere a causa di altri oggetti che,
accidentalmente, comunicano sullo stesso canale.
Stabilire che l’accensione del semaforo è scatenata dalla base che, in fase di avvio
dell’oggetto, invia il primo messaggio ai prim.
default
{
state_entry()
{
// inviamo il messaggio di "accensione rosso" all’insieme di prim
llMessageLinked(LINK_SET, 0, "rosso", NULL_KEY);
}
}
La funzione di libreria llMessageLinked ha diversi parametri; in questo caso il terzo
parametro rappresenta il messaggio che è inviato ovvero la lampada da accendere.
Il primo parametro rappresenta il destinatario del messaggio; la parola chiave LINK_SET
indica che il messaggio sarà recapitato a tutti i prim dell’insieme.
Il codice presente nelle tre lampade è simile; nello stato predefinito si mette all’ascolto dei
messaggi linkati per mezzo del metodo link_message.
Ogni qualvolta arriva un messaggio si verifica se è diretto al prim che si sta codificando; in
caso positivo si attiva una transizione di stato, ovvero si accende la lampada, in caso
negativo si spegne la lampada.
File LAMPADA ROSSO.LSL
default
{
// ascoltiamo i messaggi inviati all’insieme dei prim
link_message(integer sender_num, integer num, string str, key id)
{
// se il messaggio è indirizzato all’oggetto
if(str == "rosso") {
// andiamo allo stato di accensione della lampada
state accesa;
}
TPSIT 5
434
um 371 di
Else {
// spegnamo la lampada
llSetAlpha(0.0, ALL_SIDES);
}
}
}
Per accendere e spegnere la lampada si usa la funzione di libreria llSetAlpha che
definisce la trasparenza/opacità dell’oggetto.
Quando si deve spegnere la lampada s’impone il valore del primo parametro a 0.
Il secondo parametro della funzione serve per definire su quali facce dell’oggetto si deve
imporre il valore di opacità; la parola chiave ALL_SIDES impone il valore in ogni faccia.
// stato di accensione della lampada
state accesa
{
state_entry()
{
// accendiamo la lampada
llSetAlpha(1.0, ALL_SIDES);
// imponiamo la durata dell’accensione della lampada
llSetTimerEvent(3);
}
timer()
{
// cancelliamo il timer
llSetTimerEvent(0);
// informiamo l’insieme di prim che bisogna accendere il "verde"
llMessageLinked(LINK_SET, 0, "verde", NULL_KEY);
// spegniamo la lampada
llSetAlpha(0.0, ALL_SIDES);
// ci riposizioniamo nello stato predefinito
state default;
}
}
Nello stato accesa, come prima operazione si accende la lampada passando al metodo
llSetAlpha il valore 1 come primo parametro.
Contestualmente si attiva un timer llSetTimerEvent che rappresenta la durata
dell’accensione della lampada.
Trascorso il tempo prestabilito, tramite il metodo timer si devono effettuare le seguenti
operazioni: annullare il timer, inviare un messaggio ai prim relativo alla successiva
lampada da accendere, spegnere la lampada attuale, riposizionarsi nello stato di default.
TPSIT 5
434
um 372 di
File LAMPADA VERDE.LSL
default
{
// ascoltiamo i messaggi inviati all’insieme dei prim
link_message(integer sender_num, integer num, string str, key id)
{
// se il messaggio è indirizzato all’oggetto
if(str == "verde") {
// andiamo allo stato di accensione della lampada
state accesa;
}
else {
// spegnamo la lampada
llSetAlpha(0.0, ALL_SIDES);
}
}
}
// stato di accensione della lampada
state accesa
{
state_entry()
{
// accendiamo la lampada
llSetAlpha(1.0, ALL_SIDES);
// imponiamo la durata dell’accensione della lampada
llSetTimerEvent(3);
}
timer()
{
// cancelliamo il timer
llSetTimerEvent(0);
// informiamo l’insieme di prim che bisogna accendere il "verde"
llMessageLinked(LINK_SET, 0, "giallo", NULL_KEY);
// spegniamo la lampada
llSetAlpha(0.0, ALL_SIDES);
// ci riposizioniamo nello stato predefinito
state default;
}
}
File LAMPADA GIALLO.LSL
default
TPSIT 5
434
um 373 di
{
// ascoltiamo i messaggi inviati all’insieme dei prim
link_message(integer sender_num, integer num, string str, key id)
{
// se il messaggio è indirizzato all’oggetto
if(str == "verde") {
// andiamo allo stato di accensione della lampada
state accesa;
}
else {
// spegnamo la lampada
llSetAlpha(0.0, ALL_SIDES);
}
}
}
// stato di accensione della lampada
state accesa
{
state_entry()
{
// accendiamo la lampada
llSetAlpha(1.0, ALL_SIDES);
// imponiamo la durata dell’accensione della lampada
llSetTimerEvent(3);
}
timer()
{
// cancelliamo il timer
llSetTimerEvent(0);
// informiamo l’insieme di prim che bisogna accendere il "verde"
llMessageLinked(LINK_SET, 0, "giallo", NULL_KEY);
// spegniamo la lampada
llSetAlpha(0.0, ALL_SIDES);
// ci riposizioniamo nello stato predefinito
state default;
}
}
File PULSANTE0.LSL
Non tutti gli oggetti possono essere rappresentati in modo compatto linkando più prim.
Nel caso di sistemi più complessi, le varie componenti possono trovarsi distribuite nello
spazio come nel caso di un ascensore che è formato dalla cabina e dai pulsanti ai piani da
utilizzare per chiamare l’ascensore.
default
{
state_entry()
{
// creiamo un testo flottante al di sopra dell’oggetto
llSetText("Chiamata",<0,0,0>,1.0);
}
touch_start(integer total_number)
{
// "gridiamo" sul canale 999 il messaggio "1"
llShout(999, "0");
}
}
Per dare un aiuto agli avatar si deve mostrare un testo flottante in corrispondenza del tasto
tramite la funzione di libreria llSetText.
TPSIT 5
434
um 374 di
Quindi si deve gestire la pressione del pulsante di chiamata codificando il metodo
touch_start; in questo caso si usa la funzione di libreria llShout in modo che anche in un
edificio alto le chiamate possano essere ascoltate dalla cabina.
Si è scelto il canale 999 per trasmettere i messaggi rappresentati dal numero del piano cui
deve portarsi la cabina.
File PULSANTE1.LSL
default
{
state_entry()
{
// creiamo un testo flottante al di sopra dell’oggetto
llSetText("Chiamata",<0,0,0>,1.0);
}
touch_start(integer total_number)
{
// "gridiamo" sul canale 999 il messaggio "1"
llShout(999, "1");
}
}
File PULSANTE2.LSL
default
{
state_entry()
{
// creiamo un testo flottante al di sopra dell’oggetto
llSetText("Chiamata",<0,0,0>,1.0);
}
touch_start(integer total_number)
{
// "gridiamo" sul canale 999 il messaggio "1"
llShout(999, "2");
}
}
File PULSANTE3.LSL
default
{
state_entry()
{
// creiamo un testo flottante al di sopra dell’oggetto
llSetText("Chiamata",<0,0,0>,1.0);
}
touch_start(integer total_number)
{
// "gridiamo" sul canale 999 il messaggio "1"
llShout(999, "3");
}
}
File CABINA.LSL
Si deve gestire sia la chiamata ad un piano sia il comando inviato dall’interno della cabina,
dall’avatar che sale o scende.
Presupporre che la cabina, indipendentemente dalla sua forma, possegga una seduta che
consentirà all’avatar che si siede, di scegliere il piano cui portarsi.
Lo script dovrà essere posto all’interno della seduta che dovrà essere l’ultimo oggetto
selezionato prima di linkare tutti i componenti della cabina ovvero il root prim dell’oggetto
composito.
Definire due variabili globali: una lista che contiene le altezze dei vari piani in modo da
poter individuare immediatamente dove muovere la cabina e un’ulteriore lista per
TPSIT 5
434
um 375 di
contenere la lista dei piani.
Le ulteriori variabili globali servono per definire la velocità con cui si muove la cabina, il
piano e l’altezza attuale della cabina.
Le variabili globali vanno dichiarate in testa allo script e sono accessibili in ogni metodo di
ogni stato all’interno dello script.
Nel metodo state_entry dello stato predefinito, default, tramite la funzione di libreria
llListen, ci si pone in ascolto dei messaggi inviati sul canale 999, quello dei pulsanti ai
piani.
Su questo canale si devono trasmettere anche i comandi della pulsantiera della cabina.
La funzione di libreria llSitTarget, predispone la seduta ad accogliere opportunamente
l’avatar, accomodandolo esattamente sulla seduta e fronte verso l’esterno.
L’ultima istruzione indica all’avatar di sedersi.
list ALTEZZE = [28, 29, 30, 31];
list PIANI = ["0", "1", "2", "3"];
float SPEED = 0.1;
integer piano;
integer altezza;
default
{
state_entry()
{
// ci poniamo all’ascolto del canale 999
llListen(999, "", NULL_KEY, "");
// facciamo in modo che l’avatar si segga corettamente
llSitTarget(<0,-0.5,0.5>, llEuler2Rot(<0,0,-90>) );
// messaggio informativo per l’avatar
llSetText("Sedersi prego",<0,0,0>,1.0);
}
// metodo che riceve i messaggi inviati dagli altri oggetti
listen(integer channel, string name, key id, string message)
{
// verifichiamo che il messaggio proviene dal canale 999
if(channel == 999) {
// impostiamo il piano a cui portare la cabina
piano = (integer)message;
// cambiamo stato
state moving;
}
}
// metodo scatenato dall’avatar che si siede in cabina
changed(integer Change)
{
// mostriamo all’avatar la lista dei piani
llDialog(llAvatarOnSitTarget(), "Scegliere il piano", PIANI, 999);
}
}
Nello stato predefinito della cabina, si deve altresì codificare il metodo di ascolto dei
messaggi, listen e il metodo scatenato dall’avatar che si siede in cabina, changed.
Nel primo metodo, dopo aver verificato che il messaggio provenga dal canale 999, si
valorizza la variabile globale piano con il valore del messaggio che contiene, appunto, il
piano cui è stata richiesta la cabina; quindi posizionarsi sullo stato moving.
Nel metodo changed che si accorge che un avatar si è seduto in cabina, si usa una
funzione di libreria che consente d’interagire con gli avatar llDialog che mostra un dialogo
ad un avatar, dove sono presenti diversi pulsanti che possono essere usati per consentire
TPSIT 5
434
um 376 di
una scelta.
La funzione accetta quattro parametri: la key dell’avatar cui mostrare il dialogo, un
messaggio esplicativo, una lista di opzioni che saranno trasformate in pulsanti e un canale
su cui trasmettere la scelta effettuata.
Affinché il dialogo sia mostrato effettivamente all’avatar che si siede in cabina, si usa la
funzione di libreria llAvatarOnSitTarget che restituisce la key dell’avatar che è attualmente
seduto sull’oggetto.
Il canale che si utilizza per trasmettere la scelta dell’avatar è proprio il 999 dove sono
trasmessi anche i comandi dei pulsanti ai piani.
In questo modo si è centralizzata la gestione del movimento della cabina che, tramite il
metodo listen, gestirà sia i messaggi provenienti dai piani sia quelli provenienti dalla
cabina.
// la cabina si sta muovendo
state moving
{
state_entry()
{
// definiamo un timer che scatta ogni decimo di secondo
llSetTimerEvent(0.1);
// ricaviamo l’altezza cui portare la cabina
altezza = llList2Integer(ALTEZZE, piano);
}
// metodo invocato ogni decimo di secondo
timer()
{
// ricaviamo la posizione attuale della cabina
vector pos = llGetPos();
// se non siamo arrivati al piano …
if( pos.z!=altezza ) {
// se la cabina è più in alto del piano a cui portarsi …
if( pos.z>altezza ) {
// facciamo scendere la cabina
pos.z = pos.z - SPEED;
}
else {
// facciamo salire la cabina
pos.z = pos.z + SPEED;
}
}
// se siamo in un "intorno" del piano inferiore allo step di movimento
if( llFabs(pos.z - altezza) < SPEED ) {
// definiamo precisamente la posizione della cabina
pos.z = altezza;
// azzeriamo il timer
llSetTimerEvent(0);
// applichiamo la posizione alla cabina
llSetPos(pos);
// informiamo l’avatar che siamo arrivati
llSay(0,"Ascensore al piano" );
// ci posizioniamo nello stato predefinito
state default;
}
// applichiamo la posizione alla cabina
llSetPos(pos);
TPSIT 5
434
um 377 di
}
}
Per implementare il movimento della cabina, nel metodo state_entry dello stato moving,
definire un timer che scatta ogni decimo di secondo.
Nel corrispondente metodo timer, fare un’operazione di confronto tra l’altezza attuale della
cabina, tramite la componente Z della sua posizione attuale e l’altezza cui la si deve
portare.
La differenza tra queste due grandezze consente di aggiungere o togliere uno step di
movimento, definito tramite la variabile globale SPEED.
Dato che questa operazione è eseguita ripetutamente ciò che si ricava è un movimento
abbastanza fluido della cabina.
Quando si è in corrispondenza del piano, ovvero quando la differenza tra la posizione
attuale della cabina e l’altezza cui si deve portarla è minore, in valore assoluto, allo step di
movimento, vuol dire che si è arrivati.
In questa situazione si azzera il timer e s’informa l’avatar che si è arrivati.
L’implementazione del movimento si può gestire indicando la posizione della cabina con il
valore dell’altezza cui si deve portarla.
L’effetto collaterale è che l’animazione che il simulatore produce a fronte di una semplice
variazione di posizione è estremamente rapida.
Questo vuol dire che ogni volta si è “sparati” al piano piuttosto che “portati”.
Per questa ragione si è parcellizzato il movimento, scomponendolo in tanti piccoli
movimenti.
Per provare gli script non si deve necessariamente associarli agli oggetti che in
produzione saranno animati da ciò che si produce.
Nel caso dell’ascensore, per esempio, non si deve, per forza, creare un palazzo, una serie
di piani, una cabina più o meno verosimile.
Per provare lo script è sufficiente lavorare con i cubi che chiunque è in grado di costruire.
Ad ogni cubo può essere associato il relativo script e quindi fare le prove del caso.
Nella figura è presente l’ascensore e i pulsanti di chiamata tutti rappresentati da cubi.
Il risultato grafico è tutt’altro che esaltante ma si riescono a testare le funzionalità.
Questo consente di essere sicuri del risultato quando si metteranno gli script negli oggetti
creati dai grafici.
File PORTA CARDINI.LSL
Progettare una classica porta che ruota attorno ai sui suoi cardini, bisogna studiare come
far ruotare i corpi in LSL.
TPSIT 5
434
um 378 di
Si usa un oggetto composito formato dall’anta della porta e dai suoi cardini.
Si possono rappresentare questi ultimi come un minuscolo parallelepipedo che si accosta
ad uno dei lati dell’anta.
Premesso che l’oggetto composito ha come root prim, i cardini, si mette proprio in questo
oggetto il codice.
Nello stato predefinito, default, considerare la porta chiusa.
Per aprire la porta basta toccare la sua anta che scatena l’evento touch_start.
Come prima operazione memorizzare la rotazione attuale della porta nella variabile
globale rot, quindi eseguire una transizione di stato che posiziona l’oggetto nello stato
opening.
// variabile globale per memorizzare la rotazione attuale della porta
rotation rot;
// variabile globale per memorizzare la rotazione per aprire/chiudere la porta
rotation delta;
default
{
// evento scatenato dal tocco dell’avatar
touch_start(integer total_number)
{
// memorizziamo la rotazione attuale della porta
rot = llGetLocalRot();
// cambiamo stato
state opening;
}
}
Nello stato opening definire la rotazione che deve compiere la porta quando si apre.
In questo caso, si usa la funzione di libreria llEuler2Rot che trasforma una rotazione
espressa tramite un vettore in cui s’indicano gli angoli di rotazione attorno agli assi, in una
variabile di tipo rotation che esprime la rotazione tramite una quaterna di valori.
Per definire una rotazione longitudinale, ovvero attorno all’asse Z di 90 gradi, si passa alla
funzione un vettore con le prime due componenti a zero e con la terza componente
valorizzata con la parola chiave PI/2.
L’effettiva rotazione si calcola moltiplicando il valore ottenuto dalla funzione, per la
rotazione attuale.
Tramite la funzione di libreria llSetRot, si applica la rotazione che è eseguita, come le
transizioni longitudinali, in modo fluido dal simulatore.
// la porta si sta aprendo
state opening
{
state_entry()
{
// definiamo una rotazione di 90 gradi attorno all’asse Z
delta = llEuler2Rot(<0.0,0.0,PI/2>);
// calcoliamo la rotazione sulla base della rotazione attuale
rot = delta * rot;
// applichiamo la rotazione
llSetRot(rot);
// cambiamo stato
state open;
}
}
Dato che anche in questo caso si vuole dotare la porta di chiusura automatica, nello stato
TPSIT 5
434
um 379 di
open impostare un timer che rappresenta il tempo in cui la porta resta aperta.
Trascorso il periodo di tempo impostato, posizionarsi nello stato close che compie la
rotazione inversa e riposiziona la porta nel suo stato predefinito, default.
// la porta è aperta
state open
{
state_entry()
{
// creiamo un timer di 2 secondi
llSetTimerEvent(2.0);
}
// evento scatenato dal timer
timer()
{
// cancelliamo il timer
llSetTimerEvent(0);
// cambiamo stato
state closing;
}
}
// la porta si sta chiudendo
state closing
{
state_entry()
{
// definiamo una rotazione di meno 90 gradi attorno all’asse Z
delta = llEuler2Rot(<0.0,0.0,-PI/2>);
// calcoliamo la rotazione sulla base della rotazione attuale
rot = delta * rot;
// applichiamo la rotazione
llSetRot(rot);
// ci riposizioniamo nello stato predeinito
state default;
}
}
La parola usata per indicare la creazione di un oggetto, in realtà è un termine coniato negli
anni ‘80 quando, nel film Tron, la parola “de-rezz” era utilizzata per indicare che un oggetto
del mondo 3D era distrutto o “de-resolved”, la parola usata al contrario, quindi senza il
prefisso “de”, indica, quindi, la creazione di un oggetto.
Fase di creazione di un oggetto, in gergo la fase di rez.
Ogni oggetto creato in Second LIFE è individuato univocamente dalla sua chiave che è un
identificatore univoco UUID (Universal Unique IDentifier), rappresentato da una stringa di
numeri esadecimali di 36 caratteri nel formato 00000000-0000-0000-0000-000000000000,
per esempio 66864f3c-e095-d9c8-058dd6575e6ed1b8.
Questa chiave è la via per comunicare con l’oggetto, dato che tutte le funzioni di libreria,
utili per la comunicazione, prendono come primo parametro proprio la chiave del
destinatario.
Normalmente, una volta che sviluppato l’oggetto, sia in termini grafici sia di funzionalità
interattive, lo si ripone nell’inventario per poterlo riutilizzare in seguito.
Questa operazione nasconde un’insidia che dev’essere analizzata per evitare
malfunzionamenti degli script che si basano sulle chiavi degli oggetti.
In effetti l’inventario dell’avatar non è una semplice borsa in cui porre gli oggetti.
In verità, quando si pone un oggetto nell’inventario, clic con il tasto destro del mouse
sull’oggetto, quindi clic sulla voce Take si cancella l’oggetto dal terreno e si porta
TPSIT 5
434
um 380 di
nell’inventario un suo modello; ogni qualvolta si estrae nuovamente l’oggetto
dall’inventario, si crea una nuova copia.
Nell’OOP (Object oriented Programming) è come se si ponesse nell’inventario una classe,
e ogni volta che si estrae l’oggetto si crea una sua istanza.
Queste precisazioni sono molto importanti, dato che la chiave dell’oggetto cambia ogni
volta, in realtà è creato un nuovo oggetto, per cui se si progettano script basati su chiavi
occorre stare attenti ai processi di rez e de-rez.
File REZIT.LSL
Progettare un oggetto.
default
{
// scatenato dal tocco di un avatar
touch_start(integer total_number)
{
// invia un messaggio istantaneo al proprietario dell’oggetto
llOwnerSay((string)llGetKey());
}
// scatenato ogni volta che l’oggetto è creato dall’inventario
on_rez(integer start_param)
{
// invia un messaggio istantaneo al proprietario dell’oggetto
llOwnerSay((string)llGetKey());
// resetta lo script
llResetScript();
}
}
L’evento on_rez è scatenato ogni qualvolta un oggetto è creato a partire dall’inventario.
All’interno dell’evento, si usa la funzione di libreria llOwnerSay che invia un messaggio al
proprietario dell’oggetto, la stessa funzione la si usa anche per gestire il tocco dell’oggetto
da parte di un avatar.
Il messaggio è proprio la chiave dell’oggetto che si ricava invocando la funzione di libreria
llGetKey, il risultato che è un valore di tipo key, è “castato” al tipo stringa in modo da
evitare un errore di tipo dato che la funzione di libreria llOwnerSay accetta come
parametro valori di tipo stringa.
Ogni script rappresenta la programmazione di una macchina a stati finiti orientata
agli eventi.
Lo script è lanciato la prima volta che è salvato dall’editor, quindi evolve nel susseguirsi
degli stati che rispondono agli eventi scatenati dall’oggetto o nell’oggetto.
Quando si porta l’oggetto nell’inventario, questo è memorizzato nel suo stato attuale; ciò
significa che quando lo si ricrea a partire dall’inventario, questo non “riparte” dallo stato di
default ma da quello in cui lo si era lasciato quando si era messo nell’inventario.
Di conseguenza anche tutte le variabili globali non hanno il valore iniziale ma quello che
avevano al momento dell’inserimento nell’inventario.
In realtà, quando si crea un nuovo oggetto a partire dall’inventario, l’intenzione è quella di
creare un oggetto “nuovo” quindi che parta dallo stato di default con tutte le variabili con i
valori predefiniti.
La funzione di libreria llResetScript riporta esattamente lo script allo stato iniziale, in modo
da avere un oggetto “nuovo”.
Ovviamente si deve inserire il codice relativo all’evento on_rez in tutti gli stati che
s’implementeranno per l’oggetto.
Ogni volta che si crea un nuovo oggetto, il simulatore lo nomina con l’anonimo Object.
TPSIT 5
434
um 381 di
È buona norma di programmazione modificare subito questo parametro in modo che,
quando lo si porta nell’inventario, si ha modo di riconoscerlo immediatamente.
Nell’inventario, infatti, gli oggetti sono elencati mostrando il loro “nome”; se non si adotta
un protocollo di denominazione degli oggetti, si avranno molti Object che non saremo in
grado di riconoscere.
In Second LIFE ogni residente è ben identificato all’interno del mondo.
Questo vuol dire che, a differenza di ciò che accade per i siti web, si ha la possibilità di
sapere esattamente chi visita per esempio i nostri negozi.
In altre parole ciò vuol dire che in Second LIFE non esiste l’anonimato, da non confondere
con l’incognito.
Incognito vs anonimato
Il fatto che in Second LIFE non esista anonimato è conseguenza della creazione
dell’avatar stesso che, per sua natura, ha un nome, un cognome e, soprattutto, una chiave
che lo identifica univocamente all’interno di Second LIFE.
Ovviamente ciò non vuol dire che se ci si registra in Second LIFE ogni sconosciuto ha la
possibilità di venire a bussare alla nostra porta reale.
In fase di registrazione è possibile, anche se non consigliabile, fornire le generalità del
cittadino più famoso d’Italia, il “Sig. Mario Rossi”.
Quindi, sebbene si è sempre raggiungibili, in Second LIFE non si è anonimi, si può stare
tranquilli che nella vita reale nessuno verrà a disturbare, siamo in incognito.
Il potenziale che offre questa caratteristica di Second LIFE; a differenza dei siti web in cui
si può al più avere una collezione di “hit” ovvero una serie d’indirizzi IP che fanno
riferimento alle pagine visitate, in Second LIFE si può potenzialmente identificare nome e
cognome di Second LIFE ovviamente di ogni visitatore.
Ci sono diverse tecniche che consentono d’individuare gli avatar che ci visitano; si
possono mettere in relazione le tecniche con gli eventi che consentono l’individuazione
ovvero gli eventi scatenati dal tocco, dai sensori e dalle collisioni tra gli avatar e l’oggetto.
Le tecniche differiscono fondamentalmente dal tipo d’interazione che deve effettuare
l’avatar per essere individuato; in ogni caso si utilizzano un insieme di funzioni di libreria
tutte individuate dal prefisso: llDetected.
File DETECTION TOCCO.LSL
default
{
touch_start(integer total_number)
{
// individuiamo la chiave dell?avatar che tocca l’oggetto
key avatarKey = llDetectedKey(0);
// individuiamo il nome dell?avatar che tocca l’oggetto
string avatarName = llDetectedName(0);
// inviamo alcuni messaggi all?avatar che tocca l’oggetto
llInstantMessage(avatarKey, avatarName + " mi hai toccato!");
// convertiamo la chiave dell?avatar in stringa
string savatarKey = (string)avatarKey;
llInstantMessage(avatarKey, avatarName + " la tua chiave e’: " + savatarKey);
}
// scatenato ogni volta che l?oggetto viene creato dall’inventario
on_rez(integer start_param)
{
// resetta lo script
llResetScript();
}
TPSIT 5
434
um 382 di
}
Per l’oggetto, implementare il solo evento di reazione al tocco dell’avatar touch_start.
Al suo interno s’incontrano le funzioni di libreria per la detection: llDetectedKey e
llDetectedName.
Ad entrambe le funzioni si passa un parametro che rappresenta l’indice dell’oggetto
individuato.
In questo caso si passa il valore 0 in quanto il tocco scaturisce da un solo avatar.
La prima delle due funzioni restituisce la chiave univoca dell’avatar, quindi un valore di tipo
key; la seconda restituisce il nome e il cognome in Second LIFE dell’avatar.
Nell’esempio, si mandano dei messaggi all’avatar che tocca l’oggetto.
Questa tecnica è semplice da implementare ma presuppone un intervento da parte
dell’avatar che, per essere individuato, deve esplicitamente toccare l’oggetto.
TPSIT 5
434
um 383 di
FACEBOOK
INTRODUZIONE
È un sito web di reti sociali ad accesso gratuito, il nome si riferisce agli annuari con le foto
di ogni singolo soggetto che alcuni college statunitensi pubblicano all’inizio dell’anno
accademico e distribuiscono ai nuovi studenti e al personale della facoltà come mezzo per
conoscere le persone del campus.
È stato fondato il 4 febbraio 2004 da Mark Zuckerberg (White Plains, 14 maggio 1984)
all’epoca studente diciannovenne presso l’università di Harvard, con l’aiuto di Andrew
McCollum e Eduardo Saverin.
Zuckerberg fu aiutato da Dustin Moskovitz e Chris Hughes per la promozione del sito e
Facebook si espanse in tutte le università americane.
Alla fine dell’anno accademico, Zuckerberg e Moskovitz si trasferirono a Palo Alto in
California con McCollum che aveva seguito uno stage estivo alla Electronic Arts.
Affittarono una casa vicino all’Università di Stanford dove furono raggiunti da Adam
D’Angelo e Sean Parker.
Titolo originale
Paese
Anno
Durata
Genere
Regia
Soggetto
Sceneggiatura
Casa di produzione
Fotografia
Montaggio
Musiche
Scenografia
Interpreti e personaggi
Jesse Eisenberg:
Andrew Garfield:
Justin Timberlake:
Brenda Song:
Rashida Jones:
Joseph Mazzello:
Max Minghella:
Rooney Mara:
Armie Hammer:
The Social Network
Stati Uniti
2010
121 min
Drammatico
David Fincher
Ben Mezrich
Aaron Sorkin
Columbia Pictures
Jeff Cronenweth
Kirk Baxter, Angus Wall
Trent Reznor, Atticus Ross
Donald Graham Burt
Mark Zuckerberg
Eduardo Saverin
Sean Parker
Christy Lee
Marylin Delpy
Dustin Moskovitz
Martin Turner
Erica Albright
Cameron/Tyler Winklevoss
Il film è stato adattato per il grande schermo dal libro di Ben Mezrich “Miliardari per caso L’invenzione di Facebook: una storia di soldi, sesso, genio e tradimento” (Sperling &
Kupfer).
FC (FACEBOOK CONNECT)
È possibile collegare il proprio account Facebook con quello utilizzato per inserire
commenti o articoli su un sito esterno, come ad esempio un blog.
In pratica, è possibile portare la propria identità Facebook all’esterno, utilizzandola nei siti
TPSIT 5
434
um 384 di
che supportano FC.
FC consente di rimuovere il form di registrazione, sostituendolo con il login di Facebook,
questa funzionalità è definita “single signon”.
In pratica, dopo essersi autenticati su Facebook, si può navigare sul web e ogni sito che
implementa FC riconosce l’identità senza dover fare un’ulteriore autenticazione.
Dal punto di vista tecnico FC è reso possibile da una libreria JavaScript rilasciata da
Facebook e da un’estensione del linguaggio XHTML (eXtensible HTML) chiamata FBML
che permette ai programmatori d’inserire elementi dinamici generati da Facebook sulle
pagine di un sito.
I punti fondamentali sui quali si basa la tecnologia e la strategia FC sono i seguenti.
 Identità reale e Autenticazione: gli utenti possono autenticarsi in modo sicuro e
portare la propria identità Facebook su qualsiasi sito.
 Amici: la rete di amicizie ci segue ovunque, permettendoci d’interagire con gli amici
anche nel contesto di un sito esterno.
 Comunicazione sociale: gli utenti potranno rendere note le azioni effettuate su un
determinato sito, pubblicando i classici news feed su Facebook.
 Privacy: le impostazioni fatte su Facebook saranno utilizzate anche dai siti esterni.
Per funzionare FC ha la necessità di scambiare costantemente informazioni tra il browser
dell’utente che rimane puntato sul dominio e i server di Facebook.
Tutti i browser, per ragioni di sicurezza, isolano le chiamate AJAX (Asynchronous
JavaScript and XML) su ogni dominio di primo livello.
Per esempio, www.miosito.it non può fare una chiamata a www.altrosito.it ma solo a
servzi.miosito.it, per questo motivo occorre un file per consentire il SCDC (Secure Cross
Domain Communication), un meccanismo per condividere dati fra domini differenti in
modo sicuro e controllato.
Tecnicamente si basa sullo scambio d’informazioni tra la pagina di un dominio e un iFrame
che punta a una pagina di un secondo dominio.
PROGRAMMAZIONE
Nell’autunno 2008 Facebook ha reso disponibile un sistema di API lato client (JavaScript e
PHP) e server (REST) per consentire al programmatore di realizzare applicazioni
utilizzabili per integrare i siti esterni con Facebook.
Lo scopo è di portare applicazioni di terze parti dentro la rete sociale.
Registrazione
Il primo passo per realizzare un’applicazione è possedere un account e in seguito
installare il DEVELOPER.
Per prima cosa è necessario creare una nuova applicazione Facebook.
Dall’applicazione Facebook Developer http://www.facebook.com/developers cliccare sul
tasto Set up new application.
Dopo aver accettato le condizioni d’uso, assegnare un nome all’applicazione, una volta
che è stata creata, prendere nota dell’API Key e dell’Application Secret che insieme
identificano in modo univoco l’applicazione.
Tra i campi opzionali compilare solo il campo Callback URL con l’indirizzo del server che
ospiterà la pagina.
Nella sezione dedicata a Connect, configurare il campo Connect URL e Base Domain,
perché indispensabili per il corretto funzionamento dell’applicazione.
La chiamata FB.init (Api_Key, Cross Domain Communication), inizializza la libreria
passando due parametri: l’API Key assegnata all’applicazione ottenuto da Facebook e un
file che permette di risolvere le chiamate tra domini differenti.
La funziona FB.init lancerà due funzioni diverse a seconda che l’utente sia autenticato
TPSIT 5
434
um 385 di
oppure meno.
Se l’utente non ha fatto login si visualizza all’interno della DIV 'user' il pulsante di login
<fb:login-button/>.
Altrimenti, se l’utente ha fatto login in Facebook, si visualizza l’immagine profilo utente
<fb:profile-pic/> e il nome dell’utente <fb:name/>.
Tutte le chiamate al servizio REST di Facebook sono fatte attraverso il metodo generico
SendRequest<T> che ha lo scopo di confezionare la chiamata HTTP e restituire
un’istanza del tipo specificato come parametro generico.
Le chiamate al servizio REST devono essere preparate inserendo nel body del POST i
seguenti parametri.
 api_key: è la stringa che identifica l’applicazione.
 session_key: è una stringa che rappresenta la sessione dell’utente autenticato.
 call_id: è un float che identifica la sequenza delle chiamate.
 sig: è l’hash in formato MD5 (Message Digest algorithm 5) di tutti i parametri della
richiesta più Application Secret.
 v: il numero di versione dell’API.
Il metodo SendRequest<T>, colleziona queste informazioni in un Dictionary e aggiunge i
parametri standard richiamando il metodo AddStandartParameter.
Una volta che il Dictionary contiene tutti i parametri, è necessario aggiungere il sid, uno
sbaglio nel calcolo dell’hash MD5 causerà un errore 104 “Incorrect signature”.
Silverlight è privo dell’algoritmo criptografico MD5 ma serve per la realizzazione di
applicazioni che fanno uso dell’API di Facebook.
Il delegate Action<T> è indispensabile in scenari, dove l’accesso ai dati è del tipo
asincrono, in Silverlight questo è l’unico modello disponibile, quindi il poter passare un
metodo da eseguire solo dopo la fine task asincrono risulta di fondamentale importanza.
TPSIT 5
434
um 386 di
Progettare un client per mantenersi in contatto con i collaboratori via Facebook che,
interfacciandosi con il servizio REST, permette di recuperare i propri contatti e d’inviare dei
messaggi che appariranno nella bacheca del contatto selezionato.
Fare clic su File/Nuovo/Progetto… (CTRL+MAIUSC+N), nella finestra di dialogo Nuovo
Progetto selezionare Visual C#/Silverlight/Applicazione Silverlight, scegliere come
host un’Applicazione Web ASP.NET.
TPSIT 5
434
um 387 di
BING (Bing is not Google)
INTRODUZIONE
Bing è il motore di ricerca di Microsoft, nato da MSN (MicroSoft Network) Live Search nel
giugno 2009.
Insieme al motore di ricerca sono disponibili un servizio di mappe, un servizio di ricerca
immagini, un servizio di ricerca news e un motore di ricerca dedicato allo shopping, in
Italia in partnership con ciao.it.
Per i webmaster è disponibile Webmaster Center, servizio che permette di verificare lo
stato d’indicizzazione del sito e altre statistiche utili ai webmaster.
Mette a disposizione API grazie alle quali i programmatori possono interagire con il motore
di ricerca e recuparne i risultati per poterne poi disporre come meglio credono.
PROTOCOLLO XML
Capire come è strutturato il risultato della ricerca sia in caso di risultato corretto sia
d’errore, è utile per riuscire a interpretare l’XML che si riceve a seguito della richiesta.
Se si desidera eseguire una ricerca con il protocollo XML, occorre eseguire una richiesta
web grazie alla classe HttpWebRequest, costruita concatenando l’indirizzo di endpoint del
protocollo XML con i parametri di ricerca.
Grazie alla chiamata a GetResponse della classe HttpWebRequest si ottiene un’istanza
della classe HttpWebResponse dalla quale si può ricavare lo stream XML, da interpretare.
La classe IMAGEXMLSERVICE.VB funge da servizio di business per la ricerca del
progetto.
Dim request = BuildRequest(searchKey, offSet, recordCount, filters)
Me._LastDuration = Stopwatch.StartNew()
Dim response = DirectCast(request.GetResponse, HttpWebResponse)
Me._LastDuration.Stop()
Dim stream = response.GetResponseStream()
retList = CreateImageList(response, totalCount)
BuildRequest costruisce la richiesta web concatenando i parametri della querystring.
Protected Function BuildRequest(ByVal text As String, ByVal startindex As UInteger,
ByVal recordCount As UInteger, ByVal filters As String) As HttpWebRequest
Dim reqBuilder As New StringBuilder
With reqBuilder
.Append("http://api.search.live.net/xml.aspx?")
.AppendFormat("AppId={0}&Query={1}&Sources=Image&Version={2}", AppID,
text, Version)
If Not String.IsNullOrEmpty(Market) Then
.AppendFormat("Market={0}", Market)
End If
If Not String.IsNullOrEmpty(filters) Then
.AppendFormat("Image.Filters={0}", filters)
End If
.AppendFormat("&Image.Count={0}&Image.Offset={1}", recordCount, startindex)
End With
Dim request=DirectCast(WebRequest.Create(reqBuilder.ToString()),
TPSIT 5
434
um 388 di
HttpWebRequest)
Return request
End Function
La proprietà AppID contiene l’AppID recuperato dal portale Bing.
La proprietà Market contiene il mercato di riferimento.
La proprietà Version la versione del servizio Bing da utilizzare.
La funzione CreateImageList si occupa d’interpretare lo stream di ritorno della richiesta per
costruire la lista dei risultati di ricerca o eventuale lista di errori.
Per interpretare lo stream XML di risposta è necessario fare uso della tecnica chiamata
xPath, in particolare, sarà necessario definire i namespace per recuperare eventuali errori
e i risultati della ricerca.
Protected Function CreateImageList(ByVal response As HttpWebResponse, ByRef
totalCount As UInteger) As List(Of Bing.ImageResult)
Dim retList As List(Of Bing.ImageResult) = Nothing
Dim document As New XmlDocument
document.Load(response.GetResponseStream)
Dim nsmgr As New XmlNamespaceManager(document.NameTable)
nsmgr.AddNamespace("api",
"http://schemas.microsoft.com/LiveSearch/2008/04/XML/element")
Dim errors = document.DocumentElement.SelectNodes("./api:Errors/api:Error", nsmgr)
If (errors.Count > 0) Then
For Each errorNode As XmlNode In errors
Dim [error] As New Bing.Error
With [error]
.Code =
UInteger.Parse(GetNodeValue(errorNode.SelectSingleNode("./api:Code", nsmgr)))
.HelpUrl = GetNodeValue(errorNode.SelectSingleNode("./api:HelpUrl", nsmgr))
.Message = GetNodeValue(errorNode.SelectSingleNode("./api:Message", nsmgr))
.Parameter = GetNodeValue(errorNode.SelectSingleNode("./api:Parameter", nsmgr))
End With
Me._LastErrors.Add([error])
Next
Else
retList = New List(Of Bing.ImageResult)
Dim root = document.DocumentElement
nsmgr.AddNamespace("mms",
"http://schemas.microsoft.com/LiveSearch/2008/04/XML/multimedia")
Dim imageNode = root.SelectSingleNode("./mms:Image", nsmgr)
Dim results = imageNode.SelectNodes("./mms:Results/mms:ImageResult", nsmgr)
Dim offset As UInteger
UInteger.TryParse(GetNodeValue(imageNode.SelectSingleNode("./mms:Offset", nsmgr)),
offset)
UInteger.TryParse(GetNodeValue(imageNode.SelectSingleNode("./mms:Total", nsmgr)),
totalCount)
For Each result As XmlNode In results
Dim imageResult = New Bing.ImageResult()
With imageResult
.Title = GetNodeValue(result.SelectSingleNode("./mms:Title", nsmgr))
Dim fileSize As UInteger
If
TPSIT 5
434
um 389 di
UInteger.TryParse(GetNodeValue(result.SelectSingleNode("./mms:FileSize", nsmgr)),
fileSize) Then
.FileSize = fileSize
.FileSizeSpecified = True
End If
Dim height As UInteger
If UInteger.TryParse(GetNodeValue(result.SelectSingleNode("./mms:Height",
nsmgr)), height) Then
.Height = height
.HeightSpecified = True
End If
Dim width As UInteger
If UInteger.TryParse(GetNodeValue(result.SelectSingleNode("./mms:Width",
nsmgr)), width) Then
.Width = width
.WidthSpecified = True
End If
.MediaUrl = GetNodeValue(result.SelectSingleNode("./mms:MediaUrl", nsmgr))
.Thumbnail = New Bing.Thumbnail
.Thumbnail.ContentType =
GetNodeValue(result.SelectSingleNode("./mms:Thumbnail/mms:ContentType", nsmgr))
If
UInteger.TryParse(GetNodeValue(result.SelectSingleNode("./mms:Thumbnail/mms:FileSiz
e", nsmgr)), fileSize) Then
.Thumbnail.FileSize = fileSize
.Thumbnail.FileSizeSpecified = True
End If
If
UInteger.TryParse(GetNodeValue(result.SelectSingleNode("./mms:Thumbnail/mms:Height
", nsmgr)), height) Then
.Thumbnail.Height = height
.Thumbnail.HeightSpecified = True
End If
If
UInteger.TryParse(GetNodeValue(result.SelectSingleNode("./mms:Thumbnail/mms:Width"
, nsmgr)), width) Then
.Thumbnail.Width = width
.Thumbnail.WidthSpecified = True
End If
.Thumbnail.Url =
GetNodeValue(result.SelectSingleNode("./mms:Thumbnail/mms:Url", nsmgr))
.Url = GetNodeValue(result.SelectSingleNode("./mms:Url", nsmgr))
.ContentType =
GetNodeValue(result.SelectSingleNode("./mms:ContentType", nsmgr))
.DisplayUrl = GetNodeValue(result.SelectSingleNode("./mms:DisplayUrl",
nsmgr))
End With
retList.Add(imageResult)
Next
End If
Return retList
End Function
TPSIT 5
434
um 390 di
Utilizzare la classe XmlDocument per caricare lo stream della risposta di Bing, quindi
creare un XmlNamespaceManager per gestire i namespace di Bing e aggiungere il
namespace di base che s’indica con il prefisso ”api”.
Questo consente di verificare se la risposta contiene i nodi <error> utilizzando la query
xPath nella chiamata al metodo SelectNodes.
Se si ottengono degli errori, riempire la collezione degli stessi, mentre se non si sono
ottenuti errori, si deve aggiungere il namespace Multimedia a XmlNamespaceManager per
recuperare i risultati della ricerca e, quindi, si può utilizzare il metodo SelectSingleNode
per effettuare le query xPath per recuperare tutti i risultati di ricerca, i valori di offset e il
numero totale di record.
Il metodo GetNodeValue è una funzione che permette di gestire l’assenza del nodo
passato per argomento ritornando una stringa vuota.
Private Function GetNodeValue(ByVal node As XmlNode) As String
Dim retval As String = Nothing
If node IsNot Nothing Then
retval = node.InnerText
End If
Return retval
End Function
End Class
PROTOCOLLO SOAP
Richiesta SOAP (busta + lettera) inviata ad un server.
La busta rappresenta il linguaggio che trasmette le informazioni, in altre parole il protocollo
HTTP.
La lettera rappresenta i dati, in altre parole il messaggio XML con le informazioni.
Più semplice da utilizzare è il protocollo SOAP, si può recuperare il WSDL dei servizi
SOAP all’indirizzo seguente.
http://api.bing.net/search.wsdl?AppID= <appid>&Version=2.2
Nel progetto si può referenziare il servizio tramite l’opzione Progetto/Aggiungi
riferimento al servizio… e ottenere l’insieme delle classi wrapper necessarie
all’esecuzione delle ricerche.
TPSIT 5
434
um 391 di
La figura rappresenta il diagramma delle classi, relativamente alla fonte di ricerca delle
immagini che sono generate dal WSDL del servizio Bing: BingPortTypeClient rappresenta
la classe tramite la quale è possibile effettuare ricerche con il protocollo SOAP, utilizzando
il metodo Search per le ricerche sincrone o SearchAsync per quelle asincrone.
Il metodo Search prevede come parametro un oggetto di classe SearchRequest e
restituisce un oggetto di classe SearchResponse.
L’istanza di SearchRequest contiene tutti i dati necessari per la ricerca tra cui, ad esempio,
il Market, la Version, le fonti di ricerca Sources e un oggetto di classe ImageRequest con i
parametri relativi alla ricerca delle immagini.
La classe SearchResponse contiene una proprietà Image, di tipo ImageResponse, con i
dati di offset, del numero stimato di record totali e la collezione dei risultati di ricerca.
Tutte le classi ottenute dal WSDL prevedono delle proprietà di tipo boolean per indicare la
presenza o meno di proprietà che potrebbero non essere valorizzate come, ad esempio, la
Width della classe ImageResult e la relativa WidthSpecified.
Queste classi, quindi, sono utilizzabili in linguaggi in cui si fa largo uso del binding come,
ad esempio, WPF o Silverlight, anche perché, inoltre, implementano la proprietà
INotifyPropertyChanged.
Grazie alla classe BingPortTypeClient si può implementare la funzione di ricerca.
Public Function Search(ByVal searchKey As String, _
ByVal recordCount As UInteger, _
ByRef totalCount As UInteger, _
Optional ByVal offSet As UInteger = 0, _
Optional ByVal filters As String = "") As List(Of Bing.ImageResult)
Dim retList As List(Of Bing.ImageResult) = Nothing
If Not String.IsNullOrEmpty(searchKey) Then
Dim parameters = New Bing.SearchRequest()
With parameters
.Image = New Bing.ImageRequest()
.Image.Count = recordCount
.Image.Offset = offSet
.Market = Market
.Query = searchKey
.Sources = New SourceType() {SourceType.Image}
End With
Try
Dim response = Me._Service.Search(parameters)
If response.Errors IsNot Nothing Then
Me._LastErrors = response.Errors.ToList()
Else
retList = response.Image.Results.ToList()
totalCount = response.Image.Total
End If
Catch ex As Exception
Throw
End Try
Else
retList = New List(Of Bing.ImageResult)
End If
Return retList
End Function
TPSIT 5
434
um 392 di
In pratica, si crea un’istanza della classe SearchRequest, impostare i valori dei parametri
di ricerca, richiamare il metodo search e recuperare la lista dei risultati di ricerca.
In caso di errore nella ricerca, utilizzare la collezione Errors della classe SearchResponse
per ottenere l’elenco degli errori.
Utilizzando il protocollo SOAP, non si ha la necessità d’interpretare lo streaming di ritorno
della risposta, in quanto questo lavoro è fatto dalle classi wrapper generate a partire dal
WSDL.
PROTOCOLLO JSON
I servizi di Bing sono utilizzabili con una pagina HTML ricorrendo al linguaggio Javascript.
L’endpoint per utilizzare il protocollo JSON è il seguente.
http://api.bing.net/json.aspx
La struttura della richiesta è analoga a quella del protocollo XML.
Nel seguente codice JavaScript c’è il modo con cui interrogare la piattaforma Bing.
function search(offset, numRecords) {
displayPager(false);
var totalResultLabel = document.getElementById('lblTotalResults');
var executionTimeLabel = document.getElementById('lblExecutionTime');
var parent = document.getElementById('tblResults');
removeRows(parent);
totalResultLabel.innerHTML = "<img src='./images/wait-icon.gif' style='verticalalign:middle;'/>&nbsp;Attendere prego, ricerca in corso!";
executionTimeLabel.innerHTML = "";
var search = "&query=" + document.getElementById('txtSearchText').value;
var fullUri = serviceURI + AppId + search + "&Image.count=" + numRecords +
"&Image.offset=" + offset;
var head = document.getElementsByTagName('head');
var script = document.createElement('script');
script.type = "text/javascript";
script.src = fullUri;
head[0].appendChild(script);
startTime = new Date();
}
La funzione search crea la stringa contenente l’URL necessario per la richiesta Bing
selezionando la modalità di risposta “callback”, con searchDone come funzione di
callback, crea un oggetto script e lo inserisce come figlio del nodo head della pagina.
In questo modo, nel momento in cui la piattaforma Bing avrà effettuato la ricerca e
saranno disponibili i risultati, sarà invocata la funzione searchDone che dovrà,
evidentemente, essere presente nella pagina, passandole la struttura dati dei risultati.
Un esempio di come potrebbe presentarsi la callback è il seguente codice.
function searchDone(results) {
endTime = new Date();
var result = null;
var parent = document.getElementById('tblResults');
var row = null;
var cell = null;
TPSIT 5
434
um 393 di
removeRows(parent);
var totalResultLabel = document.getElementById('lblTotalResults');
totalResultLabel.innerHTML = "Numero di risultati stimati : " +
results.SearchResponse.Image.Total;
var executionTimeLabel = document.getElementById('lblExecutionTime');
var executionTime = endTime.getTime() - startTime.getTime();
executionTimeLabel.innerHTML="Tempo di esecuzione:"+(executionTime/1000)+" sec.";
var offset = results.SearchResponse.Image.Offset;
var numSearchRecord = results.SearchResponse.Image.Results.length;
var totalPageNumber = results.SearchResponse.Image.Total / numSearchRecord + 1;
var pageNumber = offset / numSearchRecord + 1;
for (var i = 0; i < numSearchRecord; ) {
row = document.createElement('tr');
for (var j = 0; j <= 4 && i < numSearchRecord; i++, j++) {
result = results.SearchResponse.Image.Results[i];
cell = document.createElement('td');
var html = "<img onclick=" + String.fromCharCode(34) +
"javascript:showPopupImage('" + result.Url + "');" + String.fromCharCode(34) + " ";
html += "src='" + result.Thumbnail.Url + "' ";
html += "style='border-width:0px;cursor: pointer; border: solid 1px black;' /><br />";
html += result.Title + "<br>";
cell.innerHTML = html;
row.appendChild(cell);
}
parent.appendChild(row);
}
La funzione prevede che esista una tabella HTML, tblResults, nella pagina e crea delle
righe con cinque celle ciascuna contenenti altrettanti risultati della ricerca, ogni cella
contiene l’immagine descritta dal nodo Thumbnail e il titolo dell’immagine stessa.
Progettare un motore per la ricerca d’immagini.
TPSIT 5
434
um 394 di
Implementare tre pagine ASPX in cui sono utilizzate le tre modalità di accesso alla
piattaforma Bing per la ricerca d’immagini.
Per quanto riguarda i protocolli XML e SOAP sono state create delle classi che forniscono
i due servizi di ricerca.
BINGSERVICE.VB
Definisce la classe base di tutti i servizi per l’accesso a Bing, in particolare contiene
AppID, Market, Version ed eventuali altri parametri comuni ai servizi.
Inoltre, contiene una proprietà che restituisce l’elenco degli errori verificatisi nell’ultima
interrogazione e un’istanza della classe StopWatch che si utilizza per calcolare le durate
delle operazioni di ricerca.
BINGSOAPSERVICE.VB
Rappresenta la classe base per i servizi Bing che utilizzano il protocollo SOAP, rispetto
alla classe BingService, prevede un attributo protected di classe BingPortTypeClient che è
istanziato nel costruttore e può essere utilizzato dalle singole classi di servizio.
IMAGEXMLSERVICE.VB
Implementa le funzionalità di ricerca con il protocollo XML.
IMAGESOAPSERVICE.VB
Implementa le funzionalità di ricerca con il protocollo SOAP.
Per quel che riguarda il protocollo JSON, le funzionalità di ricerca sono implementate con
TPSIT 5
434
um 395 di
delle funzioni JavaScript presenti nella pagina di ricerca.
TPSIT 5
434
um 396 di
RSS
INTRODUZIONE
RSS (RDF [Resource Description Framework] Site Summary).
Oppure (Really Simple Syndication).
È uno dei formati per la distribuzione di contenuti web; è basato su XML, da cui ha
ereditato la semplicità, l’estensibilità e la flessibilità.
RSS definisce una struttura adatta a contenere un insieme di notizie, ciascuna delle quali
sarà composta da vari campi: nome autore, titolo, testo e riassunto.
Quando si pubblicano delle notizie in formato RSS, la struttura è aggiornata con i nuovi
dati; visto che il formato è predefinito, un qualunque lettore RSS potrà presentare in una
maniera omogenea notizie provenienti dalle fonti più diverse.
Il termine Syndication, nel linguaggio dei media, indica la diffusione di contenuti tramite
agenzie che provvedono a distribuirli grazie a vari canali.
Andare a consultare tutti i siti d’interesse per intercettarne le notizie può risultare un
esercizio lungo e noioso.
Questo problema è stato risolto da Netscape nel 1999 con la progettazione del formato
RSS che serve a visualizzare le novità di un sito che è aggiornato frequentemente, senza
che l’utente abbia bisogno ogni volta di controllare cosa c’è di nuovo.
Un file RSS è un documento XML il cui nodo radice è l’elemento <rss>.
All’interno del nodo <rss> è presente un elemento <channel> che raggruppa le
informazioni caratteristiche del “canale”, in altre parole del feed in esame, nel quale sono
elencati degli elementi obbligatori, per esempio <title>, <description> o <link> e degli
elementi facoltativi, per esempio <language>, <lastBuildDate> o <image>.
La vera sezione informativa si trova all’interno dell’elemento <channel>, nella collezione
degli elementi <item>.
Un <item> rappresenta l’unità atomica della fonte dati, per esempio può essere il post di
un blog, la notizia di un giornale o la rilevazione della temperatura atmosferica di una
località.
Un elemento <item> prevede, a sua volta degli elementi come <title>, <link>,
<description> e altri.
Nell’elemento <description> è possibile inserire un testo HTML con le entità codificate.
Tutti gli elementi di un <item> sono opzionali ma dev’essere almeno presente un <title o
una <description>.
Atom
Basato su XML che consente una maggiore precisione nella definizione dei contenuti.
L’elemento radice è <feed> al cui interno si trovano le informazioni caratterizzanti il feed
stesso quali <title>, <subtitle> e <id>.
In questo formato, i singoli elementi possono essere decorati con un attributo che ne
qualifica il tipo di rappresentazione, per esempio, l’elemento <title> può avere l’attributo
type il cui valore può essere: “text” il default, “html” o “xhtml”.
Le unità atomiche, contenenti le notizie, sono gli elementi <entry>, composti da <link>,
<updated>, <published> e <author>.
Altri sotto elementi utilizzabili sono: <summary>, <category>, <rights> e <source>.
Vantaggi
Nell’iscrizione ad un feed, gli utenti non rivelano il loro indirizzo di posta elettronica.
TPSIT 5
434
um 397 di
In questo modo non si espongono alle minacce tipiche dell’email quali SPAM, virus,
phishing e il furto d’identità.
Se gli utenti vogliono interrompere la ricezione di notizie, non devono inviare richieste del
tipo “annulla la sottoscrizione” ma basta che rimuovano il feed dal loro aggregatore.
Il contenuto di un giornale online è aggiornato più volte al giorno con nuovi articoli, il
formato RSS consente di avere sul PC l’informazione sull’articolo appena pubblicato con
un link.
Quando si pubblicano delle notizie in formato RSS, la struttura è aggiornata con i nuovi
dati; visto che il formato è predefinito, un qualunque lettore RSS potrà presentare in una
maniera omogenea notizie provenienti dalle fonti più diverse.
I principali siti d’informazione, i quotidiani online, i fornitori di contenuti, i blog più popolari:
tutti adottano il formato RSS.
Gli utenti possono oggi accedere a migliaia di documenti RSS, chiamati feed: alcuni siti,
directory, raccolgono i riferimenti agli innumerevoli feed disponibili sul web.
Un file RSS è letto da un’applicazione chiamata feed reader che provvede anche a
ricercare il flusso di RSS di un sito, controllando costantemente gli aggiornamenti.
Questo rapporto automatizzato che s’instaura tra chi fornisce e chi riceve il flusso
d’informazioni si chiama feed.
Queste applicazioni possono essere divise in categorie.
1. Applicazioni stand-alone: per esempio FeedReader e Sharpreader per Windows,
Netnewswire e Vienna per MC OS, Straw per Linux, Pulse per mobile; per esempio
nella parte sinistra dell’interfaccia è mostrato l’elenco dei feed impostati dall’utente;
nella parte in alto a destra l’elenco dei singoli contenuti del feed selezionato; nella parte
principale l’intero contenuto testuale di un articolo.
2. Plugin all’interno del browser: per esempio Sage, Urss e Wizz RSS New Reader per
Firefox.
3. Aggregatori web: è un sito web che offre un servizio di raccolta ed elaborazione dei
feed: per esempio Google Reader, Bloglines, Feed on Feeds e Pluck.
4. Convertitori: applicazioni in grado di convertire un feed RSS in una serie di post in
formato leggibile dai più popolari newsreader come, ad esempio, Mozilla Thunderbird,
Forté Agent.
Oltre a queste applicazioni, i browser si sono dotati di strumenti per il riconoscimento e la
gestione dei feed RSS.
Comprendere se un sito distribuisce un feed non richiede alcun accorgimento, è il browser
stesso che si accorge e lo segnala con un’icona.
Esempio, dato un DB che contiene banche dati presenti sui notebook distribuiti sul
territorio, sincronizzarli.
Basta esporre i dati via RSS in modo che i client siano liberi di aggiornarsi utilizzando un
lettore di RSS.
Il DB si compone di due tabelle.
tblProdotti
Contenente l’elenco dei prodotti con la relativa data di modifica, utile per sapere se un
prodotto è stato modificato dall’ultima volta che sono stati letti i dati.
tblTipiProdotti
TPSIT 5
434
um 398 di
Contiene le tipologie di prodotto.
TPSIT 5
434
um 399 di
Una soluzione è l’utilizzo di un feed RSS, con questa architettura.
Bisogna progettare una “risorsa web”, in particolare un HttpHandler che, a richiesta,
costruisca uno stream RSS e lo restituisca al chiamante.
Per fare questo si utilizzano le classi del namespace System.ServiceModel.Syndication.
Progettare una classe che recuperi i dati dal DB, i quali costituiranno il feed.
Si usa LINQ e si realizza una classe di servizio, RSSService, contenente un metodo il cui
scopo è quello di recuperare i record dal DB e impacchettarli in un oggetto
SyndicationFeed.
Questa operazione è eseguita dal metodo GetRSS.
Recuperare l’ultima data d’inserimento dei prodotti che costituirà la data di aggiornamento
del feed.
Dim lastUpdate As DateTime = Aggregate p In Me._DBContext.tblProdotti Into
Max(p.DataModifica)
Si crea un’instanza della classe SyndicationFeed il cui scopo è quello di contenitore
principale di un feed RSS, sia che si lavori con il protocollo Atom sia con il protocollo RSS.
Dim feed = New SyndicationFeed("RSS Test", "RSS test", Nothing, "RSSFeedID",
lastUpdate)
La classe SyndicationFeed prevede diversi costruttori, impostare il titolo del feed, la
descrizione del feed, il link alternativo per l’identificativo del feed e la data dell’ultimo
aggiornamento dello stesso feed.
Il contenitore di feed dev’essere riempito con gli item corrispondenti ai prodotti di cui si
vogliono trasferire i dati ed è per questo che si crea un oggetto di classe SyndicationItem
per ogni record presente nel DB recuperando solo quei prodotti che, alla data attuale,
sono ancora validi.
Per fare questo si usa LINQ per restituire una collezione di oggetti anonimi contenenti i
prodotti e tipi prodotti.
Dim products = From p In Me._DBContext.tblProdotti
Join t In Me._DBContext.tblTipiProdotti On p.IDTipoProdotto Equals
t.IDTipoProdotto
Order By p.DataModifica Descending
Where (Not p.DataValiditaDa.HasValue OrElse p.DataValiditaDa <= dtNow)
And (Not p.DataValiditaA.HasValue OrElse p.DataValiditaA >= dtNow)
Select New With {.Product = p, .ProductType = t}
TPSIT 5
434
um 400 di
La collezione di oggetti anonimi appena creata dev’essere convertita in una collezione di
SyndicationItem da agganciare al contenitore del feed.
Per fare questo, iterare la collezione appena recuperata e creare un oggetto di tipo
SyndicationItem del quale impostare gli attributi che consentiranno di trasportare altre
informazioni relative ai prodotti.
Dim itemList As New List(Of SyndicationItem)
For Each pair In products
Dim item As New SyndicationItem(pair.Product.Descrizione,
pair.ProductType.Descrizione, New
Uri(String.Format("http://www.miosito.it/Item.aspx?id={0}", pair.Product.IDProdotto)),
pair.Product.IDProdotto, pair.Product.DataModifica)
item.AttributeExtensions.Add(New XmlQualifiedName("noteProdotto"),
pair.Product.Note)
item.AttributeExtensions.Add(New XmlQualifiedName("dataValiditaDa"),
IIf(pair.Product.DataValiditaDa.HasValue, pair.Product.DataValiditaDa, ""))
item.AttributeExtensions.Add(New XmlQualifiedName("dataValiditaA"),
IIf(pair.Product.DataValiditaA.HasValue, pair.Product.DataValiditaA, ""))
item.AttributeExtensions.Add(New XmlQualifiedName("idTipoProdotto"),
pair.ProductType.IDTipoProdotto)
item.AttributeExtensions.Add(New XmlQualifiedName("noteTipoProdotto"),
pair.ProductType.Note)
itemList.Add(item)
Next
Si assegna la descrizione del prodotto alla proprietà Title del SyndicationItem, la
descrizione del tipo di prodotto alla proprietà Content dell’item e l’identificativo del prodotto
alla proprietà ID in modo da avere un item interpretabile nel caso in cui si visualizzi l’RSS
attraverso un aggregatore o un browser.
Le altre informazioni del prodotto sono state agganciate all’item come attributi in modo da
essere eventualmente recuperabili dal client che deve elaborare lo stream RSS.
Una volta creati gli item costituenti l’RSS, basta agganciarli al contenitore del feed con la
seguente istruzione.
feed.Items = itemList
E restituire lo stesso contenitore al metodo chiamante.
Il metodo GetRSS è utilizzato dall’HttpHandler che si occupa di formattare il
SyndicationFeed in base al protocollo scelto dall’utente e restituire lo stream XML.
Questo avviene nel metodo seguente.
Public Sub ProcessRequest(ByVal context As HttpContext) Implements
IHttpHandler.ProcessRequest
context.Response.ContentType = "text/xml"
Using srv As New
ClassLibrary.RSSService(ConfigurationManager.ConnectionStrings("DBConnectionString"
).ConnectionString)
Dim rssWriter As XmlWriter = XmlWriter.Create(context.Response.OutputStream)
Dim protocol = context.Request.QueryString("protocol")
If String.Compare(protocol, "atom", True) = 0 Then
Dim atomFormatter As Atom10FeedFormatter = New
TPSIT 5
434
um 401 di
Atom10FeedFormatter(srv.GetRSS())
atomFormatter.WriteTo(rssWriter)
Else
Dim rssFormatter As Rss20FeedFormatter = New
Rss20FeedFormatter(srv.GetRSS())
rssFormatter.WriteTo(rssWriter)
End If
rssWriter.Close()
End Using
End Sub
Nel metodo si utilizza la classe RSSService per fornire, agli opportuni formatter, l’oggetto
contenitore dello stream.
In particolare, se l’utente non definisce il protocollo RSS desiderato, il metodo utilizza un
oggetto di classe Rss20FeedFormatter per generare lo stream secondo il formato RSS 2.0
mentre se l’utente utilizza il parametro atom nella querystring della chiamata
all’HttpHandler, il metodo utilizza la classe Atom10FeedFormatter per formattare lo stream
in base al formato Atom 1.0.
In entrambi i casi, la scrittura dell’XML all’interno dello stream di output della response è
eseguita tramite un oggetto di classe XmlWriter.
Adesso bisogna progettare il client che, leggendo periodicamente lo stream, riesca a
capire quando ci sono delle variazioni in modo da poter aggiornare la banca dati remota.
Si può progettare un client con la stessa tecnologia e linguaggio utilizzati per il S/W che
genera lo stream ma essendo il formato dei dati universale, XML, il client potrebbe essere
realizzato in un qualsiasi linguaggio e su una qualsiasi piattaforma disponibile.
Il client deve comprendere come richiedere lo stream RSS, interpretare i dati ottenuti e
capire quali eventuali modifiche debbano essere apportate alla banca dati sottostante.
In particolare, si utilizza un formatter per accedere ai dati contenuti nello stream.
Dim reader = XmlReader.Create("http://localhost:60000/RSSWebSite/RSSFeed.ashx")
Dim formatter = New Rss20FeedFormatter()
formatter.ReadFrom(reader)
Il reader XML consente di richiedere la risorsa RSS remota.
Per leggere i dati dal reader è necessario un formatter RSS 2.0 e la chiamata al metodo
ReadFrom.
Ultimata la lettura, si possono ottenere i prodotti aggiornati, inseriti o modificati, utilizzando
il codice seguente.
Dim lastUpdatedatetime = formatter.Feed.LastUpdatedTime.DateTime
Dim coll = From c In formatter.Feed.Items
Where c.LastUpdatedTime.DateTime > Me.LastSyncDate
Order By c.LastUpdatedTime Ascending
Select CreateProduct(c)
In questo modo si seleziona, tra tutti gli item restituiti nello stream RSS, quelli la cui data di
modifica è successiva alla data di ultimo accesso allo stream stesso e si creano gli oggetti
Product, grazie al metodo CreateProduct.
In questo metodo si recuperano gli attributi relativi al prodotto e al tipo prodotto in maniera
analoga a quanto visto per la creazione dello stream.
Protected Function CreateProduct(ByVal item As SyndicationItem) As Product
TPSIT 5
434
um 402 di
Dim product As New Product()
With product
If item.AttributeExtensions(New XmlQualifiedName("dataValiditaDa")) = "" Then
.DateFrom = Nothing
Else
.DateFrom = DateTime.Parse(item.AttributeExtensions(New
XmlQualifiedName("dataValiditaDa")))
End If
If item.AttributeExtensions(New XmlQualifiedName("dataValiditaA")) = "" Then
.DateTo = Nothing
Else
.DateTo = DateTime.Parse(item.AttributeExtensions(New
XmlQualifiedName("dataValiditaA")))
End If
.Description = item.Title.Text
.ID = Convert.ToInt32(item.Id)
.ModifyDate = item.LastUpdatedTime.DateTime
.Notes = item.AttributeExtensions(New XmlQualifiedName("noteProdotto"))
.Type = New ProductType() With {.Description = item.Summary.Text,.ID =
Convert.ToInt32(item.AttributeExtensions(New
XmlQualifiedName("idTipoProdotto"))),.Notes = item.AttributeExtensions(New
XmlQualifiedName("noteTipoProdotto"))}
End With
Return product
End Function
Una volta recuperati i prodotti modificati, si può, ad esempio, aggiornare la banca dati
locale oppure visualizzarli a video.
WINDOWS 8.X
Leggere un feed RSS e un feed Atom
// feed RSS
var client = new SyndicationClient();
var feed = await client.RetrieveFeedAsync(
new Uri("http://weblogs.miosito.it/rss.aspx"));
// feed Atom
var client = new AtomPubClient();
var feed = await client.RetrieveFeedAsync(
new Uri("http://weblogs.miosito.it/atom.aspx"));
Le classi SyndicationClient e AtomPubClient sono contenute rispettivamente nel
namespace Windows.Web.Syndication e Windows.Web.AtomPub.
Queste classi espongono il metodo RetrieveFeesAsync che ritorna un oggetto di tipo
SyndicationFeed.
Quest’oggetto contiene i dati del feed non in formato XML ma in un formato a oggetti e
quindi è di facile consultazione.
Leggere i dati salienti del feed.
foreach (var item in feed.Items) {
TPSIT 5
434
um 403 di
var test = item.Summary.Text;
var title = item.Title.Text;
var pubdate = item.PublishedDate;
var url = item.Links[0].Uri.ToString();
...
}
Manipolare dati in formato JSON
Per manipolare dati JSON, WinRT (Windows RunTime) mette a disposizione le classi del
namespace Windows.Data.Json.
Le classi principali che permettono di lavorare con JSON sono tre.
1. JsonObject: rappresenta un oggetto JSON ovvero una lista di coppie chiave/valore, in
questo caso, la lista è l’equivalente di un oggetto e ogni coppia chiave/valore è
l’equivalente di una proprietà con il suo valore.
2. JsonArray: rappresenta un array JSON, il quale può contenere dati semplici o, a sua
volta, coppie chiave/valore.
3. JsonValue: rappresenta un valore in JSON.
Utilizzare queste classi con il servizio REST che torna dati in formato JSON.
var client = new HttpClient();
var body = await client.GetStringAsync("http://localhost:1892/api/values/1");
var json = JsonObject.Parse(body);
var id = json.GetNamedNumber("Id");
var name = json.GetNamedString("Name");
Il metodo static Parse della classe JsonObject trasforma la stringa JSON in un oggetto
JsonObject.
Poi, tramite i metodi GetNamedString e GetNamedNumber, si recuperano rispettivamente
le proprietà Name e Id, accettano in input i nomi delle proprietà e sono case sensitive.
Nell’esempio le proprietà del cliente sono semplici ma volendo si possono avere anche
proprietà complesse, come un indirizzo o una lista di contatti.
In questi casi si deve usare il metodo GetNamedObject passando in input il nome della
proprietà, per recuperare una proprietà che rappresenta un singolo oggetto sul quale poi è
possibile usare i metodi già visti e il metodo GetNamedArray per recuperare una proprietà
di tipo lista.
Esempio, recuperare dati da un array JSON che contiene la lista dei clienti.
var client = new HttpClient();
string body = await client.GetStringAsync("http://localhost:1892/api/values");
var json = JsonArray.Parse(body);
for (var i =0; i<json.Count;i++) {
var obj = json[i].GetObject();
var name = obj.GetNamedString("Name");
var id = obj.GetNamedNumber("Id");
}
Il metodo Parse della classe JsonArray permette di trasformare la stringa JSON in un
oggetto JsonArray.
La proprietà Count indica il numero di elementi nell’array.
Per ogni elemento che è un oggetto JSON contenente i dati del client, trasformare
TPSIT 5
434
um 404 di
l’elemento in un oggetto JsonObject, tramite il metodo GetObject, contenente i dati del
cliente e poi recuperare le proprietà tramite i metodi GetNameString e GetNamedNumber.
Esempio, trasformazione di un insieme di dati in una stringa JSON che dev’essere poi
inviata al servizio REST.
var obj = new JsonObject();
obj.Add("Id", JsonValue.CreateNumberValue(1));
obj.Add("Name", JsonValue.CreateStringValue("Andrea Sperelli"));
var content = new StringContent(obj.Stringify(), Encoding.UTF8, "application/json");
var client = new HttpClient();
await client.PostAsync("http://localhost:1892/api/values", content);
Creare un’istanza della classe JsonObject.
Poi, tramite il metodo Add, aggiungere le proprietà Id e Name il cui valore è impostato
sfruttando i metodi statici CreateNumberValue e CreateStringValue.
A questo punto invocare il metodo Stringify per trasformare l’oggetto JSON in una stringa
pronta per essere inviata al servizio REST.
Esempio.
{ "Id": 1, "Name": "Alessio Leoncini" }
TPSIT 5
434
um 405 di
TWITTER
INTRODUZIONE
È un servizio di social network e microblogging che fornisce agli utenti una pagina
personale aggiornabile tramite messaggi di testo con una lunghezza massima di 140
caratteri.
Gli aggiornamenti possono essere effettuati tramite il sito stesso, via SMS (Short Message
Service), con applicazioni di messaggistica istantanea, email, oppure tramite varie
applicazioni basate sulle API di Twitter.
Twitter è stato creato nel marzo 2006 dalla Obvious Corporation di San Francisco.
Per inserire i propri twit in un sito esterno si usa un widget standard, prelevabile
all'indirizzo http://twitter.com /widgets.
Per progettare applicazioni che consentano di utilizzare Twitter in modo personale, il sito
dedicato ai programmatori è: http://dev.twitter.com.
Twitter for Websites: com/about/resources/buttons
Consiste di due prodotti.
Il primo è costituito da una serie di pulsanti, riutilizzabili per associare ad un articolo un
pulsante che permette di attivare una fra quattro azioni possibili: segnalare quello che si è
letto, seguire un ID, inviare un tweet con una hashtag già impostata oppure inviare un
messaggio ad un ID.
Il secondo rivolto ai siti web consente di citare un tweet mantenendo una formattazione
aderente a quella della presentazione sul sito di Twitter.
Per creare una cornice con un tweet bisogna aprire l’interfaccia web di Twitter twitter.com,
fare un clic sulla foto dell’autore e cliccare sul link Details e, quando appare il lightbox con
tutti i dettagli del tweet, fare un clic su Embed this tweet.
Si apre un’interfaccia che permette di specificare come creare il codice che include il tweet
in una pagina esterna.
Se il tweet è in risposta ad un altro apparirà la conversazione e la formattazione sarà
simile a quella originale.
Componenti della piattaforma
1. Autenticazione e gestione dell’identità.
2. Ricerca nel DB.
3. Streaming in tempo reale.
SEARCH API
La ricerca nel DB di Twitter avviene con due distinte interfacce, entrambe basate sul web,
quindi l’invio della richiesta e la ricezione della risposta sono su una chiamata HTTP.
1. Search API: il formato della richiesta è una chiamata ad un indirizzo specifico, seguita
dai parametri dell’interrogazione: http://search.twitter.com/search.json?q=cosmic53,
ricerca i tweet in cui si fa menzione di uno specifico utente Twitter.
2. REST: GET e POST, alcuni metodi richiedono autenticazione, quelli che accedono a
dati pubblici, non ne necessitano; http://twitter.com/statuses/public_timeline.xml e gli
elementi delle URL sono dati dal nome della risorsa (statuses), dal metodo invocato
(public_timeline), dall’ID dell'elemento su cui si richiede l’azione e dal formato in cui si
desidera ricevere la risposta, una volta costruite le URL, per trasmettere le richieste, si
usa cURL.
TPSIT 5
434
um 406 di
Libreria cURL (Client URL Request Library)
1. cURL
Funziona da prompt dei comandi, supportato nativamente da Linux, mentre va installato
sugli altri SO.
F:\>curl http://twitter.com/statuses/public_timeline.xml
In risposta si otterranno i 20 twit più recenti degli utenti con profilo pubblico
(public_timeline), oltre ad una serie d’informazioni di base su ogni utente, in formato XML.
È possibile twittare direttamente dal prompt dei comandi.
F:\>curl -u username:password –d status="inserisci qui il tuo messaggio"
http://twitter.com/statuses/update.xml
L'opzione -u imposta i parametri di autenticazione.
L'opzione -d (data) imposta il metodo POST di trasmissione dei dati.
Oltre a inserire il nuovo status dell’utente, l’API restituisce una stringa che riporta i dati
inseriti, nel formato richiesto nella URL.
2. libcURL
È una libreria portabile lato client, permette d’inviare comandi attraverso svariati linguaggi
di programmazione.
Esempio, progettare una pagina o un widget in cui siano visualizzati i 20 twit, esattamente
come questi si susseguono nella timeline della pagina.
<?php
$url = "http://twitter.com/statuses/friends_timeline.xml";
// curl_init() apre la connessione
$ch = curl_init();
// curl_setopt() imposta i parametri della richiesta, CURLOPT_RETURN TRANSFER
// consente di ottenere una stringa con il valore della risposta
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_VERBOSE, 1);
// CURLOPT_URL imposta l'ULR da trasmettere
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_HTTPAUTH, CURLAUTH_BASIC);
// CURLOPT_USERPWD imposta i valori di username e password
curl_setopt($ch, CURLOPT_USERPWD, "username:password");
// curl_exec() esegue la sessione cURL
$result = curl_exec($ch);
// curl_close() chiude la connessione
curl_close($ch);
// simplexml_load_string() accede ai dati: la risposta di twitter,
// nel formato richiesto nell'URL (XML) è disponibile in $result
$xml = simplexml_load_string($result);
foreach ($xml->status as $status) {
$icon = '<img src="' . $status->user->profile_image_url . '" title="' . $status->user>screen_name . '" align="middle" />';
$text = utf8_decode($status->text);
TPSIT 5
434
um 407 di
echo "<p>" . $icon . " " . $text . "</p>\n";
}
?>
Esempio, inviare le richieste di aggiornamento dello status, in pratica trasmettere i
messaggi di testo di 140 caratteri.
<html>
<head>
<title></title>
<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1">
</head>
<body>
<form name="myform" method="post" action="<?php $_SERVER['PHP_SELF']; ?>">
<input name="mytwit" type="text" size="60" maxlength="140">
<input type="submit" name="invia" value="Invia">
</form>
<?php
// verifichiamo che siano stati trasmessi i dati attraverso il form
if(isset($_POST['invia'])) {
// attiviamo la connessione a cURL e fissiamo i parametri necessari
$msg = $_POST['mytwit'];
$url = $url = 'http://twitter.com/statuses/update.xml';
$username = "username";
$password = "password";
$ch = curl_init();
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_URL, $url);
// CURLOPT_CONNECTTIMEOUT numero di secondi d'attesa prima dell'interruzione
della connessione
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 2);
curl_setopt($ch, CURLOPT_POST, 1);
// CURLOPT_POSTFIELDS imposta i parametri da trasmettere col metodo POST
curl_setopt($ch, CURLOPT_POSTFIELDS,
stripslashes(utf8_encode("status=$msg")));
curl_setopt($ch, CURLOPT_USERPWD, "$username:$password");
$result = curl_exec($ch);
curl_close($ch);
// Twitter risponde, nel formato richiesto nella URL, con l'elemento status
$status = simplexml_load_string($result);
$image = $status->user->profile_image_url;
$name = $status->user->screen_name;
$icon = '<img src="' . $image . '" title="' . $name . '" align="middle" />';
$text = utf8_decode($status->text);
echo "<p>" . $icon . " " . $text . "</p>";
}
?>
</body>
</html>
TPSIT 5
434
um 408 di
MODULO 4
CLOUD COMPUTING
Introduzione
Google
Microsoft Azure
TPSIT 5
434
um 409 di
INTRODUZIONE
GENERALITÀ
La realizzazione di un sito è un’operazione semplice: si sceglie la grafica e si pubblica
l’home page e qualche pagina informativa.
Per pubblicare le pagine su un server web, dopo la creazione del DNS, si devono eseguire
le seguenti operazioni.
1. Assegnare un IP libero al server web.
2. Creare una cartella sul server web.
3. Creare un nuovo sito in IIS.
4. Aprire un punto FTP per consentire la pubblicazione del progetto da parte dei
programmatori.
5. Marcare il sito come applicazione e assegnare la versione corretta di ASP.NET.
6. Altre operazioni manuali.
I compiti descritti richiedono un intervento amministrativo sul server che comporta il
coinvolgimento di una persona diversa dal programmatore sia per un problema di
competenza sia per una questione di permessi di accesso.
In conclusione, si può verificare una serie di problemi quali la versione di ASP.NET non è
stata configurata correttamente, manca spazio su disco per i file, IIS non è configurato
bene.
In altre parole, un sito web dovrebbe essere gestito da chi ha competenze sistemistiche e
dovrebbe essere inserito in una topologia di rete che ne renda sicuro l’accesso.
Per le aziende piccole, dove il programmatore “fa un po’ di tutto”, il problema è evidente in
quanto una sola persona non può più essere in grado di conoscere i dettagli dell’intero
spettro di tecnologie/prodotti/leggi.
Nelle grandi aziende che hanno una suddivisione netta dei ruoli fra sistemisti, esperti di
sicurezza, programmatori e quindi una competenza elevata in tutti gli ambiti coinvolti dalla
creazione di un’applicazione, il problema è ancora maggiore: entrano in gioco meccanismi
e dinamiche strane, secondo le quali diventa complesso richiedere anche solamente un
ambiente di test per le applicazioni.
Scalabilità
Chi investe nello sviluppo di applicazioni S/W desidera realizzare prodotti che
garantiscano tempi di risposta accettabili anche quando il traffico e la quantità di dati
elaborati aumentano.
Il programmatore, la scalabilità la gestisce già in fase di progettazione e realizzazione.
La gestione dell’applicazione e del sistema stesso ha un costo, l’applicazione vedrà
versioni successive da installare, così come devono essere aggiornate frequentemente le
tecnologie di base, dal SO al motore di DB.
Queste operazioni richiedono molto tempo sia lato sviluppo sia lato sistemistico.
Un altro problema è rappresentato dai costi.
Acquistare H/W per una nuova applicazione è un’operazione costosa: oltre al costo
iniziale, ci sono i costi di manutenzione e sostituzione di componenti obsoleti, senza
contare che spesso è difficile, soprattutto per le applicazioni web, dimensionare l’H/W, per
esempio per gestire i momenti di “picco”.
Hosting
Si mette il sito web sul server dell’ISP.
 Vantaggi: costo basso.
TPSIT 5
434
um 410 di
 Svantaggi: si condivide lo stesso server e la stessa banda con gli altri clienti.
Housing
Il server è di proprietà e gestito dell’azienda ma è collocato nei locali dell’ISP.
 Vantaggi: si possono scegliere tutti i componenti desiderati.
 Svantaggi: è costoso e richiede esperienza nella gestione del server.
Se si portano le macchine fisiche da un ISP, non si risolve il problema del costo iniziale e il
problema della gestione dell’applicazione; non si risolve neanche il problema di
competenze/passaggi d’informazioni: si è semplicemente spostata la locazione fisica dove
accadono i problemi.
Se si creano VM da mettere in hosting si risolve il problema del costo iniziale in quanto
non si deve acquistare H/W ma si possono sfruttare i server dell’ISP; si abbatte anche il
tempo di aggiornamento visto che molti upgrade saranno montati direttamente dall’ISP.
In ogni caso la gestione della VM è un compito dell’azienda che si complica notevolmente
quando si vuole garantire load-balancing o fault-tolerance: non solo si devono installare e
manutenere più macchine ma si rende necessario anche effettuare la distribuzione
dell’applicazione e delle release/patch successive su tutte le macchine.
La risposta a queste esigenze è il cloud computing.
In informatica con il termine inglese cloud computing (letteralmente nuvola informatica)
s’indica un insieme di tecnologie che permettono, sotto forma di un servizio offerto da un
ISP al cliente, di elaborare e/o memorizzare dati grazie all’utilizzo di risorse H/W e S/W
distribuite e virtualizzate in rete.
L’implementazione effettiva delle risorse non è definita in modo dettagliato; anzi l’idea è
proprio che l’implementazione sia un insieme eterogeneo e distribuito: the cloud di risorse
le cui caratteristiche non sono note all’utilizzatore.
La definizione mette però in luce due aspetti.
1. Utilizzo di risorse distribuite.
2. Risorse le cui caratteristiche non sono note all’utilizzatore, fa riferimento alle
caratteristiche fisiche dell’H/W che non sono note neanche al programmatore della
soluzione stessa; per risorse fisiche s’intendono i dischi su cui leggere e scrivere, i
nomi delle cartelle sul file system, l’indirizzo IP della macchina, il nome della macchina
stessa.
Per esempio, un VPS (Virtual Private Server) ha la possibilità di espandere le proprie
risorse secondo le capacità del nodo fisico che lo ospita.
Di fatto, può richiedere più CPU, più memoria e persino più spazio disco, qualora questi
valori non dovessero essere sufficienti; tutto ciò, però, non solo non avviene in automatico
ma è confinato ad una infrastruttura composta da pochi server.
La virtualizzazione rimane pertanto alla base della tecnologia cloud ma in essa trova
fattivamente una scalabilità maggiore, data da un’intera infrastruttura progettata per
scalare, non solo a livello di server ma anche di storage e networking: è il data center a
essere concepito per l’offerta di un servizio cloud e non la singola parte di esso, come
avveniva per la virtualizzazione nelle prime infrastrutture create negli anni precedenti.
TIPOLOGIE DI SISTEMI CLOUD
 ASP (Application Service Providing).
 SOA.
 SAAS (Software As A Service): applicazioni S/W erogate come servizi online, l’utente
utilizza l’applicazione tramite il browser, per esempio posta elettronica, scambio di
documenti e produttività individuale.
 PAAS (Platform As A Service): la gestione dell’infrastruttura H/W e S/W della
TPSIT 5
434
um 411 di
piattaforma di sviluppo è totalmente trasparente al programmatore che si deve solo
concentrare esclusivamente sulla propria applicazione.
 IAAS (Infrastructure As A Service): offre l’architettura H/W necessaria per l’erogazione
di un’applicazione web, per esempio uno o più server, un bilanciatore di carico, un pool
d’indirizzi IP e un firewall.
 HAAS (Hardware As A Service).
Esempi di sistemi cloud.
Microsoft Azure: SO per il cloud.
Antivirus: F-Secure, McAfee, Kaspersky, Panda.
Google Docs di Google.
Office 365 di Microsoft, MobileMe di Apple.
ePrint di HP (Hewlett Packard): cloud printing.
MicroERP (Enterprise Resource Planning) di Zucchetti.
InpresaSemplice di Telecom Italia, MyCustomerEasy di Olivetti.
I costi sono in funzione dei seguenti fattori.
 Numero di processi utilizzati.
 Traffico dati.
 Spazio occupato.
Pay only for use
Servizi su Internet da utilizzare quando servono e solo per il tempo necessario.
Al cliente è fornito un pannello di controllo nativo da cui gestire la creazione di nuove
istanze, un’istanza è di fatto una VM e soprattutto un set di API che s’interfacciano
direttamente con l’infrastruttura e permettono, quindi, ai clienti di automatizzare,
direttamente nelle loro applicazioni, la gestione delle risorse cloud.
Quel che si può fare è impostare regole per la gestione delle risorse, per la creazione di
nuove VM che distribuiscano il carico con quelle già presenti o modificare il
comportamento dell’applicazione nel caso vi sia un alto carico in ingresso.
Sono tre le parole che servono per capire cosa differenzia il cloud computing dalla
virtualizzazione e cosa quindi porta una VM a essere un’istanza cloud.
1. Scalabilità.
2. Ridondanza.
3. Flessibilità.
Il cloud computing è una soluzione ottimale quando si deve gestire un carico non costante,
per esempio picchi di utenti e che nel tempo è destinato a crescere.
Questo, unito alla necessità di non affrontare un corposo investimento iniziale, rende il
TPSIT 5
434
um 412 di
cloud computing la soluzione ideale per le aziende che portano online le loro applicazioni,
aprendole al pubblico e quindi dovendo affrontare qualsiasi livello di richieste.
Cloud Print
È una tecnologia concepita per le applicazioni web e mobile, stampare da qualsiasi
dispositivo e applicazione su qualsiasi stampante, senza l’uso di alcun driver locale.
Utilizzando un componente che tutti i principali dispositivi e SO hanno in comune,
l’accesso alla nuvola, il servizio permette a qualsiasi applicazione web, desktop o mobile
su qualsiasi dispositivo di stampare su qualsiasi stampante.
Anziché affidarsi al driver e all’interfaccia di stampa del SO sottostante, con Cloud Print le
applicazioni possono inviare le richieste di stampa ad un proxy remoto insieme ad alcune
informazioni sulla stampante locale: il server utilizza queste informazioni per tradurre
opportunamente le richieste e inoltrarle direttamente alla stampante attiva.
Il sistema che si premura altresì d’informare costantemente l’applicazione sullo stato della
stampa, funziona con tutte le stampanti collegate all’USB (Universal Serial Bus) o
direttamente alla rete.
Il grande vantaggio fornito dalla stampa via cloud è che si bypassa completamente il driver
nativo: questo consente di stampare anche dai dispositivi e dai SO per i quali non sono
disponibili i driver o che non supportano le funzioni di stampa.
Il principio di funzionamento di Cloud Print è basato su stampanti cloud-aware, ossia
capaci di comunicare direttamente con la cloud senza l’intermediazione di un PC o di un
router: tali stampanti permetteranno, ad esempio, di stampare direttamente da uno
smartphone anche quando il PC è spento.
Gli elementi con icone differenti fanno riferimento a servizi distinti.
1. dev
Rappresenta un servizio di tipo Hosted Service, ovvero il progetto che consente di
gestire, testare ed effettuare la distribuzione dell’applicazione.
Fornisce la potenza computazionale necessaria all’esecuzione del codice; i parametri di
configurazione consentono di aumentare o diminuire il numero delle istanze attive senza
effettuare la distribuzione delle applicazioni su nuovi server o nuove VM.
Ogni applicazione è installata automaticamente sui nodi indicati nella configurazione
TPSIT 5
434
um 413 di
liberando il programmatore e il sistemista da tutti i compiti di configurazione di virtual
directory, copia di file, gestione dei firewall o di apparati di load-balancing.
Il SO fornisce uno spazio in cui le applicazioni possono memorizzare informazioni.
2. dev storage
Rappresenta un servizio di tipo Storage Account, ovvero la definizione di uno spazio a
disposizione per memorizzare risorse.
Il termine servizio indica il “servizio offerto” dal SO: nel primo caso il servizio consta nella
possibilità di far girare del codice, mentre nel secondo caso il servizio mette a disposizione
spazio per memorizzare le risorse.
CLOUD VPS
È possibile creare infrastrutture di cloud computing che non necessariamente si
appoggiano su più data center e prevedono l’utilizzo di migliaia di server virtualizzati.
Cloud privata
S’indica una porzione del data center aziendale adibita al cloud computing, composta
anche da una serie di Cloud VPS.
Cloud pubblica
S’intende un servizio esterno che fornisce un’infrastruttura HAAS utilizzabile dai clienti,
con il modello pay per use.
Un ISP che intende offrire un servizio di cloud computing, deve organizzare la propria
struttura in modo da garantire ridondanza a livello di rete, di storage e di server.
Occorre un hypevisor che si occupa di gestire tutte le risorse del data center, rendendole
dinamiche, ovvero facendo sì che queste possano essere allocate al crescere
TPSIT 5
434
um 414 di
dell’infrastruttura e possano essere monitorate e gestite da una sola soluzione S/W.
In un’infrastruttura di cloud computing si cerca di avere un buon numero di macchine
perché queste devono consentire alle singole istanze di migrare da una macchina all’altra
nel caso sia necessario e di poter supportare la creazione, in poco tempo, di un buon
numero di nuove istanze.
Si utilizzano sistemi che uniscono una ridondanza dei dischi ad una velocità di
trasmissione, utile per non rendere l’I/O un collo di bottiglia poco sopportabile.
Per esempio, SAN (Storage Area Network) collegate alle macchine server in fibra ottica.
Le istanze cloud permettono al cliente di scegliere il numero di core di CPU assegnati e di
aggiungere più RAM qualora sia necessaria e la banda.
Creare nuove istanze e associarle ad un pool comune di VPS è un’altra delle
caratteristiche offerte da molti servizi, questo consente di creare reti private fra queste
macchine e porre alcune di esse come bilanciatori sulle altre per il traffico in entrata.
Amministrazione
Deve consentire di riavviare ed eseguire il re-image di ogni istanza senza l’intervento
dell’ISP: dev’essere possibile la reinstallazione del SO direttamente da pannello di
controllo, oltre al controllo di accensione, spegnimento e riavvio delle macchine.
Lo storage associato può essere fornito insieme alla VM o risiedere su un servizio di cloud
storage separato che permette di creare volumi.
L’amministrazione di una soluzione cloud VPS non differisce da quella di qualsiasi altro
sistema server.
Un browser non è più soltanto un S/W con cui accedere ad una pagina web ma un vero e
proprio ambiente in cui far girare applicazioni di ogni tipo.
Se il browser diventa ambiente di lavoro, allora lo si può estendere fino a farlo diventare
SO, per esempio Google Chrome OS.
Il cloud computing è di due tipi.
1. Low-level: memoria, spazio su disco, in pratica virtualizzazione dei sistemi.
2. High-level: accesso ad un DB, ad un sistema di pagamento online, in pratica servizi di
larga scala.
Quello che ieri si comprava, oggi si affitta, trasformando così i costi fissi in variabili, il cui
ammontare è legato alle necessità dell’azienda.
TPSIT 5
434
um 415 di
GOOGLE
INTRODUZIONE
Offre i seguenti servizi.
Google Compute Engine
Fornisce un sistema di cloud computing basato su VM interconnesse in rete, usato da
Google Search e Gmail.
Google Cloud Storage
Infrastruttura di memorizzazione, backup compreso, con accesso semplice e veloce via
REST API.
Google BigQuery
Permette l’analisi dei dati in real-time, valutandone i trend.
Google Cloud SQL
Permette di usufruire di DB relazionali MySQL nel cloud.
Google App Engine
È una piattaforma di hosting semplice da gestire e scalabile.
Permette lo sviluppo di applicazioni web con i linguaggi: Java, Python e Go.
Le applicazioni sono eseguite in una sandbox, con un accesso limitato al SO e
indipendentemente dall’H/W, in modo da poter distribuire il carico di lavoro su più server.
I principali servizi offerti con le relative API sono i seguenti.
 Google Accounts: è possibile integrare nell’applicazione l’autenticazione dell’utente
con l’account di Google.
 URL Fetch: l’applicazione può accedere alle risorse di rete come WS e dati.
 Mail: è possibile usare l’infrastruttura di Google Mail.
 Memcache: cache di tipo key-value accessibile da istanze multiple dell’applicazione,
utile per tenere traccia di dati temporanei che non devono essere resi permanenti.
 Image Manipulation: servizio di manipolazione delle immagini.
 GWT (Google Web Toolkit): permette di progettare interfacce mediante il drag & drop
e la generazione automatica del codice.
Sandbox
È lo spazio privato in cui un’applicazione vive e non può accedere a sandbox di altre
applicazioni.
L’accesso alle risorse è vincolato solo all’interno della sandbox di ciascuna applicazione.
TPSIT 5
434
um 416 di
MICROSOFT AZURE
INTRODUZIONE
È il servizio cloud di Microsoft, a differenza degli altri servizi simili, Azure è stata progettato
come un vero e proprio SO per ambienti cloud.
C’è coerenza tra ambienti S/W in locale e nel cloud con lo sviluppo S/W, infatti, la
gestione e l’installazione funzionano sia in locale sia in remoto.
È composto dai seguenti componenti.
1. AppFabric Controller: è il servizio di gestione dell’intera infrastruttura e si occupa
della distribuzione, dell’aggiornamento, della gestione e del monitoraggio delle
applicazioni.
2. Storage: memorizza dati.
3. Compute: è l’ambiente di esecuzione per due tipi di applicazioni.
3.1. Web Role: applicazioni web in grado di esaudire richieste di tipo HTTP o HTTPS.
3.2. Worker Role: è un progetto di tipo back-end, non è esposto su un URL pubblico,
non è accessibile dall’esterno e gira in background, come i servizi Windows.
4. Cloud service: esegue le applicazioni in maniera scalabile e affidabile.
5. SQL Azure: permette di gestire RDBMS (Relational DBMS) nel cloud.
6. Web site: ospita un sito Internet.
7. Mobile service: servizi che permettono alle app di fornire un back-end e un sistema di
push notification.
TPSIT 5
434
um 417 di
8. CDN (Content Delivery Network): servizi di memorizzazione ridondanti tra i server
per migliorare la velocità delle risposte, rende più rapido l’accesso ai dati binari tramite
un sistema di cache, l’obiettivo è migliorare le performance di lettura degli utenti
“lontani” rispetto allo storage.
9. Service bus: permettono la veicolazione di messaggi tramite code e sottoscrizione.
10. Media service: automatizza i processi di codifica dei contenuti multimediali.
11. Connessione: gestisce il livello IP.
Azure è un SO scalabile, fault-tolerant, una piattaforma di servizi: ASP (Azure Services
Platform) dove è possibile acquistare una serie di unità elaborative, unità di conto o
cloud unit, sotto forma di VM, basate su Windows Server.
Azure SQL Data Warehouse
È un AAS che gestisce PB di dati e porta il data analytics ad un nuovo livello di scalabilità
ed efficienza.
Azure Data Lake
È un repository per i big data a livello di PB.
SQL Elastic DataBase
PROGRAMMAZIONE
Bisogna installare Azure SDK e l’emulatore locale per l’ambiente di hosting.
L’integrazione con l’ambiente di sviluppo è possibile tramite l’installazione di Azure Tools
per Visual Studio, per la creazione, la configurazione e il test di applicazioni e servizi per
Azure.
Per creare un nuovo progetto in Visual Studio per questa piattaforma, fare clic su
File/Nuovo/Progetto… (CTRL+MAIUSC+N).
Nella finestra di dialogo Nuovo Progetto selezionare un linguaggio a scelta dall’elenco,
per esempio Visual C#/Cloud.
Selezionare il template Progetto servizio cloud Windows Azure.
Immettere il nome nel campo Nome: WindowsAzure1, quindi per creare il progetto,
premere OK.
TPSIT 5
434
um 418 di
Nella finestra successiva, specificare il tipo di applicazione, per esempio selezionare
Ruolo Web ASP.NET.
Eventualmente, se si avesse un progetto ASP.NET, è possibile creare un nuovo progetto
e aggiungerlo in seguito al progetto esistente.
Premuto il tasto OK, si apre la finestra seguente.
TPSIT 5
434
um 419 di
Premuto il tasto OK, Visual Studio crea una soluzione con al suo interno due progetti.
TPSIT 5
434
um 420 di
WebRole1
È il Web Role da sviluppare, un servizio di front-end esposto su un URL pubblico e
accessibile, in pratica è una normale applicazione ASP.NET o ASP.NET MVC.
File WEBROLE.CS
Contiene l’importazione dei namespace per lo sviluppo in the cloud.
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.WindowsAzure;
using Microsoft.WindowsAzure.Diagnostics;
using Microsoft.WindowsAzure.ServiceRuntime;
namespace WebRole1 {
public class WebRole : RoleEntryPoint {
public override bool OnStart()
TPSIT 5
434
um 421 di
{ return base.OnStart(); }
}
}
File DEFAULT.ASPX
<%@ Page Title="Home page" Language="C#" MasterPageFile="~/Site.master"
AutoEventWireup="true"
CodeBehind="Default.aspx.cs" Inherits="TestWebRole1._Default" %>
<asp:Content ID="HeaderContent" runat="server" ContentPlaceHolderID="HeadContent">
</asp:Content>
<asp:Content ID="BodyContent" runat="server" ContentPlaceHolderID="MainContent">
<h2>
ASP.NET
</h2>
<div>
Inserisci il tuo nome:
<asp:TextBox runat ="server" ID = "txtNnome" />
<asp:Button runat ="server" ID = "btnPulsante" Text ="Premi" OnClick =
"btnPulsante_Click"/>
<br />
<asp:Label runat ="server" ID = "lblNome" />
</div>
</asp:Content>
File DEFAULT.ASPX.CS
using System;
using System.Web.UI;
namespace WebRole1 {
public partial class _Default : Page {
protected void Page_Load(object sender, EventArgs e)
{ }
protected void btnPulsante_Click(object sender, EventArgs e)
{ lblNome.Text = String.Format("Ciao, {0}!", txtNnome.Text); }
}
}
WindowsAzure1
Rappresenta l’istanza di Windows Azure, in questo caso simulata dallo SDK, sulla quale
effettuare la distribuzione dell’applicazione, si trova in tutti i progetti per Windows Azure.
File SERVICECONFIGURATION.CLOUD.CSCFG
È il modello che descrive le necessità dell’applicazione, in pratica è il file di configurazione
del servizio e contiene la definizione del <Role>, al suo interno si trova l’elemento
<Instances> il cui attributo count è impostato a uno.
Con questa sintassi s’indica a Windows Azure che si vuole una sola istanza del servizio.
<?xml version="1.0" encoding="utf-8"?>
<ServiceConfiguration serviceName="WindowsAzure1"
xmlns="http://schemas.microsoft.com/ServiceHosting/2008/10/ServiceConfiguration"
osFamily="4" osVersion="*" schemaVersion="2013-10.2.2">
<Role name="WebRole1">
<Instances count="1" />
TPSIT 5
434
um 422 di
<ConfigurationSettings>
<Setting name="Microsoft.WindowsAzure.Plugins.Diagnostics.ConnectionString"
value="UseDevelopmentStorage=true" />
</ConfigurationSettings>
</Role>
</ServiceConfiguration>
File SERVICECONFIGURATION.LOCAL.CSCFG
<?xml version="1.0" encoding="utf-8"?>
<ServiceConfiguration serviceName="WindowsAzure1"
xmlns="http://schemas.microsoft.com/ServiceHosting/2008/10/ServiceConfiguration"
osFamily="4" osVersion="*" schemaVersion="2013-10.2.2">
<Role name="WebRole1">
<Instances count="1" />
<ConfigurationSettings>
<Setting name="Microsoft.WindowsAzure.Plugins.Diagnostics.ConnectionString"
value="UseDevelopmentStorage=true" />
</ConfigurationSettings>
</Role>
</ServiceConfiguration>
File SERVICEDEFINITION.CSDEF
Le impostazioni descrivono il servizio per il nodo su cui sarà installato: in base al numero
delle istanze, Windows Azure esegue la distribuzione su VM diverse; in base
all’impostazione di questo file ogni nodo risponde in ingresso sulla porta 80.
<?xml version="1.0" encoding="utf-8"?>
<ServiceDefinition name="WindowsAzure1"
xmlns="http://schemas.microsoft.com/ServiceHosting/2008/10/ServiceDefinition"
schemaVersion="2013-10.2.2">
<WebRole name="WebRole1" vmsize="Small">
<Sites>
<Site name="Web">
<Bindings>
<Binding name="Endpoint1" endpointName="Endpoint1" />
</Bindings>
</Site>
</Sites>
<Endpoints>
<InputEndpoint name="Endpoint1" protocol="http" port="80" />
</Endpoints>
<Imports>
<Import moduleName="Diagnostics" />
</Imports>
</WebRole>
</ServiceDefinition>
Compilare ed eseguire la soluzione con i diritti di Amministratore, Visual Studio esegue
il componente di Azure SDK denominato Compute Emulator.
L’emulatore dell’applicazione server di Azure che ha il compito di effettuare la distribuzione
su cartelle locali senza necessità di configurare IIS e l’esecuzione dell’applicazione.
In pratica, legge il file SERVICECONFIGURATION.CSCFG per identificare il numero delle
TPSIT 5
434
um 423 di
istanze e avvia i processi relativi.
Nell’area di notifica compare una nuova icona.
A fronte delle richieste che arrivano via HTTP, l’emulatore effettua il load-balancing sui
processi avviati, si può verificare questo comportamento aprendo la console di
amministrazione che si presenta con tanti box quanti sono i processi.
Sono visualizzati messaggi di trace.
È possibile scrivere nel trace tramite il metodo WriteToLog della classe RoleManager.
TPSIT 5
434
um 424 di
In Gestione attività Windows si trova il processo chiamato WAIISHOST.EXE che
rappresenta l’istanza creata in automatico.
Eseguire il kill del processo: l’emulatore ricrea immediatamente l’istanza simulando quello
che avverrà su Azure perché è garantita la fault-tolerance anche fra nodi, ovverosia: se
una VM è disconnessa oppure va in crash, il SO si occupa di distribuire il servizio su una
nuova VM.
L’emulatore apre il browser e avvia l’applicazione.
TPSIT 5
434
um 425 di
Dopo aver sottoscritto un abbonamento per i servizi di Azure, si ha a disposizione
un’istanza dove poter caricare l’applicazione, in due ambienti.
1. Production: ambiente di produzione che conterrà l’applicazione e che potrà essere
visto dagli utenti del sito web.
2. Staging: ambiente di test per il caricamento temporaneo dell’applicazione modificata.
Il contenuto dei due ambienti può essere scambiato in qualsiasi momento, permettendo di
testare l’applicazione nell’ambiente di produzione.
In caso di malfunzionamento, si possono scambiare nuovamente i contenuti dei due
ambienti per ripristinare l’applicazione pubblica e per sottoporre a nuova fase di test
l’applicazione non ancora perfettamente funzionante.
Per eseguire l’upload sul portale, selezionare il progetto WindowsAzure1 e fare clic con il
pulsante destro, dal menu contestuale selezionare Pacchetto….
TPSIT 5
434
um 426 di
Visual Studio crea due file.
File WINDOWSAZURE1.CSPKG
Rappresenta il package dell’applicazione, ovvero il codice compilato dell’applicazione.
Qualunque modifica al codice nei file contenuti nel progetto implica un nuovo processo di
distribuzione, è possibile eseguire l’upgrade in-place dei progetti senza dovere seguire per
intero il processo di distribuzione.
È necessario eseguire l’upload del CSPKG e decidere quali Web Role aggiornare, così
come quali eventuali modifiche effettuare alla configurazione esistente.
File SERVICECONFIGURATION.CLOUD.CSCFG
<?xml version="1.0" encoding="utf-8"?>
<!-- **********************************************************************************************
Questo file è stato generato mediante uno strumento dal file di progetto:
ServiceConfiguration.Cloud.cscfg
Le modifiche a questo file possono causare un comportamento non corretto e verranno
perse se il file viene rigenerato.
********************************************************************************************** -->
<ServiceConfiguration serviceName="WindowsAzure1"
xmlns="http://schemas.microsoft.com/ServiceHosting/2008/10/ServiceConfiguration"
osFamily="4" osVersion="*" schemaVersion="2013-10.2.2">
<Role name="WebRole1">
<Instances count="1" />
<ConfigurationSettings>
<Setting name="Microsoft.WindowsAzure.Plugins.Diagnostics.ConnectionString"
value="UseDevelopmentStorage=true" />
</ConfigurationSettings>
</Role>
</ServiceConfiguration>
Si aprirà la pagina web che consente di autenticarsi con il Live ID per accedere all’istanza
di Azure.
TPSIT 5
434
um 427 di
Una volta effettuato l’accesso fare clic sul pulsante Deploy, si apre una pagina di
caricamento dei due file generati.
Una volta terminata la fase di caricamento, l’applicazione sarà posta nello stato di
Stopped.
Con un clic sul pulsante Run, l’applicazione passerà prima allo stato Initializing, poi Busy
e infine Ready.
Nel momento in cui si vuole sospendere l’applicazione, sia nell’ambiente di produzione sia
in quello di staging, premere il pulsante Suspend e l’applicazione della relativa area sarà
sospesa, con la possibilità di essere riavviata in un altro momento.
Appena si procede alla sospensione, apparirà un messaggio che informa che non sarà
sospeso l’addebito per il servizio.
Per evitare di sostenere spese inutili per un’applicazione non avviata, è quindi necessario
eliminare l’applicazione, pulsante Delete e cancellare il servizio.
Azure segue le istruzioni e durante una distribuzione installerà l’applicazione in un solo
nodo del data center.
In qualunque momento si può premere il pulsante Configure sul portale e modificare
questa impostazione per far sì che il SO distribuisca il carico su tanti nodi quante sono le
istanze configurate.
Una volta premuto Run l’applicazione sarà raggiungibile al suo URL.
In pratica, il programmatore “deve” concentrarsi sul codice dell’applicazione, quindi
scriverlo il meglio possibile e indicare ad Azure le necessità.
Maggiori sono le informazioni che si forniscono nel modello applicativo, migliore sarà il
supporto che il SO fornirà per gestire le politiche di ripartizione del carico.
L’onere della gestione fisica delle macchine e degli strumenti di amministrazione è compito
di chi offre la piattaforma, nell’esempio Microsoft che offre Azure.
Questo coinvolge anche l’aspetto della manutenzione.
Sicuramente nessuno meglio di Microsoft conosce il .NET Framework, il kernel del SO e
servizi come IIS e SQL Server.
La lettura/scrittura sul file system è identica al PC: l’unica differenza è rappresentata dal
percorso in cui si deve scrivere che potrà essere sempre diverso a seconda del nodo
virtuale su cui l’applicazione gira.
Per ottenere la root per gestire file e cartelle, si usa la classe RoleManager.
lblPercorso.Text = RoleEnvironment.GetLocalResource("StorageLocale").RootPath;
Se si testa l’applicazione in locale si vede che ogni istanza avrà la sua cartella su disco
creata durante la distribuzione dell’applicazione da parte dell’emulatore.
Data la natura “locale” del LS, è indispensabile utilizzare questo spazio solo per dati
temporanei: questo spazio non è condiviso dalle varie istanze e quindi non può adattarsi
ad un’applicazione che deve poter essere scalabile.
Inoltre, è uno spazio che è perso nel caso in cui l’istanza vada in crash e Azure decida di
spostare l’istanza su un server più potente in base al modello applicativo.
Il LS si utilizza anche per gestire la cache applicativa ma non dev’essere usata per i dati
gestiti dall’applicazione.
STORAGe
LS (Local Storage)
In Azure è possibile configurare LS per sopravvivere ai recycle del processo, tramite
l’attributo cleanOnRoleRecycle della configurazione <LocalStorage>.
TPSIT 5
434
um 428 di
Questa impostazione consente di evitare la cancellazione dello store locale in caso di
riciclo del processo: ad esempio, a fronte di una modifica alla configurazione oppure in
seguito ad un upgrade, è probabile che il processo dove sta girando l’applicazione debba
essere riciclato, come accade in IIS nelle soluzioni che girano su un PC.
Questo flag consente di evitare di perdere il contenuto del LS, in ogni caso lo storage è
locale all’istanza ed è comunque perso in caso di spostamento del codice su un altro nodo
del cloud.
È sufficiente la definizione di uno storage nel file SERVICECONFIGURATION.CSDEF.
<LocalStorage name="StorageLocale" sizeInMB="10"/>
Il file indica ad Azure e durante lo sviluppo in locale con l’emulatore di creare uno storage
sull’istanza denominato Storage Locale.
Indicare la dimensione è un’informazione preziosissima per il load-balancer che dovrà
scegliere una VM con capacità sufficiente ad accogliere lo storage.
Storage Account
Questo servizio rappresenta un “posto” dove le applicazioni, quelle legate all’account,
possono gestire i dati e permette l’utilizzazione di tre strutture dati specifiche.
Sono accessibili da applicazioni ospitate su Azure così come da applicazioni che girano su
1. BLOB (Binary Large OBject)
File binari di grande dimensione.
2. Queue
Code di messaggi necessarie per gestire la comunicazione asincrona tra uno o più Worker
Role e uno o più Web Role, come MSMQ.
3. Table
Tabelle semplici per gestire dati strutturati, questo nome è fuorviante, in quanto ricorda un
DB in cui memorizzare dati in tabelle; invece è uno spazio diverso.
 Non necessita della definizione delle colonne: è possibile memorizzare e ricercare
un’entità composta da un numero arbitrario di proprietà.
 Nella stessa tabella possono essere memorizzate informazioni diverse.
 Non si definiscono indici o relazioni fra tabelle, è gestito un indice sulla chiave primaria
ed è l’unico a poter essere utilizzato.
 Non ci sono relazioni fra tabelle in quanto il servizio ha nulla a che vedere con un DB
relazionale.
 Non esistono le tipologie di colonne; la tipologia di dato è definito direttamente nella
struttura dell’entità che si vuole memorizzare.
Lo storage creato diventa quindi accessibile “da ovunque” senza bisogno di acquistare e
TPSIT 5
434
um 429 di
configurare server e dischi, politiche di accesso dal firewall, creazione di servizi
raggiungibili dall’esterno e problema di gestione della sicurezza.
L’accesso via REST garantisce l’utilizzo da Python, PHP, Ruby e JavaScript.
Affinity group
Ha un duplice obiettivo.
1. Indicare in quale data center, regione geografica, devono essere memorizzati
fisicamente i dati.
2. Se anche l’applicazione è ospitata su Azure, è importante tenere “vicini” codice e
storage per evitare roundtrip fuori dal data center per ogni richiesta.
Questa vicinanza può essere garantita con la creazione di un affinity group in cui tenere le
applicazioni e lo storage: se questi due elementi stanno nello stesso affinity group è
garantito l’utilizzo di macchine dello stesso data center per entrambi.
Fornisce tre endpoint pubblici, esposti tramite HTTP che rappresentano gli indirizzi di base
per l’accesso a BLOB, queue e table.
Per gestire i BLOB si deve effettuare una richiesta GET su questa URL per recuperare un
file, una richiesta POST per inserire un nuovo file, una richiesta PUT per modificare un file,
una richiesta DELETE per cancellare un file.
Ognuna di queste richieste deve autenticarsi verso lo storage prima di poter essere
eseguita.
Le due chiavi, Primary e Secondary servono a questo scopo: è necessario firmare le
richieste con un algoritmo di hashing utilizzando una delle due chiavi.
Custom Domains
Consente di gestire gli URL degli endpoint tramite i propri indirizzi DNS.
DISTRIBUZIONE E DEBUG
Nel momento in cui si sviluppa un’applicazione, ci si trova di fronte al problema di dover
spostare tutti servizi: hosting, storage e DB, dal PC locale ad Azure.
Quando tutti i servizi sono ospitati nel PC locale di sviluppo, si può eseguire il debug come
con qualsiasi altra applicazione, fermando l’esecuzione su una specifica istruzione, per
esempio per verificare il contenuto delle variabili.
Quando tutti i servizi sono su Azure, invece, le possibilità di debug sono ridotte.
Ci possono essere tre situazioni diverse.
1. local:local: si ha tutto nel PC di sviluppo: sia l’applicazione sia i servizi cui esso si
collega; in questa fase si può completare lo sviluppo dell’applicazione e si può
effettuare il debug di circa il 70% dell’applicazione perché non è utilizzata la rete.
2. cloud:cloud: si ha tutta l’applicazione e tutti i servizi sul cloud all’interno di uno stesso
data center; si può verificare il funzionamento dell’applicazione e terminare lo sviluppo.
3. local:cloud: si ha l’applicazione nel PC di sviluppo, mentre i servizi di storage e di DB
si trovano su Azure; si può ancora eseguire il debug dell’applicazione; questa fase
permette di completare e di effettuare il debug per un altro 20-25% dell’applicazione,
questa fase ha le prestazioni peggiori rispetto alle altre due.
L’ambiente che esegue l’applicazione è il PC, mentre l’ambiente che offre i servizi di
storage e di DB è in un data center.
Questo significa che sull’applicazione incide fortemente l’attività di comunicazione in rete,
tra il PC e il data center.
Si può usare questa fase per mettere sotto stress l’applicazione: gli eventuali ritardi e time
out di comunicazione possono così essere testati nelle condizioni peggiori possibili.
Progettare un client di chat il cui server utilizza il DB SQL Azure per memorizzare gli utenti
registrati.
TPSIT 5
434
um 430 di
Codice per creare e aggiornare il DB degli utenti.
USE [ChatApp]
GO
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
DROP TABLE [dbo].[Users];
GO
CREATE TABLE [dbo].[Users] (
[ScreenName] NVARCHAR (50) NOT NULL,
[EmailAddress] NVARCHAR (250) NOT NULL,
[Password] NTEXT NOT NULL,
[Online] BIT NOT NULL,
[ClientID] INT IDENTITY (1, 1) NOT NULL);
USE [ChatApp]
GO
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
DROP TABLE [dbo].[Friends];
GO
CREATE TABLE [dbo].[Friends] (
[UserID] INT NOT NULL,
[FriendsUserID] INT NOT NULL );
TPSIT 5
434
um 431 di
Client
FILE FRMCHAT.CS
Applicazione client.
File FRMLOGIN.CS
Il form di login per l’utente.
File FRMREGISTER.CS
Il form di registrazione per l’utente, per evitare il collegamento al server web di
registrazione.
Server
CLASSES/AZURESQL.CS
Il modulo contiene tutti le istruzioni SQL e le query di consultazione del FB.
CLASSES/PUBLICIP.CS
Il modulo contiene il codice per ottenere l’IP pubblico.
FORMS/FRMAZUREDATABASELOGIN.CS
Il form di login per il DB di Azure.
File FRMSERVER.CS
Applicazione server.
TPSIT 5
434
um 432 di
MOBILE SERVICES
È lo strumento per progettare un back-end ospitato su un DB SQL Server sul cloud che
potrà essere interrogato tramite chiamate REST con supporto a OData per il filtraggio e
manipolazione dei dati, senza bisogno di scrivere esplicitamente il codice di accesso,
poiché lato server è tutto gestito automaticamente dall’infrastruttura.
In caso di necessità si può usare uno script basato su NODE.JS per specificare le
operazioni da eseguire in fase d’inserimento, lettura, modifica, cancellazione dei dati e
definire attività schedulate su richiesta o a intervalli di tempo.
I Mobile Services possiedono anche le seguenti funzionalità.
 Supporto all’autenticazione: Microsoft Account.
 Twitter.
 Facebook.
 Google: protocollo OAuth.
 Modifiche push verso i dispositivi.
Il Mobile Services SDK gestisce la comunicazione con il server ed è disponibile anche per
IOS e Android.
Progettare un Mobile Services per gestire un’agenda da un’app Windows Store, con
autenticazione Facebook e uno scheduler per inviare promemoria delle attività inserite.
Creare il Mobile Services in Azure, quindi nella procedura guidata assegnare il nome alla
risorsa e specificare il DB da usare.
Definire due tabelle.
1. Appointments: memorizza gli appuntamenti.
2. Channels: gestisce le notifiche push.
Registrare l’app sul Windows Store per ottenere il token di accesso al servizio,
memorizzare Identificatore di sicurezza del pacchetto (SID) e Chiave privata client perché
si dovrà inserire nei campi Package SID e Client.Secret della sezione PUSH del pannello
di amministrazione del Mobile Services.
Progettare un’app su Facebook cui collegare il servizio, memorizzare App ID/API Key e
Chiave segreta dell’applicazione perché si dovranno inserire nei campi App ID/API Key e
App Secret della sezione IDENTITY del Mobile Services sotto la voce facebook settings.
TPSIT 5
434
um 433 di
Fare clic con il pulsante destro sul nome del progetto in Esplora soluzioni, dal menu
contestuale selezionare Store/Associa applicazione a store… seguire la procedura
guidata per aggiornare il file manifest con i dati del Windows Store.
Aggiungere il Mobile Services SDK al progetto.
L’UI è composta da due parti.
1. L’area per inserire i nuovi appuntamenti.
2. L’elenco delle attività.
TPSIT 5
434
um 434 di