JavaRush /Blog Java /Random-PL /Klasy Socket i ServerSocket lub „Witaj, serwerze? Czy mni...
Sergey Simonov
Poziom 36
Санкт-Петербург

Klasy Socket i ServerSocket lub „Witaj, serwerze? Czy mnie słyszysz?"

Opublikowano w grupie Random-PL
Wprowadzenie: „Na stole stał komputer, za nim koder…” Klasy Socket i ServerSocket lub „Witaj, serwerze?  Czy mnie słyszysz?"  - 1 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: Klasy Socket i ServerSocket lub „Witaj, serwerze?  Czy mnie słyszysz?"  - 2Tutaj 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 Socketimplementuje ideę gniazda. Klient i serwer będą się komunikować poprzez swoje kanały wejścia/wyjścia: Klasy Socket i ServerSocket lub „Witaj, serwerze?  Czy mnie słyszysz?"  - 3 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, InetAddressmoż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 Socketklient, 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 Socketimplementuje 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 ServerSocketi 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 ServerSocketjesteś 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 ServerSocketjest 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 ServerSocketmusisz 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 Socketpo stronie klienta i ponowne utworzenie go po ServerSocketstronie 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 InputStreami OutputStreamobiektó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ż:
  1. 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.
  2. 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.
A poza tym dopiero zaczynamy rozwijać program, co oznacza, że ​​nie jest on na tyle stabilny, aby od razu współpracować z prawdziwą siecią, dlatego jako adres połączenia wykorzystamy hosta lokalnego. Oznacza to, że teoretycznie klient i serwer nadal nie będą połączone w żaden sposób, chyba że przez gniazdo, ale w celu debugowania programu będą na tej samej maszynie, bez prawdziwego kontaktu przez sieć. Aby wskazać w konstruktorze, Socketże adres jest lokalny, istnieją 2 sposoby:
  1. 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.
  2. Korzystanie z adresu Inet:
    1. InetAddress.getByName(null)- punkty zerowe dla localhost
    2. InetAddress.getByName("localhost")
    3. InetAddress.getByName("127.0.0.1")
Dla uproszczenia użyjemy „localhost” typu String. Ale wszystkie inne opcje są również wykonalne. Head Six: Czas na rozmowę Mamy więc już wszystko co potrzebne do realizacji sesji konwersacyjnej z serwerem. Pozostaje tylko złożyć to w całość: Poniższy list pokazuje, jak klient łączy się z serwerem, wysyła mu jedną wiadomość, a serwer z kolei potwierdza, że ​​otrzymał wiadomość, używając jej jako argumentu w swoim: „Server. Jawa"
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:
  1. Numer portu.
  2. Lista, na której zapisuje nowe połączenie.
  3. I ServerSocketw 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 thisprzejdzie 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ę :)
  1. Myślenie w Java Enterprise, Bruce Eckel i in. Glin. 2003
  2. Java 8, Kompletny przewodnik, Herbert Schildt, wydanie 9, 2017 (rozdział 22)
  3. Programowanie gniazd w języku Java, artykuł o gniazdach
  4. Gniazdo w oficjalnej dokumentacji
  5. ServerSocket w oficjalnej dokumentacji
  6. źródła na GitHubie
Komentarze
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION