Wprowadzenie: „Na stole stał komputer, za nim koder…” Pewnego razu jeden z moich kolegów zamieścił kolejny wynik swojej nauki Javy w postaci zrzutu ekranu nowego programu. Ten program był czatem dla wielu użytkowników. W tamtym czasie dopiero zaczynałem własną przygodę z programowaniem w tym języku, ale zdecydowanie stwierdziłem sobie, że „chcę tego!” Czas mijał i po zakończeniu pracy nad kolejnym projektem w ramach pogłębiania wiedzy programistycznej przypomniałem sobie tamto wydarzenie i stwierdziłem, że już czas. Jakoś zacząłem już zgłębiać ten temat z czystej ciekawości, ale w moim głównym podręczniku Java (był to kompletny podręcznik Schildta) podano tylko 20 stron pakietu java.net. Jest to zrozumiałe – książka jest już bardzo obszerna. Były tam tablice metod i konstruktorów klas głównych, ale to wszystko. Następnym krokiem jest oczywiście wszechmocny Google: niezliczona ilość różnych artykułów, w których prezentowane jest to samo – dwa, trzy słowa o gniazdach i gotowy przykład. Klasyczne podejście (przynajmniej w moim stylu studiów) polega na tym, aby najpierw zrozumieć, jakich narzędzi potrzebuję do pracy, czym są, po co są potrzebne, a dopiero potem, jeśli rozwiązanie problemu nie jest oczywiste, majstrować przy gotowych -robiłem zestawienia, odkręcając nakrętki i śruby. Ale zorientowałem się, co jest co i ostatecznie napisałem czat dla wielu użytkowników. Na zewnątrz wyglądało to mniej więcej tak: Tutaj postaram się wyjaśnić podstawy aplikacji klient-serwer opartych na gniazdach Java na przykładzie projektowania czatu. Na kursie Javarash będziesz prowadzić czat. Będzie na zupełnie innym poziomie, piękny, duży, wielofunkcyjny. Ale przede wszystkim zawsze trzeba położyć fundament, więc tutaj musimy dowiedzieć się, co leży u podstaw takiej sekcji. (Jeśli znajdziesz jakieś niedociągnięcia lub błędy pisz na PW lub w komentarzu pod artykułem). Zaczynajmy. Głowa pierwsza: „Dom, który…” Aby wyjaśnić, w jaki sposób powstaje połączenie sieciowe między serwerem a jednym klientem, weźmy klasyczny już przykład budynku mieszkalnego. Załóżmy, że klient musi w jakiś sposób nawiązać połączenie z określonym serwerem. Co poszukiwacz musi wiedzieć o wyszukiwanym obiekcie? Tak, adres. Serwer nie jest magicznym bytem w chmurze i dlatego musi być zlokalizowany na konkretnej maszynie. Analogicznie do domu, w którym powinno odbywać się spotkanie dwóch uzgodnionych stron. A żeby odnaleźć się w apartamentowcu, nie wystarczy jeden adres budynku, trzeba podać numer mieszkania, w którym odbędzie się spotkanie. Podobnie na jednym komputerze może znajdować się jednocześnie kilka serwerów i aby klient mógł skontaktować się z konkretnym, musi także podać numer portu, przez który nastąpi połączenie. A więc adres i numer portu. Adres oznacza identyfikator maszyny w przestrzeni internetowej. Może to być nazwa domeny, na przykład „javarush.ru” lub zwykły adres IP. Port- unikalny numer, z którym jest skojarzone dane gniazdo (termin ten zostanie omówiony później), innymi słowy jest ono zajęte przez określoną usługę, dzięki czemu można się z nim skontaktować. Żeby więc na terytorium jednego (serwera) spotkały się co najmniej dwa obiekty, właściciel obszaru (serwera) musi zajmować na nim określone mieszkanie (port) (samochód), a drugi musi znaleźć miejsce spotkania znając adres domu (domena lub ip) i numer mieszkania (port). Głowa druga: Poznaj Socket Wśród pojęć i terminów związanych z pracą w sieci bardzo ważnym jest Socket. Oznacza punkt, przez który następuje połączenie. Mówiąc najprościej, gniazdo łączy dwa programy w sieci. Klasa
Socket
implementuje ideę gniazda. Klient i serwer będą się komunikować poprzez swoje kanały wejścia/wyjścia: ta klasa jest deklarowana po stronie klienta, a serwer odtwarza ją, otrzymując sygnał połączenia. Tak właśnie działa komunikacja online. Na początek oto możliwe konstruktory klas Socket
:
Socket(String Nazwa_хоста, int порт) throws UnknownHostException, IOException
Socket(InetAddress IP-adres, int порт) throws UnknownHostException
„nazwa_hosta” – oznacza konkretny węzeł sieciowy, adres IP. Jeśli klasa gniazda nie będzie mogła przekonwertować go na rzeczywisty, istniejący adres, zostanie zgłoszony wyjątek UnknownHostException
. Port jest portem. Jeśli jako numer portu podano 0, system sam przydzieli wolny port. Wyjątek może również wystąpić w przypadku utraty połączenia IOException
. Należy zauważyć, że typem adresu w drugim konstruktorze jest InetAddress
. Przychodzi z pomocą np. wtedy, gdy jako adres trzeba podać nazwę domeny. Ponadto, gdy domena oznacza kilka adresów IP, InetAddress
możesz ich użyć, aby uzyskać ich tablicę. Jednak działa również z IP. Możesz także uzyskać nazwę hosta, tablicę bajtów tworzącą adres IP itp. Porozmawiamy o tym nieco szerzej, ale aby uzyskać szczegółowe informacje, musisz przejść do oficjalnej dokumentacji. Po zainicjowaniu obiektu typu Socket
klient, do którego należy, ogłasza w sieci, że chce połączyć się z serwerem pod określonym adresem i numerem portu. Poniżej znajdują się najczęściej stosowane metody klasy Socket
: InetAddress getInetAddress()
– zwraca obiekt zawierający dane o gnieździe. Jeżeli gniazdo nie jest połączone - null int getPort()
- zwraca port, na którym następuje połączenie z serwerem int getLocalPort()
- zwraca port, z którym gniazdo jest powiązane. Faktem jest, że klient i serwer mogą „komunikować się” na jednym porcie, ale porty, do których są przywiązani, mogą być zupełnie inne boolean isConnected()
- zwraca wartość true, jeśli połączenie zostanie nawiązane void connect(SocketAddress adres)
- wskazuje nowe połączenie boolean isClosed()
- zwraca wartość true, jeśli gniazdo jest zamknięte boolean isBound()
- zwraca wartość true, jeśli gniazdo jest faktycznie powiązane z adresem, klasa Socket
implementuje interfejs AutoCloseable
, dzięki czemu można go używać w try-with-resources
. Można jednak zamknąć gniazdo także w klasyczny sposób, używając funkcji Close(). Głowa trzecia: a to jest ServerSocket. Powiedzmy, że zadeklarowaliśmy w formie klasy Socket
żądanie połączenia po stronie klienta. Jak serwer odgadnie nasze życzenie? W tym celu serwer ma klasę taką jak ServerSocket
i metodę Accept(). Poniżej przedstawiono jego konstruktorów:
ServerSocket() throws IOException
ServerSocket(int порт) throws IOException
ServerSocket(int порт, int максимум_подключений) throws IOException
ServerSocket(int порт, int максимум_подключений, InetAddress локальный_adres) throws IOException
Podczas deklaracji ServerSocket
nie trzeba podawać adresu połączenia, ponieważ komunikacja odbywa się na serwerze. Tylko w przypadku hosta wielokanałowego należy określić, z którym adresem IP jest powiązane gniazdo serwera. Głowa trzecia. Pierwsza: Serwer, który mówi nie Ponieważ zapewnienie programowi większej ilości zasobów, niż potrzebuje, jest zarówno kosztowne, jak i nieuzasadnione, dlatego w konstruktorze ServerSocket
jesteś proszony o zadeklarowanie maksymalnej liczby połączeń akceptowanych przez serwer podczas pracy. Jeśli nie zostanie to określone, domyślnie liczba ta zostanie uznana za równą 50. Tak, teoretycznie możemy założyć, że ServerSocket
jest to to samo gniazdo, tylko dla serwera. Pełni jednak zupełnie inną rolę niż klasa Socket
. Jest to potrzebne jedynie na etapie tworzenia połączenia. Po utworzeniu obiektu typu ServerSocket
musisz dowiedzieć się, że ktoś chce połączyć się z serwerem. Metoda Accept() jest tutaj połączona. Cel czeka, aż ktoś będzie chciał się z nim połączyć, a gdy to nastąpi, zwraca obiekt typu Socket
, czyli odtworzone gniazdo klienta. Teraz, gdy po stronie serwera utworzono gniazdo klienta, można rozpocząć dwukierunkową komunikację. Utworzenie obiektu typu Socket
po stronie klienta i ponowne utworzenie go po ServerSocket
stronie serwera to minimum wymagane do połączenia. Głowa czwarta: List do Świętego Mikołaja Вопрос:
Jak dokładnie komunikują się klient i serwer? Ответ:
Poprzez strumienie we/wy. Co już mamy? Gniazdo z adresem serwera i numerem portu klienta i to samo, dzięki funkcji Accept(), po stronie serwera. Rozsądnie jest więc założyć, że będą się komunikować za pośrednictwem gniazda. Aby to zrobić, istnieją dwie metody, które dają dostęp do strumieni InputStream
i OutputStream
obiektów typu Socket
. Tutaj są:
InputStream getInputStream()
OutputStream getOutputStream()
Ponieważ odczytywanie i zapisywanie samych bajtów nie jest tak wydajne, strumienie można owijać w klasy adapterów, buforowane lub nie. Na przykład:
BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
BufferedWriter out = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
Aby komunikacja była dwukierunkowa, operacje te muszą być wykonywane po obu stronach. Teraz możesz wysłać coś za pomocą wejścia i odebrać coś za pomocą wyjścia i odwrotnie. Właściwie jest to praktycznie jedyna funkcja klasy Socket
. I tak, nie zapomnij o metodzie Flush() BufferedWriter
- opróżnia ona zawartość bufora. Jeśli nie zostanie to zrobione, informacje nie zostaną przesłane, a zatem nie zostaną odebrane. Wątek odbierający również czeka na wskaźnik końca linii – „\n”, w przeciwnym razie wiadomość nie zostanie przyjęta, ponieważ w rzeczywistości wiadomość nie jest ukończona i nie jest kompletna. Jeśli wydaje Ci się to niewygodne, nie martw się, zawsze możesz użyć klasy PrintWriter
, którą należy zakończyć, podać true jako drugi argument, a wtedy wyskoczenie z bufora nastąpi automatycznie:
PrintWriter out = new PrintWriter(new BufferedWriter(new OutputStreamWriter(socket.getOutputStream())), true);
Nie ma też potrzeby wskazywania końca linii, ta klasa zrobi to za Ciebie. Ale czy ciągi we/wy stanowią granicę możliwości gniazda? Nie, czy chcesz wysyłać obiekty poprzez strumienie gniazd? Na litość Boską. Serializuj je i gotowe:
ObjectOutputStream out = new ObjectOutputStream(socket.getOutputStream());
ObjectInputStream in = new ObjectInputStream(socket.getInputStream());
Głowa piąta: Prawdziwa komunikacja przez Internet Bo żeby połączyć się przez prawdziwą sieć z prawdziwym adresem IP trzeba mieć pełnoprawny serwer, a ponieważ:
- Nasz przyszły czat jako narzędzie nie ma takich możliwości. Może jedynie nawiązać połączenie i odebrać/wysłać wiadomość. Oznacza to, że nie ma rzeczywistych możliwości serwera.
- Nasz serwer, zawierający jedynie dane gniazdowe i strumienie I/O, nie może działać jako prawdziwy serwer WEB lub FTP, wówczas tylko z tym nie będziemy mogli połączyć się przez Internet.
Socket
że adres jest lokalny, istnieją 2 sposoby:
- Wpisz „localhost” jako argument adresu, co oznacza lokalny odcinek pośredni. Nadaje się do tego również „127.0.0.1” - to po prostu cyfrowa forma kodu pośredniczącego.
- Korzystanie z adresu Inet:
InetAddress.getByName(null)
- punkty zerowe dla localhostInetAddress.getByName("localhost")
InetAddress.getByName("127.0.0.1")
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
public class Server {
private static Socket clientSocket; // gniazdo do komunikacji
private static ServerSocket server; // gniazdo serwera
private static BufferedReader in; // strumień odczytu gniazda
private static BufferedWriter out; // strumień zapisu gniazda
public static void main(String[] args) {
try {
try {
server = new ServerSocket(4004); // gniazdo serwera nasłuchuje na porcie 4004
System.out.println("Serwer działa!"); // serwer by się przydał
// ogłosić swój start
clientSocket = server.accept(); // accept() będzie czekać do
//ktoś nie chce się połączyć
try { // po nawiązaniu połączenia i odtworzeniu gniazda do komunikacji z klientem można przejść
// aby utworzyć strumienie we/wy.
// teraz możemy odbierać wiadomości
in = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));
// i wyślij
out = new BufferedWriter(new OutputStreamWriter(clientSocket.getOutputStream()));
String word = in.readLine(); // poczekaj, aż klient coś do nas napisze
System.out.println(word);
// bez wahania odpowiada klientowi
out.write("Cześć, tu serwer! Potwierdzam, że napisałeś: " + word + "\n");
out.flush(); // wypchnij wszystko z bufora
} finally { // w każdym przypadku gniazdo zostanie zamknięte
clientSocket.close();
// strumienie również dobrze byłoby zamknąć
in.close();
out.close();
}
} finally {
System.out.println("Serwer zamknięty!");
server.close();
}
} catch (IOException e) {
System.err.println(e);
}
}
„Klient.java”
import java.io.*;
import java.net.Socket;
public class Client {
private static Socket clientSocket; // gniazdo do komunikacji
private static BufferedReader reader; // potrzebujemy czytnika, który czyta z konsoli, w przeciwnym razie
// czy wiemy, co klient chce powiedzieć?
private static BufferedReader in; // strumień odczytu gniazda
private static BufferedWriter out; // strumień zapisu gniazda
public static void main(String[] args) {
try {
try {
// adres - host lokalny, port - 4004, taki sam jak serwer
clientSocket = new Socket("localhost", 4004); // tą linią żądamy
// serwer ma dostęp do połączenia
reader = new BufferedReader(new InputStreamReader(System.in));
// odczytanie wiadomości z serwera
in = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));
// napisz tam
out = new BufferedWriter(new OutputStreamWriter(clientSocket.getOutputStream()));
System.out.println(„Czy masz coś do powiedzenia? Wpisz to tutaj:”);
// jeśli połączenie nastąpiło i wątki zostały pomyślnie utworzone - możemy
// pracuj dalej i zaoferuj klientowi coś do wpisania
// jeśli nie, zostanie zgłoszony wyjątek
String word = reader.readLine(); // poczekaj, aż klient coś zrobi
// nie zapisze do konsoli
out.write(word + "\n"); // wyślij wiadomość do serwera
out.flush();
String serverWord = in.readLine(); // poczekaj, aż serwer powie
System.out.println(serverWord); // otrzymano - wyświetl
} finally { // w każdym razie musisz zamknąć gniazdo i strumienie
System.out.println("Klient został zamknięty...");
clientSocket.close();
in.close();
out.close();
}
} catch (IOException e) {
System.err.println(e);
}
}
}
Oczywiście należy najpierw uruchomić serwer, bo z czym klient będzie się łączył przy starcie, jeśli nie ma czegoś, co go połączy? :) Wynik będzie następujący: /* Czy chciałeś coś powiedzieć? Wpisz go tutaj: Witaj, serwerze? Czy mnie słyszysz? Witam, tu serwer! Potwierdzam, napisałeś: Halo, serwer? Czy mnie słyszysz? Klient został zamknięty... */ Hurra! Nauczyliśmy serwer komunikować się z klientem! Aby komunikacja odbywała się nie w dwóch replikach, ale w dowolnej liczbie, po prostu zawiń odczyt i zapis wątków w pętlę while (true) i wskaż dla wyjścia, że zgodnie z określonym komunikatem, na przykład „wyjdź” , cykl został przerwany i program się zakończył. Head Seven: Lepiej jest korzystać z wielu użytkowników.Fakt , że serwer nas słyszy, jest dobry, ale byłoby znacznie lepiej, gdybyśmy mogli komunikować się z kimś z naszego gatunku. Wszystkie źródła załączę na końcu artykułu, więc tutaj pokażę nie zawsze duże, ale ważne fragmenty kodu, które przy prawidłowym użyciu umożliwią utworzenie czatu dla wielu użytkowników. Chcemy więc móc komunikować się z innym klientem za pośrednictwem serwera. Jak to zrobić? Oczywiście, ponieważ program kliencki ma swoją własną metodę main
, oznacza to, że można go uruchomić niezależnie od serwera i równolegle z innymi klientami. Co nam to daje? W jakiś sposób konieczne jest, aby przy każdym nowym połączeniu serwer nie od razu nawiązał komunikację, ale zapisał to połączenie na jakiejś liście i zaczął czekać na nowe połączenie, a w komunikacji z konkretnym konkretnym połączeniem zaangażowana jest jakaś usługa pomocnicza klient. A klienci muszą pisać do serwera i czekać na odpowiedź niezależnie od siebie. Na ratunek przychodzą nici. Załóżmy, że mamy klasę odpowiedzialną za zapamiętywanie nowych połączeń: Powinna ona zawierać następujące elementy:
- Numer portu.
- Lista, na której zapisuje nowe połączenie.
- I
ServerSocket
w jednym (!) egzemplarzu.
public class Server {
public static final int PORT = 8080;
public static LinkedList<ServerSomthing> serverList = new LinkedList<>(); // lista wszystkich wątków
public static void main(String[] args) throws IOException {
ServerSocket server = new ServerSocket(PORT);
try {
while (true) {
// Blokuje do nawiązania nowego połączenia:
Socket socket = server.accept();
try {
serverList.add(new ServerSomthing(socket)); // dodaj nowe połączenie do listy
} catch (IOException e) {
// Jeśli się nie powiedzie, gniazdo jest zamykane,
// w przeciwnym razie wątek zamknie go po zakończeniu:
socket.close();
}
}
} finally {
server.close();
}
}
}
OK, teraz każde odtworzone gniazdo nie zostanie utracone, ale zostanie zapisane na serwerze. Dalej. Każdy klient potrzebuje kogoś, kto go wysłucha. Stwórzmy wątek z funkcjami serwera z poprzedniego rozdziału.
class ServerSomthing extends Thread {
private Socket socket; // gniazdo, przez które serwer komunikuje się z klientem,
// poza tym - klient i serwer nie są w żaden sposób połączone
private BufferedReader in; // strumień odczytu gniazda
private BufferedWriter out; // strumień zapisu gniazda
public ServerSomthing(Socket socket) throws IOException {
this.socket = socket;
// если потоку ввода/вывода приведут к генерированию исключения, оно пробросится дальше
in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
out = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
start(); // wywołanie run()
}
@Override
public void run() {
String word;
try {
while (true) {
word = in.readLine();
if(word.equals("stop")) {
break; }
for (ServerSomthing vr : Server.serverList) {
vr.send(word); // wyślij otrzymaną wiadomość za pomocą
// związał klienta ze wszystkimi innymi, w tym z nim
}
}
} catch (IOException e) {
}
}
private void send(String msg) {
try {
out.write(msg + "\n");
out.flush();
} catch (IOException ignored) {}
}
}
Zatem w konstruktorze wątku serwera należy zainicjować gniazdo, przez które wątek będzie komunikował się z konkretnym klientem. Również wątki we/wy i wszystko inne, czego potrzebujesz, aby rozpocząć wątek bezpośrednio od konstruktora. OK, ale co się stanie, gdy wątek serwera odczyta wiadomość od klienta? Odesłać tylko do swojego klienta? Mało skuteczny. Prowadzimy czat dla wielu użytkowników, więc każdy podłączony klient musi otrzymać to, co napisała jedna osoba. Musisz skorzystać z listy wszystkich wątków serwera powiązanych z ich klientami i każdą wysłaną wiadomość wysłać do konkretnego wątku, aby wysłał ją do swojego klienta:
for (ServerSomthing vr : Server.serverList) {
vr.send(word); // wyślij otrzymaną wiadomość
// od połączonego klienta do wszystkich innych, w tym do niego
}
private void send(String msg) {
try {
out.write(msg + "\n");
out.flush();
} catch (IOException ignored) {}
}
Teraz wszyscy klienci będą wiedzieć, co powiedział jeden z nich! Jeśli nie chcesz, aby wiadomość została wysłana do osoby, która ją wysłała (on już wie, co napisał!), po prostu iterując po wątkach określ, że podczas przetwarzania obiektu pętla this
przejdzie do następnego elementu bez wykonywania jakiekolwiek działania na ten temat. Lub, jeśli wolisz, wyślij wiadomość do klienta z informacją, że wiadomość została pomyślnie odebrana i wysłana. Z serwerem wszystko jest już jasne. Przejdźmy do klienta, a raczej do klientów! Wszystko jest tam takie samo, analogicznie do klienta z poprzedniego rozdziału, tylko przy tworzeniu instancji trzeba, jak pokazano w tym rozdziale z serwerem, stworzyć wszystko, co niezbędne w konstruktorze. Co jednak w sytuacji, gdy tworząc klienta nie zdążył jeszcze niczego wpisać, a coś zostało już do niego wysłane? (Na przykład historia korespondencji tych, którzy już przed nim połączyli się z czatem). Trzeba więc oddzielić cykle, w których będą przetwarzane wysłane wiadomości, od tych, w których wiadomości są odczytywane z konsoli i wysyłane na serwer w celu przekazania innym. Z pomocą znów przychodzą wątki. Tworzenie klienta jako wątku nie ma sensu. Wygodniej jest zrobić wątek z pętlą w metodzie run, która czyta wiadomości, a także analogicznie pisze:
// wątek odczytuje komunikaty z serwera
private class ReadMsg extends Thread {
@Override
public void run() {
String str;
try {
while (true) {
str = in.readLine(); // oczekiwanie na wiadomość od serwera
if (str.equals("stop")) {
break; // wyjdź z pętli, jeśli jest to „stop”
}
}
} catch (IOException e) {
}
}
}
// wątek wysyłający komunikaty przychodzące z konsoli do serwera
public class WriteMsg extends Thread {
@Override
public void run() {
while (true) {
String userWord;
try {
userWord = inputUser.readLine(); // wiadomości z konsoli
if (userWord.equals("stop")) {
out.write("stop" + "\n");
break; // wyjdź z pętli, jeśli jest to „stop”
} else {
out.write(userWord + "\n"); // wyślij na serwer
}
out.flush(); // posprzątać
} catch (IOException e) {
}
}
}
}
W konstruktorze klienta wystarczy uruchomić te wątki. Jak prawidłowo zamknąć zasoby klienta, jeśli chce odejść? Czy muszę zamykać zasoby wątku serwera? Aby to zrobić, najprawdopodobniej będziesz musiał utworzyć osobną metodę, która będzie wywoływana przy wyjściu z pętli komunikatów. Tam będziesz musiał zamknąć gniazdo i strumienie we/wy. Ten sam sygnał zakończenia sesji dla konkretnego klienta musi zostać wysłany do jego wątku serwera, który musi zrobić to samo ze swoim gniazdem i usunąć się z listy wątków w głównej klasie serwera. Head Eight: Perfekcja nie ma ograniczeń. Możesz bez końca wymyślać nowe funkcje, aby ulepszyć swój projekt. Ale co dokładnie należy przenieść do nowo podłączonego klienta? Myślę, że ostatnie dziesięć wydarzeń miało miejsce przed jego przybyciem. W tym celu należy utworzyć klasę, w której ostatnia akcja z dowolnym wątkiem serwera zostanie wpisana na zadeklarowaną listę, a jeśli lista jest już pełna (czyli jest ich już 10), usuń pierwszą i dodaj ostatni, który przyszedł. Aby zawartość tej listy mogła zostać odebrana przez nowe połączenie, tworząc wątek serwera, należy w strumieniu wyjściowym wysłać je do klienta. Jak to zrobić? Na przykład tak:
public void printStory(BufferedWriter writer) {
// ...
}
Wątek serwera utworzył już strumienie i może przekazać strumień wyjściowy jako argument. Następnie wystarczy przekazać w cyklu wyszukiwania wszystko, co należy przenieść do nowego klienta. Wniosek: To tylko podstawy i najprawdopodobniej ta architektura czatu nie będzie działać podczas tworzenia prawdziwej aplikacji. Program ten powstał w celach edukacyjnych i na jego podstawie pokazałem jak można zmusić klienta do komunikacji z serwerem (i odwrotnie), jak to zrobić dla kilku połączeń i oczywiście jak jest to zorganizowane na gniazdach. Poniżej uporządkowano źródła oraz załączono kod źródłowy analizowanego programu. To moje pierwsze doświadczenie z pisaniem artykułu) Dziękuję za uwagę :)
- Myślenie w Java Enterprise, Bruce Eckel i in. Glin. 2003
- Java 8, Kompletny przewodnik, Herbert Schildt, wydanie 9, 2017 (rozdział 22)
- Programowanie gniazd w języku Java, artykuł o gniazdach
- Gniazdo w oficjalnej dokumentacji
- ServerSocket w oficjalnej dokumentacji
- źródła na GitHubie
GO TO FULL VERSION