Arhn - архитектура программирования

Сокеты SocketChannel не читают данные

Вот мой код. С TestServer я пытаюсь отправить данные через поток вывода и получить их от тестового клиента. Я использую SocketChannel, потому что мне нужно, чтобы клиент одновременно прослушивал 3 порта. На данный момент я пытаюсь читать только из одного сокета. Однако, похоже, он не получает никаких данных с сервера. Для метода запуска KBThread, если я раскомментирую nodata println, он будет выполняться снова и снова.

TestServer.java

import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.ArrayList;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.*;


public class TestServer extends JPanel implements KeyListener, MouseListener, MouseMotionListener {

    private final int MAX_CLIENTS = 8;

    JPanel listenerPanel = new JPanel();
    JFrame listenerFrame = new JFrame();

    static DataOutputStream kbOut;
    static DataOutputStream mOut;
    static Socket dataSocket;

    public TestServer() {
        this.setFocusable(true);
        listenerPanel.addKeyListener(this);
        listenerPanel.addMouseMotionListener(this);

        listenerFrame.add(listenerPanel);
        listenerFrame.setSize(1376,808); // 10 more x, 40 more y.
        listenerFrame.setVisible(true);
        listenerFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

        listenerPanel.requestFocusInWindow();

    }

    public static void main(String[] args) {

        new TestServer().startServer();
    }

    public void startServer() {

        final ExecutorService clientProcessingPool = Executors.newFixedThreadPool(MAX_CLIENTS);

        Runnable serverTask = () -> {
            try {
                ServerSocket serverSocket = new ServerSocket(1111);
                System.out.println("Waiting for clients.");
                while (true) {
                    Socket clientSocket = serverSocket.accept();
                    clientProcessingPool.submit(new ClientTask(clientSocket));
                }
            } catch (IOException ex) {
                System.err.println("Error with client socket.");
            }
        };

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

    private class ClientTask implements Runnable {
        private final Socket clientSocket;

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

        @Override
        public void run() {

            try {
                String clientIP = clientSocket.getInetAddress().getHostAddress();
                System.out.println("Client connected from " + clientIP);

                Socket kbSocket = new Socket(clientIP, 1112);
                System.out.println("Keyboard socket connected to " + clientIP);
                kbOut = new DataOutputStream(kbSocket.getOutputStream());

                Socket mSocket = new Socket(clientIP, 1113);
                System.out.println("Mouse socket connected to " + clientIP);
                mOut = new DataOutputStream(mSocket.getOutputStream());

                //new TestServer().startKBServer(clientIP);
                //new TestServer().startMServer(clientIP);

                try {
                    clientSocket.close();
                } catch (IOException ex) {
                }
            } catch (IOException ex) {
                Logger.getLogger(TestServer.class.getName()).log(Level.SEVERE, null, ex);
            }
        }
    }

    public void startKBServer(String clientAddress) {

        Runnable kbTask = () -> {
            try {
                Socket kbSocket = new Socket(clientAddress, 1112);
                System.out.println("Keyboard socket connected to " + clientAddress);
                new KBTask(kbSocket);

            } catch (IOException ex) {
                System.out.println("Error Calling Back " + clientAddress);
            }
        };

        Thread kbThread = new Thread(kbTask);
        kbThread.start();
    }

    private class KBTask implements Runnable {
        private final Socket kbSocket;

        private KBTask(Socket kbSocket) {
            this.kbSocket = kbSocket;
        }

        @Override
        public void run() {
            try {
                kbOut = new DataOutputStream(kbSocket.getOutputStream());
            } catch (IOException ex) {
                Logger.getLogger(TestServer.class.getName()).log(Level.SEVERE, null, ex);
            }
        }
    }

    @Override
    public void keyPressed(KeyEvent ke) {
        try {
            int key = ke.getKeyCode();

            System.out.println("Key Pressed: " + key);

            kbOut.writeInt(key);
            kbOut.flush();

        } catch (IOException ex) {
            System.out.println("Error writing key data to server");
        }
    }

    @Override
    public void keyReleased(KeyEvent ke) {
        try {
            int key = ke.getKeyCode();

            System.out.println("Key Pressed: " + -key);

            kbOut.writeInt(-key);
            kbOut.flush();

        } catch (IOException ex) {
            System.out.println("Error writing -key data to server");
        }
    }

        @Override
    public void mouseMoved(MouseEvent me) {
        try {
            int mouseX = me.getX();
            int mouseY = me.getY();

            if (mOut != null) {
                mOut.writeInt(mouseX);
                mOut.writeInt(mouseY);
                mOut.flush();
                System.out.println("Mouse Moved To: " + mouseX + "," + mouseY);
            }


        } catch (IOException | NullPointerException ex) {
            System.out.println("Error writing mouse data to server");
        }
    }

    @Override
    public void mouseClicked(MouseEvent me) {

    }

    @Override
    public void mousePressed(MouseEvent me) {

    }

    @Override
    public void mouseReleased(MouseEvent me) {

    }

    @Override
    public void mouseEntered(MouseEvent me) {

    }

    @Override
    public void mouseExited(MouseEvent me) {

    }

    @Override
    public void mouseDragged(MouseEvent me) {

    }

    @Override
    public void keyTyped(KeyEvent ke) {

    }
}

TestClient.java

import java.awt.AWTException;
import java.awt.Robot;
import java.io.BufferedReader;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.ConnectException;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Iterator;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.JFrame;
import javax.swing.JPanel;


public class TestClient {

    private final static String SERVER_IP = "192.168.0.50";

    JPanel clientPanel = new JPanel();
    JFrame clientFrame = new JFrame();

    public void setupGUI() {

        clientFrame.add(clientPanel);
        clientFrame.setSize(200,200);
        clientFrame.setVisible(true);
        clientFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

        clientPanel.requestFocusInWindow();
    }

    public static void main(String[] args) {

        try {
            new TestClient().setupGUI();

            Robot keyRobot = new Robot();

            Socket firstSocket = new Socket(SERVER_IP, 1111);
            System.out.println("Connected to Commander. Address sent. Waiting for callback.");
            firstSocket.close();

            Selector selector = Selector.open();

            int ports[] = new int[] { 1112, 1113 };

            for (int port : ports) {
                ServerSocketChannel serverChannel = ServerSocketChannel.open();
                serverChannel.configureBlocking(false);
                serverChannel.socket().bind(new InetSocketAddress(port));
                serverChannel.register(selector, SelectionKey.OP_ACCEPT);
            }

            while (true) {
                // After the 2 accept methods fire, it stops here and program doesnt continue.
                selector.select();
                Set setKeys = selector.selectedKeys();
                Iterator selectedKeys = setKeys.iterator();


                while (selectedKeys.hasNext()) {
                    SelectionKey selectedKey = (SelectionKey) selectedKeys.next();
                    if (selectedKey.isAcceptable()) {
                        SocketChannel socketChannel = ((ServerSocketChannel) selectedKey.channel()).accept();
                        socketChannel.configureBlocking(false);

                        switch (socketChannel.socket().getLocalPort()) {
                            case 1112:
                                System.out.println("Keyboard socket open.");
                                Runnable kbr = new KBThread(socketChannel.socket());
                                new Thread(kbr).start();
                                break;

                            case 1113:
                                System.out.println("Mouse socket open.");
                                break;
                        }
                    }
                    selectedKeys.remove();
                }


            }

            } catch (ConnectException ece) {
            System.out.println("Failed to connect to server: " + SERVER_IP);
        } catch (IOException | AWTException eio) {
            Logger.getLogger(TestClient.class.getName()).log(Level.SEVERE, null, eio);
        }
    }

    private static class KBThread implements Runnable {
        private final Socket kbSocket;
        private int dataID = 0;

        private KBThread(Socket kbSocket) {
            this.kbSocket = kbSocket;
        }

        @Override
        public void run() {

            try {
                DataInputStream kbDis = new DataInputStream(kbSocket.getInputStream());
                while (true) {
                    try {


                        if (kbDis.available() > 0) {
                            dataID = kbDis.readInt();
                            System.out.println(dataID);
                        }
                        //else System.out.println("noData");
                    } catch (IOException ex) {
                        Logger.getLogger(TestClient.class.getName()).log(Level.SEVERE, null, ex);
                    }
                }
            } catch (IOException ex) {
                Logger.getLogger(TestClient.class.getName()).log(Level.SEVERE, null, ex);
            }
        }
    }
}
13.05.2015

Ответы:


1

Это не то, как мультиплексирование ввода-вывода должно работать.

Во-первых, не удаляйте из selectedKeys — после того, как вы приняли два подключения, выбирать нечего, поэтому цикл в основном потоке блокируется, и, если приходят новые подключения, вы их больше не принимаете. — это было неправильно, я запутался в итераторах Java, это было давно…

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

В качестве альтернативы, если вы хотите использовать потоки, не устанавливайте принятое клиентское соединение как неблокирующее, просто выполняйте регулярные чтения из него в выделенном потоке.

Редактировать 0:

Лучший совет, который я могу предложить, — просмотреть хороший учебник по Java NIO. В инете их много, но начать можно здесь:

13.05.2015
  • Будет ли это работать для нескольких клиентов, подключающихся к серверу? Мне нужен клиент, который будет подключаться в случайное время для подключения к серверу. Этот клиент дает серверу свой IP-адрес. Затем клиент открывает 3 сокета сервера, и сервер подключается к этим 3 сокетам. Затем сервер отправляет данные в каждый сокет, а клиент считывает данные из каждого сокета. 13.05.2015
  • Это сработает. Однако вам не нужно обратно подключаться к клиенту — как только клиент устанавливает TCP-соединение, это двунаправленный поток байтов. Кроме того, клиенты могут находиться за брандмауэром с NAT, и сервер не сможет подключиться к ним. 13.05.2015
  • Я попытался добавить OP_READ в селектор, и я получил исключения из недопустимых аргументов, хотя, вероятно, я добавлял их не в нужное место. Можете ли вы объяснить свое предложение немного больше? Это имеет смысл, я просто не уверен, как именно реализовать. Это приложение будет работать только в локальной сети, поэтому брандмауэры не беспокоят. Мне, наверное, не нужны темы, просто это казалось единственным способом сделать это. Раньше у меня также был isReadable для канала сокета, но это не сработало. 13.05.2015
  • Другая проблема заключается в том, что клиент будет продолжать выполнять код в isAcceptable, и он попытается снова установить configureBlocking и наткнется на нулевой указатель. 13.05.2015
  • Я смог заставить его отправлять данные, изменив несколько вещей. Мне пришлось сохранить selectedKeys.remove, чтобы он сначала прекратил выполнение метода accept. Однако в моем операторе switch я бы добавил селектор обратно с OP_READ, как вы предложили, и теперь он работает. Теперь мне просто нужно выяснить, как декодировать байтовый буфер в целое число. 13.05.2015
  • Еще одна странная проблема. Хотя у меня это работает, когда я тестирую клиент и сервер на своем компьютере, он правильно отправляет данные (коды клавиш). Однако, когда я запускаю клиент на своем ноутбуке, подключенном к той же сети, когда я нажимаю клавишу на своем ПК, ноутбук генерирует исключение опустошения буфера. Не знаю, почему на одном компе работает, а на другом нет. 13.05.2015
  • Поэтому, когда клиент запускается на отдельном компьютере, numBytesRead возвращает 1 вместо 4, поэтому попытка чтения на нем вызывает ошибку. Я понятия не имею, почему он читает только 1 байт. 13.05.2015
  • Соединение TCP представляет собой поток байтов* — базовый стек ОС может нарезать отправляемые вами байты любым удобным для него способом. Вы должны иметь дело с этим. 13.05.2015
  • Я исходил из этого предположения, что он не читал все байты. Я сделал несколько вызовов socketchannel.read() и проверил их размер, однако он сообщает 1 байт, а затем 0 после этого, поэтому остальные байты не отправляются. Это проблема отправителя или получателя? 13.05.2015
  • После этого последнего комментария я сделал еще кое-что, добавив цикл while и проверив наличие буфера.осталось, и теперь он работает. Большое спасибо! 13.05.2015

  • 2

    Недостаточно представителей для комментариев, поэтому публикуйте как ответ. Я не эксперт, но я считаю, что вам придется использовать PrintWriter в потоке вывода сокетов для передачи текста туда и обратно. Здесь thisClient является сокетом.

    writer = new PrintWriter(thisClient.getOutputStream());
    System.out.println("Transmitting");
    writer.println(msg);
    writer.flush();
    
    13.05.2015
  • Я использовал потоки вывода данных, когда использовал обычный ввод-вывод вместо NIO. Будет ли это иметь значение? 13.05.2015
  • Думаю, да. Вот что я разработал несколько месяцев назад. Это базовый чат/серверный клиент. Надеюсь, это поможет. сервер чата отправляет сообщение только 1 клиенту"> stackoverflow.com/questions/24536934/ 13.05.2015
  • Я не уверен, что это сработает. Мне нужно несколько клиентских подключений, и каждый клиент подключается к нескольким портам, поэтому я использую NIO. dataoutputstreams отлично работал с обычным вводом-выводом в сокетах, однако сокеты блокируются при принятии, поэтому мне нужно использовать NIO. 13.05.2015
  • Новые материалы

    Коллекции публикаций по глубокому обучению
    Последние пару месяцев я создавал коллекции последних академических публикаций по различным подполям глубокого обучения в моем блоге https://amundtveit.com - эта публикация дает обзор 25..

    Представляем: Pepita
    Фреймворк JavaScript с открытым исходным кодом Я знаю, что недостатка в фреймворках JavaScript нет. Но я просто не мог остановиться. Я хотел написать что-то сам, со своими собственными..

    Советы по коду Laravel #2
    1-) Найти // You can specify the columns you need // in when you use the find method on a model User::find(‘id’, [‘email’,’name’]); // You can increment or decrement // a field in..

    Работа с временными рядами спутниковых изображений, часть 3 (аналитика данных)
    Анализ временных рядов спутниковых изображений для данных наблюдений за большой Землей (arXiv) Автор: Рольф Симоэс , Жильберто Камара , Жильберто Кейрос , Фелипе Соуза , Педро Р. Андраде ,..

    3 способа решить квадратное уравнение (3-й мой любимый) -
    1. Методом факторизации — 2. Используя квадратичную формулу — 3. Заполнив квадрат — Давайте поймем это, решив это простое уравнение: Мы пытаемся сделать LHS,..

    Создание VR-миров с A-Frame
    Виртуальная реальность (и дополненная реальность) стали главными модными терминами в образовательных технологиях. С недорогими VR-гарнитурами, такими как Google Cardboard , и использованием..

    Демистификация рекурсии
    КОДЕКС Демистификация рекурсии Упрощенная концепция ошеломляющей О чем весь этот шум? Рекурсия, кажется, единственная тема, от которой у каждого начинающего студента-информатика..