Mikä on oikeaoppinen tapa lähettää tcp:llä paljon dataa (winsock)? Kun laitan testiohjelman clientin lähettämään jatkuvana virtana paketteja silmukassa antaa kohta serveriltä virheilmoituksen
*Server Error(10053): Software caused connection abort*
Tällanen virheenkäsittelijä on lähetysvaiheessa mutta silti seuraava viesti ei mene perille kun serveri sammu
function TForm1.ClientWrite(Client: TTCPClient; data: pointer; len: integer): integer; var tries: integer; begin tries:=0; repeat result:=client.WriteBuffer(data,len); if result=len then exit // Success else begin // Not ok application.ProcessMessages; sleep(20); if result>0 then begin // Sent only part of data? memo1.Lines.Add(format('fail: %d/%d',[result,len])); exit; end; end; inc(tries); until tries>9; end;
Pitäskö mun saada jostain selville paljonko TCP:n lähetyspuskurissa on tilaa jäljellä vai vaan raakasti laittaa serveri taas päälle kun tulee virhe? Ja testailen ihan localhostina.
Ellei Delphi tee jotain ihan omiaan, puskurista ei tarvitse itse huolehtia. Säädöistä riippuen funktio joko jumittaa ohjelman, kunnes homma on hoidettu, tai ilmoittaa jotenkin, paljonko lähetettiin.
Mitä WriteBuffer tarkalleen tekee? Palauttaako se mahdollisesti lähetettyjen tavujen määrän? Tässä tapauksessa oikea toimintatapa olisi silmukassa jatkaa lähetystä aina siitä, mihin edellinen lähetys jäi.
function MySend(data: Pointer, len: Integer): Boolean; var sent: Integer; begin { Oletustulos: OK. } MySend := True; while len > 0 do begin sent := send(data, len); if ERROR then begin { Virhe. } MySend := False; Break; end; Inc(data, sent); Dec(len, sent); if len > 0 then begin { Kesken. } Sleep(10); Application.ProcessMessages; end; end; end;
Writebuffer kutsuu suoraan winsockin send, ei tee mitään extraa ennen tai jälkeen
http://msdn.microsoft.com/en-us/library/ms740149(VS.85).aspx
Kyllä, se palauttaa joko tavujen määrän tai virheen (luulen että negatiivinen).
"fail.." tekstiä ei koskaan tulostunut joten luulen että se jättää lähettämättä koko paketin jos ei mahdu.
Edit: Jep, winsock määrittelee virheen:
SOCKET_ERROR = -1;
Kyllä se sitten toimisi aivan tuon koodini mukaan. Ainoa ongelma on, että SOCKET_ERROR taitaa tulla myös silloin, kun dataa ei lähetetä ollenkaan (eli nollaa ei palauteta koskaan), joten pitää muualta selvittää, mikä on tilanne. Normaalisti tämän voisi tehdä select-funktiolla, ja olisi hassua, jos tuossa luokassa ei olisi jotain vastaavaa.
Tuo virhe 10053 hävis serverin logista joka ei enää valita mitään, johtui siitä etten varmistanu että headerin sanoma datan pituus riittää varattuun muuttujaan. Jossain vaiheessa menee headeri siis datan sekaan ja lopputulos on mitä on.
Testiohjelmani lähettää silmukassa kahdelta eri clientilta dataa serverille. Client 1:n data menee sekaisin (suunnilleen 14-20:n paketin jälkeen) mutta Client 2 data tulee täysin oikein. Ja koodi siltä osin moneen kertaan tarkistettu että 1 ja 2 on oikein. Lähetän headerin ja datan omilla sendeillään, molempiin soketteihin yhtäaikaa tarkoituksella satunnaisesti limittäin (esim järjestyksessä header1> header2> data1> data2>). Silloin tällöin molemmilta toimii kaikki 20 pakettia mutta client 1:ltä tuli perään paljon jotain sekalaista. Sinänsä kumma nimittäin uudelleenlähetys tapahtui vain 2 kertaa.
Edelleen send() palauttaa joko tasan koko paketin koon tai virheen, ei mitään siltä väliltä, muuten kokeilisin tuota antamaasi funktiota.
Eli siis päänsärkyä riittää vielä ^^
Niin, pitääkö headeri lähettää samassa recordissa datan kanssa vai voinko lähettää ne peräkkäin? Ei siinä ongelmaa pitäisi olla.
Edit2: Datavirheitä tulee samalla tavalla vaikka lähetän yhdistettyinä paketteina ilman randomia.
ClientWrite(client,@p,4); // Header 1: 4 tavua ClientWrite(client2,@p2,4); // Header 2: 4 tavua if random<0.5 then begin // Data ClientWrite(client,@p.data,p.len); ClientWrite(client2,@p2.data,p2.len); end else begin ClientWrite(client2,@p2.data,p2.len); ClientWrite(client,@p.data,p.len); end;
Edit: Nykyisellä ohjelmalla client.WriteBuffer() palauttaa aina joko 0 tai len. Minkäänlaisia virheitä winsockilta ei siis tule.
TCP:ssä data lähetetään virtana, eli kaikki toimii, kunhan järjestys on oikea. Asiaa voisi verrata vaikka tiedoston kirjoittamiseen.
Mahtaako olla niin, että sockettisi on blokkaavassa tilassa? Silloin paketti tosiaan joko mahtuu tai ei. Voit vaihtaa ei-blokkaavaan tilaan (non-blocking mode) tai ehkä lähettää paketin käsin pienempinä paloina eli vaihtaa ehdottamassani silmukassa lähetysfunktiolle datan pituuden tilalle Min(len, 1024), jotta dataa lähtisi kerralla enintään kilo (pitäisi hyvinkin mahtua puskuriin).
Ongelma ratkaistu, datavirta tulee molemmilta soketeilta oikein. Nostin kerralla lähetettävän ryöpyn vielä n. 2Mt:n ja joka paketti on eri kokoinen, max 7k.
Tosiaan headeri piti lähettää samalla sendillä datan kanssa, heti kun muutan sen erillisiks niin client 1 bugaa.
Mutta tämä oli ihan hyvä uutinen, blokkaava tila taitaa käytännössä helpottaa minun osaa. En sitten tiedä hidastaako paljon jossain tapauksissa kun paketit pitää koota yhteen ennen lähetystä.
Edit: Virkeellä päällä tänään... Löyty sitten vielä miks se bugas. Tosiaan jos headerin vielä lukee oikein niin kävi niin että dataa ei oltu vielä aina lähetetty ja sen luku palauttaa nolla tavua. Sitä pitäis odotella sitten lukupäässäkin.
Noh, simppelin tiedostonlähetysohjelman tein kokeiluna ja virhettä pukkaa. Lyhyesti sanottuna mitä suurempia paketteja lähetän, sitä suurempi mahdollisuus tulla virhe lähetyksessä. Virhe tässä tapauksessa tarkottaa että clientin yhteys katkeaa tiedostonlähetyksen alun jälkeen (5%+). Testasin samalla koneella kahden ohjelman välillä sekä netin yli kuitenkin Suomen sisällä. Jos lähetin yhtenäisenä jonona paketteja niin vastaanottajan yhteys katkeaa alkuunsa oli koko mikä tahansa, siispä aloin lähettämään pienen paluuviestin jonka vastauksena lähetetään uusi data-paketti.
Paketin koko 1kt:
- Omalla koneella toimii ilman ongelmia.
- Internetin yli siirtyi jopa MP3 alle minuutin ilman ongelmia.
Paketin koko 10kt:
- Omalla koneella siirrossa ongelmia.
- Internetin yli ongelmia.
Paketin koko 4kt:
- Omalla koneella toimii hyvin, tässä vaiheessa mittasin nopeudeksi 10Mt/s.
- Internetin yli ongelmia, VÄLILLÄ?? Siirtyi toiselle maksimi upload-kaistalla 60kt/s sillon kun toimi. Jos ei toimi niin katkasee clientin alkuvaiheessa.
Ideoita mistä tämä johtuu? Ja onko sille jotain erityistä syytä että paluuviesti pitää aina lähettää? Kokeilin niinkin että asetin paketeille 50ms aikavälin mutta ei auta.
Yhtenäisenä pakettivirtana siis ei ollut ongelmia jos oman koneen sisällä siirrän.
Koitan löytää vastauksia muilta forumeilta mutta odotin Ohjelmointiputkasta löytyvän jotain Winsock osaajia. Ei tämän ollut tarkoitus olla Delphi-kohtainen aihe, sillä dll jota komponentti käyttää on ihan sama mitä C++ ja muut. Tarkotus oli löytää jotain eroavaisuutta tai yleistä sääntöä mikä rajoittaa TCP käyttöä.
Viimeisin testi oli Tanskaan johon ei 2k suuruset paketit menny ilman yhteyden katkeamista, 1k edelleen toimi kyllä.
Ohjelma lähdekoodeineen:
http://www.easy-share.com/1908565547/FileSend_full.zip
WSockets komponentti:
http://www.torry.net/vcl/internet/sockets/
Kun käytät Delphin luokkia, olet jo aika kaukana Winsockista. Siis kyllähän minä voin tähän Winsockilla toimivaa koodia kirjoittaa, jos luulet, että siitä on jotain apua. Koodi on C++:aa, mutta eipä se Winsock siitä miksikään muutu.
#include <winsock2.h> #include <iostream> #include <cstdio> // Winsockin tyhmät alustukset... struct _WSAHelper { WSADATA data; _WSAHelper() { if (WSAStartup(MAKEWORD(2, 2), &data) == SOCKET_ERROR) { throw "WSAStartup: virhe!"; } } ~_WSAHelper() { WSACleanup(); } } _WSAHelper_; int main(int argc, char **argv) { // Ohjelma ottaa kolme parametria, IP-osoitteen, portin ja tiedoston. if (argc < 4) { std::cout << argv[0] << " palvelin portti tiedosto" << std::endl; return 0; } // Avataan tiedosto ja lasketaan koko. FILE *tiedosto = fopen(argv[3], "rb"); if (!tiedosto) { return 1; } fseek(tiedosto, 0, SEEK_END); size_t koko = (size_t) ftell(tiedosto); fseek(tiedosto, 0, SEEK_SET); // Avataan socket. int sock; sockaddr_in osoite; if ((sock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0) { return 2; } memset(&osoite, 0, sizeof(osoite)); osoite.sin_family = AF_INET; osoite.sin_port = htons(atoi(argv[2])); osoite.sin_addr.s_addr = inet_addr(argv[1]); if (connect(sock, (struct sockaddr *)&osoite, sizeof(osoite)) < 0) { return 4; } // Luetaan tiedosto ja lähetetään dataa. while (!feof(tiedosto)) { char data[1024]; size_t maara = fread(data, 1, sizeof(data), tiedosto); // Silmukassa lähetetään luettua palaa, kunnes koko pala menee. // Muuttuja i kertoo, mihin asti on lähetetty. for (size_t i = 0; i < maara;) { // Lähetetään kohdasta i enintään maara - i tavua. int ret = send(sock, data + i, maara - i, 0); if (ret <= 0) { throw "Virhe."; } // Nostetaan i:tä sen mukaan, paljonko lähti. i += ret; } } // Katkaistaan yhteys. closesocket(sock); return 0; }
Tämä koodi lähettää aivan sujuvasti netissä ihan minkä tahansa tiedoston, nopeus vastaa nettiyhteyden nopeutta. (80 kilotavua sekunnissa on aivan normaali lähetysnopeus kotilaajakaistalle.) Mitään ylimääräisiä vastauspaketteja ja muita ei tarvita, ne kuuluvat muutenkin TCP:n sisäiseen toimintaan. TCP:n ideana on, että paketit kulkevat järjestyksessä perille ja niistä muodostuu lopussa sama yhtenäinen data kuin alkupäässä riippumatta siitä, millaisina palasina data on syötetty send-funktiolla ja miten se on sattunut verkossa kulkemaan.
Nyt alko löytyä jotakin
http://msdn.microsoft.com/en-us/library/ms740102(VS.85).aspx
OOB Data in TCP
To minimize interoperability problems, applications writers are advised not to use OOB data unless this is required to interoperate with an existing service. Windows Sockets suppliers are urged to document the OOB semantics (BSD or RFC 1122) that their product implements.
...It might, therefore, point to data that has not yet been received.
WSockets koodista TClient.Open sisältä:
SockOpt:= true; {Enable OOB Data inline} if setsockopt(FLocalSocket, SOL_SOCKET, SO_OOBINLINE, PChar(@SockOpt), SizeOf(SockOpt)) <> 0 then
Muuta erikoista ei alustuksesta tai viestien vastaanotosta löydy..
WSAASyncSelect() lisäks kutsuttu viestien vastaanottamista varten.
SOCK_STREAM on käytössä myös.
FLastError:= WSAStartup($101, WSAData);
korvasin komennolla (toimi edelleen ainakin local kokeilulla, mutta käyttäniskö nyt ainakin uusinta versiota):
FLastError:= WSAStartup(MakeWord(2,2), WSAData);
Toimiiko siis paremmin, jos otat nuo OOB-jutut pois?
Kommentoin setsockopt() koodin pois kokonaan ja ainakin localhostilla tuli tiedosto läpi. Se vissiin ei enää blokkaa sitten? Pitää seurata alkaako paketit lähtemään palasina...
On se on kummallinen. Sain kyllä sen clientin yhteyden pysymään päällä, oli ihan muistivuoto omassa koodissa... Mutta vaikka edelleen vielä säilytin tuon vastauksen vaatimisen niin paketit ei tule oikein.
Nostin paketin koon 10000 tavuun ja kerran onnistuin sillä lähettämään koko 10Mt:n mp3 tiedoston läpi, muulloin se vaan pysähtyy jossain satunnaisessa välissä. Pysähtyy siksi että vastaanottaja ei saa dataa oikein eikä lähetä lupaa uuteen pakettiin. Huom! Silti sitä dataa jostain aina tulee kuin tyhjästä useita kymmeniä tuhansia tavuja vaikka lähettäjä pysähtyisi ensimmäiseen pakettiin.
Edit: Hmm, itseasiassa eipäs tulekaan... se lukee headereita peräjälkeen niin kauan kun dataa socketissa riittää. Tämä tieto auttaa tekemään purkkakorjauksen mutta olis silti hyvä tietää mistä se virhe tulee.
Tämä logi on serverin joka on vastaanottajana:
...
S.Sending: 12 // Lähettää vastauspaketin (12 bytes)
S.h.datasize: 9993 // Vastaanottaa dataa
S.Sending: 12
S.h.datasize: 9993
S.Sending: 12
S.h.datasize: 9993
S.Sending: 12
S.h.datasize: 9993
S.Sending: 12
S.h.datasize: 9993
S.h.datasize: 11894 // Tässä kohtaa headerissä sanoo datan määrän virheelliseksi
S.h.datasize: 34514 // kerran sekasin, loputkin sekasin
S.h.datasize: 25474
S.h.datasize: 40149
S.h.datasize: 15945
S.h.datasize: 40
S.h.datasize: 46475
....
Tämä taas client joka lähettää:
...
C.Sending: 10000 // lähettää dataa
C.h.datasize: 5 // Saa vastauksen - uutta dataa lähetetään serverille
C.Sending: 10000
C.h.datasize: 5
C.Sending: 10000
C.h.datasize: 5
...
Mitä jos kirjoittaisit ihan alusta asti oman luokan? Winsock-oppaita on lukuisia, ja MSDN tarjoaa pitkiä esimerkkejä funktioiden käytöstä. Voit myös etsiä valmista luokkaa, joka olisi vähän yksinkertaisempi kuin tuo nykyisesi.
Taas nähdään, että heti tulee ongelmia, kun yksinkertaisista asioista tehdään liian mutkikkaita. :)
Ei tuo loppujenlopuks kovin monimutkanen luokka ollu, mutta vanha se kieltämättä on. Olisko kannattavampaa yrittää SDL_net?
Sain muuten sen toimimaan 16k paketeilla (omalla koneella - 44Mt/s) kun käskin lähettämään paketin uudestaan jos se tuli väärin. Jos tuli yksikin header väärin niin luki sokettia niin kauan turhaan temp muuttujaan kunnes se tyhjeni.
Toisaalta jos otan lähtökohdaksi että data ei tule oikein niin käyttäisin suorilla UDP:ta. Se että dataa tulee vielä virheellisen headerin jälkeen tarkoittaa sitä että se ei ainakaan ole tullut puolittain perille.
En tiedä liittyykö MTU asiaan, mutta 1500 tavua saattaa olla suurin paketin koko mitä kerrallaan sallitaan lähetettäväksi. Tiedän että verkkoliikenteessä on eri "tasoja" jotka saa pään sekasin (joku vois sanoa että 1500 raja koskee jotain alempaa kerrosta josta sovellusten ei tarvitse välittää... tarvitseeko?), käytäntö näyttäs nyt viittaavan tähän.
http://en.wikipedia.org/wiki/
Toiselta sivulta:
"FDDI can handle an IP datagram of up to 4,470 bytes. In contrast, a regular Ethernet frame uses a frame format that limits the size of the payload it sends to 1,500 bytes"
No se nyt kuitenkin toi ihan kokonaan uuden idean esille miten tiedoston voi lähettää vaikka olis 1500 max, vaikka nyt 1000 osissa jotta idea pysyy simppelinä. Eli lähettäjä pitäisi olio-listaa kaikista paketeista jotka odottavat vastausta. Se voisi lähettää niin monta pakettia kerrallaan kuin socket antaa ilman erroria, pitäen vaikka jonkun max 30 kerrallaan elossa. Jos vastausta ei kuulu niin se lähettää osan uudestaan tietyn ajan päästä.
Voi tietysti olla että vastaanottajan ei tarvitse lähettää vastausta ollenkaan ja ettei koko listaa tarvita mutta jos jollekin on iloa ideasta vaikka UDP:n kanssa. Pittää testailla kun viitsii.
Vielä UDP haulla löytyy kivaa luettavaa:
http://support.microsoft.com/kb/233401
"For example, when a WinSock application attempts to send a single UDP datagram with 12501 bytes of data, the IP layer performs fragmentation and generates nine IP fragments on an Ethernet. The first eight fragments are discarded and only the last fragment is kept while waiting for an ARP reply. When the first ARP reply is received, only the last fragment is sent."
Aihe on jo aika vanha, joten et voi enää vastata siihen.