Java 如何让我的多线程服务器/客户端聊天程序使用套接字向所有客户端回显消息?

Java 如何让我的多线程服务器/客户端聊天程序使用套接字向所有客户端回显消息?,java,multithreading,sockets,chat,serversocket,Java,Multithreading,Sockets,Chat,Serversocket,现在我有一个java程序,它使用线程和套接字来回送文本响应,就像一个真实的聊天窗口。目前,我的程序是通过运行服务器来工作的,并且可以运行任意多的客户端。当客户机输入消息时,该消息将回显到服务器以及发送消息的客户机 我的问题是,我希望任何客户端输入的消息不仅发送到服务器和它们自己,还发送到其他所有客户端 以下是它目前的工作原理: 服务器: 收到的客户端消息:test1 客户1: 输入消息:test1 测试1 客户2: 输入消息: 客户机1进入test1,返回test1,服务器也接收test1。客户

现在我有一个java程序,它使用线程和套接字来回送文本响应,就像一个真实的聊天窗口。目前,我的程序是通过运行服务器来工作的,并且可以运行任意多的客户端。当客户机输入消息时,该消息将回显到服务器以及发送消息的客户机

我的问题是,我希望任何客户端输入的消息不仅发送到服务器和它们自己,还发送到其他所有客户端

以下是它目前的工作原理:

服务器:

收到的客户端消息:test1

客户1:

输入消息:test1

测试1

客户2:

输入消息:

客户机1进入test1,返回test1,服务器也接收test1。客户端2什么也得不到。我的目标是让在客户端中输入的任何消息显示在发送消息的客户端以及其他客户端和服务器上

工作示例:

服务器:

收到的客户端消息:test1

收到客户端消息:您好

客户1:

输入消息:test1

测试1

客户2:你好

客户2:

输入消息:

来自客户端1:test1

你好

格式不必完全像那样,但这就是想法。我的代码如下。我读到,我需要把我的客户添加到一个列表中,然后循环发送给他们所有的信息,但我不确定。任何帮助都会很好

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.Socket;
import java.net.SocketTimeoutException;
import java.util.Scanner;

public class EchoMultiThreadClient {

    public static void main(String[] args) {
        try (Socket socket = new Socket("localhost", 4000)) {
            
            //socket.setSoTimeout(5000);
            BufferedReader br = new BufferedReader(
                    new InputStreamReader(socket.getInputStream()));
            PrintWriter pw = new PrintWriter(socket.getOutputStream(), true);

            Scanner scanner = new Scanner(System.in);
            String echoString;
            String response;

            do {
                System.out.println("Enter string to be echoed: ");
                echoString = scanner.nextLine();

                pw.println(echoString);
                if(!echoString.equals("exit")) {
                    response = br.readLine();
                    System.out.println(response);
                }                          
                
                
            } while(!echoString.equals("exit"));
            
       // }catch(SocketTimeoutException e) {
        //  System.out.println("The Socket has been timed out");

        } catch (IOException e) {
            System.out.println("Client Error: " + e.getMessage());

        }
    }
    
}   
服务器代码

    import java.io.BufferedReader;
    import java.io.IOException;
    import java.io.InputStreamReader;
    import java.io.PrintWriter;
    import java.net.ServerSocket;
    import java.net.Socket;
import java.util.Vector;

    public class EchoMultiThreadServer {
        private static Vector<Echoer> clients = new Vector<Echoer>();
        public static void main(String [] args) {
            try(ServerSocket serverSocket = new ServerSocket(4000)){
                while(true) {                                           
                        
                         Socket socket = serverSocket.accept();
                         Echoer echoer = new Echoer(socket);
                         echoer.start(); 
                         clients.add(echoer);

                        } 
                    }catch(IOException e) {
                        System.out.println("Server Exception"+e.getMessage());
                }
                
        }
}

我发现您当前的逻辑存在两个问题:

  • 在客户端,您实际上是在读取用户输入,然后发送到服务器并获得(单个)响应。所以这里的问题是,您只能得到一个响应,而每个用户输入行应该有多个响应:即用户的输入加上其他用户的输入。由于您不知道其他用户的输入将在何时以及有多少,因此需要进行异步。我的意思是,您需要两个线程:一个用于读取用户输入,另一个用于读取服务器输入/响应(注意:我们仍然在客户端)。由于您已经有两个线程中的一个,即运行
    main
    方法的线程,因此您可以使用它而不是创建新的线程
  • 在服务器端,您的
    echore
    正在读取用户输入,但只将其发送回同一客户端。例如,您需要一个循环来将客户机的输入发送到所有其他客户机
  • 因此,在我看来,正确的逻辑是:

    客户端: 正在读取服务器的响应线程逻辑:

    forever, do: get server's message. print server's message to user. 服务器端:
    回声器
    线程:

    forever, do: read client's message. for every client, do: send the message to the client. 尽管有一些缺失的地方,比如如何维护所有客户端的列表,但是我可以看到您已经在服务器端使用了
    vectorclients
    。因此,只需将该
    向量
    传递给您创建的每个
    回音器
    ,这样他们就可以广播每个输入消息此处的重要注意事项:在服务器端,您有多个线程:主线程和每个
    回音器
    ,因此在主线程上修改向量时以及在
    回音器
    上广播时,请确保在
    向量上同步


    笔记:
  • 我假设在上述所有逻辑中,客户端发送消息的顺序都不特定。例如,如果总是先发送客户机A,然后发送客户机B,以此类推,并且整个过程都在重复,那么您根本不需要进行多线程处理
  • 请慢慢来。首先实施它,然后告诉我你是否遇到任何问题

  • 编辑1:完整示例代码。 客户端代码:

    import java.io.BufferedReader;
    import java.io.BufferedWriter;
    import java.io.IOException;
    import java.io.InputStream;
    import java.io.InputStreamReader;
    import java.io.OutputStream;
    import java.io.OutputStreamWriter;
    import java.net.Socket;
    import java.nio.charset.StandardCharsets;
    import java.util.Scanner;
    
    public class Client {
        
        //This is the "Reading server's responses thread" I am talking about in the answer.
        private static class ReadingRunnable implements Runnable {
            
            private final BufferedReader serverInput;
            
            public ReadingRunnable(final InputStream is) {
                serverInput = new BufferedReader(new InputStreamReader(is, StandardCharsets.UTF_8));
            }
            
            @Override
            public void run() {
                try {
                    //While the server is not disconnected, we print each line to 'System.out':
                    for (String line = serverInput.readLine(); line != null; line = serverInput.readLine())
                        System.out.println(line);
                }
                catch (final IOException iox) {
                    iox.printStackTrace(System.out);
                }
                finally {
                    System.out.println("Input from server stopped.");
                }
            }
        }
        
        public static void main(final String[] args) {
            try {
                System.out.print("Connecting... ");
                try (final Socket sck = new Socket("localhost", 50505);
                     final OutputStream os = sck.getOutputStream();
                     final InputStream is = sck.getInputStream()) {
                    System.out.println("Connected.");
                    new Thread(new ReadingRunnable(is)).start();
                    final BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(os, StandardCharsets.UTF_8));
                    final Scanner scan = new Scanner(System.in);
                    for (String userInput = scan.nextLine(); !"exit".equalsIgnoreCase(userInput); userInput = scan.nextLine()) {
                        bw.write(userInput);
                        bw.newLine();
                        bw.flush();
                    }
                }
            }
            catch (final IOException iox) {
                iox.printStackTrace(System.out);
            }
            finally {
                System.out.println("Output from user stopped.");
            }
        }
    }
    
    服务器代码:

    import java.io.BufferedReader;
    import java.io.BufferedWriter;
    import java.io.IOException;
    import java.io.InputStream;
    import java.io.InputStreamReader;
    import java.io.OutputStream;
    import java.io.OutputStreamWriter;
    import java.net.ServerSocket;
    import java.net.Socket;
    import java.nio.charset.StandardCharsets;
    import java.util.ArrayList;
    import java.util.Objects;
    
    public class Server {
        
        private static class Echoer implements Runnable {
            private final ArrayList<Echoer> all;
            private final BufferedWriter bw;
            private final BufferedReader br;
            
            public Echoer(final ArrayList<Echoer> all,
                          final InputStream is,
                          final OutputStream os) {
                this.all = Objects.requireNonNull(all);
                bw = new BufferedWriter(new OutputStreamWriter(os, StandardCharsets.UTF_8));
                br = new BufferedReader(new InputStreamReader(is, StandardCharsets.UTF_8));
            }
            
            //Instead of exposing 'bw' via a getter, I just built a helper method to send a message to the Echoer:
            public void send(final String msg) throws IOException {
                bw.write(msg);
                bw.newLine();
                bw.flush();
            }
            
            @Override
            public void run() {
                try {
                    for (String line = br.readLine(); line != null; line = br.readLine()) {
                        System.out.println(line); //Print the received line at the server.
                        synchronized (all) { //We are reading from a collection which may be modified at the same time by another (the main) Thread, so we need to synchronize.
                            //Broadcast the received line:
                            for (int i = all.size() - 1; i >= 0; --i) {
                                try {
                                    all.get(i).send(line);
                                }
                                catch (final IOException iox) {
                                    all.remove(i); //In case we cannot send to the client, disconnect him, ie remove him from the list in this simple case.
                                }
                            }
                        }
                    }
                }
                catch (final IOException iox) {
                }
                finally {
                    synchronized (all) {
                        all.remove(this); //Disconnect him, ie remove him from the list in this simple case.
                    }
                    System.out.println("Client disconnected.");
                }
            }
        }
        
        public static void main(final String[] args) throws IOException {
            System.out.print("Starting... ");
            try (final ServerSocket srv = new ServerSocket(50505)) {
                final ArrayList<Echoer> all = new ArrayList<>();
                System.out.println("Waiting for clients...");
                while (true) {
                    final Socket sck = srv.accept();
                    try {
                        final OutputStream os = sck.getOutputStream();
                        final InputStream is = sck.getInputStream();
                        final Echoer e = new Echoer(all, is, os); //Pass all the Echoers at the new one.
                        synchronized (all) { //We will write to a collection which may be accessed at the same time by another (an Echoer) Thread, so we need to synchronize.
                            all.add(e); //Update list of Echoers.
                        }
                        new Thread(e).start(); //Start serving Echoer.
                    }
                    catch (final IOException iox) {
                        System.out.println("Failed to open streams for a client.");
                    }
                }
            }
        }
    }
    
    导入java.io.BufferedReader;
    导入java.io.BufferedWriter;
    导入java.io.IOException;
    导入java.io.InputStream;
    导入java.io.InputStreamReader;
    导入java.io.OutputStream;
    导入java.io.OutputStreamWriter;
    导入java.net.ServerSocket;
    导入java.net.Socket;
    导入java.nio.charset.StandardCharset;
    导入java.util.ArrayList;
    导入java.util.Objects;
    公共类服务器{
    私有静态类回声器实现可运行{
    私人最终ArrayList all;
    专用最终缓冲写入程序bw;
    私有最终缓冲区读取程序br;
    公共回声器(最终阵列列表全部,
    最终输入流为,
    最终输出流(os){
    this.all=Objects.requirennoull(all);
    bw=新的BufferedWriter(新的OutputStreamWriter(os,StandardCharsets.UTF_8));
    br=新的BufferedReader(新的InputStreamReader(is,StandardCharsets.UTF_8));
    }
    //我没有通过getter公开“bw”,而是构建了一个helper方法来向echomer发送消息:
    public void send(最终字符串msg)引发IOException{
    bw.write(msg);
    换行符();
    bw.flush();
    }
    @凌驾
    公开募捐{
    试一试{
    对于(字符串line=br.readLine();line!=null;line=br.readLine()){
    System.out.println(line);//在服务器上打印接收到的行。
    synchronized(all){//我们正在读取一个集合,该集合可能同时被另一个(主)线程修改,因此我们需要同步。
    //广播接收到的线路:
    对于(int i=all.size()-1;i>=0;--i){
    试一试{
    全部。获取(i)。发送(行);
    }
    捕获(最终iox异常iox){
    all.remove(i);//如果我们无法发送给客户,请断开他的连接(在这个简单的例子中,将他从列表中删除)。
    }
    }
    }
    }
    }
    捕获(最终IOException)
    start server socket.
    forever, do:
        accept incoming connection.
        start an Echoer thread for the accepted connection.
    
    import java.io.BufferedReader;
    import java.io.BufferedWriter;
    import java.io.IOException;
    import java.io.InputStream;
    import java.io.InputStreamReader;
    import java.io.OutputStream;
    import java.io.OutputStreamWriter;
    import java.net.Socket;
    import java.nio.charset.StandardCharsets;
    import java.util.Scanner;
    
    public class Client {
        
        //This is the "Reading server's responses thread" I am talking about in the answer.
        private static class ReadingRunnable implements Runnable {
            
            private final BufferedReader serverInput;
            
            public ReadingRunnable(final InputStream is) {
                serverInput = new BufferedReader(new InputStreamReader(is, StandardCharsets.UTF_8));
            }
            
            @Override
            public void run() {
                try {
                    //While the server is not disconnected, we print each line to 'System.out':
                    for (String line = serverInput.readLine(); line != null; line = serverInput.readLine())
                        System.out.println(line);
                }
                catch (final IOException iox) {
                    iox.printStackTrace(System.out);
                }
                finally {
                    System.out.println("Input from server stopped.");
                }
            }
        }
        
        public static void main(final String[] args) {
            try {
                System.out.print("Connecting... ");
                try (final Socket sck = new Socket("localhost", 50505);
                     final OutputStream os = sck.getOutputStream();
                     final InputStream is = sck.getInputStream()) {
                    System.out.println("Connected.");
                    new Thread(new ReadingRunnable(is)).start();
                    final BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(os, StandardCharsets.UTF_8));
                    final Scanner scan = new Scanner(System.in);
                    for (String userInput = scan.nextLine(); !"exit".equalsIgnoreCase(userInput); userInput = scan.nextLine()) {
                        bw.write(userInput);
                        bw.newLine();
                        bw.flush();
                    }
                }
            }
            catch (final IOException iox) {
                iox.printStackTrace(System.out);
            }
            finally {
                System.out.println("Output from user stopped.");
            }
        }
    }
    
    import java.io.BufferedReader;
    import java.io.BufferedWriter;
    import java.io.IOException;
    import java.io.InputStream;
    import java.io.InputStreamReader;
    import java.io.OutputStream;
    import java.io.OutputStreamWriter;
    import java.net.ServerSocket;
    import java.net.Socket;
    import java.nio.charset.StandardCharsets;
    import java.util.ArrayList;
    import java.util.Objects;
    
    public class Server {
        
        private static class Echoer implements Runnable {
            private final ArrayList<Echoer> all;
            private final BufferedWriter bw;
            private final BufferedReader br;
            
            public Echoer(final ArrayList<Echoer> all,
                          final InputStream is,
                          final OutputStream os) {
                this.all = Objects.requireNonNull(all);
                bw = new BufferedWriter(new OutputStreamWriter(os, StandardCharsets.UTF_8));
                br = new BufferedReader(new InputStreamReader(is, StandardCharsets.UTF_8));
            }
            
            //Instead of exposing 'bw' via a getter, I just built a helper method to send a message to the Echoer:
            public void send(final String msg) throws IOException {
                bw.write(msg);
                bw.newLine();
                bw.flush();
            }
            
            @Override
            public void run() {
                try {
                    for (String line = br.readLine(); line != null; line = br.readLine()) {
                        System.out.println(line); //Print the received line at the server.
                        synchronized (all) { //We are reading from a collection which may be modified at the same time by another (the main) Thread, so we need to synchronize.
                            //Broadcast the received line:
                            for (int i = all.size() - 1; i >= 0; --i) {
                                try {
                                    all.get(i).send(line);
                                }
                                catch (final IOException iox) {
                                    all.remove(i); //In case we cannot send to the client, disconnect him, ie remove him from the list in this simple case.
                                }
                            }
                        }
                    }
                }
                catch (final IOException iox) {
                }
                finally {
                    synchronized (all) {
                        all.remove(this); //Disconnect him, ie remove him from the list in this simple case.
                    }
                    System.out.println("Client disconnected.");
                }
            }
        }
        
        public static void main(final String[] args) throws IOException {
            System.out.print("Starting... ");
            try (final ServerSocket srv = new ServerSocket(50505)) {
                final ArrayList<Echoer> all = new ArrayList<>();
                System.out.println("Waiting for clients...");
                while (true) {
                    final Socket sck = srv.accept();
                    try {
                        final OutputStream os = sck.getOutputStream();
                        final InputStream is = sck.getInputStream();
                        final Echoer e = new Echoer(all, is, os); //Pass all the Echoers at the new one.
                        synchronized (all) { //We will write to a collection which may be accessed at the same time by another (an Echoer) Thread, so we need to synchronize.
                            all.add(e); //Update list of Echoers.
                        }
                        new Thread(e).start(); //Start serving Echoer.
                    }
                    catch (final IOException iox) {
                        System.out.println("Failed to open streams for a client.");
                    }
                }
            }
        }
    }