简体   繁体   English

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

[英]Server client chat room in Java

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

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). 每当您从inputstream.readUTF()中检索某些内容时,该循环就会循环(当然,它不是null)。 Hopefully that example will help you 希望这个例子对你有帮助


After actually reading your code and testing it, i have noticed that your structure seems a bit off. 在实际阅读您的代码并对其进行测试之后,我注意到您的结构似乎有些偏离。 First, your Thread n in 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 n is handling accepting connections, so with the loop, Thread n is fine. Thread n正在处理接受连接,因此对于循环, Thread n很好。 Thread input is handling input retrieved from clients. Thread input正在处理从客户端检索到的输入。 This is where you are getting confused. 这就是您感到困惑的地方。

Each client should have their own InputStream and OutputStream server-sided. 每个客户端应在服务器端拥有自己的InputStream和OutputStream。 Similar to how you have a list for Sockets (since you are gonna be creating multiple Sockets server-sided), you need multiple Streams aswell. 类似于您有一个Sockets列表的方式(因为您将在服务器端创建多个Sockets),因此您还需要多个Streams。 For this, I suggest making a User class, then if anything, have your ArrayList for User, not Socket. 为此,我建议创建一个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). in.readLine()已过时,这意味着它已过时且不应使用(以避免错误)。 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. 现在您已经设置了处理数据的User类,现在可以在每次接收到连接时创建一个新的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. 首先,一个大问题是,您需要在每个循环中重新初始化DataOutputStream,这将导致流服务器端关闭。 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). 一定要把它带出循环,或者放在前面(或者看一下我的服务器示例以更好地处理流初始化)。

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 n or Thread input , other than to start it, you should remove the reference variable. 第三,与网络无关,但要了解除了如何启动Thread nThread input ,不要启动它,您应该删除引用变量。 (even though im pretty sure local vars are picked up by the Garbage Collector, still looks nicer). (即使我非常确定垃圾收集器会拾取本地变量,但看起来仍然更好)。 Example: 例:

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. PS:这是一个非常基本的服务器,实际上不应该用于大型应用程序。 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. 创建新线程(new User())可能会导致大量开销,因此最好使用ExecutorService或其他类型的线程池服务来处理连接。 Good luck! 祝好运!

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 this little Oracle doc very useful. 当我第一次开始编写我的客户端-服务器聊天程序时,我发现这个小的Oracle文档非常有用。 Hopefully it will help you too. 希望它也会对您有所帮助。

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

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

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM