Java中的服务器客户端聊天室

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/20807941/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-13 04:29:32  来源:igfitidea点击:

Server client chat room in Java

javaclient-serverchat

提问by user3100088

I was making a chat room project where the server accepts many clients, and whatever the client writes reaches the other clients and so on. Unfortunately the server accepts maximum 2 clients and after one client writes an input it gives errors.

我正在制作一个聊天室项目,其中服务器接受许多客户端,并且客户端写入的任何内容都会到达其他客户端等等。不幸的是,服务器最多接受 2 个客户端,在一个客户端写入输入后,它会出错。

public class Server2 {

    private static ArrayList<Socket> clients;
    private ServerSocket server;
    DataOutputStream os;
    DataInputStream in;
    Socket s;

    public Server2() throws IOException {
        server = new ServerSocket(5000);
        clients = new ArrayList<Socket>();
        System.out.println("Waiting for connections...");
        runOutput();
    }

    public void addClient() throws IOException {
        s = server.accept();
        clients.add(s);
        System.out.println("A new Client has joined");
    }

    public void runOutput() {

        Thread n = new Thread(new Runnable() {
            public void run() {
                try {
                    addClient();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        });
        n.start(); 

        Thread input = new Thread(new Runnable() {
            public void run() {
                try {
                    addClient();
                    in = new DataInputStream(s.getInputStream());
                    os = new DataOutputStream(s.getOutputStream());
                    String st = in.readLine();
                    System.out.println(st);
                    os.writeBytes(st);
                    for(int i = 0; i < clients.size(); i++)
                    {
                        DataOutputStream oo = new DataOutputStream(clients.get(i).getOutputStream());
                        oo.writeBytes(st);
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        });
        input.start();
    }

    public static void main(String[] args) throws IOException {
        Server2 server = new Server2();
    }
}

and the client class :

和客户端类:

public class Client2 {
    final public static String host = "localhost";
    final public static int port = 5000;
    Socket socket;
    DataInputStream in;
    DataOutputStream ou;
    Scanner chat;
    boolean run;
    String name;

    public Client2(String n) throws IOException {
        name = n ;
        socket = new Socket(host , port);
        System.out.println("Connection Successful");
        run = true;
        runOutput();
    }

    public void runOutput() {
        Thread input = new Thread(new Runnable() {
            public void run() {
                while (run) {
                    try {
                        in = new DataInputStream(socket.getInputStream());
                        String s = in.readLine();
                        System.out.println(s);
                        if(chat.nextLine().compareTo("QUIT") == 0)
                            run = false;
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
        });
        input.start();

        Thread t = new Thread(new Runnable() {
            public void run() {
                while (run) {
                    try {
                        ou = new DataOutputStream(socket.getOutputStream());
                        chat = new Scanner(System.in);
                        ou.writeBytes(name + " says :" + chat.nextLine() + "\n");

                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
        });

        t.start(); 
    } 

    public static void main(String[] args) throws IOException {
        Client2 client = new Client2("Ahmed");
    }   
}

采纳答案by Vince Emigh

Your 'n' thread has no loop, meaning its gonna run once (accept a connection), then die off.

您的“n”线程没有循环,这意味着它会运行一次(接受连接),然后消失。

Thread n = new Thread(new Runnable() {
    public void run() {
        while(true) { //control this with your own boolean, or it will run forever
            try {
                addClient();
            }catch(IOException e) { }
        }
    }
});

Dont worry, your thread will be paused at 'ss.accept()', since it will wait until a socket is accepted to continue.

别担心,您的线程将在 'ss.accept()' 处暂停,因为它将等到套接字被接受才能继续。

Thread thread = new Thread(new Runnable() {
    public void run() {
        while(running) {
             String input;

             while((input = inputstream.readUTF()) != null) {
                  //handle input
             }
         }
    }
});

Sorry, wrote that really quick, dont have much time. Ill come back and relate it to your code in a bit if it doesnt help.

抱歉,写得真快,没太多时间。如果它没有帮助,我会回来并将其与您的代码联系起来。

You need some way of continuously retrieving input from the Server's input stream, in which you can then handle the input.

您需要某种方式从服务器的输入流中持续检索输入,然后您可以在其中处理输入。

That loop loops each time you retrieve something from inputstream.readUTF() (and isnt null, of course). Hopefully that example will help you

每次您从 inputstream.readUTF() 中检索某些内容时,该循环都会循环(当然不是 null)。希望这个例子对你有帮助



After actually reading your code and testing it, i have noticed that your structure seems a bit off. First, your Thread nin your server is responsible for accepting connections (addClient()), but you also call it first thing in your Thread input?

在实际阅读您的代码并对其进行测试后,我注意到您的结构似乎有点偏离。首先,您Thread n在服务器中负责接受连接 ( addClient()),但您在Thread input?

Thread nis handling accepting connections, so with the loop, Thread nis fine. Thread inputis handling input retrieved from clients. This is where you are getting confused.

Thread n正在处理接受连接,所以循环,Thread n很好。 Thread input正在处理从客户端检索的输入。这就是你感到困惑的地方。

Each client should have their own InputStream and OutputStream server-sided. Similar to how you have a list for Sockets (since you are gonna be creating multiple Sockets server-sided), you need multiple Streams aswell. For this, I suggest making a User class, then if anything, have your ArrayList for User, not Socket.

每个客户端都应该有自己的 InputStream 和 OutputStream 服务器端。与您拥有 Sockets 列表的方式类似(因为您将在服务器端创建多个 Sockets),您还需要多个 Streams。为此,我建议创建一个 User 类,然后如果有的话,为 User 使用 ArrayList,而不是 Socket。

public class User extends Thread { //create a Thread for each connection to handle data seperately
    DataInputStream in;
    DataInputStream out;

    Socket socket;
    public User(Socket socket) { //creates a stream for the socket that the user connected with
        this.socket = socket;

        initStream(); 
    }

    public void initStream() { //inits streams after socket is initiated (through ss.accept())
        try {
            out = new DataOutputStream(socket.getOutputStream());
            in = new DataInputStream(socket.getInputStream());
        }catch(IOException e) { e.printStackTrace(); }
    }

    public void closeStream() throws IOException { //throws exception since i have a chance to catch it in my run() method
            out.close(); in.close();
            socket.close();
    }

    public void sendMessage(String string) {
        try {
            out.writeUTF(string);
        }catch(IOException e) { e.printStackTrace(); }
    }

    public void run() { //when thread starts, accept/handle data
        String input;

        try {
            while((input = in.readUTF()) != null) {
                //handle input
            }
            closeStream();
        }catch(IOException e) { e.printStackTrace(); }
    }
}

in.readLine()is depreciated, which means it's out-dated and shouldn't be used (to avoid bugs). Now that you have your User class set up, which handles the data, you can now create a new User each time a connection is received.

in.readLine()已折旧,这意味着它已过时,不应使用(以避免出现错误)。现在您已经设置了处理数据的 User 类,现在您可以在每次收到连接时创建一个新用户。

public class Server {
    private static boolean running = true;

    public static ArrayList<User> userlist;

    public static synchronized void stop() { //synchronized in-case another thread other than the main thread stops the server
        if(!running) return;
        running = false;
    }

    public static void main(String[] args) {
        /* uses the JVM thread (main thread for applications), so if you dont wanna
        ** dedicate your entire server to accepting connections only (since Users are in new thread)
        ** create a new thread for the code in this void method. */

        try {
            ServerSocket ss = new ServerSocket(8000);

            userlist = new ArrayList<User>();
            User user; //holds the user so we can put it in the list, then start
        while(running) {
            user = new User(ss.accept());

            list.add(user);
            user.start();
        }
        } catch(IOException e) {
            e.printStackTrace(); }
    }
}

Hopefully, that'll give you a better idea for how your server should run.

希望这能让您更好地了解服务器应该如何运行。

As for your client.. Well, there are a ton of things you should realize:

至于你的客户……嗯,你应该意识到很多事情:

First, big problem, you're re-initializing your DataOutputStream each loop, which is gonna cause the stream server-side to close. Gotta take it out of the loop, possibly put it before (or look at my server example for a better idea of handling stream initialization).

首先,大问题,您在每个循环中都重新初始化 DataOutputStream,这将导致流服务器端关闭。必须将它从循环中取出,可能放在之前(或查看我的服务器示例以更好地处理流初始化)。

Second, the names should be kept on the Server, rather than the Client. This way, its easy to iterate through the names, check if its taken, ect..

其次,名称应该保存在服务器上,而不是客户端上。这样,它很容易遍历名称,检查它是否被采用,等等。

Third, nothing with networking, but seeing how you never refer to Thread nor Thread input, other than to start it, you should remove the reference variable. (even though im pretty sure local vars are picked up by the Garbage Collector, still looks nicer). Example:

第三,与网络无关,但看看你如何从不引用Thread nor Thread input,除了启动它,你应该删除引用变量。(尽管我很确定垃圾收集器会收集本地变量,但看起来仍然更好)。例子:

public void method() {
    new Thread(new Runnable() {
        public void run() {
            //code
        }
    }).start();

   new Thread(new Runnable() {
        //run method
   }).start();
}

Hope this helps you understand how a multi-threaded server works! :)

希望这可以帮助您了解多线程服务器的工作原理!:)

PS: This is a very basic server, and really shouldn't be used for large scale applications. Creating new threads (new User()) can cause a lot of overhead, so its best to handle connections using an ExecutorService, or some other type of threadpool service. Good luck!

PS:这是一个非常基本的服务器,真的不应该用于大型应用程序。创建新线程 (new User()) 会导致大量开销,因此最好使用 ExecutorService 或某些其他类型的线程池服务来处理连接。祝你好运!

回答by JamoBox

I think you should rethink the logic of a lot of the server code. You should have one thread that handles connecting clients, and assigns them their own input threads. You can then have a method on that input reading thread that will iterate through the clients sending them the message.

我认为您应该重新考虑很多服务器代码的逻辑。您应该有一个线程来处理连接客户端,并为它们分配自己的输入线程。然后,您可以在该输入读取线程上使用一个方法,该方法将遍历向它们发送消息的客户端。

When I first started writing my Client-Server chat program I found thislittle Oracle doc very useful. Hopefully it will help you too.

当我第一次开始编写我的客户端-服务器聊天程序时,我发现这个小 Oracle 文档非常有用。希望它也能帮助你。

You should note this part of the document in particular:

您应该特别注意文档的这一部分:

Client connection requests are queued at the port, so the server must accept the connections sequentially. However, the server can service them simultaneously through the use of threads—one thread per each client connection.

The basic flow of logic in such a server is this:

客户端连接请求在端口排队,因此服务器必须按顺序接受连接。但是,服务器可以通过使用线程同时为它们提供服务——每个客户端连接一个线程。

这种服务器中的基本逻辑流程是这样的:

while (true) { accept a connection; create a thread to deal with the client; }

while (true) { accept a connection; create a thread to deal with the client; }