Zmęczeni już tą biblioteką "Swing"? Spokojnie, zmieniamy temat na całkiem inny i przejdziemy teraz do zagadnień związanych z połączeniem sieciowym. Java pozwala na łatwe tworzenie połączenia sieciowego na podstawie architektury klient-serwer. Przedstawiam Wam klasę "Socket" w języku Java - to od niej zaczyna się cała historia!
Tweet |
OBIEKT "SOCKET" W JĘZYKU JAVA POCZĄTKIEM DROGI DO POŁĄCZENIA
Tytułowy obiekt stanowi elementarny budulec każdego połączenia sieciowego. To jest najzwyklejsze w świecie gniazdo sieciowe. Tworząc jego instancję, zwykle podaje się dwa parametry: adres IP lub nazwę hosta, do którego chcemy się podłączyć oraz numer portu TCP (szczegóły znajdują się na samym dole). Potem taki "Socket" podpina się pod strumień wejściowy (jeśli ma być odczyt danych z gniazda) lub wyjściowy (jeśli ma być zapis danych do gniazda). Należy też wspomnieć, że próby nawiązania połączenia mogą zakończyć się niepowodzeniem, więc konieczne jest otoczenie kodu obsługą wyjątków.
Aby zaprezentować działanie praktyczne, potrzeba i klienta, i serwera. W Javie, obu "aktorów" programuje się nieco inaczej. Po stronie klienta tworzony jest nowy "Socket" w języku Java w bloku "try-catch", i łączy go ze strumieniem wejściowym zwanym "InputStreamReader", a serwer ma za zadanie przechwytywać klientów i ich gniazda w nieskończonej pętli "while". W środku niej z kolei, dorzuca się wszystko to, co ma stanowić czynności wykonywane po stronie serwera. Na przykładzie skorzystamy z klasy "PrintWriter" celem wypisania komunikatu jako odpowiedzi w terminalu klienta.
PRZYKŁAD KODU ŹRÓDŁOWEGO
Znam ten zawód na tyle dobrze, aby wiedzieć, że same słowa nie będą w stanie poruszyć dostatecznie zwojów mózgowych, aby to sobie wyobrazić. Wobec tego, zastosujemy dwa osobne programy współdzielące tę samą klasę "main" oraz skorzystamy z prywatnego adresu IP znanego pod hasłem "localhost" (127.0.0.1), aby wszystko odbywało się za pomocą jednego stanowiska komputerowego. Zróbcie sobie dwa osobne projekty i wklejcie poniższe kody do każdego z nich z osobna:
KLASA "MAIN"
public class Main {
public static void main(String[] args) {
new Launcher();
}
}
KLASA "LAUNCHER" (klient)
import java. io.IOException;
import java. io.BufferedReader;
import java. io.InputStreamReader;
import java.net.Socket;
public class Launcher {
public Launcher() {
try {
initiateConnection();
}
catch (Exception exception) {
exception.printStackTrace();
}
}
private void initiateConnection() throws IOException {
Socket socket = new Socket("127.0.0.1", 6700);
InputStreamReader isr = new InputStreamReader(socket.getInputStream());
BufferedReader br = new BufferedReader(isr);
System.out.println(br.readLine());
br.close();
}
}
KLASA "LAUNCHER" (serwer)
import java. io.IOException;
import java. io.PrintWriter;
import java.net.Socket;
import java.net.ServerSocket;
public class Launcher {
public Launcher() {
try {
handleConnections();
}
catch (Exception exception) {
exception.printStackTrace();
}
}
private void handleConnections() throws IOException {
ServerSocket ss = new ServerSocket(6700);
while (true) {
Socket socket = ss.accept();
PrintWriter pw = new PrintWriter(socket.getOutputStream());
pw.println("Połączenie z serwerem zostało nawiązane.");
pw.close();
}
}
}
Musicie dostrzec jedną bardzo ważną rzecz! Podane numery portów muszą być ze sobą zgodne (w naszym przypadku to jest 6700)!!! Ponadto wysoce zalecanym jest, aby był on powyżej 1023. Dlaczego konkretnie tyle, to zajmiemy się tym na końcu. "Socket" w języku Java jest po stronie klienta, a serwer dodatkowo musi być zaopatrzony w gniazdo o nieco odmiennej nazwie "ServerSocket", który będzie odpowiedzialny za "przyjmowanie" klientów i nawiązywanie z nimi połączenia.
Aby zaobserwować prawidłowe działanie tej operacji, musicie najpierw skompilować i uruchomić program serwera, a potem NIE PRZERYWAJĄC DZIAŁANIA SERWERA, skompilować sobie program klienta i go uruchomić. Dopiero wtedy można liczyć na oczekiwane połączenie klienta z serwerem.
Traktujcie to jako bardzo gigantyczne uproszczenie operacji nawiązywania połączenia. Takie manewry wymagają wielu innych usprawnień jak na przykład działanie współbieżne. W chwili obecnej, nasz serwer jest w stanie "obsłużyć" tylko jednego klienta w tym samym czasie. W praktyce, żaden inny klient nie może nawiązać połączenia, dopóki obecny klient który się podłączył, nie zakończy swojego połączenia. Gdyby takie działanie wypuścić na rynek, okazałoby się ono bezużyteczne, dlatego też wprowadza się do takich spraw osobne "wątki", które mają na celu egzekwowanie wielu czynności w tym samym czasie. Ten przypadek idealnie pasuje, aby zastosować w nim wątki.
Zrzut ekranu ukazujący pomyślne nawiązanie połączenia z serwerem lokalnym w języku Java poprzez obiekt "Socket" klienta, który otrzymał odpowiedź w formie komunikatu.
WYTŁUMACZENIE PORTU PROTOKOŁU "NA CHŁOPSKI ROZUM"
Zanim zakończę temat, trzeba jeszcze zrozumieć sens wstawianej liczby za parametr instancji klasy "Socket", gdyż są pewne zasady jakie trzeba przestrzegać. Gdy chcemy podjąć próbę nawiązania połączenia z serwerem, znajomość samego adresu IP nie wystarczy. Potrzebna jest jeszcze jedna informacja liczbowa. I to nie byle jaka. Ona musi być dodatnia i trzymająca się pewnego przedziału. Nie można podać -1. Liczba 80? Niby tak, ale to jest przeznaczone dla serwerów HTTP. Jak podamy zbyt dużą, też jest niedobrze. No to jaka ta liczba musi być?
Port protokołu to liczba całkowita dodatnia, która pozwala serwerowi ustalić z jaką aplikacją klient próbuje nawiązać połączenie. Niemniej ważną wiadomością od tej jest taka, że ta liczba musi być 16-bitowa, czyli teoretycznie wspierane numery muszą znaleźć się w przedziale od 0 do 65,535! Przecież przykładowo serwery obsługujące pocztę zwane jako POP3 czy też serwery transferów plików FTP również muszą mieć swoje "miejsce". Zajęte porty są już dostosowane do pewnych wymagań i one "wiedzą" jak obsługiwać takie połączenie gdy poda się taki, a nie inny port. Z tego względu nie wolno wprowadzać byle jakiego numerka!
PORT MUSI SPEŁNIAĆ PEWNE WARUNKI
Kolejna rzecz to przestrzeganie pewnego kryterium. Wspomniałem o przedziale <0; 65,535>. To jest tylko teoretyczny przedział wynikający z zakresu szesnastobitowej liczby w systemie binarnym (216 - 1). Port protokołu musi dodatkowo być większy od 1023. Dlaczego akurat tyle? Bo wszystkie mniejsze porty zostały już zajęte przez usługi powszechnie wykorzystywane przez firmy i klientów. Poniżej została zamieszczona tabelka ukazujące przykładowe numery portów jakie powinny być wykorzystywane dla określonych celów. Co więcej, programując usługi w jakiejś firmie zalecane jest skontaktowanie się z administratorem systemu, celem zdobycia informacji które jeszcze inne porty zostały zarezerwowane.
Oto małe zestawienie niektórych powszechnie wykorzystywanych usług mających zarezerwowany port protokołu:
NUMER PORTU | USŁUGA |
20 | File Transfer Protocol (tryb aktywny) |
21 | File Transfer Protocol (tryb pasywny) |
22 | Secure Shell |
23 | Telnet |
25 | Simple Mail Transfer Protocol |
53 | Domain Name System |
67 | Dynamic Host Configuration Protocol (serwer) |
68 | Dynamic Host Configuration Protocol (klient) |
Tych usług jest dużo, dużo więcej, natomiast chciałem nakreślić tylko niektóre z nich. To miało na celu przybliżenie Wam istoty portu protokołu oraz zasad bezpośrednio z nim związanych. Teraz będziecie wiedzieć, że jak konstruktor klasy "Socket" w języku Java wymaga podania pewnego portu, to nie można dać byle czego. Po pierwsze, musi przestrzegać kryteriów wymienionych powyżej, a po drugie, musi być zgodny zarówno po stronie serwera, jak i klienta. Tyle!
Na sam początek tyle informacji wystarczy. Pamiętajcie na odchodne, że każdy strumień należy zamknąć korzystając z metody "close". Bardzo łatwo o tym zapomnieć, a nie radzę tego lekceważyć. Dziękuję za przeczytanie.