Pieter Opublikowano 23 Lutego 2011 Udostępnij Opublikowano 23 Lutego 2011 Myślałem nad pomocą w robieniu serwerka w C++ dla Almory, miałem pomarańczowe światło od Gnyska i zrobiłem silnik sieciowy oparty na threadach + konsole + liste klientów w C++ dla linuxa. Po paru poprawkach łatwo go odpalić także pod Windows. Silnik korzysta z buffora 39dll dzięki czemu możecie przesyłać dane pomiędzy waszymi grami a tym serwerkiem. Projekt zrobiony w Code::Blocks, nie proście o nic bo nie mam zamiaru tego poprawiać/przepisywać itd... wszystkie pliki potrzebne do dalszego kodzenia są udostępnione w paczce. powtarzam... kod jest pod LINUX'a by mógł być uruchamiany na dedykach albo vps'ach. Róbcie co chcecie, tylko nie podpisujcie się pod moim kodem... Troszkę szacunku do czyjejś pracy. Creditsy mile widziane. https://gmclan.org/up44_3_almora_serwer.html edit: dla tych co nie wiedzą, serwery pod linux najbardziej są przydatne dla gier MMO lub serwerow które mają być otwarte 24/7. Odnośnik do komentarza Udostępnij na innych stronach Więcej opcji udostępniania...
Administratorzy gnysek Opublikowano 24 Lutego 2011 Administratorzy Udostępnij Opublikowano 24 Lutego 2011 Dzisiaj miałem chwilę aby to sprawdzić i ta aplikacja mi jakieś totalnie inne numery pakietów pokazuje, np. odpalając Almorę 0.7.6 wysłało pakiet który odpowiada za branie itemów z ziemii, a w 0.8 pakiet którego jeszcze nie używam. Sprawdzałeś to, czy tylko u mnie jest coś nie tak? Btw. jeden plik był nazwany w CamelCase i się nie chciało kompilować przez to. Odnośnik do komentarza Udostępnij na innych stronach Więcej opcji udostępniania...
Pieter Opublikowano 24 Lutego 2011 Autor Udostępnij Opublikowano 24 Lutego 2011 to dlatego, że nie miałem czasu przetestować buffora od 39dll, ale sama zasada zostaje... wystarczy znaleźć błąd... Odnośnik do komentarza Udostępnij na innych stronach Więcej opcji udostępniania...
Kofel Opublikowano 24 Lutego 2011 Udostępnij Opublikowano 24 Lutego 2011 wow, dobry programmer co wydaje bibliotekę bez przetestowania :D Odnośnik do komentarza Udostępnij na innych stronach Więcej opcji udostępniania...
Pieter Opublikowano 24 Lutego 2011 Autor Udostępnij Opublikowano 24 Lutego 2011 wow, dobry programmer co wydaje bibliotekę bez przetestowania :D bo zrobiłem to na kopi mojego serwerka który pisze dla VoIP, tamten już jest daleeeeeeeeko przed tym co tu dałem, poza wstawieniem buffora od 39dll... Już mówiłem, że silnik sieciowy sam w sobie działa, trzeba tylko poprawić czytanie pakietów (ustawić kursor na odpowiedniej pozycji) ;) edit: jak ktoś mi wytłumaczy dlaczego bufforki są odwrócone, to pogadamy ;) Odnośnik do komentarza Udostępnij na innych stronach Więcej opcji udostępniania...
Pieter Opublikowano 24 Lutego 2011 Autor Udostępnij Opublikowano 24 Lutego 2011 dobra... po prostu zamiast czytać pakietID to czytał długość pakietu... po prostu pod Pakiet.addBuffer(buffer, length); trzeba dodać Pakiet.readpos = 2; całość: Pakiet.addBuffer(buffer, length); Pakiet.readpos = 2; (lol) https://gmclan.org/up44_3_almora_serwer.html Odnośnik do komentarza Udostępnij na innych stronach Więcej opcji udostępniania...
Snake Opublikowano 25 Lutego 2011 Udostępnij Opublikowano 25 Lutego 2011 edit: jak ktoś mi wytłumaczy dlaczego bufforki są odwrócone, to pogadamy ;) Kolejność obliczania wartości dla parametrów przekazywanych do funkcji. W standardzie C++ nie ma ustalonej żadnej standardowej kolejności -- kompilatory używają takiej, która w danym momencie jest najbardziej optymalna. W tym przypadku przy tym drugim "printf" metody readbyte były wywoływane od prawej do lewej, dlatego bufory wyglądały na odwrócone. EDIT: Jeśli serwer ma być skalowalny to model I/O sieci, który użyłeś w swoim silniku się raczej do tego nie nadaje. Może lepiej obsługiwać sockety metodą z "select()", + ew. z podziałem na niewielką ilość wątków? Też jeden z prostszych sposobów napisania serwera i przy tym wydajniejszy. Chociaż nie programowałem jeszcze socketów pod linuxem, więc nie jestem pewien jak by się to sprawowało. Poza tym zdaje się, że w ogóle nie napisałeś obsługi pakietów i wczytujesz dane do bufora jak leci, po czym natychmiastowo je przetwarzasz, co będzie się kończyć błędem jak wiadomość od klienta nie dotrze w jednym kawałku (co zdarza się praktycznie cały czas, jak nie testujesz na localhoście czy jak pakiet będzie większy niż te parę bajtów). Mogę być w błędzie, ale przeglądając klasę CBuffer na szybko niczego nie zauważyłem w stylu dzielenia bufora na osobne pakiety itp. :P Odnośnik do komentarza Udostępnij na innych stronach Więcej opcji udostępniania...
Administratorzy gnysek Opublikowano 27 Lutego 2011 Administratorzy Udostępnij Opublikowano 27 Lutego 2011 O, i co powiesz Pieter na temat tego co Snake dał w "edycie" ? Odnośnik do komentarza Udostępnij na innych stronach Więcej opcji udostępniania...
Pieter Opublikowano 27 Lutego 2011 Autor Udostępnij Opublikowano 27 Lutego 2011 O, i co powiesz Pieter na temat tego co Snake dał w "edycie" ? to, że Snake ma racje i ja dobrze o tym wiem, więc nie musisz docinać ;) multi-threading na skale masową nie jest zbyt wydajny... są testy, które jasno mówią, że przy 300+ klientach są znacznie bardziej zamulone niż 2/3 threadów obsługujących wszystkich. Co prawda, większa ilość tych threadów dobrze sprawuje się przy MMO, ja jednak nie projektowałem tego dla masowych połączeń ;) Musimy tu zrozumieć, że projektowanie na czytaniu a select to zupełnie inna struktura (nie zawsze lepsza) aplikacji. Co do dzielenia pakietów, jestem tego świadom... ale nie spodziewam się by ktoś wysyłał 16000 bajtów za 1 razem. Reszta należy do "reszty", to bardzo uproszczony "silniczek". Odnośnik do komentarza Udostępnij na innych stronach Więcej opcji udostępniania...
Administratorzy gnysek Opublikowano 27 Lutego 2011 Administratorzy Udostępnij Opublikowano 27 Lutego 2011 Więc tak, tym razem mnie zalogowało, nie mniej moich wątpliwości swoim postem nie rozwiałeś zwłaszcza w przypadku większych pakietów. Odnośnik do komentarza Udostępnij na innych stronach Więcej opcji udostępniania...
Pieter Opublikowano 27 Lutego 2011 Autor Udostępnij Opublikowano 27 Lutego 2011 omg... 2 pierwsze bajty w Cbuffor, to długość pakietu jaki został wysłany (takie zabezpieczenie), wystarczy po 1 recv sprawdzić czy ilość bajtów odebranych jest zgodna z wartością U16 (2 pierwsze bajty) i jeżeli nie to zrobić pętle recv aż będzie zgodne... coś w deseń: GML // thread PARSER wlasciwy void TCPThread::ExecuteParser() { int len, i, j, trueLength, readedLength; char buffer[MAX_CLIENT_BUFFER], temp[MAX_CLIENT_BUFFER]; printf("[TCPThread] Parser thread executed!\n"); memset((char *)buffer, 0, MAX_CLIENT_BUFFER); while (1) { for ( i = 0; i < MAX_CLIENTS; i++ ) { if (Clients->Client.Sock != 0) { len = recv(Clients->Client.Sock, buffer, MAX_CLIENT_BUFFER, 0); if (len == 0) // klient sie rozlaczyl { printf("[TCPThread] Client %s disconnected (by peer)\n", Clients->Client.IP.c_str()); pthread_mutex_lock(&new_connection_mutex); // blokujemy dostep do danych Clients->Delete(i); // usuwamy klienta ktory sie rozlaczyl pthread_mutex_unlock(&new_connection_mutex); // inne thready moga juz korzystac z danych } else if (len < 0) // blad polaczenia czy cos. Klient traci polaczenie. { if(errno != EAGAIN) // to nie jest blad braku odbioru { printf("[TCPThread] Client %s disconnected (by server & ERRNO: %d)\n", Clients->Client.IP.c_str(), errno); pthread_mutex_lock(&new_connection_mutex); // blokujemy dostep do danych Clients->Delete(i); // usuwamy klienta ktory sie rozlaczyl pthread_mutex_unlock(&new_connection_mutex); // inne thready moga juz korzystac z danych } } else // przyszedl pakiet i mozemy przetwarzac! { trueLength = (buffer[0] | (buffer[1] << 8)); // rzeczywista długość (2 pierwsze bajty buffora) readedLength = len; // trzeba ustalic wartosc ile odczytalismy while (readedLength < trueLength) { memset((char *)temp, 0, MAX_CLIENT_BUFFER); // resetujemy tymczasowy buffor len = recv(Clients->Client.Sock, temp, MAX_CLIENT_BUFFER, 0); // staramy sie czytac reszte for (j = 0; j < len; j++) { buffer[readedLength + j] = temp[j]; // mozna pewnie to szybciej zrobic za pomoca memcpy ale nie chce mi sie zaglebiac } readedLength += len; // dodajemy to co odczytalismy } ParsePacket(i, buffer, len); // przetwarzamy pakiet } memset((char *)buffer, 0, MAX_CLIENT_BUFFER); // resetujemy buffor } } } } kodu nie sprawdzałem, ale w teorii powinien dać pozytywny skutek ;) co ty na to sz... (rym dopowiedz sobie sam szerloku ;) Odnośnik do komentarza Udostępnij na innych stronach Więcej opcji udostępniania...
Snake Opublikowano 2 Marca 2011 Udostępnij Opublikowano 2 Marca 2011 Ten "uproszczony silniczek" to Twoja pierwsza aplikacja sieciowa z wykorzystaniem socketów? Co do dzielenia pakietów, jestem tego świadom... ale nie spodziewam się by ktoś wysyłał 16000 bajtów za 1 razem Małe wiadomości też mogą nie dojść w jednym kawałku. Co więcej, jeśli recv nie wczyta tych "2 pierwszych bajtów w CBuffor" a 1 to bardzo źle się skończy. Ponadto, gdy klient wyśle kilka wiadomości kolejno po sobie w małym odstępie czasu to wszystkie na raz mogą zostać wczytane przy jednym wywołaniu recv na serwerze, co poskutkuje porzuceniem wszystkiego, co znajduje się za pierwszą wiadomością w buforze. Podobnie jest z wysyłaniem danych -- wysyłasz nieblokującym gniazdem bufor i nie sprawdzasz nawet czy wszystko zostało wysłane. Co do tego kodu powyżej, to wychodzi podobnie co przy metodzie ze sprawdzaniem rozmiaru otrzymanych danych przy blokujących gniazdach -- jak zaczniesz wczytywać te dane w drugiej pętli to przymuli resztę klientów i tak po kolei przy każdym obsługiwanym połączeniu (w dodatku jak ktoś dostanie "laga" albo nastąpi jakiś błąd połączenia to zamuli jeszcze bardziej). Odnośnik do komentarza Udostępnij na innych stronach Więcej opcji udostępniania...
Pieter Opublikowano 2 Marca 2011 Autor Udostępnij Opublikowano 2 Marca 2011 Ten "uproszczony silniczek" to Twoja pierwsza aplikacja sieciowa z wykorzystaniem socketów? C++ + linux YES. Małe wiadomości też mogą nie dojść w jednym kawałku. Co więcej, jeśli recv nie wczyta tych "2 pierwszych bajtów w CBuffor" a 1 to bardzo źle się skończy. Ponadto, gdy klient wyśle kilka wiadomości kolejno po sobie w małym odstępie czasu to wszystkie na raz mogą zostać wczytane przy jednym wywołaniu recv na serwerze, co poskutkuje porzuceniem wszystkiego, co znajduje się za pierwszą wiadomością w buforze. Podobnie jest z wysyłaniem danych -- wysyłasz nieblokującym gniazdem bufor i nie sprawdzasz nawet czy wszystko zostało wysłane. Co do tego kodu powyżej, to wychodzi podobnie co przy metodzie ze sprawdzaniem rozmiaru otrzymanych danych przy blokujących gniazdach -- jak zaczniesz wczytywać te dane w drugiej pętli to przymuli resztę klientów i tak po kolei przy każdym obsługiwanym połączeniu (w dodatku jak ktoś dostanie "laga" albo nastąpi jakiś błąd połączenia to zamuli jeszcze bardziej). klient nie może nie wczytać pierwszych 2 lini buffora ponieważ to nie jest UDP a TCP. TCP jest liniowy, pakiety nie przychodzą pomieszane ORAZ mają swój protokołowy hash który zapewnia że pakiet dotrze w całości (choć może być niby podzielony na części) to jednak dotrze "cały". Dopisanie procedury, która sprawdziła by czy pakiety nie są połączone jest równie łatwe co to co napisałem wyżej. Jeżeli nie podoba się silnik to go nie używaj. Sprawdź sobie źródła 39dll i zobacz, że nie ma tam zabezpieczeń. Proste... konstruktywna krytyka swoją drogą ale narzekać ciągle każdy może :) edit: poza tym można ustalić wartość ILE bitów ma być odebranych np zamiast recv(Clients->Client[i].Sock, temp, MAX_CLIENT_BUFFER, 0); można recv(Clients->Client[i].Sock, temp, ILOSC_BYTE_POZOSTALYCH_W_PAKIECIE, 0); Odnośnik do komentarza Udostępnij na innych stronach Więcej opcji udostępniania...
Snake Opublikowano 2 Marca 2011 Udostępnij Opublikowano 2 Marca 2011 klient nie może nie wczytać pierwszych 2 lini buffora ponieważ to nie jest UDP a TCP. TCP jest liniowy, pakiety nie przychodzą pomieszane ORAZ mają swój protokołowy hash który zapewnia że pakiet dotrze w całości (choć może być niby podzielony na części) to jednak dotrze "cały". Tak, TCP zapewnia, że wszystkie wysłane dane dotrą w postaci niezmienionej, ale nie że w jednym momencie. TCP to protokół strumieniowy -- operujesz na danych gdy tylko jest to możliwe, a nie gdy dojdą już wszystkie pakiety składające się na twoje dane. Podział wiadomości musisz implementować sam. Te 2 bajty zawierające rozmiar wiadomości _mogą_ zostać nie wczytane w całości przy pojedynczym receive, chociaż jest to mało prawdopodobne by osobno dotarł pakiet z tylko jednym bajtem, to jednak możliwe. A 39dll najwidoczniej ma błąd. Oznajmiłeś, że świadomie nie napisałeś dzielenia danych odebranych od klienta na pakiety/wiadomości, chociaż jest to poważny błąd, który praktycznie uniemożliwia korzystanie z silnika. Uznałem więc, że wcześniej nie wiedziałeś że i dlaczego należy je dzielić :P Odnośnik do komentarza Udostępnij na innych stronach Więcej opcji udostępniania...
Pieter Opublikowano 2 Marca 2011 Autor Udostępnij Opublikowano 2 Marca 2011 Tak, TCP zapewnia, że wszystkie wysłane dane dotrą w postaci niezmienionej, ale nie że w jednym momencie. TCP to protokół strumieniowy -- operujesz na danych gdy tylko jest to możliwe, a nie gdy dojdą już wszystkie pakiety składające się na twoje dane. Podział wiadomości musisz implementować sam. Te 2 bajty zawierające rozmiar wiadomości _mogą_ zostać nie wczytane w całości przy pojedynczym receive, chociaż jest to mało prawdopodobne by osobno dotarł pakiet z tylko jednym bajtem, to jednak możliwe. A 39dll najwidoczniej ma błąd.w takim razie rozwiązaniem będzie tworzyć wątek dla każdego klienta ;] ale to już przy multum klientów nie jest takie wydaje i straaaaasznie niewygodne w operowaniu na danych. Poza tym, skoro nie tak jak opisałem, to jak :)? po prostu dodać kolejny warunek... "if len == 1 recv bla bla bla pfff. Dobre sa takie dyskusje, bo nie wszystko można wykryć podczas "testów". Wszystko o czym tu mówimy to "co by było gdyby". Nie ma aplikacji idealnej, ani silniku idealnego ;] Odnośnik do komentarza Udostępnij na innych stronach Więcej opcji udostępniania...
Rekomendowane odpowiedzi
Jeśli chcesz dodać odpowiedź, zaloguj się lub zarejestruj nowe konto
Jedynie zarejestrowani użytkownicy mogą komentować zawartość tej strony.
Zarejestruj nowe konto
Załóż nowe konto. To bardzo proste!
Zarejestruj sięZaloguj się
Posiadasz już konto? Zaloguj się poniżej.
Zaloguj się