JavaRush /مدونة جافا /Random-AR /فئات المقبس وServerSocket، أو "مرحبًا، الخادم؟ أيمكنك سما...
Sergey Simonov
مستوى
Санкт-Петербург

فئات المقبس وServerSocket، أو "مرحبًا، الخادم؟ أيمكنك سماعي؟"

نشرت في المجموعة
مقدمة: "كان هناك جهاز كمبيوتر على الطاولة، وخلفه كان هناك برنامج تشفير..." فئات المقبس وServerSocket، أو "مرحبًا، الخادم؟  أيمكنك سماعي؟"  - 1 ذات مرة، نشر أحد زملائي نتيجة أخرى لدراسته لجافا، في شكل لقطة شاشة لبرنامج جديد. كان هذا البرنامج عبارة عن دردشة متعددة المستخدمين. في ذلك الوقت كنت قد بدأت للتو رحلتي الخاصة في إتقان البرمجة بهذه اللغة، لكنني بالتأكيد قلت لنفسي "أريد ذلك!" مر الوقت، وبعد أن انتهيت من العمل على المشروع التالي كجزء من تعميق معرفتي بالبرمجة، تذكرت تلك الحادثة وقررت أن الوقت قد حان. لقد بدأت بالفعل بطريقة أو بأخرى في البحث في هذا الموضوع بدافع الفضول فقط، ولكن في كتاب Java المدرسي الرئيسي الخاص بي (كان دليل شيلدت الكامل) تم توفير 20 صفحة فقط لحزمة java.net. هذا أمر مفهوم - الكتاب كبير جدًا بالفعل. كانت هناك جداول للطرق ومنشئي الفئات الرئيسية، ولكن هذا كل شيء. الخطوة التالية، بالطبع، هي Google سبحانه وتعالى: عدد لا يحصى من المقالات المختلفة، حيث يتم تقديم نفس الشيء - كلمتين أو ثلاث كلمات حول المقابس، ومثال جاهز. النهج الكلاسيكي (على الأقل في أسلوب دراستي) هو أن أفهم أولاً ما أحتاجه من أدوات العمل، وما هي، ولماذا أحتاج إليها، وعندها فقط، إذا لم يكن حل المشكلة واضحًا، فاختر ما يلي: القوائم الجاهزة، وفك الصواميل والمسامير. لكنني اكتشفت ما هو الأمر وكتبت في النهاية محادثة متعددة المستخدمين. ظاهريًا، اتضح شيئًا كهذا: فئات المقبس وServerSocket، أو "مرحبًا، الخادم؟  أيمكنك سماعي؟"  - 2هنا سأحاول أن أقدم لك فهمًا لأساسيات تطبيقات خادم العميل بناءً على مآخذ Java باستخدام مثال تصميم الدردشة. في دورة جافاراش سوف تقوم بالدردشة. سيكون على مستوى مختلف تمامًا، جميل، كبير، متعدد الوظائف. ولكن أولا وقبل كل شيء، تحتاج دائما إلى وضع الأساس، لذلك نحتاج هنا إلى معرفة ما يكمن وراء هذا القسم. (إذا وجدت أي قصور أو أخطاء فاكتبها في رسالة خاصة أو في تعليق أسفل المقال). هيا نبدأ. العنوان الأول: "المنزل الذي..." لشرح كيفية حدوث اتصال الشبكة بين الخادم وعميل واحد، لنأخذ المثال الكلاسيكي الآن لمبنى سكني. لنفترض أن العميل يحتاج إلى إنشاء اتصال بطريقة أو بأخرى مع خادم معين. ما الذي يجب أن يعرفه الباحث عن كائن البحث؟ نعم العنوان. الخادم ليس كيانًا سحريًا على السحابة، وبالتالي يجب أن يكون موجودًا على جهاز معين. وقياساً على البيت الذي يجب أن يتم فيه اجتماع الطرفين المتفق عليهما. ولكي نجد بعضنا البعض في مبنى سكني، لا يكفي عنوان واحد للمبنى، يجب عليك الإشارة إلى رقم الشقة التي سيتم فيها الاجتماع. وبالمثل، على جهاز كمبيوتر واحد يمكن أن يكون هناك عدة خوادم في وقت واحد، ولكي يتمكن العميل من الاتصال بخادم معين، فإنه يحتاج أيضًا إلى تحديد رقم المنفذ الذي سيتم من خلاله الاتصال. لذلك، العنوان ورقم المنفذ. العنوان يعني معرف الجهاز في مساحة الإنترنت. يمكن أن يكون اسم مجال، على سبيل المثال، "javarush.ru" أو IP عادي. ميناء- رقم فريد يرتبط به مقبس معين (سيتم مناقشة هذا المصطلح لاحقا)، بمعنى آخر، تشغله خدمة معينة بحيث يمكن استخدامه للاتصال بها. لذلك لكي يلتقي جسمان على الأقل في إقليم أحدهما (الخادم)، يجب على صاحب المنطقة (الخادم) أن يشغل شقة (ميناء) محددة عليها (السيارة)، ويجب على الثاني أن يجد مكان الالتقاء وهو يعلم عنوان المنزل (الدومين أو IP)، ورقم الشقة (المنفذ). الرأس الثاني: التعرف على المقبس من بين المفاهيم والمصطلحات المتعلقة بالعمل على الشبكة، أحد المفاهيم المهمة جدًا هو المقبس. يشير إلى النقطة التي يتم من خلالها حدوث الاتصال. ببساطة، يقوم المقبس بتوصيل برنامجين على الشبكة. ينفذ الفصل Socketفكرة المقبس. سيتواصل العميل والخادم من خلال قنوات الإدخال/الإخراج الخاصة بهما: فئات المقبس وServerSocket، أو "مرحبًا، الخادم؟  أيمكنك سماعي؟"  - 3 يتم الإعلان عن هذه الفئة من جانب العميل، ويقوم الخادم بإعادة إنشائها، وتلقي إشارة اتصال. هذه هي الطريقة التي يعمل بها التواصل عبر الإنترنت. في البداية، إليك منشئي الفئة المحتملين Socket:
Socket(String Name_хоста, int порт) throws UnknownHostException, IOException
Socket(InetAddress IP-address, int порт) throws UnknownHostException
"اسم_المضيف" - يشير إلى عقدة شبكة محددة وعنوان IP. إذا لم تتمكن فئة المقبس من تحويلها إلى عنوان حقيقي موجود، فسيتم طرح استثناء UnknownHostException. الميناء هو الميناء. إذا تم تحديد 0 كرقم المنفذ، فسيقوم النظام نفسه بتخصيص منفذ مجاني. قد يحدث استثناء أيضًا في حالة فقدان الاتصال IOException. وتجدر الإشارة إلى أن نوع العنوان في المنشئ الثاني هو InetAddress. يتعلق الأمر بالإنقاذ، على سبيل المثال، عندما تحتاج إلى تحديد اسم المجال كعنوان. وأيضًا، عندما يعني المجال عدة عناوين IP، InetAddressيمكنك استخدامها للحصول على مصفوفة منها. ومع ذلك، فهو يعمل مع IP أيضًا. يمكنك أيضًا الحصول على اسم المضيف، ومصفوفة البايت التي تشكل عنوان IP، وما إلى ذلك. سنتطرق إليها قليلًا، ولكن سيتعين عليك الانتقال إلى الوثائق الرسمية للحصول على التفاصيل الكاملة. عند تهيئة كائن من النوع Socket، يعلن العميل الذي ينتمي إليه على الشبكة أنه يريد الاتصال بالخادم على عنوان ورقم منفذ محددين. فيما يلي الأساليب الأكثر استخدامًا للفئة Socket: InetAddress getInetAddress()– إرجاع كائن يحتوي على بيانات حول المقبس. إذا لم يكن المقبس متصلاً - فارغًا int getPort()- يُرجع المنفذ الذي يحدث عليه الاتصال بالخادم int getLocalPort()- يُرجع المنفذ الذي يرتبط به المقبس. الحقيقة هي أنه يمكن للعميل والخادم "الاتصال" على منفذ واحد، ولكن المنافذ التي يرتبطان بها يمكن أن تكون مختلفة تمامًا boolean isConnected()- تُرجع صحيحًا إذا تم إنشاء الاتصال void connect(SocketAddress address)- تشير إلى اتصال جديد boolean isClosed()- تُرجع صحيحًا إذا كان المقبس مغلقًا boolean isBound()- يُرجع صحيحًا، إذا كان المقبس مرتبطًا بالفعل بعنوان ما، فإن الفئة Socketتنفذ الواجهة AutoCloseable، بحيث يمكن استخدامها في try-with-resources. ومع ذلك، يمكنك أيضًا إغلاق المقبس بالطريقة الكلاسيكية، باستخدام دالة Close(). الرأس الثالث: وهذا هو 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 الذي يرتبط به مقبس الخادم. الرأس الثالث: الأول: الخادم الذي يقول لا نظرًا لأن تزويد البرنامج بموارد أكثر مما يحتاج إليه هو أمر مكلف وغير معقول، لذلك ServerSocketيُطلب منك في المُنشئ الإعلان عن الحد الأقصى من الاتصالات التي يقبلها الخادم أثناء التشغيل. إذا لم يتم تحديده، فسيتم اعتبار هذا الرقم مساويًا لـ 50 افتراضيًا. نعم، من الناحية النظرية يمكننا أن نفترض أن ServerSocketهذا هو نفس المقبس، فقط للخادم. لكنها تلعب دورًا مختلفًا تمامًا عن الطبقة Socket. إنه مطلوب فقط في مرحلة إنشاء الاتصال. بعد إنشاء كائن كتابة، ServerSocketتحتاج إلى معرفة ما إذا كان شخص ما يريد الاتصال بالخادم. طريقة قبول () متصلة هنا. ينتظر الهدف حتى يرغب شخص ما في الاتصال به، وعندما يحدث ذلك، فإنه يُرجع كائنًا من النوع Socket، أي مقبس عميل مُعاد إنشاؤه. والآن بعد أن تم إنشاء مأخذ توصيل العميل على جانب الخادم، يمكن أن يبدأ الاتصال ثنائي الاتجاه. يعد إنشاء كائن كتابة Socketعلى جانب العميل وإعادة إنشائه باستخدام ServerSocketجانب الخادم هو الحد الأدنى المطلوب للاتصال. الرأس الرابع: رسالة إلى سانتا كلوز Вопрос: كيف يتواصل العميل والخادم بالضبط؟ Ответ:من خلال تيارات الإدخال/الإخراج. ماذا لدينا بالفعل؟ مقبس بعنوان الخادم ورقم منفذ العميل، ونفس الشيء، وذلك بفضل قبول () من جانب الخادم. لذلك فمن المعقول أن نفترض أنهم سوف يتواصلون عبر مأخذ توصيل. للقيام بذلك، هناك طريقتان تتيحان الوصول إلى التدفقات InputStreamوالكائنات OutputStreamمن النوع Socket. ها هم:
InputStream getInputStream()
OutputStream getOutputStream()
نظرًا لأن قراءة وكتابة وحدات البايت المجردة ليست بنفس الكفاءة، فيمكن تغليف التدفقات في فئات المحولات، سواء كانت مخزنة مؤقتًا أم لا. على سبيل المثال:
BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
BufferedWriter out = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
لكي يكون الاتصال ثنائي الاتجاه، يجب تنفيذ هذه العمليات على كلا الجانبين. يمكنك الآن إرسال شيء باستخدام الداخل واستلام شيء باستخدام الخارج، والعكس صحيح. في الواقع، هذه هي الوظيفة الوحيدة للفئة تقريبًا Socket. ونعم، لا تنس طريقة التدفق () BufferedWriter- فهي تقوم بمسح محتويات المخزن المؤقت. إذا لم يتم ذلك، فلن يتم نقل المعلومات، وبالتالي لن يتم استلامها. ينتظر مؤشر الترابط المتلقي أيضًا مؤشر نهاية السطر - "\n"، وإلا فلن يتم قبول الرسالة، نظرًا لأن الرسالة في الواقع لم تكتمل ولم تكتمل. إذا كان هذا يبدو غير مريح بالنسبة لك، فلا تقلق، يمكنك دائمًا استخدام الفئة PrintWriterالتي تحتاج إلى الالتفاف، وتحديد true كوسيطة ثانية، ثم سيحدث الخروج من المخزن المؤقت تلقائيًا:
PrintWriter out = new PrintWriter(new BufferedWriter(new OutputStreamWriter(socket.getOutputStream())), true);
أيضًا، ليست هناك حاجة للإشارة إلى نهاية السطر، فهذا الفصل يقوم بذلك نيابةً عنك. ولكن هل سلسلة الإدخال/الإخراج هي الحد الأقصى لما يمكن أن يفعله المقبس؟ لا، هل تريد إرسال الكائنات عبر تيارات مأخذ التوصيل؟ من أجل الله. قم بتسلسلها وأنت على ما يرام:
ObjectOutputStream out = new ObjectOutputStream(socket.getOutputStream());
ObjectInputStream in = new ObjectInputStream(socket.getInputStream());
الرأس الخامس: الاتصال الحقيقي عبر الإنترنت حيث أنه لكي تتمكن من الاتصال عبر شبكة حقيقية بعنوان IP حقيقي، يجب أن يكون لديك خادم كامل، ومنذ ذلك الحين:
  1. الدردشة المستقبلية لدينا، كأداة مساعدة، لا تملك مثل هذه القدرات. يمكنه فقط إنشاء اتصال واستقبال/إرسال رسالة. أي أنه لا يتمتع بقدرات خادم حقيقية.
  2. لا يمكن لخادمنا، الذي يحتوي فقط على بيانات المقبس وتدفقات الإدخال/الإخراج، أن يعمل كخادم ويب أو خادم FTP حقيقي، وبهذا فقط لن نتمكن من الاتصال عبر الإنترنت.
وإلى جانب ذلك، بدأنا للتو في تطوير البرنامج، مما يعني أنه غير مستقر بما فيه الكفاية للعمل على الفور مع شبكة حقيقية، لذلك سنستخدم المضيف المحلي كعنوان اتصال. وهذا يعني، من الناحية النظرية، أن العميل والخادم لن يكونا متصلين بأي شكل من الأشكال إلا من خلال مأخذ توصيل، ولكن لتصحيح أخطاء البرنامج سيكونان على نفس الجهاز، دون اتصال حقيقي عبر الشبكة. للإشارة في المنشئ Socketإلى أن العنوان محلي، هناك طريقتان:
  1. اكتب "المضيف المحلي" كوسيطة العنوان، مما يعني كعب روتين محلي. "127.0.0.1" مناسب أيضًا لهذا - وهذا مجرد شكل رقمي للكعب.
  2. باستخدام إينيتدريس:
    1. InetAddress.getByName(null)- نقاط فارغة إلى المضيف المحلي
    2. InetAddress.getByName("localhost")
    3. InetAddress.getByName("127.0.0.1")
للتبسيط، سوف نستخدم "المضيف المحلي" من النوع String. ولكن جميع الخيارات الأخرى قابلة للتطبيق أيضًا. الرأس السادس: حان وقت المحادثة ، لدينا بالفعل كل ما نحتاجه لتنفيذ جلسة محادثة مع الخادم. كل ما تبقى هو تجميعها معًا: توضح القائمة التالية كيف يتصل العميل بالخادم، ويرسل له رسالة واحدة، ويؤكد الخادم بدوره أنه تلقى الرسالة باستخدامها كوسيطة في: "Server. جافا"
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);
        }

    }
}
بالطبع، يجب عليك تشغيل الخادم أولاً، لأنه ما الذي سيتصل به العميل عند بدء التشغيل إذا لم يكن هناك شيء يمكنه توصيله؟ :) سيكون الناتج كالتالي: /* هل تريد أن تقول شيئًا ما؟ أدخله هنا: مرحبا أيها الخادم؟ أيمكنك سماعي؟ مرحبا، هذا هو الخادم! أؤكد أنك كتبت: مرحبا أيها الخادم؟ أيمكنك سماعي؟ تم إغلاق العميل... */ مرحى! لقد علمنا الخادم التواصل مع العميل! بحيث لا يتم الاتصال في نسختين متماثلتين، ولكن بالعدد الذي تريده، ما عليك سوى لف قراءة وكتابة سلاسل الرسائل في حلقة زمنية (حقيقية) والإشارة إلى الخروج، وفقًا لرسالة معينة، على سبيل المثال، "خروج" ، انقطعت الدورة، وانتهى البرنامج. الرأس السابع: تعدد المستخدمين أفضل، حقيقة أن الخادم يمكنه سماعنا أمر جيد، ولكن سيكون من الأفضل بكثير أن نتمكن من التواصل مع شخص من نفس نوعنا. سأرفق جميع المصادر في نهاية المقالة، لذا سأعرض هنا أجزاء كبيرة من التعليمات البرمجية، ولكنها مهمة، والتي ستجعل من الممكن، إذا تم استخدامها بشكل صحيح، إجراء محادثة متعددة المستخدمين. لذلك، نريد أن نكون قادرين على التواصل مع بعض العملاء الآخرين من خلال الخادم. كيف افعلها؟ من الواضح، بما أن برنامج العميل له طريقته الخاصة 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) {}
    }
}
لذلك، في مُنشئ مؤشر ترابط الخادم، يجب تهيئة مأخذ توصيل يتواصل من خلاله مؤشر الترابط مع عميل معين. أيضًا سلاسل الإدخال/الإخراج وكل ما تحتاجه لبدء سلسلة رسائل مباشرة من المُنشئ. حسنًا، ولكن ماذا يحدث عندما يقرأ مؤشر ترابط الخادم رسالة من العميل؟ إرسال مرة أخرى فقط إلى العميل الخاص بك؟ ليست فعالة جدا. نحن نقوم بإجراء محادثة متعددة المستخدمين، لذلك نحتاج إلى أن يتلقى كل عميل متصل ما كتبه شخص واحد. تحتاج إلى استخدام قائمة جميع سلاسل الخوادم المرتبطة بعملائها وإرسال كل رسالة مرسلة إلى سلسلة رسائل محددة بحيث يرسلها إلى عميلها:
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) {

            }

        }
    }
}
في منشئ العميل تحتاج فقط إلى بدء هذه المواضيع. كيف يتم إغلاق موارد العميل بشكل صحيح إذا أراد المغادرة؟ هل أحتاج إلى إغلاق موارد مؤشر ترابط الخادم؟ للقيام بذلك، ستحتاج على الأرجح إلى إنشاء طريقة منفصلة يتم استدعاؤها عند الخروج من حلقة الرسالة. هناك سوف تحتاج إلى إغلاق المقبس وتدفقات الإدخال/الإخراج. يجب إرسال نفس إشارة نهاية الجلسة لعميل معين إلى مؤشر ترابط الخادم الخاص به، والذي يجب أن يفعل الشيء نفسه مع المقبس الخاص به ويزيل نفسه من قائمة سلاسل الرسائل في فئة الخادم الرئيسية. الرأس الثامن: ليس هناك حد للكمال يمكنك ابتكار ميزات جديدة إلى ما لا نهاية لتحسين مشروعك. ولكن ما الذي يجب نقله بالضبط إلى العميل المتصل حديثًا؟ أعتقد أن الأحداث العشرة الأخيرة حدثت قبل وصوله. للقيام بذلك، تحتاج إلى إنشاء فئة يتم فيها إدخال الإجراء الأخير مع أي مؤشر ترابط خادم في القائمة المعلنة، وإذا كانت القائمة ممتلئة بالفعل (أي، هناك بالفعل 10)، فاحذف الأول وأضفه آخر الذي جاء. لكي يتم استلام محتويات هذه القائمة من خلال اتصال جديد، عند إنشاء مؤشر ترابط خادم، في دفق الإخراج، تحتاج إلى إرسالها إلى العميل. كيف افعلها؟ على سبيل المثال، مثل هذا:
public void printStory(BufferedWriter writer) {
// ...
}
قام مؤشر ترابط الخادم بالفعل بإنشاء تدفقات ويمكنه تمرير دفق الإخراج كوسيطة. بعد ذلك، تحتاج فقط إلى تمرير كل ما يلزم نقله إلى العميل الجديد في دورة البحث. الخلاصة: هذه مجرد الأساسيات، وعلى الأرجح لن تعمل بنية الدردشة هذه عند إنشاء تطبيق حقيقي. تم إنشاء هذا البرنامج لأغراض تعليمية وعلى أساسه أوضحت كيف يمكنك جعل العميل يتواصل مع الخادم (والعكس صحيح)، وكيفية القيام بذلك لعدة اتصالات، وبالطبع، كيف يتم تنظيم ذلك على مآخذ توصيل. تم إعادة ترتيب المصادر أدناه، كما تم إرفاق الكود المصدري للبرنامج الذي يتم تحليله. هذه أول تجربة لي في كتابة مقال) شكرا لاهتمامكم :)
  1. التفكير في Java Enterprise، بقلم بروس إيكل وآخرون. آل. 2003
  2. جافا 8، الدليل الكامل، هربرت شيلدت، الطبعة التاسعة، 2017 (الفصل 22)
  3. برمجة المقبس في مقالة جافا حول المقابس
  4. المقبس في الوثائق الرسمية
  5. ServerSocket في الوثائق الرسمية
  6. المصادر على جيثب
تعليقات
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION