简体   繁体   English

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

[英]How do I get my multithreaded server/client chat program to echo messages to all clients using sockets?

right now I have a java program that uses threads and sockets to echo text responses like a real chat window.现在我有一个 java 程序,它使用线程和 sockets 来回显文本响应,例如真实聊天 window。 Currently, my program works by running the server and than as many clients as I want.目前,我的程序通过运行服务器而不是我想要的任意数量的客户端来工作。 When a client enters a message, that message is echoed to the server and also to the client that sent the message.当客户端输入消息时,该消息会回显到服务器以及发送消息的客户端。

My problem is that I want the message any client enters to be sent not only to the server and to themselves, but to every other client as well.我的问题是我希望任何客户端输入的消息不仅发送到服务器和他们自己,而且发送到其他所有客户端。

Heres how it currently works:以下是它目前的工作方式:

Server:服务器:

Received client message: test1收到客户端消息:test1

Client 1:客户 1:

Enter message: test1输入消息:test1

test1测试1

Client 2:客户 2:

Enter message:输入消息:

Client 1 enters test1, receives test1 back and the server also receives test1.客户端 1 进入 test1,收到 test1,服务器也收到 test1。 Client 2 gets nothing.客户 2 一无所获。 My goal is to have any messages entered in the clients display on the client that sent the message as well as the other clients and server.我的目标是让在客户端中输入的任何消息都显示在发送消息的客户端以及其他客户端和服务器上。

Working example:工作示例:

Server:服务器:

Received client message: test1收到客户端消息:test1

Received client message: hello收到客户端消息:你好

Client 1:客户 1:

Enter message: test1输入消息:test1

test1测试1

From client 2: hello来自客户 2:你好

Client 2:客户 2:

Enter message:输入消息:

From client 1: test1从客户端 1:test1

hello你好

The formatting doesnt have to be exactly like that, but thats the idea.格式不必完全一样,但这就是想法。 My code so far is below.到目前为止,我的代码如下。 Ive read that I need to add my clients to a list and then loop over them and send them all the message but im not sure.我读过我需要将我的客户添加到列表中,然后遍历它们并将所有消息发送给他们,但我不确定。 Any help would be great.任何帮助都会很棒。

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());

        }
    }
    
}   

server code服务器代码

    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());
                }
                
        }
}

thread code线程代码

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.Socket;

public class Echoer extends Thread{
    
    private Socket socket;
    public Echoer(Socket socket) {
        this.socket = socket;
    }
    
    @Override
    public void run() {
        try {
            BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
            PrintWriter wr = new PrintWriter(socket.getOutputStream(), true);
            
            while(true) {
                
                String echoString = in.readLine();
                System.out.println("Received Client Input: " + echoString);
                if(echoString.equals("exit")) {
                    break;
                }

                wr.println(echoString);
            }
        }catch(IOException e) {
            System.out.println("Oooops " + e.getMessage());
        }finally {
            try {
                socket.close();
            }catch(IOException e) {
                // later
            }
            
        }
        
    }

}

I can see two problems with your current logic:我可以看到您当前的逻辑存在两个问题:

  1. At the client side, you are essentially reading user input, then sending to server and getting a (single) response.在客户端,您实际上是在读取用户输入,然后发送到服务器并获得(单个)响应。 So the problem here is that you only get one response, while you should take more than one for each user input line: that is the user's input plus the other users' input.所以这里的问题是你只能得到一个响应,而你应该为每个用户输入行多取一个:即用户的输入加上其他用户的输入。 Since you don't know when and how many the other users' inputs are going to be, you need to go asynchronous.由于您不知道其他用户的输入何时以及有多少,您需要异步 go。 I mean that you need 2 threads: one for reading user input and the other for reading server input/response (note: we are still at the client side).我的意思是您需要 2 个线程:一个用于读取用户输入,另一个用于读取服务器输入/响应(注意:我们仍在客户端)。 Since you already have one of the 2 threads, ie the one which runs the main method, then you can use it instead of creating a new one.由于您已经拥有 2 个线程之一,即运行main方法的线程,那么您可以使用它而不是创建一个新线程。
  2. At the server side, your Echoer is reading user input but only sending it back to the same client.在服务器端,您的Echoer正在读取用户输入,但仅将其发送回同一个客户端。 You need for example a loop to send the client's input to all other clients too.例如,您还需要一个循环来将客户端的输入发送给所有其他客户端。

So what would seem to me a proper logic is:所以在我看来,正确的逻辑是:

Client side:客户端:

Reading server's responses thread logic:读取服务器的响应线程逻辑:

forever, do:
    get server's message.
    print server's message to user.

main method: main方法:

connect to server.
start a "Reading server's responses thread".
get user input.
while the user's input it not "exit", do:
    send user's input to server.
    get user input.
disconnect from server.

Server side:服务器端:

Echoer thread: Echoer线程:

forever, do:
    read client's message.
    for every client, do:
        send the message to the client.

main method: main方法:

start server socket.
forever, do:
    accept incoming connection.
    start an Echoer thread for the accepted connection.

There are some missing bits though, such as how to maintain the list of all clients, but for that I can see you are already using a Vector<Echoer> clients at the server side.虽然有一些缺失的部分,例如如何维护所有客户端的列表,但为此我可以看到您已经在服务器端使用Vector<Echoer> clients端。 So just pass that Vector to every Echoer you create, so they can do the broadcasting of each incomming message.所以只需将该Vector传递给您创建的每个Echoer ,这样他们就可以广播每个传入的消息。 Important note here : at the server side, you have more than one threads: the main one and each Echoer , so make sure you synchronize on the Vector while you are modifying it at the main thread and also while broadcasting at the Echoer s.重要说明:在服务器端,您有多个线程:主线程和每个Echoer ,因此请确保在主线程修改 Vector 以及在Echoer广播时在Vector上同步。


Notes:笔记:

  1. I am assuming in all the above logic that there is no particular order in which the clients send their messages.我假设在上述所有逻辑中,客户端发送消息没有特定的顺序。 For example if always client A sent first, then client B and so on, and the whole process was repeating, then you wouldn't need to go multithreading at all.例如,如果总是先发送客户端A ,然后发送客户端B ,以此类推,并且整个过程在重复,那么您根本不需要 go 多线程。
  2. Please take your time.请慢慢来。 First implement it and then tell me if you encouter any problems.首先实施它,然后告诉我您是否遇到任何问题。

Edit 1: full sample code.编辑 1:完整的示例代码。

Client code:客户端代码:

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.");
        }
    }
}

Server code:服务器代码:

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.");
                }
            }
        }
    }
}

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 无法使用JAVA套接字通过服务器通过双向客户端聊天接收消息…(多线程) - Unable to receive messages in 2 way client chat via Server using JAVA Sockets…(MultiThreaded) 如何向多线程服务器上的所有客户端发送消息? - How do I send a Message to all Clients on a multithreaded Server? 具有Java套接字的基本聊天程序,客户端未从服务器接收消息 - Basic Chat Program with Java sockets, Client not receiving messages from Server 如何在服务器/聊天客户端程序中向除发件人以外的所有聊天用户发送消息 - How to send messages to all chat users except sender in server/chat client program 如何让我的 Java 聊天客户端和服务器从文本字段交换消息 - How can I get my java chat client and server to exchange messages from a textfield 套接字和服务器套接字服务器/客户端GUI聊天程序 - Sockets and ServerSockets Server/Client GUI Chat Program 使用套接字制作多线程聊天服务器 - Making a multithreaded chat server with sockets 客户端-服务器聊天应用程序 - 消息在 fwd_all 和 add_user 方法中重复,但我该如何解决? - Client-Server Chat App - Messages are duplicating within the fwd_all and add_user methods but how do I resolve? 多客户聊天程序,向所有客户广播聊天? - Multi-client chat program, broadcasting chat to all clients? 多线程客户端 - 服务器聊天,使用套接字 - multithread client-server chat, using sockets
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM