PAŃSTWOWA WYśSZA SZKOŁA ZAWODOWA W ELBLĄGU INSTYTUT INFORMATYKI STOSOWANEJ Sprawozdanie Komunikator internetowy w C# autor: Artur Domachowski Elbląg, 2009 r.
Komunikacja przy uŝyciu poczty internetowej jest najbardziej powszechną i ogólnodostępną formą komunikacji poprzez Internet. Pozwala ona na przesyłanie wiadomości pomiędzy uŝytkownikami, którzy posiadają konta na róŝnych serwerach. Taka forma komunikacji zaspokaja wiele potrzeb, jednakŝe nie wszystkie. Mimo iŝ wiadomości przekazywane są przewaŝnie bardzo szybko to prowadzenie rozmowy w czasie rzeczywistym jest niemoŝliwe. Powstały więc aplikacje sieciowe pozwalające na rozmowę tekstową w czasie rzeczywistym. Komunikatory internetowe (ang. instant Messenger, IM) pozwalają komunikować się poprzez natychmiastowe przesyłanie wiadomości między uŝytkownikami. Tylko one dają namiastkę prawdziwej rozmowy w formie tekstowej. Dla uŝytkownika przesyłanie wiadomości jest proste i wygodne, a co najwaŝniejsze natychmiastowe. Komunikator internetowy oparty o architekturę klient - serwer składa się z aplikacji klienckiej oraz serwerowej. KaŜdy klient łączy się z serwerem, a ten z kolei zbiera informacje o klientach i udostępnia innym klientom. Serwer pośredniczy takŝe w przekazywaniu informacji między klientami. Komunikator internetowy Domacho działa w oparciu o architekturę klient - serwer. Serwer tego komunikatora jest wielowątkową aplikacją konsolową, napisaną w języku C#(csharp). Dzięki wykorzystaniu protokołu TCP/IP, przesyłanie wiadomości moŝliwe jest w sieciach lokalnych (LAN, local area network) jak i przy uŝyciu sieci rozległych (WAN, wide area network). Serwer komunikatora wykorzystuje bazę danych Microsoft SQL Server 2005 do przechowywania informacji o klientach oraz zapisywania wiadomości do niedostępnych uŝytkowników. Całość działa pod kontrolą systemu operacyjnego Microsoft Windows 2003 R2 Enterprise Edition with SP2. Do prawidłowego funkcjonowania aplikacja serwerowa jak i kliencka wymaga zainstalowanego środowiska Microsoft.NET Framework w wersji 2.0 lub wyŝszej. Serwer komunikatora Domacho akceptuje połączenia na porcie 8000. Po połączeniu się z klientem wysyła Ŝądanie przesłania nazwy uŝytkownika oraz hasła. Hasło przekazywane jest w postaci zaszyfrowanego kodu, do szyfrowania uŝyto algorytmu kryptograficznego SHA1. Podczas logowania serwer porównuje otrzymany zaszyfrowany kod z tym zapisanym w bazie danych. Po zalogowaniu wszystkie dane dotyczące klienta, połączenia z nim oraz z wszystkimi innymi klientami przekazywane są do osobnego wątku. KaŜdemu zalogowanemu klientowi odpowiada jeden wątek w serwerze. 2
Rys1. Klasy w kodzie źródłowym serwera komunikatora Domacho, opracowanie własne. Podstawę do przekazywania informacji z klasy głównej Serwer do klasy Sesja stanowi struktura clientinfo. Przechowuje ona dwie wartości: pierwszą z nich jest ciąg znaków odpowiadający nazwie uŝytkownika, drugą zaś jest obiekt klasy TcpClient, który przechowuje wszystkie informacje dotyczące połączenia. Klasa TcpClient jest elementem środowiska.net Framework. Hashtable klienci = new Hashtable();... struct clientinfo public String login; public TcpClient tcpclient; KaŜdy nowo utworzony obiekt struktury jest dodawany do tablicy Hashtable o nazwie klienci, która jako klucz przyjmuje nazwę uŝytkownika, a jako wartość obiekt. Tablica Hashtable deklarowana jest poza klasą serwer. 3
clientinfo clientinf = new clientinfo(); //tworzenie obiektu struktury clientinf.login = login; clientinf.tcpclient = client; klienci.add(clientinf.login, clientinf); // dodawanie do Hashtable Do nowoutworzonego wątku przekazywane są wszystkie niezbędne dane do obsługi połączenia z klientem komunikatora, nazwa uŝytkownika oraz informacje o połączeniach z innymi klientami w sieci. new Thread(new ThreadStart(new sesja(clientinf.login, client, klienci).run)).start(); KaŜdy wątek tworzy nowy obiekt klasy sesja, w którym obsługuje klienta do momentu, aŝ nie wyśle on wiadomości o chęci zakończenia połączenia, bądź teŝ połączenie nie zostanie zerwane. NajwaŜniejszą funkcją jaką pełni kaŝdy wątek jest przekazywanie informacji pomiędzy klientami. Operacja moŝliwa jest poprzez pobranie odpowiedniego obiektu struktury clientinfo z tablicy klienci. Następnie ze struktury clientinfo pobierany jest obiekt klasy TcpClient. Z pobranego obiektu klasy TcpClient tworzy się nowy strumień do wysyłania danych do tego klienta BinaryWriter. clientinfo info = (clientinfo)users[login]; wclient = new BinaryWriter(info.tcpclient.GetStream()); Posiadając strumień wysyłania do danego uŝytkownika, moŝna wysłać do niego dane w postaci ciągów znaków. Po wysłaniu wiadomości naleŝy zwolnić zasób. wclient.write(wiadomosc); wclient.flush(); Serwer przyjmuje i wysyła dane oddzielone specjalnym znakami #*, dla przykładu wiadomość dla uŝytkownika odbiorca od uŝytkownika nadawca przedstawia się następująco: 1#* nadawca#* odbiorca#* wiadomość#* data. Na początku kaŝdej wiadomości jest typ polecenia wyraŝany liczbą, następnie nazwa uŝytkownika od którego pochodzi wiadomość, potem nazwa uŝytkownika, do którego wiadomość ma być przekazana. Treść tej wiadomości oraz na samym końcu data, którą dodaje serwer podczas przekazywania wiadomości. 4
Rys2. Okno serwera komunikatora Domacho, opracowanie własne. Dzięki zastosowaniu prostego modelu architektury, klient nie musi nawiązywać wielu połączeń. Nawiązuje jedynie jedno z serwerem komunikatora. Połączenie nawiązywane jest przez wątek działający w tle (backgroundworker). Po zalogowaniu się następuje zmiana grafiki w głównym oknie klienta. Rys3. Okna klienta komunikatora Domacho, przed i po zalogowaniu, opracowanie własne. 5
Głównym problemem przy konstrukcji klienta komunikatora było rozróŝnianie okien formularza drugiego (Form2). W oknie drugiego formularza prowadzone są rozmowy z innymi uŝytkownikami. KaŜde pojedyncze okno Form2 odpowiada rozmowie z jednym uŝytkownikiem. Przykładowo, gdy klient komunikatora otrzyma wiadomość od uŝytkownika artdom, musi wyświetlić ją oknie rozmowy z uŝytkownikiem artdom. Z uwagi na to, Ŝe uŝytkownik moŝe prowadzić wiele rozmów jednocześnie, będzie istniało wiele obiektów From2. Rys4. Okno Form2 komunikatora Domacho, opracowanie własne. RozróŜnianie obiektów Form2 jest niezbędnym elementem przekazywania wiadomości do odpowiednich okien rozmowy. Problem ten rozwiązano przy uŝyciu słownika przechowującego obiekty From2. Słownik Dictionary, o nazwie rozmowy jest elementem środowiska.net Framework, moŝe przechowywać dowolne zmienne i obiekty. Dictionary<String, Form2> rozmowy = new Dictionary<String, Form2>(); Przyjmuje on dwie wartości, pierwszą jest nazwa uŝytkownika, z którym prowadzona jest rozmowa, drugą zaś obiekt formularza rozmowy Form2. Klient otrzymując wiadomość sprawdza, czy słownik rozmowy zawiera informacje o uŝytkowniku, od którego pochodzi wiadomość. JeŜeli tak, pobiera obiekt Form2 ze słownika Dictionary i przekazuje wiadomość do niego. JeŜeli nie, tworzy nowe okno rozmowy. if (rozmowy.containskey(nadawca)) rozmowy[nadawca].wyswietlanie_wiadomosci(fullnadawca, wiad, dataczas); else TheForm = new Form2(w, nadawca, odbiorca, wiad, fullnadawca, fullodbiorca, dataczas); // uwtorzenie nowego okna Form2 TheForm.Text = "Rozmowa z " + fullnadawca; // napis na belce okna Form2 rozmowy.add(nadawca, TheForm); // dodajemy rozmowę do Dictionary TheForm.Show(); 6
Funkcja showwindow odpowiedzialna jest za przekazywanie wiadomości do odpowiednich okien rozmowy lub tworzenie nowych okien rozmowy. Aby bezpiecznie i bezawaryjnie wywołać ją z wątku obsługującego połączenie naleŝy uŝyć delegaty. Delegata najprościej moŝna opisać jako wskaźnik na funkcję. private delegate void showwindowcallback(binarywriter w, string nadawca, string odbiorca, string wiad, string dataczas); // Delegata private void showwindow(binarywriter w, string nadawca, string odbiorca, string wiad, string dataczas) // Funkcja if (this.invokerequired) showwindowcallback f = new showwindowcallback(showwindow); this.invoke(f, new object[] w, nadawca, odbiorca, wiad, dataczas ); else // Treść funkcji showwindow Gdy funkcja showwindow wymaga wywołania poprzez metodę Invoke, tworzony jest nowy obiekt delegaty showwindowcallback, który ponownie wywołuje funkcję showwindow. Delegata przyjmuje i przekazuje takie same zmienne jak funkcja, z której została wywołana. Tworzony jest nowy obiekt delegaty, przez co odwołanie się do funkcji showwindow z innego wątku jest bezpieczne. Komunikator posiada równieŝ dodatkowe metody, jak na przykład moŝliwość zmiany hasła, czy wylogowania się. Komunikator Domacho nadal jest w fazie testów, najnowsze wiadomości dotyczące jego rozwoju moŝna otrzymać pod adresem: http://domacho.pl. 7