[ Yeremiya @ 10.09.2009. 14:07 ] @
Nedavno sam se suočio sa problemima vezanim za download fajla sa servera (serverska aplikacjia, thread-ovan TcpServer, pisan za .NET 3.5 SP1) na klijent (klijentska aplikacija, konektuje se na server putem socket-a). Klijent radi na Motorola Symbol uređaju, Windows Mobile platforma, sasvim korektno, korišćen je .NET Compact Framework. Server radi na Windows XP mašini na koji je nakačen Access Point za bežično povezivanje sa klijentskim mašinama.

Klijent i server koristim za "kratku" komunikaciju koristeći sopstveni kvazi-protokol koji se zasniva na slanju UTF8 stringova sa separatorima. Npr: klijent pošalje DATA1,DATA2,DATA3, server primi, parsira i vrati odgovor tipa ODGOVOR1,ODGOVOR2,ODGOVOR3. Ovakav način komunikacije sam testirao i testirao i sve radi kako treba.

Međutim, nedavno sam dobio potrebu da sa servera pošaljem binarni fajl na klijent, na zahtev klijenta. Osmislio sam komunikaciju ovako:

1) klijent pošalje specifičan zahtev za download fajla
2) server primi zahtev, isparsira ga i pošalje odgovor u formatu IME,VELIČINA (šalje kao string), a zatim šalje fajl
3) klijent prima odgovor i parsira ga, pravi FileStream f u folderu sa imenom iz odgovora servera, pravi byte[] buffer = new byte[VELIČINA], i zatim prima fajl

Fajl je binarni, šaljem array bajtova "u komadu" jer se nisam snašao sa implementacijom algoritma koji bi podatke slao u delovima (npr po 1024 bajta odjednom).

U C# kodu bi to izgledalo ovako:

Klijent:
Code:

// slanje zahteva serveru
string msg = "DL,FILE1";
byte[] tosend = new UTF8Encoding().GetBytes(msg);
socket.Send(tosend, tosend.Length, SocketFlags.None);

// prijem odgovora
byte[] received = new Byte[1024];
int bRead = socket.Receive(received);
string serverMsg = Encoding.UTF8.GetString(received, 0, bRead);

// parsiranje odgovora, odgovor stiže u formatu IME,VELIČINA
string[] split = serverMsg.Split(",".ToCharArray());
string ime = split[0];
long length = Convert.ToInt64(split[1]);

// priprema za download fajla
string curFolder = Path.GetDirectoryName(Assembly.GetExecutingAssembly().GetModules()[0].FullyQualifiedName);
string fname = curFolder + "\\" + ime;
byte[] fajl = new byte[length]; // buffer za prijem fajla

// prijem fajla
long primljeno = socket.Receive(fajl, SocketFlags.None);

// upis fajla na disk
FileStream fs = new FileStream(fname, FileMode.Create);
fs.Write(fajl, 0, fajl.Length);
fs.Close();


Server:
Code:

TcpClient client; // ovde ovo navodim samo radi razjašnjavanja koda, client je već konektovan u ovom koraku!!!!
NetworkStream ns = client.GetStream();

// server je već primio i parsirao zahtev za download ispravno, pa krećem od dela gde server šalje odgovor na klijentov zahtev

FileStream fs = new FileStream(Path.GetDirectoryName(Assembly.GetExecutingAssembly().GetModules()[0].FullyQualifiedName) + "\\file.bin", FileMode.Open);
FileInfo fi = new FileInfo(Path.GetDirectoryName(Assembly.GetExecutingAssembly().GetModules()[0].FullyQualifiedName) + "\\file.bin");
long total = fi.Length; // dužina fajla

string msg =  fi.Name + "," +  total.ToString(); // poruka za slanje, "IME,DUŽINA"
byte[] tosend = new UTF8Encoding().GetBytes(msg);

// odgovor koji sadrži dužinu fajla i ime
ns.Write(tosend, 0, tosend.Length);

byte[] fajl = new byte[total]; // buffer za čitanje fajla sa diska i slanje klijentu

fs.Read(fajl, 0, fajl.Length);
fs.Close();

// slanje fajla klijentu
ns.Write(fajl, 0, fajl.Length);


Ono što se dešava nakon što pokušam da fajl prenesem je da prenos u stvari uspe. Međutim, u testovima fajl ni jednom nije stigao do klijenta a da nije bio corrupted. Napominjem da sam pokušavao i sa dodavanjem BinaryReader-a na serverskoj strani za čitanje fajla i BinaryWriter-a na klijentskoj za upis fajla, ali i pored toga sam dobijao identične rezultate.

Da li neko zna gde grešim? Možda je način slanja celog fajla odjednom loš, s'obzirom na to da je veličina fajla oko 5MB? Ako je tako, kako da implementiram slanje "deo-po-deo"?

Unapred zahvalan na odgovorima,
Yeremiya
[ deerbeer @ 10.09.2009. 14:27 ] @
Citat:

Da li neko zna gde grešim? Možda je način slanja celog fajla odjednom loš, s'obzirom na to da je veličina fajla oko 5MB? Ako je tako, kako da implementiram slanje "deo-po-deo"?

Napravis jednu petlju koja radi slanje fajla deo po deo (npr. po 1024 bajta u svakoj iteraciji ) .

Code:
 
int offset = 0 ; 
while (offset  < fajl.Lenght) 
{
   if (offset + 1024 > fajl.Lenght ) 
     { 
         ns.Write(fajl, offset , fajl.Lenght - offset ) ; 
         offset +=  fajl.Lenght - offset ;
     }
   else 
    {
       ns.Write(fajl, offset , 1024) ; 
       offset += 1024 ; 
     }
}

[ mmix @ 10.09.2009. 14:54 ] @
A zasto bas insistiras na socket komunikaciji? Sto ne uradis ovo preko HTTP protokola (gde headere mozes da iskoristis za metadata o fajlu).

[ Yeremiya @ 11.09.2009. 09:38 ] @
Citat:
deerbeer: Napravis jednu petlju koja radi slanje fajla deo po deo (npr. po 1024 bajta u svakoj iteraciji ) .


Hvala na predlogu. U toku dana ću pokušati da implementiram ovakvo slanje i prijem, pa ću raportirati sa rezultatima.

Citat:
mmix: A zasto bas insistiras na socket komunikaciji? Sto ne uradis ovo preko HTTP protokola (gde headere mozes da iskoristis za metadata o fajlu).


Na žalost, nemam izbor načina povezivanja jer nisam jedini koji radi na ovom projektu. Zahtev koji imam pred sobom je da ovo realizujem baš preko socketa. :( Ideje?
[ Yeremiya @ 15.09.2009. 13:16 ] @
Ima li neko možda razvijenu funkciju za slanje i prijem fajla preko socketa? Ovo za slanje što je poslao deerbeer izgleda jako fino, ali nikako ne uspevam da sa klijentske strane uhvatim čitav fajl.

Saveti?
[ deerbeer @ 15.09.2009. 13:32 ] @
Citat:
Yeremiya: Ima li neko možda razvijenu funkciju za slanje i prijem fajla preko socketa? Ovo za slanje što je poslao deerbeer izgleda jako fino, ali nikako ne uspevam da sa klijentske strane uhvatim čitav fajl.

Saveti?

Kako primas podatke sa strane klijenta ?
AKo sa servera saljes parce po parce fajla onda treba i da primas parce po parce sa strane klijenta
http://msdn.microsoft.com/en-u...ockets.networkstream.read.aspx
s tim sto ne zatvaras fajl u koji upisujes bajtove dok NetworkStream objekat ne vrati false u petlji na DataAvailable property.




[ Yeremiya @ 21.09.2009. 13:12 ] @
Pokušavao sam da napravim "komplement" onoj funkciji za slanje na sličan način, sa istim bufferom, ali mi DataAvailable postane false posle prvog chunk-a podataka. Pronašao sam po internetu informacija o tome da ta funkcija "ponekad ne radi kako treba". :( Pokušavao sam čak i da primam podatke u mrtvoj petlji, pa da izlazim iz petlje break-om kada primim dovoljno podataka jer unapred znam koliki je fajl. Ni to nije uspelo. Razmišljam da se vratim na pristup sa prenosom celog fajla, što radi, ali me vraća na prvobitni problem:

Primljeni fajl je corrupted. Veličina je ista u bajt, ali fajl nije ispravan.

Da dodam: testirao sam prvobitnu funkciju za slanje (u komadu) na dva mesta

1) Server i desktop verzija klijenta, isti komp (=> isti IP), prenos uspe u deliću sekunde i preneti fajl je ok.
2) Server, AP, Symbol handheld verzija klijenta, prenos uspe za oko 4 sekunde, preneti fajl je corrupted.

Da li postoji mogućnost da FileStream.Write(byte[] buffer) drugačije radi na uređaju?