JavaRush /Blog Java /Random-VI /Các lớp Socket và ServerSocket hoặc “Xin chào, máy chủ? B...
Sergey Simonov
Mức độ
Санкт-Петербург

Các lớp Socket và ServerSocket hoặc “Xin chào, máy chủ? Bạn có thể nghe tôi không?"

Xuất bản trong nhóm
Giới thiệu: “Có một chiếc máy tính trên bàn, đằng sau nó có một bộ mã hóa…” Các lớp Socket và ServerSocket hoặc “Xin chào, máy chủ?  Bạn có thể nghe tôi không?"  - 1 Ngày xửa ngày xưa, một người bạn cùng lớp của tôi đã đăng một kết quả khác về việc học Java của anh ấy, dưới dạng ảnh chụp màn hình của một chương trình mới. Chương trình này là một cuộc trò chuyện nhiều người dùng. Vào thời điểm đó, tôi mới bắt đầu hành trình thành thạo lập trình bằng ngôn ngữ này, nhưng tôi chắc chắn đã ghi nhớ với bản thân rằng “Tôi muốn nó!” Thời gian trôi qua và sau khi hoàn thành dự án tiếp theo như một phần của việc đào sâu kiến ​​thức lập trình của mình, tôi nhớ lại sự việc đó và quyết định đã đến lúc. Bằng cách nào đó, tôi đã bắt đầu tìm hiểu chủ đề này hoàn toàn vì tò mò, nhưng trong sách giáo khoa Java chính của tôi (đó là cẩm nang hoàn chỉnh của Schildt) chỉ có 20 trang được cung cấp cho gói java.net. Điều này có thể hiểu được - cuốn sách đã rất lớn rồi. Có các bảng phương thức và hàm tạo của các lớp chính, nhưng chỉ có vậy thôi. Tất nhiên, bước tiếp theo là Google toàn năng: vô số bài viết khác nhau trình bày cùng một nội dung - hai hoặc ba từ về ổ cắm và một ví dụ làm sẵn. Cách tiếp cận cổ điển (ít nhất là theo phong cách nghiên cứu của tôi) là trước tiên hãy hiểu tôi cần gì từ các công cụ làm việc, chúng là gì, tại sao chúng lại cần thiết và chỉ sau đó, nếu giải pháp cho vấn đề không rõ ràng, hãy chọn các danh sách làm sẵn, tháo đai ốc và bu lông. Nhưng tôi đã tìm ra cái gì là gì và cuối cùng đã viết một cuộc trò chuyện với nhiều người dùng. Nhìn bề ngoài thì nó thành ra thế này: Các lớp Socket và ServerSocket hoặc “Xin chào, máy chủ?  Bạn có thể nghe tôi không?"  - 2Ở đây tôi sẽ cố gắng giúp bạn hiểu những điều cơ bản về ứng dụng máy khách-máy chủ dựa trên các ổ cắm Java bằng cách sử dụng ví dụ về thiết kế trò chuyện. Trong khóa học Javarash, bạn sẽ trò chuyện. Nó sẽ ở một đẳng cấp hoàn toàn khác, đẹp, rộng, đa chức năng. Nhưng trước hết, bạn luôn cần phải đặt nền móng, vì vậy ở đây chúng ta cần tìm hiểu xem điều gì làm nền tảng cho một phần như vậy. (Nếu phát hiện thiếu sót, sai sót xin PM hoặc bình luận dưới bài viết). Hãy bắt đầu nào. Phần đầu: “Ngôi nhà mà…” Để giải thích cách kết nối mạng xảy ra giữa máy chủ và một máy khách, hãy lấy ví dụ cổ điển hiện nay về một tòa nhà chung cư. Giả sử một khách hàng cần bằng cách nào đó thiết lập kết nối với một máy chủ cụ thể. Người tìm kiếm cần biết gì về đối tượng tìm kiếm? Vâng, địa chỉ. Máy chủ không phải là một thực thể ma thuật trên đám mây và do đó nó phải được đặt trên một máy cụ thể. Tương tự như một ngôi nhà, nơi diễn ra cuộc họp của hai bên đã thỏa thuận. Và để tìm thấy nhau trong một tòa nhà chung cư, một địa chỉ của tòa nhà là không đủ, bạn phải cho biết số căn hộ nơi cuộc gặp sẽ diễn ra. Tương tự, trên một máy tính có thể có nhiều máy chủ cùng một lúc và để khách hàng liên hệ với một máy chủ cụ thể, anh ta cũng cần chỉ định số cổng mà kết nối sẽ diễn ra qua đó. Vì vậy, địa chỉ và số cổng. Địa chỉ có nghĩa là số nhận dạng của một máy trong không gian Internet. Nó có thể là một tên miền, ví dụ: "javarush.ru" hoặc một IP thông thường. Hải cảng- một số duy nhất mà một ổ cắm cụ thể được liên kết (thuật ngữ này sẽ được thảo luận sau), nói cách khác, nó được chiếm giữ bởi một dịch vụ nhất định để có thể sử dụng nó để liên hệ với nó. Vì vậy, để có ít nhất hai đối tượng gặp nhau trên lãnh thổ của một (máy chủ), chủ sở hữu khu vực (máy chủ) phải chiếm một căn hộ (cảng) cụ thể trên đó (ô tô) và đối tượng thứ hai phải tìm địa điểm gặp nhau biết địa chỉ nhà (tên miền hoặc ip ) và số căn hộ (cổng). Phần thứ hai: Gặp gỡ Socket Trong số các khái niệm và thuật ngữ liên quan đến hoạt động trên mạng, một thuật ngữ rất quan trọng là Socket. Nó biểu thị điểm mà kết nối xảy ra. Nói một cách đơn giản, một ổ cắm kết nối hai chương trình trên mạng. Lớp Socketthực hiện ý tưởng về một socket. Máy khách và máy chủ sẽ giao tiếp thông qua các kênh đầu vào/đầu ra của nó: Các lớp Socket và ServerSocket hoặc “Xin chào, máy chủ?  Bạn có thể nghe tôi không?"  - 3 Lớp này được khai báo ở phía máy khách và máy chủ sẽ tạo lại nó, nhận tín hiệu kết nối. Đây là cách giao tiếp trực tuyến hoạt động. Để bắt đầu, đây là các hàm tạo lớp có thể có Socket:
Socket(String Name_хоста, int порт) throws UnknownHostException, IOException
Socket(InetAddress IP-address, int порт) throws UnknownHostException
“host_name” - ngụ ý một nút mạng, địa chỉ IP cụ thể. Nếu lớp socket không thể chuyển đổi nó thành địa chỉ thực, hiện có thì một ngoại lệ sẽ được đưa ra UnknownHostException. Cảng là cảng. Nếu 0 được chỉ định làm số cổng, hệ thống sẽ tự phân bổ một cổng còn trống. Một ngoại lệ cũng có thể xảy ra nếu kết nối bị mất IOException. Cần lưu ý rằng loại địa chỉ trong hàm tạo thứ hai là InetAddress. Ví dụ, nó sẽ hữu ích khi bạn cần chỉ định một tên miền làm địa chỉ. Ngoài ra, khi một miền có nghĩa là một số địa chỉ IP, InetAddressbạn có thể sử dụng chúng để lấy một mảng địa chỉ IP. Tuy nhiên, nó cũng hoạt động với IP. Bạn cũng có thể lấy tên máy chủ, mảng byte tạo nên địa chỉ IP, v.v. Chúng ta sẽ đề cập sâu hơn một chút nhưng bạn sẽ phải truy cập tài liệu chính thức để biết đầy đủ chi tiết. Khi một đối tượng thuộc loại được khởi tạo Socket, máy khách chứa nó sẽ thông báo trên mạng rằng nó muốn kết nối với máy chủ tại một địa chỉ và số cổng cụ thể. Dưới đây là các phương thức được sử dụng thường xuyên nhất của lớp Socket: InetAddress getInetAddress()– trả về một đối tượng chứa dữ liệu về socket. Nếu ổ cắm không được kết nối - null int getPort()- trả về cổng nơi xảy ra kết nối với máy chủ int getLocalPort()- trả về cổng mà ổ cắm được liên kết. Thực tế là máy khách và máy chủ có thể "giao tiếp" trên một cổng, nhưng các cổng mà chúng bị ràng buộc có thể hoàn toàn khác nhau boolean isConnected()- trả về true nếu kết nối được thiết lập void connect(SocketAddress address)- cho biết kết nối mới boolean isClosed()- trả về true nếu ổ cắm bị đóng boolean isBound()- trả về true, nếu socket thực sự được liên kết với một địa chỉ, lớp Socketsẽ triển khai giao diện AutoCloseable, vì vậy nó có thể được sử dụng trong try-with-resources. Tuy nhiên, bạn cũng có thể đóng socket theo cách cổ điển bằng cách sử dụng close(). Đầu thứ ba: và đây là ServerSocket Giả sử chúng ta đã khai báo, dưới dạng một lớp Socket, một yêu cầu kết nối ở phía máy khách. Máy chủ sẽ đoán được mong muốn của chúng ta như thế nào? Đối với điều này, máy chủ có một lớp như ServerSocket, và phương thức Accept() trong đó. Các hàm tạo của nó được trình bày dưới đây:
ServerSocket() throws IOException
ServerSocket(int порт) throws IOException
ServerSocket(int порт, int максимум_подключений) throws IOException
ServerSocket(int порт, int максимум_подключений, InetAddress локальный_address) throws IOException
Khi khai báo, ServerSocket bạn không cần chỉ định địa chỉ kết nối, vì quá trình giao tiếp diễn ra trên máy chủ. Chỉ với máy chủ đa kênh, bạn cần chỉ định IP mà ổ cắm máy chủ được liên kết. Đầu thứ ba.Một: Máy chủ nói không Vì việc cung cấp một chương trình với nhiều tài nguyên hơn mức cần thiết vừa tốn kém vừa không hợp lý, do đó, trong hàm tạo, ServerSocketbạn được yêu cầu khai báo các kết nối tối đa được máy chủ chấp nhận trong quá trình hoạt động. Nếu nó không được chỉ định thì theo mặc định, con số này sẽ được coi là bằng 50. Có, về lý thuyết, chúng ta có thể giả định rằng ServerSocketđây là cùng một ổ cắm, chỉ dành cho máy chủ. Nhưng nó đóng một vai trò hoàn toàn khác so với class Socket. Nó chỉ cần thiết ở giai đoạn tạo kết nối. Sau khi tạo một đối tượng kiểu, ServerSocketbạn cần biết rằng ai đó muốn kết nối với máy chủ. Phương thức Accept() được kết nối ở đây. Mục tiêu đợi cho đến khi ai đó muốn kết nối với nó và khi điều này xảy ra, nó sẽ trả về một đối tượng thuộc loại Socket, tức là một ổ cắm máy khách được tạo lại. Và bây giờ socket máy khách đã được tạo ở phía máy chủ, quá trình giao tiếp hai chiều có thể bắt đầu. Tạo một đối tượng loại Socketở phía máy khách và tạo lại nó bằng ServerSocketphía máy chủ là yêu cầu tối thiểu cho kết nối. Phần thứ tư: Thư gửi ông già Noel Вопрос: Chính xác thì máy khách và máy chủ giao tiếp như thế nào? Ответ:Thông qua các luồng I/O. Chúng ta đã có gì rồi? Một ổ cắm có địa chỉ máy chủ và số cổng của máy khách, cũng như điều tương tự, nhờ vào Accept(), ở phía máy chủ. Vì vậy, thật hợp lý khi cho rằng họ sẽ giao tiếp qua ổ cắm. Để thực hiện việc này, có hai phương thức cấp quyền truy cập vào luồng InputStreamOutputStreamđối tượng thuộc loại Socket. Họ đây rồi:
InputStream getInputStream()
OutputStream getOutputStream()
Vì việc đọc và ghi các byte trần không hiệu quả nên các luồng có thể được gói trong các lớp bộ điều hợp, dù có được lưu vào bộ đệm hay không. Ví dụ:
BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
BufferedWriter out = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
Để giao tiếp được hai chiều, các hoạt động như vậy phải được thực hiện ở cả hai bên. Bây giờ bạn có thể gửi thứ gì đó bằng cách sử dụng in và nhận thứ gì đó bằng cách sử dụng out và ngược lại. Trên thực tế, đây thực tế là chức năng duy nhất của lớp Socket. Và vâng, đừng quên phương thức tuôn ra() BufferedWriter- nó xóa nội dung của bộ đệm. Nếu điều này không được thực hiện, thông tin sẽ không được truyền đi và do đó sẽ không được nhận. Chuỗi nhận cũng đợi chỉ báo cuối dòng – “\n”, nếu không tin nhắn sẽ không được chấp nhận, vì trên thực tế tin nhắn chưa hoàn thành và chưa hoàn chỉnh. Nếu điều này có vẻ bất tiện với bạn, đừng lo lắng, bạn luôn có thể sử dụng lớp PrintWritercần gói gọn, chỉ định true làm đối số thứ hai, sau đó việc bật ra khỏi bộ đệm sẽ tự động diễn ra:
PrintWriter out = new PrintWriter(new BufferedWriter(new OutputStreamWriter(socket.getOutputStream())), true);
Ngoài ra, không cần phải chỉ ra cuối dòng; lớp này sẽ làm việc đó cho bạn. Nhưng liệu I/O chuỗi có phải là giới hạn mà ổ cắm có thể làm được không? Không, bạn có muốn gửi đối tượng qua luồng ổ cắm không? Vì Chúa. Sắp xếp chúng theo thứ tự và bạn đã sẵn sàng:
ObjectOutputStream out = new ObjectOutputStream(socket.getOutputStream());
ObjectInputStream in = new ObjectInputStream(socket.getInputStream());
Đầu năm: Giao tiếp thực qua Internet Vì để kết nối qua mạng thực với địa chỉ IP thực, bạn cần phải có một máy chủ chính thức và vì:
  1. Trò chuyện trong tương lai của chúng tôi, với tư cách là một tiện ích, không có những khả năng như vậy. Nó chỉ có thể thiết lập kết nối và nhận/gửi tin nhắn. Tức là nó không có khả năng của máy chủ thực sự.
  2. Máy chủ của chúng tôi, chỉ chứa dữ liệu ổ cắm và luồng I/O, không thể hoạt động như một máy chủ WEB hoặc FTP thực, khi đó chỉ với điều này, chúng tôi sẽ không thể kết nối qua Internet.
Và bên cạnh đó, chúng tôi mới bắt đầu phát triển chương trình, nghĩa là chương trình chưa đủ ổn định để hoạt động ngay với mạng thực, vì vậy chúng tôi sẽ sử dụng máy chủ cục bộ làm địa chỉ kết nối. Nghĩa là, về mặt lý thuyết, máy khách và máy chủ sẽ vẫn không được kết nối theo bất kỳ cách nào ngoại trừ thông qua ổ cắm, nhưng để gỡ lỗi chương trình, chúng sẽ ở trên cùng một máy, không có liên hệ thực sự qua mạng. Để chỉ ra trong hàm tạo Socketrằng địa chỉ là cục bộ, có 2 cách:
  1. Viết “localhost” làm đối số địa chỉ, nghĩa là sơ khai cục bộ. “127.0.0.1” cũng phù hợp cho việc này - đây chỉ là một dạng kỹ thuật số còn sơ khai.
  2. Sử dụng địa chỉ Inet:
    1. InetAddress.getByName(null)- null điểm vào localhost
    2. InetAddress.getByName("localhost")
    3. InetAddress.getByName("127.0.0.1")
Để đơn giản, chúng ta sẽ sử dụng "localhost" kiểu String. Nhưng tất cả các lựa chọn khác cũng có thể thực hiện được. Head Six: Đã đến lúc trò chuyện Vì vậy, chúng tôi đã có mọi thứ cần thiết để triển khai phiên trò chuyện với máy chủ. Tất cả những gì còn lại là ghép chúng lại với nhau: Danh sách sau đây cho thấy cách máy khách kết nối với máy chủ, gửi cho nó một tin nhắn và đến lượt máy chủ xác nhận rằng nó đã nhận được tin nhắn bằng cách sử dụng nó làm đối số trong: "Server. java"
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);
        }
    }
"Khách hàng.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);
        }

    }
}
Tất nhiên, bạn nên khởi động máy chủ trước, vì máy khách sẽ kết nối với cái gì khi khởi động nếu không có thứ gì kết nối nó? :) Kết quả đầu ra sẽ như thế này: /* Bạn có muốn nói gì không? Nhập nó vào đây: Xin chào, máy chủ? Bạn có thể nghe tôi không? Xin chào, đây là Máy chủ! Tôi xác nhận, bạn đã viết: Xin chào, máy chủ? Bạn có thể nghe tôi không? Khách hàng đã bị đóng... */ Hoan hô! Chúng tôi đã dạy máy chủ giao tiếp với khách hàng! Vì vậy, giao tiếp đó diễn ra không phải ở hai bản sao mà bao nhiêu tùy thích, bạn chỉ cần gói việc đọc và ghi các luồng trong một vòng lặp while (true) và chỉ ra lối thoát, theo một thông báo nhất định, chẳng hạn như “exit” , chu trình bị gián đoạn và chương trình sẽ kết thúc. Đầu Bảy: Nhiều người dùng thì tốt hơn. Thực tế là máy chủ có thể nghe thấy chúng tôi là tốt, nhưng sẽ tốt hơn nhiều nếu chúng tôi có thể giao tiếp với ai đó cùng loại với chúng tôi. Tôi sẽ đính kèm tất cả các nguồn ở cuối bài viết, vì vậy ở đây tôi sẽ hiển thị không phải lúc nào cũng lớn nhưng các đoạn mã quan trọng sẽ giúp bạn có thể tạo ra một cuộc trò chuyện nhiều người dùng nếu được sử dụng đúng cách. Vì vậy, chúng tôi muốn có thể giao tiếp với một số khách hàng khác thông qua máy chủ. Làm thế nào để làm nó? Rõ ràng, vì chương trình máy khách có phương thức riêng mainnên nó có thể được khởi chạy riêng biệt với máy chủ và song song với các máy khách khác. Điều này mang lại cho chúng ta điều gì? Bằng cách nào đó, điều cần thiết là với mỗi kết nối mới, máy chủ không bắt đầu liên lạc ngay lập tức mà ghi kết nối này vào một loại danh sách nào đó và tiến hành chờ kết nối mới, đồng thời một số loại dịch vụ phụ trợ sẽ tham gia liên lạc với một đối tượng cụ thể. khách hàng. Và các máy khách phải ghi thư vào máy chủ và chờ phản hồi độc lập với nhau. Chủ đề đến để giải cứu. Giả sử chúng ta có một lớp chịu trách nhiệm ghi nhớ các kết nối mới: Nó phải có các thông số sau:
  1. Số cổng.
  2. Danh sách trong đó nó ghi kết nối mới.
  3. ServerSocket, trong một bản sao duy nhất (!).
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();
        }
    }
}
Được rồi, bây giờ mỗi socket được tạo lại sẽ không bị mất mà sẽ được lưu trữ trên máy chủ. Hơn nữa. Mỗi khách hàng đều cần có người lắng nghe họ. Hãy tạo một chủ đề với các chức năng của máy chủ ở chương trước.
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) {}
    }
}
Vì vậy, trong hàm tạo của luồng máy chủ, một ổ cắm phải được khởi tạo thông qua đó luồng sẽ giao tiếp với một máy khách cụ thể. Ngoài ra, các luồng I/O và mọi thứ khác bạn cần để bắt đầu một luồng trực tiếp từ hàm tạo. Được rồi, nhưng điều gì sẽ xảy ra khi luồng máy chủ đọc tin nhắn từ máy khách? Chỉ gửi lại cho khách hàng của bạn? Không hiệu quả lắm. Chúng tôi đang thực hiện cuộc trò chuyện với nhiều người dùng, vì vậy chúng tôi cần mỗi khách hàng được kết nối để nhận được những gì một người đã viết. Bạn cần sử dụng danh sách tất cả các luồng máy chủ được liên kết với các máy khách của họ và gửi từng tin nhắn được gửi đến một luồng cụ thể để nó gửi nó đến máy khách của nó:
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) {}
}
Bây giờ tất cả khách hàng sẽ biết một trong số họ đã nói gì! Nếu bạn không muốn tin nhắn được gửi đến người gửi (anh ta đã biết mình viết gì rồi!), đơn giản khi duyệt qua các luồng, hãy xác định rõ rằng khi xử lý một đối tượng, vòng lặp thissẽ chuyển sang phần tử tiếp theo mà không cần thực hiện bất kỳ hành động nào trên đó. Hoặc, nếu bạn muốn, hãy gửi một tin nhắn cho khách hàng thông báo rằng tin nhắn đã được nhận và gửi thành công. Bây giờ mọi thứ đã rõ ràng với máy chủ. Hãy chuyển sang khách hàng, hay đúng hơn là khách hàng! Mọi thứ đều giống nhau ở đó, tương tự với máy khách ở chương trước, chỉ khi tạo một phiên bản bạn cần, như được trình bày trong chương này với máy chủ, để tạo mọi thứ cần thiết trong hàm tạo. Nhưng điều gì sẽ xảy ra nếu khi tạo một ứng dụng khách, anh ta chưa kịp nhập bất cứ thứ gì nhưng đã có thứ gì đó được gửi cho anh ta? (Ví dụ: lịch sử trao đổi thư từ của những người đã kết nối với cuộc trò chuyện trước anh ấy). Vì vậy, các chu kỳ xử lý tin nhắn đã gửi phải được tách biệt với các chu kỳ trong đó tin nhắn được đọc từ bảng điều khiển và gửi đến máy chủ để chuyển tiếp cho người khác. Chủ đề đến để giải cứu một lần nữa. Không có ích gì khi tạo một ứng dụng khách dưới dạng một chuỗi. Sẽ thuận tiện hơn khi tạo một luồng có vòng lặp trong phương thức chạy để đọc tin nhắn và cũng viết:
// 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) {

            }

        }
    }
}
Trong hàm tạo máy khách, bạn chỉ cần bắt đầu các luồng này. Làm cách nào để đóng tài nguyên của khách hàng đúng cách nếu anh ta muốn rời đi? Tôi có cần đóng tài nguyên luồng máy chủ không? Để thực hiện việc này, rất có thể bạn sẽ cần tạo một phương thức riêng được gọi khi thoát khỏi vòng lặp thông báo. Ở đó bạn sẽ cần phải đóng ổ cắm và luồng I/O. Tín hiệu kết thúc phiên tương tự cho một máy khách cụ thể phải được gửi đến luồng máy chủ của nó, luồng này phải thực hiện tương tự với ổ cắm của nó và tự xóa chính nó khỏi danh sách các luồng trong lớp máy chủ chính. Head Eight: Không có giới hạn cho sự hoàn hảo Bạn có thể không ngừng phát minh ra các tính năng mới để cải thiện dự án của mình. Nhưng chính xác những gì cần được chuyển đến một máy khách mới được kết nối? Tôi nghĩ mười sự kiện cuối cùng đã xảy ra trước khi anh ấy đến. Để thực hiện việc này, bạn cần tạo một lớp trong đó hành động cuối cùng với bất kỳ luồng máy chủ nào sẽ được nhập vào danh sách đã khai báo và nếu danh sách đã đầy (nghĩa là đã có 10), hãy xóa cái đầu tiên và thêm người cuối cùng đã đến. Để nội dung của danh sách này được kết nối mới nhận được, khi tạo một luồng máy chủ, trong luồng đầu ra, bạn cần gửi chúng cho máy khách. Làm thế nào để làm nó? Ví dụ như thế này:
public void printStory(BufferedWriter writer) {
// ...
}
Chuỗi máy chủ đã tạo luồng và có thể truyền luồng đầu ra làm đối số. Tiếp theo, bạn chỉ cần chuyển mọi thứ cần chuyển sang máy khách mới trong một chu trình tìm kiếm. Kết luận: Đây chỉ là những điều cơ bản và rất có thể kiến ​​trúc trò chuyện này sẽ không hoạt động khi tạo một ứng dụng thực sự. Chương trình này được tạo ra cho mục đích giáo dục và trên cơ sở đó, tôi đã chỉ ra cách bạn có thể làm cho máy khách giao tiếp với máy chủ (và ngược lại), cách thực hiện điều này đối với một số kết nối và tất nhiên, cách tổ chức chương trình này trên các ổ cắm. Các nguồn được sắp xếp lại bên dưới và mã nguồn của chương trình đang được phân tích cũng được đính kèm. Đây là trải nghiệm đầu tiên của tôi khi viết một bài báo) Cảm ơn bạn đã quan tâm :)
  1. Suy nghĩ trong Java Enterprise, bởi Bruce Eckel et. Al. 2003
  2. Java 8, Hướng dẫn đầy đủ, Herbert Schildt, tái bản lần thứ 9, 2017 (Chương 22)
  3. Lập trình socket trong Java bài viết về socket
  4. Ổ cắm trong tài liệu chính thức
  5. ServerSocket trong tài liệu chính thức
  6. nguồn trên GitHub
Bình luận
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION