[inżynier] komunikator w Javie oparty o XML

0

Witam!
Jestem w trakcie wybierania tematu swojej pracy inżynierskiej i zdecydowałem się napisać prosty komunikator w Javie oparty o XML.

Mam nadzieję, że jako mądre głowy pomożecie mi rozważyć kilka kwestii. Jak się do tego zabrać? Co się opłaca pisać, a co będzie tylko stratą czasu? Może wskażecie mi jakieś biblioteki?

A więc do rzeczy:

  1. Komunikacja na gniazdkach i wątkach. Jedno połączenie - jeden wątek.
  2. Protokół w XML (logowanie, autoryzacja kontaktu, lista kontaktów, stany użytkowników, wiadomości, wylogowanie, zarządzanie serwerem). Tu chyba lepiej oprzeć na własnym uproszczonym protokole niż na XMPP. Nie chcę się porywać z motyką na słońce.
  3. GUI klienta - Swing, Spring czy coś z zupełnie innej beczki? Myślałem, żeby w oknie rozmowy umieścić panel renderujący HTML oparty na CSS. Łatwo wtedy o skórki, generowanie linków, uśmieszków, etc. Tylko jak jest z wykonaniem czegoś takiego?

O co jeszcze warto zadbać? Gdzie mogę się spodziewać potencjalnych min? Na razie zająłem się gniazdkami i wątkami, jak się z nimi uporam zacznę bawić się resztą.

0
  1. Warto mieć własny serwer/serwery z bazą "kto jest", a samo połączenie realizować w oparciu o sockety. Scentralizowany system z informacją o użytkownikach jest dość prosty i łatwy w napisaniu.
  2. Do XMPP masz już gotowe biblioteki. Własny protokół jest jak zawsze wielką niewiadomą.
  3. Swing + http://en.wikipedia.org/wiki/Synth_Look_and_Feel czyli mechanizm definiowania F&F w XML. Spring można zastosować w kliencie jako narzędzie DI/IoC. W Springu bardzo ciężko napisać jakiekolwiek GUI. Była kiedyś biblioteka do tego (coś a la Spring Web dla Swinga), ale umarła dość szybko.
0

Dziękuję!
Bardzo ciekawe uwagi.

Jeszcze jakieś?

0

Właśnie skończyłem pisać taki prosty komunikator w Javie na laborki. Oczywiście opierał się o "własny protokół XML" + serializację danych.

Jak Koziołek napisał - zrobienie tego nie jest tak naprawdę trudne.
Mogę Ci powiedzieć na co zwracać uwagę:
Masz do obsługi 7 podstawowych żądań do serwera -

  • Tworzenie konta (informacje zapisywane w katalogu usera to będzie nazwa, hasło, status, opis i lista znajomych)
  • Usuwanie konta
  • Logowanie - serwer dowiaduje się kto jest obecnie online.
  • Zmiana statusu/opisu - musi być wysyłana do wszystkich znajomych na bieżąco (Tych co nie mamy na naszej liście znajomych pomijamy).
  • Dodawanie znajomych ( zapisujemy to w naszym pliku + pobieramy status znajomego (jeśli on ma nas na liście oczywiście)).
  • Usuwanie znajomych
  • Wiadomości - tutaj serwer będzie po prostu przekazywał wiadomości drugiemu userowi.

Na każde z nich serwer odpowiada prostym komunikatem np ("Konto Istnieje"), ("Wiadomość wysłana")
Idąc dalej - czy będziesz na serwerze przechowywał wiadomości do późniejszego wysłania - tzn
ktoś pisze do drugiej osoby która jest akurat offline, albo coś ja wywaliło. Możesz wysłać komunikat o tym
że wiadomość nie została dostarczona albo mieć prostą kolejkę komunikatów oczekujących do wysłania i gdy
tylko user się zaloguje ślesz mu je.
Komunikacje miedzy serwerem a klientem oparłem na serializacji (założenia zadania), czyli konwertuje klasę
Message, Login, Status itd do XML'a za pomocą xstream i następnie wysyłam jednego stringa. Odbieranie informacji
staje się równie proste.
Możesz też pomyśleć o prostym zabezpieczeniu - tzn chociażby wysłać jakiś unikalny klucz do każdego
usera(za każdym logowaniem inny) aby nikt nie mógł się pod niego podszywać. To bardziej w celach demonstracyjnych. Może warto na podstawie tego klucza wrzucić jakieś proste szyfrowanie ?! Dalej to już dodajesz tylko inne funkcjonalności.

Do GUI myślę, że dobrym wyborem będzie swing - skoro to ma być prosty komunikator to takie coś jak skórki czy
emotoikonki nie są potrzebne według mnie.

Mam nadzieję że trochę rozjaśniłem sprawę. Większość z tych rzeczy wydaje się na pierwszy rzut oka oczywista,
ale dopiero w praniu wychodzi o ilu z nich tak naprawdę się zapomniało.

0
gosc napisał(a)

Większość z tych rzeczy wydaje się na pierwszy rzut oka oczywista,
ale dopiero w praniu wychodzi o ilu z nich tak naprawdę się zapomniało.

Dokładnie. Wielu znajomych mówi mi, że to prosty projekt, ale jak zacząłem go sobie rozbierać na części pierwsze i zagłębiać w szczegóły, to jest nad czym pracować.

Z resztą jak z każdym projektem: czas na napisanie = czas zakładany*2 i powiększony o rząd wartości :)

0

jeszcze nie jestem na studiach, ale mam pytanie - czy na pracę inżynierską mogę użyć swojej aplikacji którą napisałem już dużo wcześniej czy jeśli zobaczyliby na przykład że ta aplikacja krąży po necie od kilku lat to by mnie oblali?

0

Każda praca i kod są sprawdzane, pod względem plagiatu, więc musiałbyś udowodnić, że to ty jesteś autorem.

0

@czterystaczwarty, pracy nie jest dużo jeżeli rozumiesz jak tworzyć elegancki kod. Trzeba zaprząc do pracy Wzorce Projektowe, OOA&D oraz odpowiedni model dziedziczenia. Wtedy najtrudniej napisać pierwszą funkcjonalność, bo kolejne same się dodają.

0

XMPP nie jest jakoś niebywale trudny - zresztą nie musisz implementować pełnego protokołu i wszystkich jego możliwości, a co nieco ograniczyć.

Połączenie, logowanie i uwierzytelnianie możesz ograniczyć do nieużywania TLS i do logowania z użyciem np. tylko wysyłania hasła czystym tekstem w SASL. Tylko zrób tak, żeby kiedyś ten MD5 można było dodać ;-) Wylogowanie to raptem wysłanie jednego kawałka tekstu zamykającego strumień.

Lista kontaktów, stany kontaktów to po prostu parę typów wiadomości latających w tę i we w tę. Teoretycznie najgorsza jest autoryzacja kontaktów, ale tutaj najwięcej roboty ma akurat serwer - ty tylko dostajesz informacje czy się powiodło na swoje wysyłane żądania. Listę kontaktów też dostajesz zaktualizowaną od serwera.

Wiadomości są proste - możesz pominąć np. wątkowanie czy wielojęzyczność.

Pisałem prosty serwer XMPP w Javie na zaliczenie laborek i nie jest w pełni zgodny z protokołem, ale działa bez problemów z kilkoma klientami, zajęło jakieś dwa miesiące - fakt, ja nie musiałem się interesować wyświetlaniem żadnych okienek ;-)

0

Ja bym tylko dodal ze jesli juz DI, to lepiej (moim zdaniem) google guice niz Spring. Zadnego XML w ustawianiu beanow, bardzo przyjemnie sie pisze.

0

@::, a Spring 3 i jego Adnotacje. Swoją drogą wole XMLa, bo łatwiej jest coś później zmienić i nie trzeba grzebać po klasie modułu.

0

Tak, wiem ze jest. Ja po prostu nie lubie Springa :D ale zgadzam sie ze mozna i dziala. A ostatnio pracuje z Weld SE (integracja Javy SE z Weld Core) ktory jest referencyjna implementacja CDI (JSR-299) i bardzo sobie chwale, swietnie dziala. Moze do takiej pracy inzynierskiej fajnie by bylo wciagnac jakas nowa technologie?

0

@Koziołek - Właśnie tego się najbardziej obawiam, nie chcę zapędzić się w kozi :D róg kiedy projekt będzie się rozrastał.

Zacznijmy od podstaw: Gniazda i wątki.
Napisałem prosty przykład po jednym wątku na połączenie i nieskończona pętla na serwerze:

while (true) {
        //odbierz polaczenie
        connection = listener.accept();
        //obsluz polaczenie - watek
        new ConnectionHandler(connection);
}

Ale to umożliwia tylko jedno połączenie z serwerem w danym czasie. Poza tym jeden wątek na gniazdo wyklucza asynchroniczność.
Trzeba to rozbić na dwa wątki (wysyłanie i odbieranie). To proste.
Ale...

  1. Co zrobić, żeby serwer obsługiwał wiele połączeń w tym samym czasie i jak nad nimi zapanować?
  2. Jak to elastycznie podzielić na klasy? Dwie klasy Sender i Receiver dziedziczące po jednym Handlerze czy implementujące interfejs Handlera?
  3. Jakiekolwiek wskazówki na tym etapie. :)

Co myślicie?

0

Trzeba rozbić na dwa.
Mój serwer miał klasę Worker (dziedziczącą po Thread), która zajmowała się ogólnie wysyłaniem połączeń do klienta, a która zawierała w sobie obiekt klasy XMPPStreamReader (też Thread), który z kolei zajmował się odczytywaniem danych od klienta.

I gdy zaszła potrzeba, że po odczytaniu danych należało coś do klienta wysłać, to z wnętrza XMPPStreamReadera robiłem parent.sendToClient(), gdzie "parent" to był rodzic, czyli Worker.
Worker w konstruktorze przyjmował tylko gniazdko serwerowe, a do XMPPStreamReadera przekazywał tylko Reader oparty na strumieniu wejściowym.

Obsługa wielu połączeń wyglądała następująco:
była sobie tablica (lista) Workerów i do tej listy były dodawane i uruchamiane kolejne Workery wraz z kolejnym podłączonym klientem.

workerPool = new ArrayList<Worker>();

// główna pętla aplikacji
// jeśli ktoś się podłączył, to stwórz nowy wątek typu Worker
// i czekaj na połączenia dalej
while (!isStopped())
{
	Socket clientSocket = null;
	try
	{
		clientSocket = serv.accept();
	}
	catch (IOException e)
	{
		if (isStopped())
		{
			return true;
		}
	}
	workerPool.add(new Worker(clientSocket));
	workerPool.get(workerPool.size() - 1).start();
}

A gdy zaszła potrzeba wysłania wiadomości do określonego klienta to iterowałem po liście szukając klienta o określonym JabberID i wywoływałem metodę sendToClient() danego Workera. Działa całkiem sprawnie ;-)

0

Co oznacza return true w tym kontekście?

		if (isStopped())
		{
			return true;
		}
	}
}

Nie bardzo podoba mi się to, że StreamReader jest wewnątrz Workera. Wątek w wątku.
Ale z drugiej strony, jeśli chciałbym zrobić coś na kształt kontenera zajmującego się jednym użytkownikiem, który przechowywał by dwa wątki (dla wysyłania i odbierania). To miałbym trzy wątki na każde połączenie :)

Czy ma to sens pod względem wydajności? Warto jej trochę poświęcić na koszt lepszej struktury?

0

Jeśli mamy wyjątek IOException (błąd połączenia ogólnie), ale program jest już zatrzymywany/zatrzymany, to return true wychodzi z metody w której ten serwer pracował. Ogólnie return true oznacza, że ta metoda z pętlą zakończyła się poprawnie, a return false, że w ogóle mamy jakiś błąd (w rodzaju tego, że nie mogę postawić gniazda serwera).

AFAIR to to kolejny jakiś hack jest - bo metoda stopServer() zatrzymuje serwer (zamyka gniazdko) oraz ustawia flagę isStopped(), co powoduje, że accept() wywraca się w z wyjątkiem, który jest ignorowany jeśli flaga oznacza, że serwer się zatrzymuje. To dawało możliwość zatrzymania serwera "natychmiast", bez oczekiwania aż następny klient się podłączy.

Tak, wątek w wątku nie jest najelegantszym rozwiązaniem. Ale za to nie wymagał tworzenia trzech wątków, z czego jeden byłby rodzicem dwóch ;-)

0

Problemy powoli się mnożą :)

Do odbierania wiadomości używam ObjectInputStream:

message = (String) in.readObject();
System.out.println("server: " + message);

Jednak w taki sposób wątek zatrzymuje się dopóki ze strumienia nie da się zrobić stringa.
Stąd moje pytanie. Jak sprawdzić czy w strumieniu są dane, a jeśli ich tam nie ma, żeby kontynuował dalej?
Mówiąc po ludzku: Jak napisać ifa sprawdzającego dostępność strumienia?

0

witam
piszę podobny komunikator tylko w oparciu o mysql nie mam raczej problemów z kodem ale niestety teoria mi spać nie daje niespecjalnie wiem jak podejść do tematu i tu pojawia się prośba o kontakt do "czterysta czwarty" jak co jestem dostępny prawie całą dobę na gadu-gadu 1434887 ale niewidoczny lub e-mail z góry wielkie dzięki

0

Najprostsze rozwiązanie dotyczące komunikatora z centralnym serwerem jakie wymyśliłem (przed chwilą) to obsługa wszystkiego przez FTP. Każda wiadomość to nowy plik w katalogu z wysyłanymi wiadomościami się o nazwie ID klienta. Nazwa pliku to ID adresata. Odebrane wiadomości w innym katalogu jednoznacznie połączonym z adresatem wiadomości. Na serwerze skrypt, lub kilka skryptów PHP w nieskończonych pętlach, które kopiują, do folderu wiadomości odebranych wiadomości wysłane. Aplikacje klienckie w Java. Każdy użytkownik ma osobne konto FTP, tworzone przez skrypt PHP na stronie. Co o takim czymś myślicie?

0

@ds, a idź pan z takim rozwiązaniem. Wolne, bardzo podatne na błędy, mało efektywne do tego zagmatwane, a że musisz utrzymywać prawa zapisu i odczytu "każdy z każdym" to szybko znajdzie się debil, który będzie chciał ci zepsuć aplikację.
Do tego skrypty w PHP... to już lepiej w perlu albo w C napisać.

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