Ignazio Catanzaro

Sviluppatore Software

C# .Net

Archivio Posts
Anno 2012

Anno 2011

Anno 2010
Sondaggio
Introdurre argomenti di software engineering in un blog può essere utile?

Si
No

Spostare grosse quantità di dati Client-Server

In primis, un saluto a tutti gli utenti che seguono costantemente il blog nonostante la mia lunga assenza, mi scuso tanto ma tra il lavoro e un piccolo problema di salute non ho avuto veramente tempo!

Tralasciando gli aspetti personali, riapro in grande questo blog con un bel post....

Situazione reale :

Ignazio, sull'applicativo che stiamo sviluppando abbiamo la necessità di poter visionare nelle form di ricerca grossi quantitativi di dati ed effettuare una ricerca incrementale (stile ricerca di windows) su di essi.

Mio personale parere :

Questo potrebbe essere un problema, visto che l'applicativo client gira su un terminale windows mobile, ma , come dice la pubblicità della Nike, Impossible is nothing.

Incominciamo a provarle tutte, dalle soluzioni piu scandalose : passare tramite servizio WCF un semplicissimo Dataset (non fatelo mai, pratica sbagliatissima) oppure una List<> di oggetti contenente i risultati, a soluzioni meno squallide, serializzare il dataset in xml, comprimerlo e rileggerlo sul terminale mobile.

L'ultima opzione poteva essere l'opzione esatta, se non fosse che per spostare e leggere 15000/20000 record il datagrid del palmare impiegava quasi 70 secondi (il metodo ReadXml() su Windows Mobile è veramente, veramente lento)

Alla fine ho optato per questa soluzione :

Creo una struttura contenente i campi che devono essere visualizzati sul palmare :

    [StructLayout(LayoutKind.Sequential,CharSet = CharSet.Unicode)]
    public struct StrutturaEsempio
    {
        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 10)]
        public string Code;
        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 61)]
        public string Descrizione;
    }

La struttura ha un Layout sequenziale (LayoutKind.Sequential), cio mi permette di disporre i dati in sequenza durante l'esportazione nella memoria non gestita, imposto il charset ad unicode per riottenere il valore della stringa.

Da notare il paramentro SizeConst nell'attributo MarshallAs di ciascuna stringa, deve essere indicato il numero di caratteri massimi che la stringa deve memorizzare.

Ora, il trucco sta nel "trasformare" (scusate il linguaggio semplicistico, ma vorrei che l'argomento fosse alla portata di tutti) una lista di strutture "StrutturaEsempio" in array di byte e comprimere tale array :

//Compressione
int sizeOfStruct = Compressor.Compress<StrutturaEsempio>.GetSizeOfStruct();
byte[] buffer = new byte[lcs.Count * sizeOfStruct];
int startIndex = 0;
foreach (CountSearch count in lcs)
{
          Compressor.Compress<StrutturaEsempio>.CopyToBuffer(buffer, startIndex, count);
          startIndex += sizeOfStruct;
}
byte[] gzBuffer = Compressor.Compress<StrutturaEsempio>.CompressBuffer(buffer);
//Fine Compressione

Come vedete viene utilizzata una classe, Compress, che serializza in un buffer gli elementi della List<StrutturaEsempio> e poi li comprime.

Diamo Ovviamente un'occhiata alla classe Compress :

namespace AdministrationTool.Utility.Compressor
{
    public class Compress<T>
    {
        public static byte[] CompressBuffer(byte[] buffer)
        {
            MemoryStream ms = new MemoryStream();
            DeflateStream zip = new DeflateStream(ms, CompressionMode.Compress, true);
            zip.Write(buffer, 0, buffer.Length);
            zip.Close();
            ms.Position = 0;

            MemoryStream outStream = new MemoryStream();

            byte[] compressed = new byte[ms.Length];
            ms.Read(compressed, 0, compressed.Length);

            byte[] gzBuffer = new byte[compressed.Length + 4];
            Buffer.BlockCopy(compressed, 0, gzBuffer, 4, compressed.Length);
            Buffer.BlockCopy(BitConverter.GetBytes(buffer.Length), 0, gzBuffer, 0, 4);


            return gzBuffer;
        }

        public static void CopyToBuffer(byte[] buffer, int startIndex,object myStruct)
        {
            int sz = Marshal.SizeOf(typeof(T));
            IntPtr ptr = Marshal.AllocHGlobal(sz);
            Marshal.StructureToPtr(myStruct, ptr, false); (Copio la struttura nella memoria non gestita)
            Marshal.Copy(ptr, buffer, startIndex, sz);
            Marshal.FreeHGlobal(ptr);
        }

        public static int GetSizeOfStruct()
        {
            return Marshal.SizeOf(typeof(T));
        }
    }
}

Abbiamo tre metodi :

public static void CopyToBuffer(byte[] buffer, int startIndex,object myStruct)

Copia in un buffer l'oggetto passato come parametro (myStruct) :

byte[] buffer = new byte[lcs.Count * sizeOfStruct]; (buffer cui dimensione equivale al numero delle strutture create moltiplicato per la     dimensione della struttura)
int startIndex = 0; (Posizione attuale del buffer)
foreach (CountSearch count in lcs)
{
           Compressor.Compress<StrutturaEsempio>.CopyToBuffer(buffer, startIndex, count); (Scrivo nel buffer la struttura attuale)
          startIndex += sizeOfStruct; (Posiziono il cursore nella prossima posizione libera)
}


public static byte[] CompressBuffer(byte[] buffer)

Comprime il buffer creato con CopyToBuffer :

byte[] gzBuffer = Compressor.Compress<StrutturaEsempio>.CompressBuffer(buffer); (Comprimo il buffer)


public static int GetSizeOfStruct()

Ritorna la grandezza in byte dell'oggetto passato come parametro alla classe Generica.
int sizeOfStruct = Compressor.Compress<StrutturaEsempio>.GetSizeOfStruct();


Notare che per la compressione/Decompressione viene utilizzata la classe DeflateStream presente nel namespace System.IO.Compression,
Personalmente l'ho trovata piu performante della classe gzipStream (System.IO.Compression).

Dopodichè non ci resta che inviare (o esporre un metodo adatta nel servizio WCF) il buffer compresso che poi verra recepito, decompresso e deserializzato sul palmare (o su quasiasi altro client) :

byte[] buffer1 = Decompress(BufferCompresso);
int startIndex = 0;
while (startIndex < buffer1.Length)
{
      StrutturaEsempio prova = Compressor.Decompress.CopyFromBuffer(buffer1, startIndex);
      ListOfStrutturaEsempio.Add(prova);
      startIndex += 71;
}

Per fare questo utilizziamo la classe Decompress :

    public class Decompress<T>
    {
        public static byte[] DecompressBuffer(byte[] gzBuffer)
        {
            MemoryStream ms = new MemoryStream();
            int msgLength = BitConverter.ToInt32(gzBuffer, 0);
            ms.Write(gzBuffer, 4, gzBuffer.Length - 4);

            byte[] buffer = new byte[msgLength];

            ms.Position = 0;
            GZipStream zip = new GZipStream(ms, CompressionMode.Decompress);
            zip.Read(buffer, 0, buffer.Length);


            return buffer;
        }

        public static T CopyFromBuffer(byte[] buffer, int startIndex,T Struct)
        {
            int sz = Marshal.SizeOf(Struct.GetType());
            IntPtr ptr = Marshal.AllocHGlobal(sz);
            Marshal.Copy(buffer, startIndex, ptr, sz);
            T myCurrentStructObject = (T)Marshal.PtrToStructure(ptr, Struct.GetType());
            Marshal.FreeHGlobal(ptr);
            return myCurrentStructObject;
        }

        public static int GetSizeOfStruct(Type Struct)
        {
            return Marshal.SizeOf(Struct);
        }
}

Questa classe non fa altro che (come penso tutti abbiate immaginato :) ) decomprimere il buffer (public static byte[] DecompressBuffer(byte[] gzBuffer) e deserializzarlo nella struttura (public static T CopyFromBuffer(byte[] buffer, int startIndex,T Struct).

Un piccolo appunto, la funzione Marshall.SizeOf() eseguita in ambiente WindowsMobile ritorna una System.NotSupportedException quindi (a meno che non trovi una soluzione, non ho avuto tempo di sbatterci la testa neanche due minuti) impostate la dimensione della struttura manualmente, esempio , nella struttua StrutturaEsempio la dimensione è 71 (10 + 61).

Questo sistema puo trasportare circa 100000/150000 strutture formate da 2/5 campi in un secondo.

Spero possa essere un'aiuto o comunque uno stimolo a trovare soluzioni sempre prestanti ed efficaci.

Stay Tuned!
Categoria: Framework .Net
mercoledì, 21 lug 2010 Ore. 01.17

Calendario
novembre 2024
lmmgvsd
28293031123
45678910
11121314151617
18192021222324
2526272829301
2345678
Ora e Data
Statistiche
  • Views Home Page: 33.416
  • Views Posts: 46.185
  • Views Gallerie: 0
  • n° Posts: 24
  • n° Commenti: 12
Copyright © 2002-2007 - Blogs 2.0
dotNetHell.it | Home Page Blogs
ASP.NET 2.0 Windows 2003