[Builder] Server czata

0

[C++ Builder 6]

Witam, mam do Was kilka pytań.

Otóż chciałem napisać serwer czata i mi się to po części udało.
Do tego użyłem komponentów TServerSocket po stronie serwera i TClientSocket po stronie klienta.
Wszystko byłoby ok, gdyby nie jeden wielki problem. Na moim komputerze gdy uruchomię serwer i klient i połączę się, pakiety przez klienta są poprawnie odbierane. Jednak gdy uruchomię klienta na innych komputerze, osobno wysyłane pakiety z serwera są widziane przez klienta jako jeden pakiet, również sniffer tak mi je pokazuje.

Przykładowo serwer wysyła:

  1. AAABTematPokoju
  2. AAAAListaUżytkowników

a w snifferze te osobno wysyłane pakiety są widziane jako:

AAABTematPokojuAAAAListaUżytkowników

Client zamiast wyświetlać temat na ogólny i dodawać użytkowników do listy pokazuje całe pakiety na ekranie rozmowy. :-/

Mam jeszcze jedno pytanie - jak za pomocą tychże komponentów stworzyć serwer, który jest wielowątkowy, tzn. chodzi mi o to, że podczas wysyłania pakietów do klientów może jednocześnie odbierać pakiety od innych (pisanie na ogólny, pakiet logowania, itp.)

Byłbym wdzięczny za pomoc.

0

ak za pomocą tychże komponentów stworzyć serwer, który jest wielowątkowy

Wielowątkowość nie ma nic do komponentów, których używasz. Poszukaj sobie o wątkach w Delphi. na tym forum powinno być nawet. Bardzo ogólnie to wygląda tak:

  1. tworzysz funkcję, która sprawdza sobie np. w pętli czy przyszła wiadomość
  2. tworzysz watek, ktory bedzie wykonywał tą funkcję (czesto to jest coś w stylu thread = new TThread(nazwa_funkcji) //to jest tylko przykład-nie bierz go dosłownie ;)
  3. odpalasz wątek

tak samo z wysyłaniem.
Powodzenia

0
neomario napisał(a)

Na moim komputerze gdy uruchomię serwer i klient i połączę się, pakiety przez klienta są poprawnie odbierane. Jednak gdy uruchomię klienta na innych komputerze, osobno wysyłane pakiety z serwera są widziane przez klienta jako jeden pakiet, również sniffer tak mi je pokazuje.

Przykładowo serwer wysyła:

  1. AAABTematPokoju
  2. AAAAListaUżytkowników

a w snifferze te osobno wysyłane pakiety są widziane jako:

AAABTematPokojuAAAAListaUżytkowników

Client zamiast wyświetlać temat na ogólny i dodawać użytkowników do listy pokazuje całe pakiety na ekranie rozmowy. :-/

Takie "łączenie pakietów" nie jest niczym nadzwyczajnym. Tak naprawde to nie pakiety są łączone tylko dane które wysyłasz. Dane wysłane za pomocą kilku wywołań funkcji wysyłającej są umieszczane w jednym pakiecie. Powodów takiego łączenia danych może być kilka :jakiś algorytm optymalizacyjny socketów, lub wolniejszy transfer danych pomiędzy dwoma komputerami niż pomiędzy aplikacjami uruchomionymi na jednym komputerze, itd. Wyjście z takiej sytucji jest bardzo proste. Musisz zaprojektować sobie jakiś protokół transmisji (na warstwie aplikacji). np.

1 - wysyłać całą linie tekstu zakończoną znakami przejścia do nowej lini i oczywiście wtedy odbierać tylko całe linie np (z twojego przykładu).
wisyłasz - AAABTematPokoju,#13,#10
AAAAListaUżytkowników,#13,#10
W tym momencie nie ważne w ilu pakietach dane będą przesyłane. Funkcja która będzie odbierała dane czeka na znak końca lini i tak długo będzie dane odbierała aż tego znaku nie osiągnie. Gdy dane przyjądą w jednym pakiecie to funkcja spokojnie odbierze najpierw pierwszy ciąg znaków do zakończenia lini, a później drugi ciąg znaków, też aż do zakończenia lini.

2 - moim zdaniem lepszym pomysłem (może nie do przesyłania tylko ciągów znaków) jest przesyłanie na początku liczby, która okresla ile kolejnych bajtów należy odebrać aby odczytać całą linijkę (w twoim przypadku). np.
do pierwszej linijki doklejasz na początek liczbe 15 (jeśli dobrze policzyłem) a później znaki AAABTematPokoju. Funkcją odczytującą najpierw odczytujesz liczbę/rozmiar (powiedzmy że będzie to 2 bajtowa liczba) a pożniej odczytyjesz tyle bajtów ile wynośi wartość odczytanej liczby itd.

Mam nadzieje, że troche ci pomogłem.
Pozdrowienia.

0

mczarny, dzięki, naprowadziłeś mnie, jednak bede musiał wszystkie pakiety zaprojektować od nowa, a co za tym idzie - cały serwer oraz cały client :-D

AnsiString __fastcall ExtractPacketData(AnsiString AllPacket, int From, int To)
{
   int i;
   AnsiString wynik;

   for (i=From; i<=To; i++)
   {
     wynik += AllPacket[i];
   }

   return wynik;
}

AAAATestowyTematPokoju#13#10

Zatem

AnsiString Data = Socket->ReceiveText(); // odbieramy pakiet
p_type = ExtractPacketData(Data, 1, 4);

if (p_type == "AAAA")
{
  RichEdit1->Lines->Add(ExtractPacketData(Data, 5, strlen(Data.c_str())-2));
  // wyswietlamy temat w oknie rozmowy a -2 jest po to, zeby nie wyswietlaloznakow konca linii
}

Ale kombinuje... :-D Bedzie tak ? Bo nie chce mi sie bawic ze znacznikami dlugosci pakietu, akurat nie jest mi to na reke z pewnych mi wiadomych wzgledow...

I jeszcze te watki. Jakos nie daje sobie rady. Nie wiem jak to obsluzyc w tych komponentach.

0

Wiem, że odkopuję temat sprzed 4 lat, ale mam taki sam problem - jak zrobić mechanizm sesji? - tj. chcę napisać funkcję, która będzie wysyłała wiadomość do klienta o określonym id sesji (ten id klient będzie przesyłać na początku każdej wiadomości).

0

otworz kod serwera, znajdz w nim jakies miejsce w ktorym znajduje sie abstrakcyjnie mowiac "lista polaczen", do kazdego elementu tej listy dowiąż jakos troche miejsca zeby sobie tam poskladowac dane dotyczace tego jednego polaczenia. Jak to zrobisz, albo jesli juz takie bylo - zadbaj, aby w tymze miejscu-z-danymi-per-polaczenie znalazlo sie tyci miejsca na umieszczenie jakiesgo long'a, longlong'a czy GUID'a, ktore beda spelniac wartosc sessionid. Napisz nastepnie jakis kawalek kodu, ktory bedzie potrafil generowac niepowtarzajace sie numery, byc moze z uwzglednieniem tego ze po jakims czasie stare numery moga byc juz wolne i ponownie uzyte. Nastepnie znajdz kod, ktory odpowiada za obsluge nowego przychodzacego polaczenia i w ktoryms jego momencie gdy juz-sie-polaczono-i-juz-masz-socket-zwrotny, odpal te funkcje generujaca, zapamietaj w danych skojarzonych z polaczeniem jako sessid wlasnie te wartosc ktora generator zwroci, i korzystajac z wlasnie-otrzymanego socketa 'zwrotnego' wyslij te 4/8/xx bajtow wartosci do klienta.

otworz kod klienta, znajdz w nim miejsce odpowiedzialne za laczenie sie do serwera. w ktorym jego momencie, gdzie juz jestes pewien ze socket 'podlaczyl sie' do zdalnego hosta, ale przed jakakolwiek inna komunikacja, dopisz recv'a odbierajacego 4/8/xx bajtow, odebrawszy te dane, zrzutuj/zapisz sobie do dlugożyjącej zmiennej (albo globalnej, albo membera klasy aplikacji, itp). nastepnie znajdz wszystkie miejsca gdzie klient wysyla jakies pakiety. w tym miejscach, przed wyslaniem wlasciwego pakietu, wpisz send'a wysylajacego 4/8/xx bajtow, pobierajacego bajty żywcem z tejże-długożyjącej-zmiennej.

czy to wystarcza za odpowiedz na "jak zrobic mechanizm sesji" oparty o podana przez Ciebie ideę?

funkcja zaś - nie wiem za bardzo w czym masz problem -- sprowadza sie do jednego send'a ktory wysle 4/8/xx bajtow bedacych wygeneronym id. masz problem w wywolaniu wlasciwej metody t***socket?

0

Hmm... W takim razie źle zadałem pytanie. Po prostu jestem zielony w socketach (znam tylko podstawy), i szukałem jakiegoś prostego mechanizmu.

Quetzalcoatl, mógłbyś podać jakieś źródło, skąd mógłbym się lepiej nauczyć o tych socketach w builderze? Szukałem w google, tutaj też, ale jakos nie mogłem znaleźć...

//edit:
zajrzałem do examples, z tego kodu dam już chyba radę zrobić to o co mi chodzi.
Dzięki za pomoc.

//edit2:
Mam jeszcze jedno pytanie: nauczyłem się korzystać z Connections[], ale nie wiem, jak zrobić funkcję zapamiętującą - przy połączeniu zapamiętać nr. socketu z tablicy Connections[], czy lepiej zapamiętać wskaźnik do socketu zwrotnego [???]

0

Niestety, nie znam detali komponentow Borlanda, tym bardziej jego exampli..
Sprawdz, czy Connections[] zmienia "sama" swoj rozmiar w momencie gdy przychodzi nowe, lub rozlacza sie stare polaczenie? jesli tak - to index w niej nie wiele Ci da.. pozycja trzecia za 5 minut moze juz nie byc trzecią, tylko drugą. W takim przypadku lepiej kojarzyc swoje dane z czymkolwiek scisle zwiazanym z owym socketem - wskaznik na niego bedzie ok.
Jednak, jesli tablica NIE zmienia swojego rozmiaru, i raz otwarte polacznie jesli trafilo na miejsce szoste, to juz tam na zawsze pozostaje i co najwyzej po jakims czasie dostaje stan 'zamkniete' czy 'niedostepne', to smialo mozesz pamietac indeks.

..czy poprzez "nr. socketu z Connections[]" miales na mysli cos innego niz indeks?

a tak w ogole, lepiej poczekac az odpowie Ci ktos kto uzywa Borlanda na codzien:)

0

tak, mówiąc nr chodziło mi o index - zabrakło mi słowa.
Sprawdziłem - connections ma zmienną długość i rzeczywiście trzeba by wszystko poprawiać przy połączeniu/rozłączeniu każdego klienta. Będę zapamiętywał wskaźnik do socketu.

Zarejestruj się i dołącz do największej społeczności programistów w Polsce.

Otrzymaj wsparcie, dziel się wiedzą i rozwijaj swoje umiejętności z najlepszymi.