JavaRush /בלוג Java /Random-HE /מחלקות Socket ו- ServerSocket, או "הלו, שרת? אתה יכול לשמ...
Sergey Simonov
רָמָה
Санкт-Петербург

מחלקות Socket ו- ServerSocket, או "הלו, שרת? אתה יכול לשמוע אותי?"

פורסם בקבוצה
הקדמה: "היה מחשב על השולחן, מאחוריו היה מקודד..." מחלקות Socket ו- ServerSocket, או "הלו, שרת?  אתה יכול לשמוע אותי?"  - 1 פעם אחד מחבריי לכיתה פרסם תוצאה נוספת של לימוד ג'אווה שלו, בצורה של צילום מסך של תוכנה חדשה. תוכנית זו הייתה צ'אט מרובה משתמשים. באותו זמן רק התחלתי את המסע שלי בשליטה בתכנות בשפה הזו, אבל בהחלט ציינתי לעצמי ש"אני רוצה את זה!" הזמן חלף ולאחר שסיימתי לעבוד על הפרויקט הבא כחלק מהעמקת הידע שלי בתכנות, נזכרתי באירוע הזה והחלטתי שהגיע הזמן. איכשהו כבר התחלתי לחפור בנושא הזה רק מתוך סקרנות, אבל בספר הלימוד הראשי שלי ב-Java (זה היה המדריך המלא של שילדט) סופקו רק 20 עמודים לחבילת java.net. זה מובן - הספר כבר גדול מאוד. היו טבלאות של שיטות ובנאים של המחלקות הראשיות, אבל זה הכל. השלב הבא הוא, כמובן, גוגל הכל יכול: אינספור מאמרים שונים שבהם מוצג אותו הדבר - שתיים או שלוש מילים על שקעים, ודוגמה מוכנה. הגישה הקלאסית (לפחות בסגנון הלימוד שלי) היא קודם כל להבין מה אני צריך מהכלים לעבודה, מה הם, למה הם נחוצים, ורק אז, אם הפתרון לבעיה לא ברור, לבחור ב את הרישומים המוכנים, פתחו את האומים והברגים. אבל הבנתי מה זה מה ובסופו של דבר כתבתי צ'אט מרובה משתמשים. כלפי חוץ, זה התברר בערך כך: מחלקות Socket ו-ServerSocket, או "הלו, שרת?  אתה יכול לשמוע אותי?"  - 2כאן אנסה לתת לך הבנה של היסודות של יישומי שרת-לקוח המבוססים על שקעי Java תוך שימוש בדוגמה של עיצוב צ'אט. בקורס Javarash תעשה צ'אט. זה יהיה ברמה אחרת לגמרי, יפה, גדול, רב תכליתי. אבל קודם כל, אתה תמיד צריך להניח את הבסיס, אז כאן אנחנו צריכים להבין מה עומד בבסיס סעיף כזה. (אם מצאתם חסרונות או שגיאות, כתבו ב-PM או בתגובה מתחת לכתבה). בואו נתחיל. ראש ראשון: "הבית ש..." כדי להסביר כיצד מתרחש חיבור רשת בין שרת ללקוח אחד, ניקח את הדוגמה הקלאסית כעת של בניין דירות. נניח שלקוח צריך ליצור איכשהו חיבור עם שרת ספציפי. מה המחפש צריך לדעת על אובייקט החיפוש? כן, הכתובת. השרת אינו ישות קסומה בענן, ולכן הוא חייב להיות ממוקם על מכונה ספציפית. באנלוגיה לבית, שבו צריכה להתקיים פגישה של שני צדדים מוסכמים. וכדי למצוא אחד את השני בבניין דירות לא מספיקה כתובת אחת של הבניין, יש לציין את מספר הדירה בה תתקיים הפגישה. כמו כן, במחשב אחד יכולים להיות מספר שרתים בו-זמנית, וכדי שהלקוח ייצור קשר עם אחד ספציפי הוא צריך לציין גם את מספר הפורט שדרכו יתרחש החיבור. אז, הכתובת ומספר היציאה. כתובת פירושה מזהה של מכונה במרחב האינטרנטי. זה יכול להיות שם דומיין, למשל, "javarush.ru" או כתובת IP רגילה. נמל- מספר ייחודי אליו משויך שקע מסוים (מונח זה יידון בהמשך), במילים אחרות, הוא תפוס על ידי שירות מסוים כך שניתן להשתמש בו כדי ליצור איתו קשר. כך שלפחות שני חפצים יפגשו בשטחו של אחד (שרת), בעל השטח (שרת) חייב לתפוס עליו דירה (נמל) ספציפית (מכונית), והשני חייב למצוא את מקום המפגש בידיעה כתובת הבית (דומיין או ip ), ומספר דירה (יציאה). ראש שני: הכירו את Socket בין המושגים והמונחים הקשורים לעבודה ברשת, אחד חשוב מאוד הוא Socket. זה מציין את הנקודה שדרכה מתרחש החיבור. במילים פשוטות, שקע מחבר בין שתי תוכניות ברשת. הכיתה Socketמיישמת את הרעיון של שקע. הלקוח והשרת יתקשרו דרך ערוצי הקלט/פלט שלו: מחלקות Socket ו- ServerSocket, או "הלו, שרת?  אתה יכול לשמוע אותי?"  - 3 מחלקה זו מוצהרת בצד הלקוח, והשרת יוצר אותה מחדש ומקבל אות חיבור. כך עובדת תקשורת מקוונת. מלכתחילה, להלן בנאי המחלקות האפשריים Socket:
Socket(String Name_хоста, int порт) throws UnknownHostException, IOException
Socket(InetAddress IP-address, int порт) throws UnknownHostException
"שם_מארח" - מרמז על צומת רשת ספציפי, כתובת IP. אם מחלקת ה-socket לא הצליחה להמיר אותה לכתובת אמיתית וקיימת, אזי ייגרם חריג UnknownHostException. נמל הוא נמל. אם 0 מצוין כמספר היציאה, המערכת עצמה תקצה יציאה פנויה. חריג עשוי להתרחש גם אם החיבור אבד IOException. יש לציין שסוג הכתובת בבנאי השני הוא InetAddress. זה בא להצלה, למשל, כאשר אתה צריך לציין שם דומיין ככתובת. כמו כן, כאשר תחום פירושו מספר כתובות IP, InetAddressאתה יכול להשתמש בהן כדי לקבל מערך שלהן. עם זאת, זה עובד גם עם IP. אתה יכול גם לקבל את שם המארח, מערך הבתים המרכיב את כתובת ה-IP וכו'. ניגע בזה קצת יותר, אבל תצטרך ללכת לתיעוד הרשמי לפרטים מלאים. כאשר אובייקט מסוג מאותחל Socket, הלקוח שאליו הוא שייך מודיע ברשת שהוא רוצה להתחבר לשרת בכתובת ומספר יציאה ספציפיים. להלן השיטות הנפוצות ביותר של המחלקה Socket: InetAddress getInetAddress()– מחזירה אובייקט המכיל נתונים על השקע. אם השקע לא מחובר - null int getPort()- מחזיר את הפורט עליו מתרחש החיבור לשרת int getLocalPort()- מחזיר את היציאה אליה השקע קשור. העובדה היא שהלקוח והשרת יכולים "לתקשר" על יציאה אחת, אבל היציאות שאליהן הם קשורים יכולות להיות שונות לחלוטין boolean isConnected()- מחזיר אמת אם החיבור נוצר void connect(SocketAddress address)- מציין חיבור חדש boolean isClosed()- מחזיר אמת, אם השקע סגור boolean isBound()- מחזירה true, אם השקע אכן קשור לכתובת, המחלקה Socketמיישמת את הממשק AutoCloseable, כך שניתן להשתמש בו ב- try-with-resources. עם זאת, אתה יכול גם לסגור שקע בצורה הקלאסית, באמצעות close(). Head Three: וזהו ServerSocket נניח שהכרזנו, בצורה של מחלקה Socket, על בקשת חיבור בצד הלקוח. איך השרת ינחש את רצוננו? לשם כך, לשרת יש מחלקה כמו ServerSocket, ומתודה accept() בתוכו. הבנאים שלו מוצגים להלן:
ServerSocket() throws IOException
ServerSocket(int порт) throws IOException
ServerSocket(int порт, int максимум_подключений) throws IOException
ServerSocket(int порт, int максимум_подключений, InetAddress локальный_address) throws IOException
בעת הצהרה, ServerSocket אינך צריך לציין את כתובת החיבור, מכיוון שהתקשורת מתבצעת על מכונת השרת. רק עם מארח רב-ערוצי אתה צריך לציין לאיזה IP שקע השרת מחויב. Head Three.One: השרת שאומר לא מכיוון שמתן תוכנית עם יותר משאבים ממה שהיא זקוקה הוא גם יקר וגם לא סביר, לכן בבנאי ServerSocketאתה מתבקש להצהיר על מקסימום החיבורים המקובלים על השרת במהלך הפעולה. אם זה לא מצוין, אז כברירת מחדל מספר זה ייחשב שווה ל-50. כן, בתיאוריה אנו יכולים להניח שמדובר ServerSocketבאותו שקע, רק עבור השרת. אבל זה משחק תפקיד שונה לגמרי מאשר המעמד Socket. זה נחוץ רק בשלב יצירת החיבור. לאחר יצירת אובייקט סוג, ServerSocketאתה צריך לגלות שמישהו רוצה להתחבר לשרת. השיטה accept() מחוברת כאן. היעד ממתין עד שמישהו ירצה להתחבר אליו, וכשזה קורה הוא מחזיר אובייקט מסוג Socket, כלומר, שקע לקוח שנוצר מחדש. ועכשיו, לאחר ששקע הלקוח נוצר בצד השרת, ניתן להתחיל תקשורת דו-כיוונית. יצירת אובייקט סוג Socketבצד הלקוח ויצירה מחדש שלו באמצעות ServerSocketצד השרת היא המינימום הנדרש לחיבור. ראש רביעי: מכתב לסנטה קלאוס Вопрос: איך בדיוק מתקשרים הלקוח והשרת? Ответ:דרך זרמי I/O. מה כבר יש לנו? שקע עם כתובת השרת ומספר היציאה של הלקוח, ואותו דבר, בזכות accept(), בצד השרת. אז סביר להניח שהם יתקשרו דרך שקע. לשם כך, ישנן שתי שיטות שנותנות גישה לזרמים InputStreamואובייקטים OutputStreamמסוג Socket. הנה הם:
InputStream getInputStream()
OutputStream getOutputStream()
מכיוון שקריאה וכתיבה של בתים חשופים אינם יעילים באותה מידה, ניתן לעטוף זרמים במחלקות מתאמים, באחסון או לא. לדוגמה:
BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
BufferedWriter out = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
כדי שהתקשורת תהיה דו-כיוונית, פעולות כאלה חייבות להתבצע משני הצדדים. עכשיו אתה יכול לשלוח משהו באמצעות in, ולקבל משהו באמצעות out, ולהיפך. למעשה, זו למעשה הפונקציה היחידה של המחלקה Socket. וכן, אל תשכח את שיטת ה-flush() BufferedWriter- היא שוטפת את תוכן המאגר. אם זה לא נעשה, המידע לא ישודר ולכן לא יתקבל. השרשור המקבל ממתין גם למחוון סוף השורה – "\n", אחרת ההודעה לא תתקבל, שכן למעשה ההודעה לא הושלמה ואינה שלמה. אם זה נראה לך לא נוח, אל תדאג, אתה תמיד יכול להשתמש במחלקה PrintWriter, שצריכה להיסגר, לציין true בתור הארגומנט השני, ואז יציאה מהמאגר תתרחש אוטומטית:
PrintWriter out = new PrintWriter(new BufferedWriter(new OutputStreamWriter(socket.getOutputStream())), true);
כמו כן, אין צורך לציין את סוף השורה, השיעור הזה עושה את זה בשבילך. אבל האם קלט/פלט מחרוזת הוא הגבול של מה ששקע יכול לעשות? לא, האם ברצונך לשלוח אובייקטים באמצעות זרמי socket? למען השם. עשה אותם בסידרה ואתה מוכן:
ObjectOutputStream out = new ObjectOutputStream(socket.getOutputStream());
ObjectInputStream in = new ObjectInputStream(socket.getInputStream());
ראש חמישי: תקשורת אמיתית דרך האינטרנט מכיוון שכדי להתחבר דרך רשת אמיתית עם כתובת IP אמיתית צריך שרת מלא, ומכיוון:
  1. לצ'אט העתידי שלנו, בתור כלי עזר, אין יכולות כאלה. זה יכול רק ליצור חיבור ולקבל/לשלוח הודעה. כלומר, אין לו יכולות שרת אמיתיות.
  2. השרת שלנו, המכיל רק נתוני socket וזרמי I/O, לא יכול לעבוד כשרת WEB או FTP אמיתי, ואז רק עם זה לא נוכל להתחבר דרך האינטרנט.
וחוץ מזה, אנחנו רק מתחילים לפתח את התוכנית, מה שאומר שהיא לא יציבה מספיק כדי לעבוד מיד עם רשת אמיתית, אז נשתמש במארח המקומי ככתובת החיבור. כלומר, בתיאוריה, הלקוח והשרת עדיין לא יהיו מחוברים בשום צורה מלבד דרך שקע, אבל לצורך איתור באגים בתוכנית הם יהיו על אותה מכונה, ללא מגע אמיתי דרך הרשת. על מנת לציין בקונסטרוקטור Socketשהכתובת היא מקומית, ישנן 2 דרכים:
  1. כתוב "localhost" כארגומנט הכתובת, כלומר בדל מקומי. "127.0.0.1" מתאים גם לזה - זה רק צורה דיגיטלית של בדל.
  2. שימוש ב-InetAddress:
    1. InetAddress.getByName(null)- null מצביע על localhost
    2. InetAddress.getByName("localhost")
    3. InetAddress.getByName("127.0.0.1")
לשם הפשטות, נשתמש ב-"localhost" מסוג String. אבל כל האפשרויות האחרות גם ניתנות לביצוע. ראש שישי: הגיע הזמן לשיחה אז, יש לנו כבר את כל מה שאנחנו צריכים כדי ליישם סשן שיחה עם השרת. כל מה שנותר הוא לחבר את זה: הרשימה הבאה מראה כיצד הלקוח מתחבר לשרת, שולח לו הודעה אחת, והשרת, בתורו, מאשר שקיבל את ההודעה באמצעותו כארגומנט שלו: "שרת. ג'אווה"
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;

public class Server {

    private static Socket clientSocket; // socket for communication
    private static ServerSocket server; // server socket
    private static BufferedReader in; // socket read stream
    private static BufferedWriter out; // socket write stream

    public static void main(String[] args) {
        try {
            try  {
                server = new ServerSocket(4004); // server socket listening on port 4004
                System.out.println("Server is running!"); // server would be nice
                // announce your launch
                clientSocket = server.accept(); // accept() will wait until
                //someone won't want to connect
                try { // after establishing a connection and recreating the socket for communication with the client, you can go
                    // to create I/O streams.
                    // now we can receive messages
                    in = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));
                    // and send
                    out = new BufferedWriter(new OutputStreamWriter(clientSocket.getOutputStream()));

                    String word = in.readLine(); // wait for the client to write something to us
                    System.out.println(word);
                    // without hesitation responds to the client
                    out.write("Hi, this is the Server! I confirm you wrote: " + word + "\n");
                    out.flush(); // push everything out of the buffer

                } finally { // in any case, the socket will be closed
                    clientSocket.close();
                    // streams would also be nice to close
                    in.close();
                    out.close();
                }
            } finally {
                System.out.println("Server closed!");
                    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 for communication
    private static BufferedReader reader; // we need a reader that reads from the console, otherwise
    // do we know what the client wants to say?
    private static BufferedReader in; // socket read stream
    private static BufferedWriter out; // socket write stream

    public static void main(String[] args) {
        try {
            try {
                // address - local host, port - 4004, same as the server
                clientSocket = new Socket("localhost", 4004); // with this line we request
                // the server has access to the connection
                reader = new BufferedReader(new InputStreamReader(System.in));
                // read messages from the server
                in = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));
                // write there
                out = new BufferedWriter(new OutputStreamWriter(clientSocket.getOutputStream()));

                System.out.println("Did you have something to say? Enter it here:");
                // if the connection happened and the threads were successfully created - we can
                // work further and offer the client something to enter
                // if not, an exception will be thrown
                String word = reader.readLine(); // wait for the client to do something
                // will not write to the console
                out.write(word + "\n"); // send a message to the server
                out.flush();
                String serverWord = in.readLine(); // wait for the server to say
                System.out.println(serverWord); // received - display
            } finally { // in any case, you need to close the socket and streams
                System.out.println("The client has been closed...");
                clientSocket.close();
                in.close();
                out.close();
            }
        } catch (IOException e) {
            System.err.println(e);
        }

    }
}
כמובן שצריך להפעיל קודם את השרת, כי למה הלקוח יתחבר בהפעלה אם אין משהו שיחבר אותו? :) הפלט יהיה כך: /* האם רצית לומר משהו? הכנס את זה כאן: שלום, שרת? אתה יכול לשמוע אותי? שלום, זה השרת! אני מאשר, כתבת: שלום שרת? אתה יכול לשמוע אותי? הלקוח היה סגור... */ יוהרה! לימדנו את השרת לתקשר עם הלקוח! כך שהתקשורת מתרחשת לא בשני העתקים, אלא כמה שתרצו, פשוט עטפו את הקריאה והכתיבה של שרשורים בלולאה (אמיתית) זמן מה וציינו עבור היציאה שעל פי הודעה מסוימת, למשל, "יציאה" , המחזור הופסק, והתוכנית תסתיים. Head Seven: Multi-User עדיף.העובדה שהשרת יכול לשמוע אותנו היא טובה, אבל זה יהיה הרבה יותר טוב אם נוכל לתקשר עם מישהו מהסוג שלנו. אצרף את כל המקורות בסוף המאמר, אז כאן אראה פיסות קוד לא תמיד גדולות, אבל חשובות שיאפשרו, אם נעשה שימוש נכון, לרקוח צ'אט מרובה משתמשים. אז אנחנו רוצים להיות מסוגלים לתקשר עם לקוח אחר דרך השרת. איך לעשות את זה? ברור, מכיוון שלתוכנית הלקוח יש שיטה משלה main, זה אומר שניתן להפעיל אותה בנפרד מהשרת ובמקביל עם לקוחות אחרים. מה זה נותן לנו? איכשהו, יש צורך שבכל חיבור חדש השרת לא יעבור מיד לתקשורת, אלא יכתוב את החיבור הזה לרשימה כלשהי וימשיך לחכות לחיבור חדש, וסוג של שירות עזר עוסק בתקשורת עם ספציפי לָקוּחַ. ולקוחות חייבים לכתוב לשרת ולחכות לתגובה ללא תלות זה בזה. חוטים באים להצלה. נניח שיש לנו מחלקה שאחראית לזכור חיבורים חדשים: יש לציין את הדברים הבאים:
  1. מספר יציאה.
  2. הרשימה שבה הוא כותב את החיבור החדש.
  3. וכן ServerSocket, בעותק בודד (!).
public class Server {

    public static final int PORT = 8080;
    public static LinkedList<ServerSomthing> serverList = new LinkedList<>(); // list of all threads

    public static void main(String[] args) throws IOException {
        ServerSocket server = new ServerSocket(PORT);
            try {
            while (true) {
                // Blocks until a new connection is made:
                Socket socket = server.accept();
                try {
                    serverList.add(new ServerSomthing(socket)); // add a new connection to the list
                } catch (IOException e) {
                    // If it fails, the socket is closed,
                    // otherwise, the thread will close it when it exits:
                    socket.close();
                }
            }
        } finally {
            server.close();
        }
    }
}
אוקיי, כעת כל שקע שנוצר מחדש לא יאבד, אלא יאוחסן בשרת. נוסף. כל לקוח צריך מישהו שיקשיב. בואו ניצור שרשור עם פונקציות השרת מהפרק האחרון.
class ServerSomthing extends Thread {

    private Socket socket; // socket through which the server communicates with the client,
    // except for it - the client and server are not connected in any way
    private BufferedReader in; // socket read stream
    private BufferedWriter out; // socket write stream

    public ServerSomthing(Socket socket) throws IOException {
        this.socket = socket;
        // если потоку ввода/вывода приведут к генерированию исключения, оно пробросится дальше
        in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
        out = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
        start(); // call 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); // send the received message with
                   // bound client to everyone else including him
                }
            }

        } catch (IOException e) {
        }
    }

    private void send(String msg) {
        try {
            out.write(msg + "\n");
            out.flush();
        } catch (IOException ignored) {}
    }
}
לכן, בקונסטרוקטור של שרשור השרת, יש לאתחל שקע שדרכו השרשור יתקשר עם לקוח ספציפי. גם שרשורי I/O וכל מה שאתה צריך כדי להתחיל שרשור ישירות מהקונסטרוקטור. אוקיי, אבל מה קורה כששרשור השרת קורא הודעה מהלקוח? לשלוח בחזרה רק ללקוח שלך? לא מאוד יעיל. אנחנו עורכים צ'אט מרובה משתמשים, אז אנחנו צריכים שכל לקוח מחובר יקבל את מה שאדם אחד כתב. עליך להשתמש ברשימה של כל שרשורי השרת המשויכים ללקוחות שלהם ולשלוח כל הודעה שנשלחת לשרשור מסוים כך שהיא תשלח אותה ללקוח שלו:
for (ServerSomthing vr : Server.serverList) {
    vr.send(word); // send the received message
    // from the linked client to everyone else, including him
}
private void send(String msg) {
    try {
        out.write(msg + "\n");
        out.flush();
    } catch (IOException ignored) {}
}
עכשיו כל הלקוחות יידעו מה אחד מהם אמר! אם אינך רוצה שההודעה תישלח למי ששלח אותה (הוא כבר יודע מה הוא כתב!), פשוט בעת איטרציה בין השרשורים, ציין כי בעת עיבוד אובייקט, הלולאה thisתעבור לאלמנט הבא מבלי לבצע כל פעולות עליו. או, אם אתה מעדיף, שלח הודעה ללקוח המציינת שההודעה התקבלה ונשלחה בהצלחה. הכל ברור עם השרת עכשיו. בואו נעבור ללקוח, או יותר נכון ללקוחות! הכל זהה שם, באנלוגיה ללקוח מהפרק האחרון, רק בעת יצירת מופע אתה צריך, כפי שהוצג בפרק זה עם השרת, ליצור את כל הדרוש בקונסטרוקטור. אבל מה אם, בעת יצירת לקוח, הוא עדיין לא הספיק להזין שום דבר, אבל משהו כבר נשלח אליו? (למשל היסטוריית ההתכתבויות של מי שכבר התחבר לצ'אט שלפניו). אז יש להפריד בין המחזורים שבהם יעובדו הודעות שנשלחו מאלה שבהם הודעות נקראות מהמסוף ונשלחות לשרת להעברה לאחרים. שוב חוטים נחלצים להצלה. אין טעם ליצור לקוח בתור שרשור. יותר נוח ליצור שרשור עם לולאה בשיטת הריצה שקוראת הודעות, וגם, באנלוגיה, כותבת:
// thread reading messages from the server
private class ReadMsg extends Thread {
    @Override
    public void run() {

        String str;
        try {
            while (true) {
                str = in.readLine(); // waiting for a message from the server
                if (str.equals("stop")) {

                    break; // exit the loop if it's "stop"
                }
                            }
        } catch (IOException e) {

        }
    }
}
// thread sending messages coming from the console to the server
public class WriteMsg extends Thread {

    @Override
    public void run() {
        while (true) {
            String userWord;
            try {
               userWord = inputUser.readLine(); // messages from the console
                if (userWord.equals("stop")) {
                    out.write("stop" + "\n");
                    break; // exit the loop if it's "stop"
                } else {
                    out.write(userWord + "\n"); // send to the server
                }
                out.flush(); // clean up
            } catch (IOException e) {

            }

        }
    }
}
בבנאי הלקוח אתה רק צריך להתחיל את השרשורים האלה. איך לסגור נכון את המשאבים של הלקוח אם הוא רוצה לעזוב? האם אני צריך לסגור משאבי שרשור שרת? כדי לעשות זאת, סביר להניח שתצטרך ליצור שיטה נפרדת שנקראת בעת יציאה מלולאת ההודעות. שם תצטרך לסגור את השקע ואת זרמי ה-I/O. אותו אות סיום הפעלה עבור לקוח מסוים חייב להישלח אל שרשור השרת שלו, שעליו לעשות את אותו הדבר עם השקע שלו ולהסיר את עצמו מרשימת השרשורים במחלקת השרת הראשי. ראש שמיני: אין גבול לשלמות אתה יכול להמציא בלי סוף תכונות חדשות כדי לשפר את הפרויקט שלך. אבל מה בדיוק צריך להעביר ללקוח שהתחבר לאחרונה? אני חושב שעשרת האירועים האחרונים התרחשו לפני הגעתו. לשם כך, עליך ליצור מחלקה שבה הפעולה האחרונה עם שרשור שרת כלשהו תוכנס לרשימה המוצהרת, ואם הרשימה כבר מלאה (כלומר, יש כבר 10), למחוק את הראשונה ולהוסיף האחרון שהגיע. כדי שהתוכן של רשימה זו יתקבל על ידי חיבור חדש, בעת יצירת שרשור שרת, בזרם הפלט, עליך לשלוח אותם ללקוח. איך לעשות את זה? לדוגמה, כך:
public void printStory(BufferedWriter writer) {
// ...
}
שרשור השרת כבר יצר זרמים ויכול להעביר את זרם הפלט כארגומנט. לאחר מכן, אתה רק צריך להעביר את כל מה שצריך להעביר ללקוח החדש במחזור חיפוש. מסקנה: זה רק היסודות, וסביר להניח שארכיטקטורת הצ'אט הזו לא תעבוד בעת יצירת אפליקציה אמיתית. תוכנית זו נוצרה למטרות חינוכיות ועל בסיסה הראיתי כיצד ניתן לגרום ללקוח לתקשר עם השרת (ולהיפך), כיצד לעשות זאת עבור מספר חיבורים, וכמובן, כיצד זה מאורגן על שקעים. המקורות מסודרים מחדש להלן, ומצורף גם קוד המקור של התוכנית המנותחת. זו ההתנסות הראשונה שלי בכתיבת מאמר) תודה על תשומת הלב :)
  1. Thinking in Java Enterprise, מאת Bruce Eckel et. אל. 2003
  2. Java 8, המדריך השלם, הרברט שילדט, מהדורה 9, 2017 (פרק 22)
  3. תכנות שקעים ב-Java מאמר על שקעים
  4. שקע בתיעוד הרשמי
  5. ServerSocket בתיעוד הרשמי
  6. מקורות ב-GitHub
הערות
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION