Komunikator internetowy. Czy różne zadania powinny być wykonywane na różnych portach?

0

Witam,

Potrzebuję stworzyć komunikator internetowy, który oprócz możliwości komunikowania się musi posiadać funkcje takie jak:

  • dodawanie znajomych
  • rejestracja nowego użytkownika
  • uwierzytelnianie

Wiem tyle, że tego typu rzeczy powinny się odbywać po stronie serwera. (np. gadu gadu chyba większość rzeczy wykonywało po stronie klienta, ale to chyba nie jest dobre prawda?)

Moje pytanie brzmi jak dostarczyć różne funkcje po stronie serwera? Czy powinienem otworzyć różne porty i na jednym np. prowadzić uwierzytelnianie?

pozdrawiam,

0

Zdecydowanie na jednym porcie w sesji TCP, inaczej zaczynasz mieć szereg problemów typowych dla protokołów bezstanowych (jak np. HTTP) i musisz kombinować, żeby emulować sesje.
Możesz rzucić np. na protokoły XMPP albo IRC jak tam to (lub podobne dodatkowe funkcje) zostało rozwiązane.

Ad GG: tego typu rzeczy odbywają się zazwyczaj po obu stronach, tj. klient wyświetla odpowiednie UI, ale to serwer wykonuje faktyczne weryfikacje / dodanie odpowiednich wpisów w bazie danych, etc (po czym klient znowu wyświetla kolejne UI, na żądanie serwera - np. komunikat o tym czy operacja się powiodła).

0

Ahh no tak nie pomyślałem o tym...

Więc w jaki sposób serwer może się komunikować z klientem jaką wykonać operacje? Po nawiązaniu połączenia mam dostęp do strumienia i mogę przesłać dane z serwera jak i do, ale skąd serwer ma wiedzieć jakie dane zostają przesyłane? W przypadku wielu portów to jest jasne że np. na porcie 9988 użytkownicy przesyłają sobie wiadomości a np. na porcie 7766 prowadzone jest uwierzytelnianie.

0

Ponieważ strumienie są zazwyczaj niewygodne, to najlepiej jest wyemulować pakiety. Robi się to najczęściej definiując sobie strukturę pakietu w następujący sposób (pseudokod):

uint32 rodzaj_pakietu;
uint32 długość_pakietu;
byte[] dane_pakietu;  // o długości długość_pakietu

Wtedy odbierasz 8 bajtów, sprawdzasz czy pakiet jest znanego rodzaju (patrz niżej), po czym pobierasz długość_pakietu bajtów i sobie je jakoś tam interpretujesz (warto wcześniej sprawdzić czy ta długość jest rozsądna; np. dla pakietu wiadomości tekstowej raczej nie ma sensu, żeby była większa niż 64KB, itp).

Mając coś takiego możesz sobie powiedzieć, że:
rodzaj_pakietu == 0 → autoryzacja
rodzaj_pakietu == 1 → wiadomość tekstowa
rodzaj_pakietu == 2 → dodanie znajomego
rodzaj_pakietu == 3 → rejestracja
I tak dalej.

Oczywiście musisz również wymagać by pierwszym pakietem w strumieniu był zawsze 0 (autoryzacja) albo 3 (rejestracja).

(Warto też by było pomyśleć o szyfrowaniu - jakiś SSL or sth ;>)

0

Świetny pomysł wielkie dzięki, myślę że to będzie rozwiązanie mojego problemu :-)

Aha zapomniałem jeszcze zapytać o coś bo nie wiem czy dobrze wszystko zrozumiałem. Moim celem jest stworzyć serializowaną klasę pakietu i to ją klient będzie wysyłał na serwer tak?

Ok już chyba wszystko rozumiem. Sprawdziłem to w swojej aplikacji i chyba robię to dobrze. To co pisałem wcześniej o przesyłaniu serializowanych obiektów jest nieaktualne.

P.s nie wiem czy jesteś tym Gynvaelem o którym myślę, ale jeśli tak to uczyłem się ASM z Twojego bloga :-)

1

Tak, przy czym zazwyczaj używa się tak skonstruowanych pakietów w obu kierunkach.
Jeśli używasz wysokopoziomowej serializacji, to dobrze jest też mieć oddzielną klasę reprezentującą dany pakiet (w przeciwieństwie do serializowania obiektu klasy odpowiedzialnej za faktyczne wykonanie danej czynności), tak, by w żadnym wypadku nie zostały przesłane wewnętrzne pola (przykładowo, jeśli masz klasę User, i pakiet proszący o informacje o użytkowniku, to zserializowanie klasy User i przesłanie jej do klienta mogłoby ujawnić jego hasło; jeśli zamiast tego istniałaby klasa np. PacketUserInfoResponse, to można by do niej świadomie przepisać jedynie faktycznie istotne informacje, a tym samym zapobiec przypadkowemu wyciekowi informacji).

P.S. Mówisz o tych screencastach z mspainta sprzed kilku lat? Hehe cały czas myślę, żeby coś lepszego nagrać, bo mam mieszane uczucia co do tamtego ;)

0

Czyli przyjmując takie rozwiązanie nie będą mi potrzebne żadne pola w stylu rodzaj pakietu i wielkość pakietu. Po prostu stworzę sobie klasę bazową dla pakietu i konkretne podklasy które będą reprezentować już konkretne pakiety.

Na początku jak chciałem to zrobić bez przesyłania obiektów i wykorzystać to co Ty mi napisałeś.

Coś w tym stylu:


InputStream in = socket.getInputStream();

// czytam 8 bajtów żeby sprawdzić rodzaj pakietu
int packetType = in.readByte();

// kolejne 8 bajtów żeby sprawdzić długość zawartości
int packetSize = in.readByte();
// no i następnie w pętli odczytuję zawartość

Oczywiście zakładając że klient będzie przesyłał dane w kolejności.

Tak mówię o tych screencastach z mspaint :-) Fakt było to trochę prymitywne, ale dzięki temu miało to pewny klimat takiego programowania z lat 80 :-).

0

Zgoda, w przypadku Java to też przejdzie (szczególnie biorąc pod uwagę istnienie ObjectOutputStream/ObjectInputStream, które zajmują się wczytaniem odpowiedniej ilości danych i ich serializacją/deserializacją).
(z jakiegoś powodu nie zwróciłem wcześniej uwagi, że topic jest w dziale Java - ups ;)

0

Ok już chyba wszystko rozumiem. Sprawdziłem to w swojej aplikacji i chyba robię to dobrze. To co pisałem wcześniej o przesyłaniu serializowanych obiektów jest nieaktualne.

Czyli przyjmując takie rozwiązanie nie będą mi potrzebne żadne pola w stylu rodzaj pakietu i wielkość pakietu. Po prostu stworzę sobie klasę bazową dla pakietu i konkretne podklasy które będą reprezentować już konkretne pakiety.

Zgoda, w przypadku Java to też przejdzie (szczególnie biorąc pod uwagę istnienie ObjectOutputStream/ObjectInputStream, które zajmują się wczytaniem odpowiedniej ilości danych i ich serializacją/deserializacją).

Taka uwaga: dodatkowy efekt wykorzystania java-specific rzeczy w protokole - potencjalni twórcy alternatywnych klientów dla komunikatora będą zgrzytać zębami :P (co może być wadą albo zaletą, zależy czy chcesz być "open").

0

a ja dodam że lepiej użyć tutaj np protobuf od googla - polecam, bardzo fajnie się używa.

0

Ale teraz zastanawiam się nad tym jak to dobrze ogarnąć w sensie jak zaprojektować to obiektowo, żeby później nie było problemu z rozpoznaniem rodzaju pakietu i uniknąć wielu warunków if.

Mój plan jest taki żeby stworzyć klasę abstrakcyjną Packet i konkretne klasy dziedziczące po niej np. RegistrationPacket, który będzie zawierał dane do rejestracji.

Packet packet = (Packet)input.readObject();

Ale teraz nie mam chyba innego wyboru jak stworzyć warunki if np.

if(packet instanceof RegistrationPacket) {
   String username = packet.getUsername();

   //itp.
}

Ktoś ma jakieś pomysły jak to ogarnąć?

0

A czemu nie polimorfizm? o_O Niech klasa Packet ma abstrakcyjną metodę process() a ta metoda jest odpowiednio zaimplementowana w każdym konkretnym pakiecie...

0

@Shalom myślałem nad takim rozwiązaniem, ale ostatecznie zrezygnowałem ponieważ uznałem że są to klasy które pełnią rolę struktur na dane i wydawało mi się głupie żeby klasa Packet wykonywała jakieś operacje w stylu rejestracja nowego użytkownika... a tego typu metody mam w springowym serwisie. A Ty uważasz że takie podejście byłoby dobre?

0

Ja bym w ogóle zamiast tego wystawił RESTowe serwisy zamiast przesyłać binarne obiekty. Ale jak już chcesz w ten sposób przesyłać obiekty to wygodniej będzie użyć polimorfizmu. Zauważ też że to nie wyklucza używania springowego serwisu! Co to za problem zrobić metodę void process(Service service) i po odebraniu obiektu podać mu do metody referencje do tego serwisu? Wtedy obiekt jedyne co będzie robił to wołał odpowiednią metodę na serwisie.

0

No w sumie masz racje... dla rejestracji lepiej wystawić REST :-). A co do reszty to wykorzystam polimorfizm tak jak wspominałeś.

1 użytkowników online, w tym zalogowanych: 0, gości: 1