Visual Basic Simple
Trasferimento di files tra Client e Server (seconda parte)
Sincronizza Indice
Sincronizza Indice
Scarica il progetto
Scarica il progetto
Scarica il testo dell'articolo
Testo dell'articolo
Stampa l'articolo
Stampa l'articolo
Ricerca personalizzata

Difficoltà: 4 / 5

<< Continua dalla parte 1

Visto il client nella parte precedente, rimane da vedere il server, leggermente più complesso del precedente.

Il secondo progetto si compone di un solo form che farà uso del controllo FBI Shape Progress Bartrattato nella sezione Controlli utente da aggiungere al progetto.

Sulla superficie del form avremo una ListBox di nome Stati, nella quale saranno inserite alcune informazioni sulla connessione e sul trasferimento dei dati.

Sono presenti anche due controlli Winsockdi nome Socket e Passivo, due Labelindicative di nome StatoLabel ed Avanzamento. Entrambe avranno la proprietà Autosize impostata a True, ma la prima avrà anche la proprietà Wordwrap impostata a True, mentre la seconda avrà la proprietà Alignment impostata a 2-Center.
Completiamo il nostro form con un'istanza della FBI Shape Progress Bardi nome AvanzamentoProgress con la proprietà Align impostata a 2-vbAlignBottom, Alignment impostata a 2-vbCenter e la proprietà Decimali impostata su 2.

Il server non potrà fornire alcun comando, eccetto che rispondere SI o NO alla richiesta di trasferimento del file per accettarlo o rifiutarlo.
Vediamo passo dopo passo il nostro codice:

  1. Option Explicit
  2. Private FILEHANDLE As Integer
  3. Private DIMENSIONEFILE As Long
  4. Private FILEDASALVARE As String

Abbiamo dichiarato 3 variabili che verranno utilizzate più volte all'interno del programma: FILEHANDLE è l'handle del file aperto per il trasferimento; DIMENSIONEFILE è la dimensione in bytes aspettata; infine FILEDASALVARE è il nome del file da salvare compreso del percorso della cartella.

  1. Private Sub Form_Load()
  2.   StatoLabel.Caption = ""
  3.   Avanzamento.Caption = "0 bytes ricevuti su 0"
  4.   Socket.LocalPort = 1500
  5.   Socket.Listen
  6. End Sub

All'avvio del programma viene aperta la porta 1500 in ascolto per la connessione sul controllo Socket (righe 10 e 11).

  1. Private Sub Form_Unload(Cancel As Integer)
  2.   If Socket.State <> sckClosed Then Socket.Close
  3. End Sub

Così alla chiusura del form viene chiusa anche la connessione aperta con Socket.

  1. Private Sub Socket_ConnectionRequest(ByVal requestID As Long)
  2.   Socket.Close
  3.   Socket.Accept requestID
  4.   Stati.AddItem "Accettata connessione."
  5. End Sub

Nel momento in cui Socket riceve una richiesta di connessione, esso termina di ascoltare ed accetta la chiamata, senza preoccuparsi di chi sia.

  1. Private Sub Socket_Close()
  2.   If Passivo.State <> sckClosed Then Passivo.Close
  3.   If Socket.State <> sckClosed Then Socket.Close
  4.   Stati.AddItem "Connessione chiusa."
  5.   Socket.Listen
  6. End Sub

Nel momento in cui la connessione viene chiusa (dal lato client), vengono chiusi tutti i socket aperti e Socket viene posto nuovamente in attesa di chiamata.

L'eventoDataArrival sul primo socket servirà per accettare il trasferimento del file oppure annullarlo una volta avviato. Ricordiamo che il server accetta solo due comandi: "/FILE NomeFile Dimensione" e "/FINE".

  1. Private Sub Socket_DataArrival(ByVal bytesTotal As Long)
  2.   Dim DATI() As Byte
  3.   Dim NOMEFILE As String
  4.   Dim DIMENSIONE As Long
  5.   Dim POSIZIONE As Integer
  6.   Dim TEMPSTR As String

Saranno utilizzate una serie di variabili: la matrice di bytes DATI servirà per contenere i dati ricevuti dal client; NOMEFILE verrà utilizzata per contenere il nome del file in arrivo, POSIZIONE verrà utilizzata per effettuare l'estrazione dei parametri dai dati ricevuti dal client, DIMENSIONE sarà invece la dimensione in bytes del file in arrivo e TEMPSTR sarà utilizzata per manipolare le stringhe.

  1.   Call Socket.GetData(DATI)
  2.   DATI = StrConv(DATI, vbUnicode)
  3.   Select Case Left(UCase(DATI), 5)
  4.     Case "/FILE"
  5.       TEMPSTR = Mid(DATI, 7)
  6.       POSIZIONE = 0
  7.       While InStr(POSIZIONE + 1, TEMPSTR, " ") > 0
  8.         POSIZIONE = InStr(POSIZIONE + 1, TEMPSTR, " ")
  9.       Wend
  10.       NOMEFILE = Left(TEMPSTR, POSIZIONE - 1)
  11.       DIMENSIONE = CLng(Mid(TEMPSTR, POSIZIONE + 1))
  12.       TEMPSTR = "È in arrivo un file di nome " & NOMEFILE & " di " & CStr(DIMENSIONE) & " bytes."
  13.       TEMPSTR = TEMPSTR & vbNewLine
  14.       TEMPSTR = TEMPSTR & "Desideri accettarlo?"

Alla riga 38 vengono letti i dati in arrivo su Socket e subito successivamente vengono convertiti in Unicode, allo scopo di utilizzarli come stringhe.
Ottenuta la stringa inviata dal client, sarà necessario verificare se essa è uno dei due comandi accettati (righe 40, 41 e 80).

Se la stringa ricevuta inizia per "/FILE", sarà necessario estrarre da essa il nome del file e la dimensione; l'inizio di tali informazioni è alla posizione 7 della stringa.

Alle righe 43-46 viene estratta la posizione dell'ultimo spazio separatore, al fine di rintracciare il punto dove termina il nome del file ed inizia la dimensione. Tale posizione sarà salvata nella variabile POSIZIONE.

Solo allora (righe 47 e 48) sarà possibile estrarre il nome del file (salvato in NOMEFILE) e la sua dimensione (memorizzata in DIMENSIONE).

Alle righe 49-51 viene formata la stringa TEMPSTR che verrà utilizzata per mostrare un avviso all'utente. Essa conterrà anche il nome del file in arrivo e la sua dimensione.

  1.       If MsgBox(TEMPSTR, vbYesNo + vbQuestion) = vbYes Then
  2.         FILEDASALVARE = App.Path & "\RICEVUTI\" & NOMEFILE
  3.         If Dir(FILEDASALVARE) <> "" Then
  4.           TEMPSTR = "Il file " & NOMEFILE & " esiste già." & vbNewLine
  5.           TEMPSTR = TEMPSTR & "Desideri sovrascriverlo?"
  6.           If MsgBox(TEMPSTR, vbYesNo + vbQuestion) = vbYes Then
  7.             Kill FILEDASALVARE
  8.           Else
  9.             Socket.SendData " -ERR: NONACCETTATO" & vbNewLine
  10.             Stati.AddItem "File " & NOMEFILE & " rifiutato."
  11.             Exit Sub
  12.           End If
  13.         End If

Figura 4Tale avviso sarà mostrato all'utente. Se alla richiesta di trasferimento l'utente rispnderà NO, sarà inviato al client il messaggio " -ERR: NONACCETTATO" e la procedura terminerà (righe 59-63).

Se l'utente invece risponderà SI, verrà generato il percorso completo del file da salvare, composto dal percorso della cartella in cui il programma server si trova, dalla cartella RICEVUTI e dal nome del file in arrivo. Il percorso completo sarà memorizzato nella variabile FILEDASALVARE.

Figura 5Prima di procedere alla scrittura del file, sarà necessario verificare se il file esiste (le varie soluzioni possibili sono spiegate in un HowTo). Se esso esiste, sarà mostrato un avviso di sovrascrittura all'utente (righe 55-57). Se l'utente richiederà la sovrascrittura, il file originale sarà cancellato prima di ricevere il file nuovo (righe 57-58).

In caso contrario il file originale sarà lasciato lì e verrà ritornato al server il messaggi di non accettazione del file. In altre situazioni potrebbe essere comodo specificare un nuovo nome di file in cui salvare il file in arrivo.

  1.         DIMENSIONEFILE = DIMENSIONE
  2.         If Passivo.State <> sckClosed Then Passivo.Close
  3.         Passivo.LocalPort = 0
  4.         Passivo.Listen
  5.         Socket.SendData " +OK: PORTA " & Passivo.LocalPort & vbNewLine
  6.         Stati.AddItem "Accettazione del file " & NOMEFILE
  7.         Stati.AddItem "Ascolto sulla porta " & Passivo.LocalPort
  8.         StatoLabel.Caption = "Ricezione del file " & NOMEFILE & " di " & DIMENSIONE & " bytes."
  9.         Avanzamento.Caption = "0 bytes ricevuti su " & DIMENSIONE
  10.         AvanzamentoProgress.Value = 0
  11.         AvanzamentoProgress.Max = 0
  12.       Else
  13.         Socket.SendData " -ERR: NONACCETTATO" & vbNewLine
  14.         Stati.AddItem "File " & NOMEFILE & " rifiutato."
  15.       End If

Alla riga 65 viene impostata la variabile globale DIMENSIONEFILE, che verrà utilizzata dal resto del programma, uguale a DIMENSIONE.

Sarà necessario aprire un socket passivo in ascolto su una porta casuale e comunicare il numero di tale porta al client, nella forma " +OK: PORTA Numero" (righe 66-69).
Saranno anche azzerati tutti i controlli grafici per monitorare l'avanzamento del traferimento.

Se, invece, l'utete ha risposto NO alla richiesta di trasferimento, sarà inviata al client la risposta " ERR: NONACCETATO".

  1.     Case "/FINE"
  2.       Passivo.Close
  3.       Close FILEHANDLE
  4.       FILEDASALVARE = ""
  5.       Stati.AddItem "Ricezione del file completata."
  6.   End Select
  7. End Sub

In caso che il client volesse terminare il trasferimento a metà, invierà il comando "/FINE" (non implementato in questo esempio) al cui ricevimento il server si occuperà di chiudere il socket passivo e l'handle del file in scrittura.

  1. Private Sub Passivo_ConnectionRequest(ByVal requestID As Long)
  2.   FILEHANDLE = FreeFile
  3.   Open FILEDASALVARE For Binary As FILEHANDLE
  4.   Passivo.Close
  5.   Passivo.Accept requestID
  6. End Sub

Nel momento in cui il client richiede il collegamento con il socket passivo, viene aperto il file FILEDASALVARE in modalità binaria e viene accettata la richiesta di collegamento.

  1. Private Sub Passivo_Close()
  2.   Close FILEHANDLE
  3.   FILEDASALVARE = ""
  4.   Stati.AddItem "Ricezione del file completata."
  5. End Sub

Analogamente nel momento della disconnessione viene chiuso il file aperto.

Tutti i dati che arriveranno al socket passivo saranno scritti all'interno del file di output.

  1. Private Sub Passivo_DataArrival(ByVal bytesTotal As Long)
  2.   Dim DATI() As Byte
  3.   Call Passivo.GetData(DATI)
  4.   If UBound(DATI) + LOF(FILEHANDLE) + 1 >= DIMENSIONEFILE Then
  5.     ReDim Preserve DATI(DIMENSIONEFILE - LOF(FILEHANDLE) - 1)
  6.     Put FILEHANDLE, , DATI
  7.     Stati.AddItem "Salvato chunk di " & UBound(DATI) + 1 & " bytes."
  8.     Avanzamento.Caption = LOF(FILEHANDLE) & " bytes ricevuti su " & DIMENSIONEFILE
  9.     AvanzamentoProgress.Max = DIMENSIONEFILE
  10.     AvanzamentoProgress.Value = LOF(FILEHANDLE)
  11.     Socket.SendData " +OK: FINE"
  12.     DoEvents
  13.     Passivo.Close
  14.     Close FILEHANDLE
  15.     FILEDASALVARE = ""
  16.     Stati.AddItem "Ricezione del file completata."

L'eventoDataArrival indica l'arrivo di dati al socket Passivo. Abbiamo detto che tali dati andranno scritti all'interno del file. Ma è necessario effettuare un controllo d'obbligo, ovvero che la somma dei dati ricevuti sia maggiore o uguale della dimensione aspettata (DIMENSIONEFILE). In tal caso sarà necessario eliminare i dati inutili che sono stati inviati mediante l'utilizzo dell'istruzione Redim, avendo cura di specificare la parola chiave Preserve per non distruggere il resto dei dati (riga 105).
Solo allora sarà possibile completare la scrittura dei dati nel file, inviare la risposta di fine del trasferimento " +OK: FINE" e chiudere socket passivo e file.

  1.   Else
  2.     Put FILEHANDLE, , DATI
  3.     Stati.AddItem "Salvato chunk di " & UBound(DATI) + 1 & " bytes."
  4.     Avanzamento.Caption = LOF(FILEHANDLE) & " bytes ricevuti su " & DIMENSIONEFILE
  5.     AvanzamentoProgress.Max = DIMENSIONEFILE
  6.     AvanzamentoProgress.Value = LOF(FILEHANDLE)
  7.     Socket.SendData " +OK: RECV " & LOF(FILEHANDLE)
  8.   End If
  9. End Sub

Se invece la somma dei dati inviati non corrisponde alla fine del file, l'intero pacchetto sarà salvato nel file (riga 118), la barra di avanzamento sarà aggiornata (righe 121 e 122) e verrà rimandato indietro l'ACK " +OK: RECV Dimensione" del totale dei dati finora ricevuti.

  1. Private Sub Stati_DblClick()
  2.   Stati.Clear
  3. End Sub

Un'ultima funzioncina prima di vedere il funzionamento del programma è legata al doppio click sopra la LisBox Stati. L'intera lista sarà cancellata. Può essere utile quando essa diviene troppo piena o per verificare l'andamento del trasferimento senza riavviare il programma.


Avviare sia il client che il server su uno o due computer collegati. Inserire nel client l'indirizzo IP del computer in cui viene eseguito il server e premere il pulsante Connetti. Se il server viene eseguito nella stesso computer del client, specificare come IP l'indirizzo di loopback 127.0.0.1.
Specificare un file da trasferire e premere il pulsante Invia.
Se il file non esiste sarà mostrato un messaggio di errore. Specificare quindi un nome di file valido e premere il pulsante Invia.

Figura 6Il server riceverà la richiesta di trasferimento. Premendo il pulsante Si alla richiesta di trasferimento il programma client potrà avviare il trasferimento.

Man mano che il trasferimento procede saranno mostrate le informazioni di avanzamento nella ListBox del server e nella sua barra di avanzamento

Figura 7
Figura 7
Figura 8
Figura 8

Figura 9Il termine del trasferimento sarà notificato anche sul programma client con una finestra di messaggio apposita.

 

Il nostro programma è terminato.
Il protocollo di comunicazione utilizzato è molto simile all'FTP.
L'interfaccia utente è molto semplice e non effettua controlli severi sulla correttezza dei dati inseriti. Naturalmente questo esempio non pretende di essere un completo programma di trasferimento dati ma soltanto uno spunto quasi completo per comprendere il funzionamento.

Fibia FBI
2 Giugno 2001
Corretto il 25 Gennaio 2004

Scarica il progetto
Scarica il progetto
Scarica il testo dell'articolo
Scarica il testo dell'articolo
Stampa l'articolo
Stampa l'articolo
Torna all'indice Client/Server