JavaRush /Java-Blog /Random-DE /Die Klassen Socket und ServerSocket oder „Hallo, Server?“...
Sergey Simonov
Level 36
Санкт-Петербург

Die Klassen Socket und ServerSocket oder „Hallo, Server?“ Hörst du mich?"

Veröffentlicht in der Gruppe Random-DE
Einleitung: „Auf dem Tisch stand ein Computer, dahinter ein Encoder …“ Es war Die Klassen Socket und ServerSocket oder „Hallo, Server?“  Hörst du mich?"  - 1 einmal, als einer meiner Klassenkameraden ein weiteres Ergebnis seines Java-Studiums in Form eines Screenshots eines neuen Programms veröffentlichte. Dieses Programm war ein Mehrbenutzer-Chat. Zu dieser Zeit begann ich gerade meine eigene Reise, das Programmieren in dieser Sprache zu beherrschen, aber ich habe mir definitiv gemerkt: „Ich will es!“ Die Zeit verging und nachdem ich im Zuge der Vertiefung meiner Programmierkenntnisse mit der Arbeit am nächsten Projekt fertig war, erinnerte ich mich an diesen Vorfall und beschloss, dass es an der Zeit war. Irgendwie habe ich schon aus reiner Neugier begonnen, mich mit diesem Thema zu befassen, aber in meinem Haupt-Java-Lehrbuch (es war Schildts komplettes Handbuch) waren nur 20 Seiten für das java.net-Paket vorgesehen. Das ist verständlich – das Buch ist schon sehr umfangreich. Es gab Tabellen mit Methoden und Konstruktoren der Hauptklassen, aber das ist alles. Der nächste Schritt ist natürlich das allmächtige Google: Unzählige verschiedene Artikel, in denen das Gleiche vorgestellt wird – zwei, drei Worte über Steckdosen und ein fertiges Beispiel. Der klassische Ansatz (zumindest in meinem Lernstil) besteht darin, zunächst zu verstehen, was ich von den Werkzeugen für die Arbeit brauche, was sie sind, warum sie benötigt werden, und erst dann, wenn die Lösung des Problems nicht offensichtlich ist, darauf zurückzugreifen die fertigen Auflistungen, das Lösen der Schrauben und Muttern. Aber ich fand heraus, was was war, und schrieb schließlich einen Mehrbenutzer-Chat. Äußerlich stellte sich heraus, dass es ungefähr so ​​aussah: Die Klassen Socket und ServerSocket oder „Hallo, Server?“  Hörst du mich?"  - 2Hier werde ich versuchen, Ihnen die Grundlagen von Client-Server-Anwendungen auf Basis von Java-Sockets am Beispiel des Chat-Designs näher zu bringen. Im Javarash-Kurs werden Sie chatten. Es wird auf einem ganz anderen Niveau sein, schön, groß, multifunktional. Aber zuerst müssen Sie immer den Grundstein legen, also müssen wir hier herausfinden, was einem solchen Abschnitt zugrunde liegt. (Wenn Sie Mängel oder Fehler finden, schreiben Sie uns eine PN oder einen Kommentar unter den Artikel.) Lass uns anfangen. Kopf eins: „Das Haus, das ...“ Um zu erklären, wie eine Netzwerkverbindung zwischen einem Server und einem Client zustande kommt, nehmen wir das mittlerweile klassische Beispiel eines Wohnhauses. Nehmen wir an, ein Client muss irgendwie eine Verbindung zu einem bestimmten Server herstellen. Was muss der Suchende über das Suchobjekt wissen? Ja, die Adresse. Der Server ist keine magische Einheit in der Cloud und muss sich daher auf einem bestimmten Computer befinden. In Analogie zu einem Haus, in dem ein Treffen zweier vereinbarter Parteien stattfinden soll. Und um sich in einem Mehrfamilienhaus wiederzufinden, reicht eine Adresse des Gebäudes nicht aus, Sie müssen die Nummer der Wohnung angeben, in der das Treffen stattfinden soll. Ebenso kann es auf einem Computer mehrere Server gleichzeitig geben, und damit der Client einen bestimmten Server kontaktieren kann, muss er auch die Portnummer angeben, über die die Verbindung hergestellt wird. Also die Adresse und die Portnummer. Eine Adresse bezeichnet eine Kennung einer Maschine im Internetraum. Es kann sich um einen Domänennamen handeln, zum Beispiel „javarush.ru“ , oder um eine normale IP. Hafen- eine eindeutige Nummer, mit der ein bestimmter Socket verknüpft ist (dieser Begriff wird später besprochen), das heißt, er ist von einem bestimmten Dienst belegt, sodass über ihn Kontakt aufgenommen werden kann. Damit sich also mindestens zwei Objekte auf dem Territorium eines (Servers) treffen, muss der Eigentümer des Gebiets (Server) eine bestimmte Wohnung (Hafen) darauf belegen (Auto) und der zweite muss den Treffpunkt wissend finden die Adresse des Hauses (Domain oder IP) und die Wohnungsnummer (Port). Kapitel zwei: Lernen Sie Socket kennen Unter den Konzepten und Begriffen, die sich auf die Arbeit im Netzwerk beziehen, ist Socket ein sehr wichtiges. Es bezeichnet den Punkt, über den die Verbindung erfolgt. Einfach ausgedrückt verbindet ein Socket zwei Programme in einem Netzwerk. Die Klasse Socketimplementiert die Idee eines Sockets. Der Client und der Server kommunizieren über ihre Eingabe-/Ausgabekanäle: Die Klassen Socket und ServerSocket oder „Hallo, Server?“  Hörst du mich?"  - 3 Diese Klasse wird auf der Clientseite deklariert und der Server erstellt sie neu und empfängt ein Verbindungssignal. So funktioniert Online-Kommunikation. Hier sind zunächst die möglichen Klassenkonstruktoren Socket:
Socket(String Name_хоста, int порт) throws UnknownHostException, IOException
Socket(InetAddress IP-Adresse, int порт) throws UnknownHostException
„host_name“ – impliziert einen bestimmten Netzwerkknoten, eine IP-Adresse. Wenn die Socket-Klasse sie nicht in eine reale, vorhandene Adresse konvertieren konnte, wird eine Ausnahme ausgelöst UnknownHostException. Hafen ist ein Hafen. Wird als Portnummer 0 angegeben, vergibt das System selbst einen freien Port. Eine Ausnahme kann auch auftreten, wenn die Verbindung unterbrochen wird IOException. Es ist zu beachten, dass der Adresstyp im zweiten Konstruktor ist InetAddress. Es ist beispielsweise dann hilfreich, wenn Sie einen Domänennamen als Adresse angeben müssen. Wenn eine Domäne mehrere IP-Adressen bedeutet, InetAddresskönnen Sie diese auch verwenden, um ein Array davon zu erhalten. Es funktioniert jedoch auch mit IP. Sie können auch den Hostnamen, das Byte-Array, aus dem die IP-Adresse besteht, usw. abrufen. Wir werden etwas ausführlicher darauf eingehen, aber für weitere Details müssen Sie die offizielle Dokumentation lesen. Wenn ein Objekt vom Typ initialisiert wird Socket, gibt der Client, zu dem es gehört, im Netzwerk bekannt, dass er unter einer bestimmten Adresse und Portnummer eine Verbindung zum Server herstellen möchte. Nachfolgend sind die am häufigsten verwendeten Methoden der Klasse aufgeführt Socket: InetAddress getInetAddress()– Gibt ein Objekt zurück, das Daten über den Socket enthält. Wenn der Socket nicht verbunden ist – null int getPort()– gibt den Port zurück, an dem die Verbindung zum Server erfolgt int getLocalPort()– gibt den Port zurück, an den der Socket gebunden ist. Tatsache ist, dass Client und Server über einen Port „kommunizieren“ können, die Ports, an die sie gebunden sind, jedoch völlig unterschiedlich sein können boolean isConnected()– gibt „true“ zurück, wenn die Verbindung hergestellt wurde void connect(SocketAddress Adresse)– zeigt eine neue Verbindung an boolean isClosed()– gibt „true“ zurück, wenn der Socket geschlossen ist boolean isBound()- gibt true zurück. Wenn der Socket tatsächlich an eine Adresse gebunden ist, implementiert die Klasse Socketdie Schnittstelle AutoCloseable, sodass sie in verwendet werden kann try-with-resources. Sie können einen Socket jedoch auch klassisch mit close() schließen. Kopf drei: Und das ist ein ServerSocket. Nehmen wir an, wir haben in Form einer Klasse Socketeine Verbindungsanforderung auf der Clientseite deklariert. Wie wird der Server unseren Wunsch erraten? Zu diesem Zweck verfügt der Server über eine Klasse wie ServerSocketund die darin enthaltene Methode „accept()“. Seine Konstrukteure werden im Folgenden vorgestellt:
ServerSocket() throws IOException
ServerSocket(int порт) throws IOException
ServerSocket(int порт, int максимум_подключений) throws IOException
ServerSocket(int порт, int максимум_подключений, InetAddress локальный_Adresse) throws IOException
Bei der Deklaration ServerSocket ist die Angabe der Verbindungsadresse nicht erforderlich, da die Kommunikation auf dem Serverrechner stattfindet. Nur bei einem Multi-Channel-Host müssen Sie angeben, an welche IP der Server-Socket gebunden ist. Kopf drei. Eins: Der Server, der Nein sagt. Da es sowohl kostspielig als auch unvernünftig ist, einem Programm mehr Ressourcen zur Verfügung zu stellen, als es benötigt, ServerSocketwerden Sie im Konstruktor aufgefordert, die maximalen Verbindungen anzugeben, die der Server während des Betriebs akzeptiert. Wenn es nicht angegeben ist, wird diese Zahl standardmäßig mit 50 angenommen. Ja, theoretisch können wir davon ausgehen, dass ServerSocketes sich um denselben Socket handelt, nur für den Server. Aber es spielt eine ganz andere Rolle als Klasse Socket. Es wird nur in der Phase der Verbindungserstellung benötigt. Nachdem Sie ein Typobjekt erstellt haben, ServerSocketmüssen Sie herausfinden, dass jemand eine Verbindung zum Server herstellen möchte. Hier ist die Methode „accept()“ angeschlossen. Das Ziel wartet, bis jemand eine Verbindung zu ihm herstellen möchte, und gibt in diesem Fall ein Objekt vom Typ zurück Socket, also einen neu erstellten Client-Socket. Und da nun der Client-Socket auf der Serverseite erstellt wurde, kann die bidirektionale Kommunikation beginnen. Für die Verbindung ist mindestens das Erstellen eines Typobjekts Socketauf der Clientseite und dessen Neuerstellung auf der Serverseite erforderlich. Kopf vier: Brief an den Weihnachtsmann Wie genau kommunizieren Client und Server? Durch I/O-Streams. Was haben wir bereits? Ein Socket mit der Serveradresse und der Portnummer des Clients und das Gleiche, dank Accept(), auf der Serverseite. Man kann also davon ausgehen, dass die Kommunikation über einen Socket erfolgt. Hierzu gibt es zwei Methoden, die Zugriff auf Streams und Objekte vom Typ ermöglichen . Hier sind sie: ServerSocket Вопрос:Ответ:InputStreamOutputStreamSocket
InputStream getInputStream()
OutputStream getOutputStream()
Da das Lesen und Schreiben von bloßen Bytes nicht so effizient ist, können Streams in Adapterklassen eingeschlossen werden, ob gepuffert oder nicht. Zum Beispiel:
BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
BufferedWriter out = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
Damit die Kommunikation bidirektional ist, müssen solche Vorgänge auf beiden Seiten durchgeführt werden. Jetzt können Sie etwas über den Eingang senden und etwas über den Ausgang empfangen und umgekehrt. Tatsächlich ist dies praktisch die einzige Funktion der Klasse Socket. Und ja, vergessen Sie nicht die Methode „flush()“ BufferedWriter– sie löscht den Inhalt des Puffers. Geschieht dies nicht, werden die Informationen nicht übermittelt und somit auch nicht empfangen. Der empfangende Thread wartet auch auf den End-of-Line-Indikator – „\n“, andernfalls wird die Nachricht nicht akzeptiert, da die Nachricht tatsächlich nicht abgeschlossen ist und nicht vollständig ist. Wenn Ihnen das unbequem erscheint, machen Sie sich keine Sorgen, Sie können jederzeit die Klasse verwenden PrintWriter, die umgebrochen werden muss, true als zweites Argument angeben, und dann erfolgt automatisch ein Popup aus dem Puffer:
PrintWriter out = new PrintWriter(new BufferedWriter(new OutputStreamWriter(socket.getOutputStream())), true);
Außerdem ist es nicht erforderlich, das Ende der Zeile anzugeben; diese Klasse übernimmt dies für Sie. Aber ist String-I/O die Grenze dessen, was ein Socket leisten kann? Nein, möchten Sie Objekte über Socket-Streams senden? Um Gottes Willen. Serialisieren Sie sie und schon kann es losgehen:
ObjectOutputStream out = new ObjectOutputStream(socket.getOutputStream());
ObjectInputStream in = new ObjectInputStream(socket.getInputStream());
Kopf fünf: Echte Kommunikation über das Internet. Denn um sich über ein echtes Netzwerk mit einer echten IP-Adresse zu verbinden, muss man über einen vollwertigen Server verfügen, und da:
  1. Unser zukünftiger Chat verfügt als Dienstprogramm nicht über solche Fähigkeiten. Es kann lediglich eine Verbindung aufbauen und eine Nachricht empfangen/senden. Das heißt, es verfügt nicht über echte Serverfunktionen.
  2. Unser Server, der nur Socket-Daten und I/O-Streams enthält, kann nicht als echter WEB- oder FTP-Server funktionieren, dann können wir mit diesem allein keine Verbindung über das Internet herstellen.
Und außerdem fangen wir gerade erst mit der Entwicklung des Programms an, was bedeutet, dass es nicht stabil genug ist, um sofort mit einem echten Netzwerk zu arbeiten, also werden wir den lokalen Host als Verbindungsadresse verwenden. Das heißt, dass Client und Server theoretisch immer noch nicht in irgendeiner Weise außer über einen Socket verbunden sind, aber zum Debuggen des Programms befinden sie sich auf demselben Computer, ohne echten Kontakt über das Netzwerk. Um im Konstruktor anzuzeigen Socket, dass die Adresse lokal ist, gibt es zwei Möglichkeiten:
  1. Schreiben Sie „localhost“ als Adressargument, also einen lokalen Stub. Hierfür eignet sich auch „127.0.0.1“ – dabei handelt es sich lediglich um eine digitale Form eines Stubs.
  2. Verwendung von InetAddress:
    1. InetAddress.getByName(null)- null zeigt auf localhost
    2. InetAddress.getByName("localhost")
    3. InetAddress.getByName("127.0.0.1")
Der Einfachheit halber verwenden wir „localhost“ vom Typ String. Aber auch alle anderen Möglichkeiten sind machbar. Kopf sechs: Es ist Zeit für ein Gespräch. Wir haben also bereits alles, was wir brauchen, um eine Gesprächssitzung mit dem Server zu implementieren. Es bleibt nur noch, es zusammenzusetzen: Die folgende Auflistung zeigt, wie der Client eine Verbindung zum Server herstellt, ihm eine Nachricht sendet und der Server wiederum bestätigt, dass er die Nachricht erhalten hat, indem er sie als Argument in seinem verwendet: „Server. Java"
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;

public class Server {

    private static Socket clientSocket; // Socket für die Kommunikation
    private static ServerSocket server; // Server-Socket
    private static BufferedReader in; // Socket-Lesestream
    private static BufferedWriter out; // Socket-Schreibstream

    public static void main(String[] args) {
        try {
            try  {
                server = new ServerSocket(4004); // Server-Socket überwacht Port 4004
                System.out.println(„Server läuft!“); // Server wäre nett
                // kündigen Sie Ihren Start an
                clientSocket = server.accept(); // Accept() wird warten bis
                //Jemand möchte keine Verbindung herstellen
                try { // Nachdem Sie eine Verbindung hergestellt und den Socket für die Kommunikation mit dem Client neu erstellt haben, können Sie loslegen
                    // um I/O-Streams zu erstellen.
                    // jetzt können wir Nachrichten empfangen
                    in = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));
                    // und senden
                    out = new BufferedWriter(new OutputStreamWriter(clientSocket.getOutputStream()));

                    String word = in.readLine(); // warten, bis der Kunde uns etwas schreibt
                    System.out.println(word);
                    // ohne zu zögern antwortet dem Kunden
                    out.write(„Hallo, das ist der Server! Ich bestätige, dass Sie geschrieben haben:“ + word + "\n");
                    out.flush(); // Alles aus dem Puffer schieben

                } finally { // in jedem Fall wird der Socket geschlossen
                    clientSocket.close();
                    // Streams wären auch schön zu schließen
                    in.close();
                    out.close();
                }
            } finally {
                System.out.println("Server geschlossen!");
                    server.close();
            }
        } catch (IOException e) {
            System.err.println(e);
        }
    }
„Client.java“
import java.io.*;
import java.net.Socket;

public class Client {

    private static Socket clientSocket; // Socket für die Kommunikation
    private static BufferedReader reader; // ansonsten benötigen wir einen Reader, der von der Konsole liest
    // Wissen wir, was der Kunde sagen möchte?
    private static BufferedReader in; // Socket-Lesestream
    private static BufferedWriter out; // Socket-Schreibstream

    public static void main(String[] args) {
        try {
            try {
                // Adresse – lokaler Host, Port – 4004, identisch mit dem Server
                clientSocket = new Socket("localhost", 4004); // mit dieser Zeile fordern wir an
                // Der Server hat Zugriff auf die Verbindung
                reader = new BufferedReader(new InputStreamReader(System.in));
                // Nachrichten vom Server lesen
                in = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));
                // schreibe dort
                out = new BufferedWriter(new OutputStreamWriter(clientSocket.getOutputStream()));

                System.out.println(„Hatten Sie etwas zu sagen? Geben Sie es hier ein:“);
                // Wenn die Verbindung zustande kam und die Threads erfolgreich erstellt wurden, können wir
                // Weiterarbeiten und dem Kunden etwas zum Eintreten anbieten
                // wenn nicht, wird eine Ausnahme ausgelöst
                String word = reader.readLine(); // warten, bis der Client etwas tut
                // schreibt nicht auf die Konsole
                out.write(word + "\n"); // eine Nachricht an den Server senden
                out.flush();
                String serverWord = in.readLine(); // warte, bis der Server es sagt
                System.out.println(serverWord); // empfangen - Anzeige
            } finally { // In jedem Fall müssen Sie den Socket und die Streams schließen
                System.out.println(„Der Client wurde geschlossen…“);
                clientSocket.close();
                in.close();
                out.close();
            }
        } catch (IOException e) {
            System.err.println(e);
        }

    }
}
Natürlich sollten Sie zuerst den Server starten, denn womit verbindet sich der Client beim Start, wenn es nichts gibt, das ihn verbindet? :) Die Ausgabe sieht so aus: /* Wollten Sie etwas sagen? Geben Sie es hier ein: Hallo, Server? Hörst du mich? Hallo, das ist der Server! Ich bestätige, Sie haben geschrieben: Hallo, Server? Hörst du mich? Der Kunde wurde geschlossen... */ Hurra! Wir haben dem Server beigebracht, mit dem Client zu kommunizieren! Damit die Kommunikation nicht in zwei Replikaten erfolgt, sondern in so vielen, wie Sie möchten, wickeln Sie einfach das Lesen und Schreiben von Threads in eine while-Schleife (wahr) ein und geben Sie für den Exit an, dass gemäß einer bestimmten Nachricht beispielsweise „exit“ lautet. , wurde der Zyklus unterbrochen und das Programm beendet. Head Seven: Mehrbenutzer ist besser. Die Tatsache, dass der Server uns hören kann, ist gut, aber es wäre viel besser, wenn wir mit jemandem unseresgleichen kommunizieren könnten. Ich werde alle Quellen am Ende des Artikels anhängen, daher zeige ich hier nicht immer große, aber wichtige Codeteile, die es bei richtiger Verwendung ermöglichen, einen Mehrbenutzer-Chat zu erstellen. Wir möchten also in der Lage sein, über den Server mit einem anderen Client zu kommunizieren. Wie kann man das machen? Da das Client-Programm natürlich über eine eigene Methode verfügt main, bedeutet dies, dass es separat vom Server und parallel zu anderen Clients gestartet werden kann. Was bringt uns das? Irgendwie ist es notwendig, dass der Server bei jeder neuen Verbindung nicht sofort mit der Kommunikation beginnt, sondern diese Verbindung in eine Art Liste schreibt und auf eine neue Verbindung wartet und eine Art Hilfsdienst mit der Kommunikation mit einer bestimmten Verbindung beschäftigt ist Klient. Und Clients müssen unabhängig voneinander auf den Server schreiben und auf eine Antwort warten. Threads kommen zur Rettung. Nehmen wir an, wir haben eine Klasse, die dafür verantwortlich ist, sich neue Verbindungen zu merken: Sie sollte Folgendes spezifiziert haben:
  1. Port-Nummer.
  2. Die Liste, in die die neue Verbindung geschrieben wird.
  3. Und ServerSocketzwar in einer einzigen (!) Kopie.
public class Server {

    public static final int PORT = 8080;
    public static LinkedList<ServerSomthing> serverList = new LinkedList<>(); // Liste aller Threads

    public static void main(String[] args) throws IOException {
        ServerSocket server = new ServerSocket(PORT);
            try {
            while (true) {
                // Blockiert, bis eine neue Verbindung hergestellt wird:
                Socket socket = server.accept();
                try {
                    serverList.add(new ServerSomthing(socket)); // Füge eine neue Verbindung zur Liste hinzu
                } catch (IOException e) {
                    // Wenn es fehlschlägt, wird der Socket geschlossen,
                    // andernfalls schließt der Thread ihn, wenn er beendet wird:
                    socket.close();
                }
            }
        } finally {
            server.close();
        }
    }
}
Okay, jetzt geht jeder neu erstellte Socket nicht verloren, sondern wird auf dem Server gespeichert. Weiter. Jeder Kunde braucht jemanden, der zuhört. Erstellen wir einen Thread mit den Serverfunktionen aus dem letzten Kapitel.
class ServerSomthing extends Thread {

    private Socket socket; // Socket, über den der Server mit dem Client kommuniziert,
    // Abgesehen davon sind Client und Server in keiner Weise verbunden
    private BufferedReader in; // Socket-Lesestream
    private BufferedWriter out; // Socket-Schreibstream

    public ServerSomthing(Socket socket) throws IOException {
        this.socket = socket;
        // если потоку ввода/вывода приведут к генерированию исключения, оно пробросится дальше
        in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
        out = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
        start(); // run() aufrufen
    }
    @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); // die empfangene Nachricht senden mit
                   // bindet den Klienten an alle anderen, ihn eingeschlossen
                }
            }

        } catch (IOException e) {
        }
    }

    private void send(String msg) {
        try {
            out.write(msg + "\n");
            out.flush();
        } catch (IOException ignored) {}
    }
}
Daher muss im Konstruktor des Server-Threads ein Socket initialisiert werden, über den der Thread mit einem bestimmten Client kommuniziert. Außerdem I/O-Threads und alles andere, was Sie benötigen, um einen Thread direkt vom Konstruktor aus zu starten. Okay, aber was passiert, wenn der Server-Thread eine Nachricht vom Client liest? Nur an Ihren Kunden zurückschicken? Nicht sehr effektiv. Wir führen einen Mehrbenutzer-Chat durch, daher muss jeder verbundene Client das erhalten, was eine Person geschrieben hat. Sie müssen die Liste aller Server-Threads verwenden, die ihren Clients zugeordnet sind, und jede an einen bestimmten Thread gesendete Nachricht senden, damit dieser sie an seinen Client sendet:
for (ServerSomthing vr : Server.serverList) {
    vr.send(word); // die empfangene Nachricht senden
    // vom verknüpften Client an alle anderen, einschließlich ihm
}
private void send(String msg) {
    try {
        out.write(msg + "\n");
        out.flush();
    } catch (IOException ignored) {}
}
Jetzt wissen alle Kunden, was einer von ihnen gesagt hat! Wenn Sie nicht möchten, dass die Nachricht an die Person gesendet wird, die sie gesendet hat (sie weiß bereits, was sie geschrieben hat!), Geben Sie beim Durchlaufen der Threads einfach an, dass die thisSchleife bei der Verarbeitung eines Objekts ohne Ausführung zum nächsten Element wechselt irgendwelche Aktionen darauf. Wenn Sie möchten, können Sie auch eine Nachricht an den Client senden, die besagt, dass die Nachricht erfolgreich empfangen und gesendet wurde. Mit dem Server ist jetzt alles klar. Kommen wir zum Kunden, oder besser gesagt zu den Kunden! Dort ist alles gleich, analog zum Client aus dem letzten Kapitel, nur muss man beim Erstellen einer Instanz, wie in diesem Kapitel beim Server gezeigt, alles Notwendige im Konstruktor erstellen. Was aber, wenn er beim Anlegen eines Kunden noch keine Zeit hatte, etwas einzugeben, ihm aber bereits etwas zugesandt wurde? (Zum Beispiel der Korrespondenzverlauf derjenigen, die sich bereits vor ihm mit dem Chat verbunden haben). Daher müssen die Zyklen, in denen gesendete Nachrichten verarbeitet werden, von denen getrennt werden, in denen Nachrichten von der Konsole gelesen und zur Weiterleitung an andere an den Server gesendet werden. Threads kommen wieder zur Rettung. Es macht keinen Sinn, einen Client als Thread zu erstellen. Es ist bequemer, einen Thread mit einer Schleife in der run-Methode zu erstellen, die Nachrichten liest und analog auch schreibt:
// Thread, der Nachrichten vom Server liest
private class ReadMsg extends Thread {
    @Override
    public void run() {

        String str;
        try {
            while (true) {
                str = in.readLine(); // warte auf eine Nachricht vom Server
                if (str.equals("stop")) {

                    break; // Verlasse die Schleife, wenn sie „Stop“ ist
                }
                            }
        } catch (IOException e) {

        }
    }
}
// Thread, der Nachrichten von der Konsole an den Server sendet
public class WriteMsg extends Thread {

    @Override
    public void run() {
        while (true) {
            String userWord;
            try {
               userWord = inputUser.readLine(); // Nachrichten von der Konsole
                if (userWord.equals("stop")) {
                    out.write("stop" + "\n");
                    break; // Verlasse die Schleife, wenn sie „Stop“ ist
                } else {
                    out.write(userWord + "\n"); // an den Server senden
                }
                out.flush(); // Aufräumen
            } catch (IOException e) {

            }

        }
    }
}
Im Client-Konstruktor müssen Sie nur diese Threads starten. Wie kann man die Ressourcen eines Kunden richtig schließen, wenn er gehen möchte? Muss ich Server-Thread-Ressourcen schließen? Dazu müssen Sie höchstwahrscheinlich eine separate Methode erstellen, die beim Verlassen der Nachrichtenschleife aufgerufen wird. Dort müssen Sie den Socket und die I/O-Streams schließen. Das gleiche Sitzungsendesignal für einen bestimmten Client muss an seinen Server-Thread gesendet werden, der dasselbe mit seinem Socket tun und sich selbst aus der Liste der Threads in der Hauptserverklasse entfernen muss. Kopf Acht: Der Perfektion sind keine Grenzen gesetzt. Sie können endlos neue Funktionen erfinden, um Ihr Projekt zu verbessern. Doch was genau soll auf einen neu angeschlossenen Client übertragen werden? Ich glaube, die letzten zehn Ereignisse ereigneten sich vor seiner Ankunft. Dazu müssen Sie eine Klasse erstellen, in der die letzte Aktion mit einem beliebigen Server-Thread in die deklarierte Liste eingetragen wird. Wenn die Liste bereits voll ist (d. h. es sind bereits 10 vorhanden), löschen Sie die erste und fügen Sie sie hinzu der Letzte, der kam. Damit der Inhalt dieser Liste von einer neuen Verbindung empfangen werden kann, müssen Sie ihn beim Erstellen eines Server-Threads im Ausgabestream an den Client senden. Wie kann man das machen? Zum Beispiel so:
public void printStory(BufferedWriter writer) {
// ...
}
Der Server-Thread hat bereits Streams erstellt und kann den Ausgabestream als Argument übergeben. Als nächstes müssen Sie nur noch alles, was an den neuen Client übertragen werden muss, in einem Suchzyklus übergeben. Fazit: Dies sind nur die Grundlagen, und höchstwahrscheinlich wird diese Chat-Architektur beim Erstellen einer echten Anwendung nicht funktionieren. Dieses Programm wurde zu Bildungszwecken erstellt und auf seiner Grundlage habe ich gezeigt, wie man den Client mit dem Server kommunizieren lässt (und umgekehrt), wie man dies für mehrere Verbindungen macht und natürlich, wie dies auf Sockets organisiert wird. Nachfolgend sind die Quellen neu geordnet und der Quellcode des analysierten Programms ist ebenfalls beigefügt. Dies ist meine erste Erfahrung beim Schreiben eines Artikels) Vielen Dank für Ihre Aufmerksamkeit :)
  1. Thinking in Java Enterprise, von Bruce Eckel et. Al. 2003
  2. Java 8, The Complete Guide, Herbert Schildt, 9. Auflage, 2017 (Kapitel 22)
  3. Socket-Programmierung in Java -Artikel über Sockets
  4. Socket in der offiziellen Dokumentation
  5. ServerSocket in der offiziellen Dokumentation
  6. Quellen auf GitHub
Kommentare
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION