Autor Wątek: SFML 2.0 oczekiwanie na pakiet  (Przeczytany 1954 razy)

Offline Speedhero2

  • Użytkownik

# Luty 01, 2016, 17:34:17
   
Witam.
Potrzebuje pomocy - zacząłem pisać już czat konsolowy i potrzebuje pomocy odnośnie wyczekiwania pakietu.

To nie będzie czat 2 osobowy a ilość osób nie będzie ograniczona.
Jeżeli w serwerze zdeklaruje odebranie pakietu zwyczajnie to będzie crashowało serwer jeżeli przy wykonaniu pętli użytkownik nie wyśle wiadomości.
Dlatego tutaj pojawia się pytanie - Co zrobić aby serwer czekał na pakiet, ale go nie wymagał od razu - tak, że gdy któryś z klientów wyślę pakiet np. sf::Packet wiadomość to serwer dopiero wtedy zacznie działanie z nim związane.

Mam nadzieje, że zrozumieliście o co mi chodzi i posłużycie pomocą.

Offline Mr. Spam

  • Miłośnik przetworów mięsnych

Offline CheshireCat

  • Użytkownik

# Luty 01, 2016, 18:09:28
Jeżeli w serwerze zdeklaruje odebranie pakietu zwyczajnie to będzie crashowało serwer jeżeli przy wykonaniu pętli użytkownik nie wyśle wiadomości.

A dlaczego niby będzie to crashowało serwer?

Normalnie, niech sobie oddzielny wątek serwera słucha i czeka na wiadomości. Możesz sobie zrobić jakąś kolekcję podłączonych klientów, aktualizowaną zawsze gdy się ktoś podłączy lub rozłączy. Możesz zrobić to na przykład na jednym dodatkowym wątku (iterującym w kółko po kolekcji) lub oddzielny wątek per klient. Można to zrobić na wiele sposobów.
« Ostatnia zmiana: Luty 01, 2016, 18:11:58 wysłana przez CheshireCat »

Offline Xion

  • Moderator
    • xion.log

  • +2
# Luty 01, 2016, 18:34:02
Można też używać select/epoll/innego rozwiązania typu event loop i w ogóle nie mieć wątków.

Offline hashedone

  • Użytkownik

# Luty 02, 2016, 10:43:20
Jeśli bardzo chcesz użyć smfl, to w momencie utworzenia socketa zawołaj na nim metodę setBlocking z argumentem false (mySoc.setBlocking(false)) - wówczas wszystkie funkcje zamiast czekać na ukończenie danych, będą zwracały od razu - w przypadku braku danych, lub braku możliwości wysłania w danym momencie, funkcja zwróci kod błędu. Obsługujesz więc w pętli czasu rzeczywistego wszystkie sockety po kolei i jeśli są dane, to je obsługujesz. Taki kod na szybko:
std::vector<sf::TcpSocket> clients;
sf::TcpListener listener;
listener.setBlocking(false);
listener.listen(4321);

while(true)
{
  {
    TcpSocket s;
    while(listener.accept(s))
    {
      s.setBlocking(false);
      clients.push_back(s);
    }
  }

  {
    sf::Packet pack;
    for(auto & s: clients)
    {
      if(s.receive(pack))
      {
         handleDataFromClient(pack);
      }
    }
  }
}
Ten kod zakłada, że:
1. Sockety można normalnie kopiować (nie wiem czy w sfml można, jeśli nie, musisz jakoś inaczej je trzymać)
2. Nie chcesz rozróżniać klientów - jeśli chcesz, musisz trzymać je razem z klientami (ale liczę że sobie z tym poradzisz)
3. sf::Status rzutuje się na bool wg. zasady, że jeśli odpowiedź jest OK, rzutuje się na true, jeśli nie udało się wykonać funkcji, rzutuje się na false.
Trzeba na pewno dodać tu lepszą obsługę błędów niż tylko if na Status, bo nawet jeśli rzutuje się zgodnie z tym co jest w (3), to trzeba się upewnić czemu się nie udało odczytać - to może być brak czekającej odpowiedzi, ale równie dobrze zerwanie połączenia na przykład.

Odradzam to co proponuje CheshireCat - jeśli będziesz każdego klienta robił na osobnym wątku, natkniesz się na problemy. Po pierwsze wydajność - komputery nie są przystosowane do wydajnej pracy nad większą ilością wątków niż mają rdzeni w procesorze (a zakładam, że chcesz mieć np. 10 klientów - potrzebujesz minimum 11 wątków). Nawet, jeśli założyć, że to są tzw lekkie wątki i ten problem jest jakoś rozwiązany, to natkniesz się na problemy z synchronizacją - będziesz musiał w każdej obsłudze komunikatu od klienta wysłać komunikat do wszystkich innych klientów - a więc do każdego socketa w najprostszym przypadku do każdego socketa będzie dostęp z każdego wątku, a więc będziesz musiał je blokować mutexami. W dodatku jeden wątek będzie cały czas z socketa czytał, więc otrzyma odpowiedź tylko wtedy, kiedy na chwilę odpowiedni klient coś napisze i wątek zwolni mutex. Można oczywiście napisać to nieblokująco (bez mutexów w ogóle), ale jest to trudne i sprowadzi się w sumie do rozwiązania które napisałem wyżej - gniazda i tak będą musiały być nieblokujące, a jedyną różnicą będzie to, że każdy socket będzie miał swój wątek, co z kolei jest niepotrzebne i nonsensowne - chyba, że chcesz to zrobić żeby poćwiczyć znajomość algorytmów wielowątkowych nieblokujących, ale to wtedy nie pisał byś tego posta.

Offline Xion

  • Moderator
    • xion.log

# Luty 02, 2016, 18:16:39
Cytuj
Odradzam to co proponuje CheshireCat - jeśli będziesz każdego klienta robił na osobnym wątku, natkniesz się na problemy. Po pierwsze wydajność (...)
WTF?! Wspominasz o rzekomej niskiej wydajności wątków -- zapewne kierując się często powtarzaną mantrą że "context switche są kosztowne" -- a chwilę wcześniej proponujesz rozwiązanie które bez przerwy wali w system wywołaniami recv(), gdzie każdy jest potencjalnym context switchem. A nie wspominam nawet o koszcie samego wykonywania tylu calli.

Cytuj
Nawet, jeśli założyć, że to są tzw lekkie wątki i ten problem jest jakoś rozwiązany, to natkniesz się na problemy z synchronizacją - będziesz musiał w każdej obsłudze komunikatu od klienta wysłać komunikat do wszystkich innych klientów - a więc do każdego socketa w najprostszym przypadku do każdego socketa będzie dostęp z każdego wątku, a więc będziesz musiał je blokować mutexami.
Nawet jeśli jakaś synchronizacja jest faktycznie potrzebna (nie to, żeby kernel nie potrafił sobie uszeregować równoległych wywołań write() do tego samego socketa/fd), to czemu wybierać jej najgorszy wariant? Blokująca kolejka update'ów dla każdego socketa i niech wątki klientów wiszą naraz na niej i na sockecie (pod Windows np. służy do tego funkcja WaitForMultipleObjects()).

Cytuj
W dodatku jeden wątek będzie cały czas z socketa czytał, więc otrzyma odpowiedź tylko wtedy, kiedy na chwilę odpowiedni klient coś napisze i wątek zwolni mutex.
Jak dużo problemów, które kernel świetnie rozwiązuje sam, polecisz "rozwiązywać" w userspace w tak koszmarny sposób?

Cytuj
Można oczywiście napisać to nieblokująco (...)
Z kolei idei tego rozwiązania już zupełnie nie rozumiem. Jedyny przypadek gdzie async + wątki mogą mieć sens to skalowanie pętli select/epoll/etc. na wszystkie rdzenie, ale to raczej przekombinowane gdy współczesne współbieżne runtime'y (np. Quasar w Javie, goroutiny w Go) potrafią automatycznie i przezroczyście dzielić swoje mikrowątki między rdzenie.

Offline hashedone

  • Użytkownik

# Luty 03, 2016, 10:44:53
Wszystko co pisałem zakłada, że OP z jakiś powodów bardzo chce użyć biblioteki SFML - stąd nie ma dostępu choćby do funkcji typu WaitForMultipleObjects. Sam rozwiązał bym to zupełnie inaczej, użył bym po prostu asio (tego samego co jest w boost::asio, tyle że jeśli bym nie chciał całego boosta doklejać, użył bym wersji stand-alone biblioteki). Cała moja wypowiedź tyczyła się najprostszego rozwiązania, które będzie działać - na pewno nie jest to rozwiązanie najlepsze (i sam bym go nie zastosował), ale moim zdaniem op nie jest specjalnie doświadczony. Chociaż po przeczytaniu Twojego posta zgoda z jednym - nie ma potrzeby ciągłego wołania recv - nie zauważyłem, że SFML ma coś analogicznego do WaitMultipleObjects, tak więc, równie dobrze można zrobić to lepiej:
std::vector<sf::TcpSocket> clients;
sf::TcpListener listener;
sf::SocketSelector selector;
listener.listen(4321);
selector.add(listener);

while(true)
{
  selector.wait();
  if(selector.isReady(listener)
  {
    TcpSocket s;
    listener.accept(s);
    clients.push_back(s);
    selector.add(s);
  }
  else
  {
    sf::Packet pack;
    for(auto & s: clients)
    {
      if(selector.isReady(s))
      {
         s.receive(pack)
         handleDataFromClient(pack);
      }
    }
  }
}
Kwestia wydajności wątków - koszt context switcha to jedna bardzo ważna sprawa (i zwrócenie uwagi na ciągłe krzyczenie recv było faktycznie na miejscu, za bardzo chciałem to uprościć), ale druga to koszt synchronizacji. Jedna rzecz to kwestia tego, czy trzeba synchronizować dostęp do socketa - szczerze mówiąc to była moja intuicja, nigdy się nie zastanawiałem nad tym co ma kernel tu do powiedzienia - ja akurat zawsze używać do tego Asio i w ogóle nie tworzę żadnych wątków więc tego problemu nie spotykam, ale w normalnej aplikacji prawie zawsze trzeba synchronizaować zasoby (to co przyjdzie z sieci z reguły ma jednak jakiś wpływ na stan aplikacji), jeśli op nauczy się, że do obsługi sieci trzeba 99999999999999 wątków, to potem go to zabije przy prostej grze sieciowej. W tej konkretnej aplikacji ani blokowanie ani wydajność nie ma znaczenia, znaczenie ma nabywanie fatalnych nawyków.

Offline Speedhero2

  • Użytkownik

# Luty 16, 2016, 19:55:24
Nie wiem w czym leży problem. Wrzucam, więc link to programu testowego w którym w mainie odebranie packietu test1 powoduje scrashowanie aplikacji.

Serwer łączy się z klientami, przypisuję ich i wszystko działa tylko odbieranie pakietów poza funkcją w wątku login powoduje natychmiastowy crash. Co robię źle i jaka jest tego przyczyna? Mógłby ktoś zerknąć?

http://codepad.org/YKStVVfp