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) {
        //...
    }

Схващате идеята; можете да използвате това знание, за да намерите проблемите с потока и във вашия клиентски код.

С това казано, сървърите са посредник между множество клиенти. Ако искате да можете да пишете в конзолата на сървъра, за да изпратите съобщение до клиенти, то не трябва да отива само до 1 клиент (освен ако не сте имали система, която ви позволява да посочите име). Трябва да съхранявате всяка връзка в някаква колекция, така че когато пишете в конзолата на сървъра, тя отива при всеки клиент, който е свързан. Това също помага, когато клиент иска да изпрати съобщение до всеки друг клиент (глобално съобщение). Основната нишка на сървъра е предимно за приемане на клиенти; Създадох друга тема, за да ви позволя да пишете в конзолата.

Що се отнася до вашите потоци, трябва да ги създавате всеки път, когато стартирате 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 – Затваря указаните ресурси автоматично, след като опитайте да блокирате, както е приключило

РЕДАКТИРАНЕ Когато потребител се свърже, трябва да запазите връзката му по име или идентификатор. По този начин можете да изпращате данни до конкретни потребители. Дори ако вашият клиент работи на същата машина като сървъра, все още е същата идея: клиентът се свързва със сървъра, сървърът изпраща съобщение до клиента въз основа на име или идентификатор:

            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 Значи искате клиент да работи на същата машина като сървъра и искате да го показвате? Защо просто да нямате контролен панел за сървъра, който показва съобщенията? Просто накарайте клиента да се свърже със сървъра, дайте на всеки клиент, който се свързва, идентификатор или име, запазете потребителите в списък по техните идентификатори или имена (може би 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 Изгубихте ме с това последно изявление. Нека продължим тази дискусия в чат - person Dioxin; 05.04.2015
comment
Нека продължим тази дискусия в чата. - person Dioxin; 05.04.2015