Problema Winsock2

Pascal este un limbaj imperativ, creat inițial pentru a ajuta la predarea noțiunilor de programare structurati studenților. Delphi, urmașul lui Pascal, este un mediu de dezvoltare pentru aplicații Windows. Delphi este primul limbaj de programare (Object Pascal, mai exact) care a îmbinat ușurința în programare a limbajelor de nivel înalt și puterea uneltelor de lucru de nivel scăzut într-un mediu RAD.

Problema Winsock2

Postby xlad » 06 Jan 2010, 18:09

Lucrez la un proiect Server-Client si am probleme cu serverul.
Folosesc ca mijloc de transmisie al datelor WinSock2 prin TCP.

Ma intreb cum sa fac un server (asa cum sunt cele de la World of Warcraft -- un exemplu oarecare... pe care stiu ca multi il cunosc), care sa accepte si sa mentina foarte multe conexiuni deodata(poate chiar peste 100.000).

Clientul se conecteaza la server si trebuie sa mentina conexiunea activa. la client folosesc non-blocking.

Codul de la client(socket TCP blocking)
  1.  
  2. Function ClientConnectionThread.execute;
  3. Begin
  4.  // initializare
  5.  Repeat
  6.   ReceivedBytes := recv(ConnectionSocket,Buffer,SizeOf(Buffer),0);
  7.   If (ReceivedBytes = 0)
  8.    Then
  9.     Begin
  10.      OK := False;
  11.     End;
  12.   If (ReceivedBytes = SOCKET_ERROR)
  13.    Then
  14.     Begin
  15.      If (WSAGetLastError <> WSAEWOULDBLOCK)
  16.       Then
  17.        Begin
  18.         OK := False;
  19.        End
  20.     End;
  21.   If (OK = True)
  22.    Then
  23.     Begin
  24.      If (ReceivedBytes > 0)
  25.       Then
  26.        Begin
  27.         if ((verificare daca packetul corespunde formatului stabilit -- ca sa nu permita aplicatiilor oarecare sa se conecteze/sa ia informatii etc) = false)
  28.          then
  29.           Begin
  30.             ok := False;
  31.             //deconectare
  32.           End
  33.          Else
  34.           Begin
  35.             //executa ce trebuie sa faca clientul
  36.           End;
  37.        End;
  38.     End;
  39.  Until ((OK = False) Or (Terminated));
  40. End;
  41.  


Serverul trebuie sa deschida un port, sa accepte conexiuni prin el, apoi sa le ia la rand in continuu si sa primeasca datele. (socket TCP NON-blocking)
  1.  
  2. Function ServerConnectionThread.execute;
  3. Begin
  4.  // initializare
  5.  xx := 0;
  6.  While (Not Terminated) Do
  7.  Begin
  8.   ReceivedBytes := recv(Conexiune[xx],Buffer,SizeOf(Buffer),0);
  9.   if (nu s-a primit nici un mesaj in ultimele 16 secunde)
  10.    Then
  11.     Begin
  12.      OK := False;
  13.     End;
  14.   If (ReceivedBytes = 0)
  15.    Then
  16.     Begin
  17.      OK := False;
  18.     End;
  19.   If (ReceivedBytes = SOCKET_ERROR)
  20.    Then
  21.     Begin
  22.      If (WSAGetLastError <> WSAEWOULDBLOCK)
  23.       Then
  24.        Begin
  25.         OK := False;
  26.        End
  27.     End;
  28.   If (OK = True)
  29.    Then
  30.     Begin
  31.      If (ReceivedBytes > 0)
  32.       Then
  33.        Begin
  34.         if ((verificare daca packetul corespunde formatului stabilit -- ca sa nu permita aplicatiilor oarecare sa se conecteze/sa ia informatii etc) = false)
  35.          then
  36.           Begin
  37.             ok := False;
  38.           End
  39.          Else
  40.           Begin
  41.             //executa ce trebuie sa faca serverul
  42.           End;
  43.        End;
  44.     End;
  45.  if (ok = false)
  46.  Then
  47.   Begin
  48.    deconectare;
  49.   End;
  50.   xx := (xx + 1) Mod NumarulConexiunlor;
  51.  End;
  52. End;
  53.  



Acum problema este urmatoarea : in loop-ul de la server in care se repeta apelarea lui recv, cpu este ocupat 100% si celelalte threaduri din program se executa foarte incet.(serverul e non-blocking deoarece in caz ca un client nu a trimis nimic, sa treaca sa verifice urmatoarea conexiune. clientul nu are decat o singura conexiune si de aceea am pus blocking). Daca recv nu a primit nici un mesaj de la client, returneaza -1 si WSAGetLastError() returneasa WSAWOULDBLOCK = nu e nici un mesaj primit, incearca mai incolo.

Exista o metoda de a crea partea de conectivitate a serverului astfel incat sa ocupe cat mai putina memorie si sa fie cat mai rapid (fara ca cpu sa fie 100% ocupat)? Am cautat si nu am gasit raspunsuri exacte. Unele solutii ziceau de folosirea functiei Select(), dar nu stiu cum sa o folosesc (nu inteleg nimic din documentatia functiei).

Nu doresc sa folosesc metoda "un thread pe conexiune" cu socket TCP Blocking deoarece am auzit ca limita e 2000 thread-uri pe sistem deoarece fiecare thread ocupa 1MB memorie, si pe 32 biti limita de memorie RAM pe care o poate folosi un program este de 2GB (vreau sa pot sa mentin mai mult de 5000 conexiuni in acelasi timp).

PS. Nu vreau un design gen Clientul trimite, serverul executa si apoi ii trimite rezultatele inapoi la client, ci clientul trimite date, serverul le foloseste, si trimite cand vrea anumite informatii catre toti sau o parte din cliente.
0,0p / 0 votes
User avatar
xlad
Bit
 
Joined: 06 Jan 2010
Status: 2

Re: Problema Winsock2

Postby DarkByte » 06 Jan 2010, 18:19

First (nu am timp sa ma uit peste cod chiar acum), stiu ca era o problema cu blocking si nonblocking. Mai exact, clientul si serverul trebuiau setate la fel. Incearca asta, de curiozitate.

Second, ti-ar trebui, probabil, un timer care sa-ti faca o asteptare. Tu cauti continuu dupa mesaje. O asteptare de 20ms nu cred ca ar strica. O sa revin cu cod pentru asta.

Bafta
0,0p / 0 votes
User avatar
DarkByte
11011011
 
Joined: 29 Dec 2009
Status: 136

Re: Problema Winsock2

Postby xlad » 06 Jan 2010, 18:35

Nu e nici o diferenta daca ambele sunt non-blocking(si clientul la utilizator va folosi 100% cpu -- am testat mai demult cu un prieten, 2 calculatoare diferite). Inainte asa le-am setat dar daca pun clientul pe blocking se misca mai bine calculatorul :)).

Si asteptarea nu cred ca ar fi un lucru bun >.>. Sa zicem ca am 1000 jucatori (un server putin populat dupa parerea mea) si 20ms * 1000 = 20 secunde. Asta inseamna ca serverul citeste ce face un jucator odata la 20 secunde. Pentru un MMORPG nu e un lucru bun.
0,0p / 0 votes
User avatar
xlad
Bit
 
Joined: 06 Jan 2010
Status: 2

Re: Problema Winsock2

Postby DarkByte » 06 Jan 2010, 19:19

xlad wrote:Si asteptarea nu cred ca ar fi un lucru bun >.>. Sa zicem ca am 1000 jucatori (un server putin populat dupa parerea mea) si 20ms * 1000 = 20 secunde. Asta inseamna ca serverul citeste ce face un jucator odata la 20 secunde. Pentru un MMORPG nu e un lucru bun.

Nu asta am zis :) Am zis sa astepti 20ms inainte de a citi toate datele de la toti jucatorii :)
0,0p / 0 votes
User avatar
DarkByte
11011011
 
Joined: 29 Dec 2009
Status: 136

Re: Problema Winsock2

Postby xlad » 06 Jan 2010, 19:33

S-ar putea sa mearga asa.. o sa incerc mai incolo (sper sa nu dureze mult citirea a mii de sockets).

Alta intrebare legat de asta.. ce prioritate ar trebuie sa aiba un thread de citire din winsock2? Highest (asa cum am vazut) ?
0,0p / 0 votes
User avatar
xlad
Bit
 
Joined: 06 Jan 2010
Status: 2

Re: Problema Winsock2

Postby DarkByte » 06 Jan 2010, 20:00

N-as stii sa-ti raspund la intrebarea asta. M-am "jucat" foarte putin cu winsock si doar cu prioritate normala.

Aici e un exemplu de folosire a unui waitable timer.

Bafta
0,0p / 0 votes
User avatar
DarkByte
11011011
 
Joined: 29 Dec 2009
Status: 136

Re: Problema Winsock2

Postby morpheus » 06 Jan 2010, 21:42

Ar trebui sa folosesti "overlapped IO". Eventual si "completion ports", daca vrei sa tratezi un numar foarte mare de clienti.
Totusi, nu stiu cum se face asta in Delphi (nici daca se poate), nu cunosc limbajul. Incearca sa cauti documentatie in Delphi, pentru subiectele de mai sus.
0,0p / 0 votes
Curiosity killed the cat
User avatar
morpheus
Word
 
Joined: 30 Dec 2009
Location: Bucharest, Romania
Status: 54.84

Re: Problema Winsock2

Postby v0id » 07 Jan 2010, 01:36

Nu ma pot lauda cu o vasta experienta in lucrul cu socket-uri, dar... N-ar fi mai simplu sa folosesti un wrapper gata facut? Ai la dispozitie Indy-ul ce vine cu Delphi, mai este Synapse care e tot free (http://www.synapse.ararat.cz/doku.php) si multe altele. Eu sunt de principiul ca nu trebuie reinventata roata :)

Anyway, la cum arata codul tau acum, thread-ul de la server iti va tine tot timpul CPU-ul in 100%. Si daca ii pui prioritate mai scazuta acelui thread, sa zicem tpIdle, trebuie sa tii cont ca prioritatea respectiva este relativa doar la thread-ul principal al aplicatiei server. La ce ai tu acolo momentan, as propune tot o solutie cu wait, dar clar nu e optima.

Daca vrei sa ramaii in continuare pe ideea de a nu folosi chestii "third party" (Indy, Synapse, etc.) pentru lucrul cu socket-uri, cand prind un pic de timp liber o sa incerc sa gasesc o metoda mai eficienta de procesare a mesajelor pe partea de server.
0,0p / 0 votes
A good coder is never on holiday - he may be working on a different machine, that's about as far as it gets.
User avatar
v0id
Word
 
Joined: 05 Jan 2010
Location: 127.0.0.1
Status: 39.5

Re: Problema Winsock2

Postby xlad » 07 Jan 2010, 21:06

As folosi orice metoda... dar sa fie rapida si sa pot sa fac urmatoarele lucruri :
- sa primes un mesaj de la client si sa execut mesajul/sa-l adaug undeva in memorie si sa nu ii dau un raspuns direct clientului
- sa pot sa trimit mesaje oricarui client conectat, fara insa sa fie un raspuns direct al unui mesaj trimis de acesta. (de exemplu odata la fiecare 6 ore sa trimita un anumit mesaj la toti/o parte din conexiuni, sau odata la 10 secunde sa trimita anumite mesaje doar la 5-10 conexiuni)
- sa pot sa deconectez (din lista conexiunilor) orice client in care datele trimise sunt fie incorecte, sau a cazut netul utilizatorului, i-a fost scos firul, sau imediat cand au trecut 16 secunde de la ultimul mesaj (clientul trimite un keep-alive odata la 5 secunde... daca nu s-au trimis 3 keep-alive .. sa zicem dupa 16 secunde sa scoata din server imediat acea conexiune)
- sa nu ocupe multa memorie
- sa NU fie thread/connection, de preferabil sa ruleze intr-un singur thread
- sa nu fie incet (sau cpu sa fie ocupat 100%)
- un numar de 10.000+ de conexiuni, fara sa aiba performante reduse (vor avea loc un numar foarte mare de trimiteri de date de/catre cliente, si care sa fie procesate cat mai rapid)

Eu am cautat in help despre Indy, dar nu sunt exemple concrete cum sa folosesc TCPServer si TCPClient, si dupa cum am vazut, modul lor de functionare e cerere-raspuns (nu vreau sa fie asa).
La Synapse nu m-am uitat mult dar vad ca suporta doar standardele normale (HTML,FTP etc), eu vreau sa fie trimise datele prin TCP, si sa fie intr-un format pe care il pot intelege doar clientul si serverul.

Mai pe scurt : asa cum jocurile online(WoW, Lineage2 etc) au partea de transmisie a datelor de la/catre cliente.

Cel mai mult ma ajuta un exemplu concret (in Delphi). Nu ar trebui sa fie mai lung de 500-1000 randuri toata partea asta de "networking" a serverului.

PS: nu am incercat (inca) metoda cu sleep(20)/WaitableTimer dupa citirea tuturor conexiunilor, propusa de DarkByte.

PS2 : am cautat despre overlapped I/O ca ar fi potrivit, dar nu vad nici un exemplu nicaieri si unde am gasit ceva scris despre acest lucru, au mentionat faptul ca programul NU stie CARE din cliente a trimis mesajul (trebuie neaparat ca serverul sa stie exact cine a trimis mesajul)
0,0p / 0 votes
User avatar
xlad
Bit
 
Joined: 06 Jan 2010
Status: 2

Re: Problema Winsock2

Postby morpheus » 08 Jan 2010, 20:42

xlad wrote:PS2 : am cautat despre overlapped I/O ca ar fi potrivit, dar nu vad nici un exemplu nicaieri

Sunt exemple in MSDN (in C/C++).

xlad wrote:si unde am gasit ceva scris despre acest lucru, au mentionat faptul ca programul NU stie CARE din cliente a trimis mesajul (trebuie neaparat ca serverul sa stie exact cine a trimis mesajul)


Stii. Folosesti overlapped I/O si IOCP, cum spuneam.

Folosind:
HANDLE WINAPI CreateIoCompletionPort(
__in HANDLE FileHandle,
__in_opt HANDLE ExistingCompletionPort,
__in ULONG_PTR CompletionKey,
__in DWORD NumberOfConcurrentThreads
);

asociezi handle-ul unui socket cu handle-ul obiectului completion port (IOCP)
Poti asocia mai multe handle-uri la un singur obiect IOCP.
Ce operatie anume de I/O s-a efectuat, handle-ul socket-ului insusi si alte date de context ti le poti pasa printr-o structura in membrul CompletionKey, unica per handle.

xlad wrote:- sa NU fie thread/connection

OK.

xlad wrote:de preferabil sa ruleze intr-un singur thread

Depinzand de aplicatia ta, ar putea fi o idee buna sa folosesti un pool de thread-uri worker.
0,0p / 0 votes
Curiosity killed the cat
User avatar
morpheus
Word
 
Joined: 30 Dec 2009
Location: Bucharest, Romania
Status: 54.84

Re: Problema Winsock2

Postby v0id » 10 Jan 2010, 01:36

Exista niste clase in Delphi care te-ar putea ajuta - TServerSocket si TClientSocket. Am facut niste aplicatii "demo" ca sa iti faci o idee despre cum functioneaza acestea. Serverul nu creeaza un thread pentru fiecare conexiune client, are consum de memorie mic si nici nu tine CPU-ul in 100% :) De asemenea, poti trimite mesaje dinspre server catre clienti oricand doresti, nu este necesar ca mesajul sa porneasca de la server ca raspuns la un mesaj primit din partea unui client. Chiar daca in final nu vei alege sa folosesti aceste clase, macar ai un punct de plecare. Din diverse motive, n-am putut sa testez cu mai mult de 2000 de clienti. Daca poti sa testezi tu cu mai multi, te rog revino cu rezultatele testelor.

In arhiva vei gasi trei proiecte: Server, Client si Controller.

1. Server - o aplicatie cu GUI care imediat dupa lansare accepta conexiuni pe portul 1234. Intr-un Memo ti se vor afisa numarul de conexiuni active la fiecare conectare/deconectare a unui client, iar intr-un alt Memo se vor scrie mesajele trimise de catre clienti. Ai posibilitatea de a transmite tuturor clientilor un mesaj la click pe un buton - o sa vezi tu care ca e mare si fontul e Bold :)

2. Client - aplicatie fara GUI, care dupa lansare citeste din fisierul "ServerIP.txt" IP-ul server-ului si incearca sa se conecteze la acel IP pe portul 1234. Daca fisierul "ServerIP.txt" lipseste sau este gol, clientul va incerca sa se conecteze la IP-ul 127.0.0.1. Daca clientul nu se poate conecta (IP incorect, server oprit, etc.), ar trebui sa primesti un "Asynchronous socket error". Dupa conectare, clientul va trimite un mesaj serverului, "spunandu-i" acestuia ce process identifier (PID) are procesul clientului. Cand clientul primeste un mesaj de la server, genereaza un fisier TXT al carui nume contine PID-ul clientului, in care va fi scris mesajul venit din partea serverului (PID-ul aplicatiei server).

3. Controller - destinat lansarii cu usurinta a unui server, al unui numar (mare) de clienti dorit de tine si in cele din urma inchiderii acestor procese (Nu cred ca ai vrea sa stai toata noaptea sa dai End Process daca ai lansat cateva sute sau mii de clienti :). Nici un restart dupa fiecare test mai "serios" nu cred ca suna prea bine...). Modul de lucru cu aceasta aplicatie ar fi urmatorul: "Launch Server", introduci un numar de clienti care sa fie lansati in executie (Atentie la memoria disponibila a sistemului tau! Incepe cu un numar mai mic de genul 50-100), "Launch Clients". Cand ai terminat treaba da un "Kill All" pentru ca aplicatia sa inchida fortat toate procesele client si server. Da, am spus sa "inchida fortat"... Mi-a fost prea lene sa trimit WM_CLOSE :)

DISCLAIMER: Precizez ca error handling-ul este minimal si anumite parti neesentiale ale acestor aplicatii sunt facute la un nivel mai putin profesional pentru ca n-am avut timp si chef sa-mi pierd vremea cu ele :) Tot din acest motiv nu am facut enable/disable la unele controale cand poate ar fi fost necesar pentru a preveni evenimente ce pot cauza functionarea incorecta a aplicatiilor.

Poti sa iei arhiva ce contine aceste proiecte de aici. Enjoy!
2p / 1 votes
A good coder is never on holiday - he may be working on a different machine, that's about as far as it gets.
User avatar
v0id
Word
 
Joined: 05 Jan 2010
Location: 127.0.0.1
Status: 39.5


Return to Pascal / Delphi

Who is online

Users browsing this forum: No registered users and 0 guests