Java Socketing: мой серверный класс постоянно принимает ввод, а мой клиент — нет?

Попробуйте сделать параллельный обмен сообщениями между сервером и клиентом. Когда они впервые подключаются друг к другу и сервер отправляет тестовую строку, клиент получает ее с первого раза отлично. И клиент может отлично ОТПРАВЛЯТЬ сообщения на сервер. Но мой клиентский класс не может постоянно проверять сообщения, как мой сервер, и не может понять, что не так. Какие-либо предложения?

Код класса сервера:

import java.lang.*;
import java.io.*;
import java.net.*;
import java.util.Random;
import java.util.concurrent.*;
import java.util.logging.Level;
import java.util.logging.Logger;

public class Server {

String testMessage = "You are now connected and can begin chatting!";
boolean connected = false;
int port;


public Server(int port) {
    this.port = port;
}

public void Open() {

    //creates Threadpool for multiple instances of chatting
    final ExecutorService clientProcessingPool = Executors.newFixedThreadPool(10);
    Runnable serverTask = new Runnable() {

        @Override
        public void run() {
            try {
                System.out.println("Opening...");

                ServerSocket srvr = new ServerSocket(port);
                while (true) {
                    Socket skt = srvr.accept();
                    clientProcessingPool.submit(new ClientTask(skt));
                }
            } catch (Exception e) {
                try {
                    System.out.println(e);
                    System.out.print("You're opening too many servers in the same location, fool!\n");
                    ServerSocket srvr = new ServerSocket(port);
                    while (true) {
                        Socket skt = srvr.accept();
                        clientProcessingPool.submit(new ClientTask(skt));
                    }
                } catch (IOException ex) {
                    Logger.getLogger(Server.class.getName()).log(Level.SEVERE, null, ex);
                }
            }
        }
    };
    Thread serverThread = new Thread(serverTask);
    serverThread.start();
}

private class ClientTask implements Runnable {

    private final Socket skt;

    private ClientTask(Socket skt) {
        this.skt = skt;
    }

    @Override
    public void run() {
        //for sending messages
        if (connected == false) {
            System.out.println("======================");
            System.out.println("Server has connected!");
            processMessage(testMessage);
        }
        //for receiving messages
        while (true) {
            try {
                // Read one line and output it
                BufferedReader br = new BufferedReader(new InputStreamReader(skt.getInputStream()));
                String incomingMessage = br.readLine();
                if (incomingMessage != null) {
                    System.out.println("Server: Received message: " + incomingMessage);
                    processMessage(incomingMessage);
                }
                //br.close();
                //skt.close(); //maybe delete
            } catch (Exception e) {
                System.out.println("Server had error receiving message.");
                System.out.println("Error: " + e);
            }
        }
    }

    //for processing a message once it is received
    public void processMessage(String message) {
        PrintWriter out = null;
        try {
            out = new PrintWriter(skt.getOutputStream(), true);
        } catch (IOException ex) {
            System.out.println(ex);
            System.out.println("Server had error sending message.");
        }
        System.out.print("Server: Sending message: " + message + "\n");
        out.print(message);
        out.flush();
        connected = true;
        try {
            skt.shutdownOutput();
            //out.close();
        } catch (IOException ex) {
            Logger.getLogger(Server.class.getName()).log(Level.SEVERE, null, ex);
        }

    }
}
}

Код класса клиента:

import java.lang.*;
import java.io.*;
import java.net.*;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.JOptionPane;

class Client {

public String message;
Socket skt;
public int port;

public Client(int port) {
    this.port = port;
}

//for receiving messages from Server
public void receiveMessage() {
    final ExecutorService clientProcessingPool = Executors.newFixedThreadPool(10);
    Runnable serverTask = new Runnable() {
        @Override
        public void run() {
            try {
                skt = new Socket(InetAddress.getLocalHost().getHostName(), port);
                while (true) {

                    clientProcessingPool.submit(new Client.ClientTask(skt));
                }
            } catch (IOException ex) {
                Logger.getLogger(Client.class.getName()).log(Level.SEVERE, null, ex);
            }

        }
    };
    Thread serverThread = new Thread(serverTask);
    serverThread.start();
}

//for sending messages to Server
public void sendMessage(String outgoingMessage) throws IOException {
    try {
        skt = new Socket(InetAddress.getLocalHost().getHostName(), port);
        PrintWriter pw = new PrintWriter(skt.getOutputStream());
        System.out.println("Client: Sending message: " + outgoingMessage);
        pw.print(outgoingMessage);
        pw.flush();
        skt.shutdownOutput();
        //skt.close(); //maybe delete
    } catch (Exception e) {
        System.out.println(e);
        System.out.print("Client had error sending message.\n");
        JOptionPane.showMessageDialog(null, "That User is not currently online.", "ERROR!!", JOptionPane.INFORMATION_MESSAGE);
    }

}

private class ClientTask implements Runnable {

    private final Socket skt;

    private ClientTask(Socket skt) {
        this.skt = skt;
    }

    @Override
    public void run() {
        while (true) {
            try {
                BufferedReader in = new BufferedReader(new InputStreamReader(skt.getInputStream()));
                //while (!in.ready()) {}
                String incomingMessage = in.readLine();
                if (incomingMessage != null) {
                    System.out.println("Client: Received message: " + incomingMessage); // Read one line and output it
                    message = incomingMessage;
                }
                //skt.shutdownInput();
                //in.close();
                //skt.close(); //maybe delete
            } catch (Exception e) {
                System.out.print("Client had error receiving message.\n");
            }
        }
    }
}
}

person Jonny    schedule 05.04.2015    source источник
comment
@VinceEmigh Не только не нужно, но и разные задачи случайным образом крадут данные друг у друга. Поэтому неудивительно, что клиент неправильно считывает данные.   -  person Erwin Bolwidt    schedule 05.04.2015
comment
Проверьте мой ответ. Дайте знать, если у вас появятся вопросы. Сейчас я удаляю комментарии к этому вопросу. Если у вас есть какие-либо вопросы, задайте их в разделе комментариев моего ответа, пожалуйста :)   -  person Dioxin    schedule 05.04.2015


Ответы (1)


Потоки не могут быть повторно упакованы. После назначения оболочки они должны использовать эту оболочку на протяжении всего жизненного цикла потока. Вы также не должны закрывать поток, пока не закончите его использование, что в данном случае не произойдет, пока ваш клиент и сервер не закончат общение.

В вашем текущем коде несколько раз вы повторно инициализируете потоки:

while (true) {
    try {
        //Each loop, this reader will attempt to re-wrap the input stream
        BufferedReader br = new BufferedReader(new InputStreamReader(skt.getInputStream()));

        String incomingMessage = br.readLine();
        if (incomingMessage != null) {
            System.out.println("Server: Received message: " + incomingMessage);
            processMessage(incomingMessage);
        }

        //don't close your stream and socket so early!
        br.close();
        skt.close();
    } catch (Exception e) {
        //...
    }

Вы поняли идею; вы можете использовать эти знания, чтобы найти проблемы с потоком в вашем клиентском коде.

При этом серверы являются посредниками между несколькими клиентами. Если вы хотите иметь возможность печатать в консоли сервера для отправки сообщения клиентам, оно не должно направляться только одному клиенту (если только у вас нет системы, позволяющей указать имя). Вам нужно хранить каждое соединение в какой-то коллекции, поэтому, когда вы вводите консоль сервера, она переходит к каждому подключенному клиенту. Это также помогает, когда клиент хочет отправить сообщение каждому другому клиенту (глобальное сообщение). Основной поток сервера в первую очередь предназначен для приема клиентов; Я создал еще один поток, чтобы вы могли ввести консоль.

Что касается ваших потоков, вы должны создавать их всякий раз, когда запускаете ClientTask, как на стороне сервера, так и на стороне клиента:

public class Server {
    private ExecutorService executor = Executors.newFixedThreadPool(10);
    private Set<User> users = new HashSet<>();

    private boolean running;
    private int port;

    public Server(int port) {
        this.port = port;
    }

    public void start() {
        running = true;

        Runnable acceptor = () -> {
            try(ServerSocket ss = new ServerSocket(port)) {
                while(running) {
                     User client = new User(ss.accept());
                     users.add(client);
                     executor.execute(client);
                }
            } catch(IOException e) {
                //if a server is already running on this port;
                //if the port is not open;
                e.printStackTrace();
            }
        };

        Runnable userInputReader = () -> {
            try(Scanner scanner = new Scanner(System.in)) {
                while(running) {
                    String input = scanner.nextLine();

                    for(User user : users) {
                        user.send(input);
                    }
                }
            } catch(IOException e) {
                //problem sending data;
                e.printStackTrace();
            }

        };

        Thread acceptorThread = new Thread(acceptor);
        Thread userThread = new Thread(userInputReader);
        acceptorThread.start();
        userThread.start();
    }

    public void stop() {
        running = false;
    }

    public static void main(String[] args) {
        new Server(15180).start();
        System.out.println("Server started!");
    }
}

В методе run() потоки должны быть обернуты.

class User implements Runnable {
    private Socket socket;
    private boolean connected;

    private DataOutputStream out; //so we can access from the #send(String) method

    public User(Socket socket) {
        this.socket = socket;
    }

    public void run() {
        connected = true;

        try(DataInputStream in = new DataInputStream(socket.getInputStream())) {
            out = new DataOutputStream(socket.getOutputStream());

             while(connected) {
                 String data = in.readUTF();
                 System.out.println("From client: "+data);
                 //send to all clients
             }
         } catch(IOException e) {
             //if there's a problem initializing streams;
             //if socket closes while attempting to read from it;
             e.printStackTrace();
         }
    }

    public void send(String message) throws IOException {
        if(connected) {
            out.writeUTF(message);
            out.flush();
        }
    }
}

Это почти та же идея с клиентом: 1. Подключиться к серверу 2. Создать поток «связи» 3. Создать поток «пользовательский ввод» (для получения ввода с консоли) 4. Запустить потоки

public class Client {
    private final String host;
    private final int port;

    private boolean connected;
    private Socket socket;

    public Client(String host, int port) {
        this.host = host;
        this.port = port;
    }

    public void start() throws IOException {
        connected = true;
        socket = new Socket(host, port);

        Runnable serverInputReader = () -> {
            try (DataInputStream in = new DataInputStream(socket.getInputStream())) {
                while (connected) {
                    String data = in.readUTF();
                    System.out.println(data);
                }
            } catch (IOException e) {
                // problem connecting to server; problem wrapping stream; problem receiving data from server;
                e.printStackTrace();
            }
        };

        Runnable userInputReader = () -> {
            try (DataOutputStream out = new DataOutputStream(socket.getOutputStream());
                    Scanner scanner = new Scanner(System.in)) {
                while (connected) {
                    String input = scanner.nextLine();
                    out.writeUTF(input);
                }
            } catch (IOException e) {
                //problem wrapping stream; problem sending data;
                e.printStackTrace();
            }
        };

        Thread communicateThread = new Thread(serverInputReader);
        Thread userThread = new Thread(userInputReader);
        communicateThread.start();
        userThread.start();
    }

    public static void main(String[] args) throws IOException {
        new Client("localhost", 15180).start();
    }
}

Есть несколько вещей, которые я использовал в приведенном выше коде, с которыми вы, возможно, не знакомы. Они помогают упростить синтаксис вашего кода:

  • лямбда-выражения – предотвращает необходимость создания анонимного класса (или подкласс) для объявления метода
  • Try-With-Resources — автоматически закрывает указанные ресурсы после попробуйте заблокировать как завершенный

EDIT Когда пользователь подключается, вы должны сохранить его подключение по имени или идентификатору. Таким образом, вы можете отправлять данные конкретным пользователям. Даже если ваш клиент работает на том же компьютере, что и сервер, идея остается той же: клиент подключается к серверу, сервер отправляет сообщение клиенту на основе имени или идентификатора:

            while(running) {
                 User client = new User(ss.accept());
                 users.add(client); //add to set
                 executor.execute(client);
            }

Прямо сейчас вы просто добавляете пользователей в файл Set. В настоящее время нет способа получить конкретное значение из этого набора. Что вам нужно сделать, так это дать ему какой-то «ключ». Чтобы дать вам представление, вот старый алгоритм, который я использовал. У меня есть массив, полный пустых слотов. Когда кто-то подключается, я ищу первый свободный слот. Как только пустой слот найден, я передаю пользователю индекс массива, в котором он хранится (это будет идентификатор пользователя), а затем сохраняю пользователя в массиве по указанному индексу. Когда вам нужно отправить кому-то сообщение, вы можете использовать идентификатор для доступа к этому конкретному индексу массива, захватить нужного пользователя и отправить сообщение:

class Server {
    private int maxConnections = 10;
    private ExecutorService executor = Executors.newFixedThreadPool(maxConnections);
    private User[] users = new User[maxConnections];

    //...

    while(running) {
        Socket socket = ss.accept();

        for(int i = 0; i < users.length; i++) {
            if(users[i] == null) {
                users[i] = new User(socket, i);
                executor.execute(users[i]);
                break;
            }
        }
    }

    //...

    public static void sendGlobalMessage(String message) throws IOException {
        for(User user : users)
            if(user != null)
                user.send(message);
    }

    public static void sendPrivateMessage(String message, int id) {
        User user = users[id];

        if(user != null) {
            user.send(message);
        }
    }
}

class User {
    private Socket socket;
    private int id;

    private DataOutputStream out;

    public User(Socket socket, int id) {
        this.socket = socket;
        this.id = id;
    }

    public void send(String message) throws IOException {
        out.writeUTF(message);
        out.flush();
    }

    public void run() {
        DataInputStream in;
        //wrap in and out streams

        while(connected) {
            String data = in.readUTF();

            //Server.sendGlobalMessage(data);
            //Server.sendPrivateMessage(data, ...);
            sendMessage(data); //sends message back to client
        }
    }
}
person Dioxin    schedule 05.04.2015
comment
Спасибо. Вы были чрезвычайно полезны, и после небольшой настройки моего кода, как вы объяснили, теперь он работает отлично! Хотя, и я знаю, что это прозвучит странно, как мне сделать так, чтобы всякий раз, когда мой сервер получает данные, он пересылал их своему клиенту на тот же порт? спасибо - person Jonny; 05.04.2015
comment
Итак, вы хотите, чтобы клиент работал на той же машине, что и сервер, и вы хотите отображать его? Почему бы просто не иметь панель управления для сервера, который отображает сообщения? Просто подключите клиент к серверу, дайте каждому клиенту, который подключается, идентификатор или имя, сохраните пользователей в списке по их идентификатору или имени (возможно, Map), а затем отправьте его нужному пользователю, используя идентификатор или имя. - person Dioxin; 05.04.2015
comment
@user3461709 user3461709 Итак, вы хотите, чтобы клиент работал на том же компьютере, что и сервер, и вы хотите отображать его? Почему бы просто не иметь панель управления для сервера, который отображает сообщения? Просто подключите клиент к серверу, дайте каждому клиенту, который подключается, идентификатор или имя, сохраните пользователей в списке по их идентификатору или имени (возможно, Map), а затем отправьте его нужному пользователю, используя идентификатор или имя. - person Dioxin; 05.04.2015
comment
Да в принципе. Я пытаюсь сделать так, чтобы сервер получил сообщение, а затем переслал его клиенту для отображения в области сообщений для пользователя, хотя это оказалось трудным. попробую то, что вы посоветовали, еще раз спасибо - person Jonny; 05.04.2015
comment
Ваш комментарий был очень полезным, но, извините, я имел в виду, есть ли способ, чтобы клиент (не на том же порту, что и сервер) отправлял сообщение всем пользователям сервера? Или отправить сообщение на этот сервер, который отправит сообщение всем пользователям на порту? Спасибо - person Jonny; 05.04.2015
comment
Да; в примере у меня есть метод sendGlobalMessage. Это отправляет сообщение, полученное от клиента, всем подключенным клиентам. - person Dioxin; 05.04.2015
comment
Проблема, с которой я сталкиваюсь, заключается в том, что клиент на сервере отправляет, например, Hello на другой сервер. Это сообщение получено сервером, который я хочу отправить своим клиентам. Однако, когда я использую метод sendGlobalMEssage, сервер, на котором находится исходный клиент, отправляющий сообщение, является сервером, который отправляет сообщение всем своим клиентам, а не сервером назначения, отправляющим полученное сообщение всем своим клиентам. Извините, если это звучит запутанно, ха-ха - person Jonny; 05.04.2015
comment
@user3461709 user3461709 Вы потеряли меня из-за этого последнего заявления. Продолжим обсуждение в чате - person Dioxin; 05.04.2015
comment
Давайте продолжим обсуждение в чате. - person Dioxin; 05.04.2015